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); }