using AssetRipper.Text.SourceGeneration;
using Microsoft.CodeAnalysis.CSharp;
using System.CodeDom.Compiler;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
namespace AssetRipper.GUI.Web.Dependencies.SourceGenerator;
public static class Program
{
private readonly record struct Dependency(string Name, string Website, string ProductionDownloadUrl, string? ProductionIntegrity, string? DevelopmentDownloadUrl, string? DevelopmentIntegrity, string Path, string MimeType);
private static readonly Dependency[] dependencies =
[
new(
"BabylonMain",
"https://www.babylonjs.com/",
"https://cdn.babylonjs.com/babylon.js",
null,
null,
null,
"/js/babylon.js",
"application/javascript"),
new(
"BabylonLoader",
"https://www.babylonjs.com/",
"https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js",
null,
null,
null,
"/js/babylonjs.loaders.min.js",
"application/javascript"),
new(
"BootstrapStyleSheet",
"https://getbootstrap.com/",
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css",
"sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB",
null,
null,
"/css/bootstrap.min.css",
"text/css"),
new(
"BootstrapScript",
"https://getbootstrap.com/",
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js",
"sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI",
null,
null,
"/js/bootstrap.bundle.min.js",
"application/javascript"),
new(
"Popper",
"https://jquery.com/",
"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js",
"sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r",
null,
null,
"/js/popper.min.js",
"application/javascript"),
new(
"Vue",
"https://vuejs.org/",
"https://unpkg.com/vue@3.5.27/dist/vue.global.prod.js",
null,
"https://unpkg.com/vue@3.5.27/dist/vue.global.js",
null,
"/js/vue.js",
"application/javascript"),
];
public static void Main(string[] args)
{
string outputDirectory = args[0];
using HttpClient client = CreateClient();
string outputPath = Path.Combine(outputDirectory, "Dependencies");
IndentedTextWriter writer = IndentedTextWriterFactory.Create(outputDirectory, "EmbeddedDependency");
writer.WriteGeneratedCodeWarning();
writer.WriteLineNoTabs();
writer.WriteUsing("System.Text");
writer.WriteLineNoTabs();
writer.WriteFileScopedNamespace("AssetRipper.GUI.Web.Dependencies");
writer.WriteLineNoTabs();
writer.WriteLine("public readonly record struct EmbeddedDependency(string Content, string Path, string MimeType, bool IsDevelopment)");
using (new CurlyBrackets(writer))
{
writer.WriteLine("public bool IsScript => MimeType == \"application/javascript\";");
writer.WriteLine("public bool IsStyleSheet => MimeType == \"text/css\";");
writer.WriteLine("public bool IsProduction => !IsDevelopment;");
foreach (Dependency dependency in dependencies)
{
writer.WriteSummaryDocumentation($"");
WriteProperty(client, writer, dependency.Name, dependency.ProductionDownloadUrl, dependency.ProductionIntegrity, dependency.Path, dependency.MimeType, false);
if (dependency.DevelopmentDownloadUrl is null)
{
writer.WriteLine($"internal static EmbeddedDependency {dependency.Name}Development => {dependency.Name};");
}
else
{
writer.WriteSummaryDocumentation($"");
WriteProperty(client, writer, dependency.Name + "Development", dependency.DevelopmentDownloadUrl, dependency.DevelopmentIntegrity, dependency.Path, dependency.MimeType, true);
}
}
writer.WriteLine("public static IReadOnlyList GetAllProduction() =>");
writer.WriteLine('[');
using (new Indented(writer))
{
foreach (Dependency dependency in dependencies)
{
writer.WriteLine($"{dependency.Name},");
}
}
writer.WriteLine("];");
writer.WriteLine("public static IReadOnlyList GetAllDevelopment() =>");
writer.WriteLine('[');
using (new Indented(writer))
{
foreach (Dependency dependency in dependencies)
{
writer.WriteLine($"{dependency.Name}Development,");
}
}
writer.WriteLine("];");
}
}
private static void WriteProperty(HttpClient client, IndentedTextWriter writer, string name, string url, string? integrity, string path, string mimeType, bool isDevelopment)
{
writer.WriteLine($"public static EmbeddedDependency {name}");
using (new CurlyBrackets(writer))
{
writer.WriteLine("get");
using (new CurlyBrackets(writer))
{
using (new If(writer, "field == default"))
{
byte[] data = DownloadAsync(client, url, integrity).GetAwaiter().GetResult();
string content = GetLiteral(Encoding.UTF8.GetString(data));
string isDevelopmentString = isDevelopment ? "true" : "false";
writer.WriteLine($"ReadOnlySpan data = {content}u8;");
writer.WriteLine($"field = new(Encoding.UTF8.GetString(data), {GetLiteral(path)}, {GetLiteral(mimeType)}, {isDevelopmentString});");
}
writer.WriteLine("return field;");
}
}
}
private static HttpClient CreateClient()
{
HttpClient client = new();
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AssetRipper.GUI.Web.Dependencies.SourceGenerator", "1.0"));
return client;
}
private static async Task DownloadAsync(HttpClient client, string url, string? integrity)
{
using HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
byte[] result = await response.Content.ReadAsByteArrayAsync();
if (!ValidateIntegrity(result, integrity))
{
throw new InvalidOperationException($"Integrity check failed for {url}");
}
return result;
}
private static bool ValidateIntegrity(byte[] data, string? integrity)
{
if (string.IsNullOrEmpty(integrity))
{
return true;
}
const string Sha384Prefix = "sha384-";
if (integrity.StartsWith(Sha384Prefix, StringComparison.Ordinal))
{
byte[] hash = SHA384.HashData(data);
byte[] integrityHash = Convert.FromBase64String(integrity[Sha384Prefix.Length..]);
return hash.SequenceEqual(integrityHash);
}
else
{
return false;
}
}
private static string GetLiteral(string content) => SymbolDisplay.FormatLiteral(content, true);
}