diff --git a/AssetRipper.sln b/AssetRipper.sln
index 2aac040d7..0b0ece250 100644
--- a/AssetRipper.sln
+++ b/AssetRipper.sln
@@ -162,6 +162,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.SmartEnums", "S
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.Processing.SourceGenerator", "Source\AssetRipper.Processing.SourceGenerator\AssetRipper.Processing.SourceGenerator.csproj", "{6679D5D0-8D40-44AC-9D20-0FD1303D45A2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.GUI.Licensing.SourceGenerator", "Source\AssetRipper.GUI.Licensing.SourceGenerator\AssetRipper.GUI.Licensing.SourceGenerator.csproj", "{6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -340,6 +342,10 @@ Global
{6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj b/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj
new file mode 100644
index 000000000..ad10dee81
--- /dev/null
+++ b/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0
+ false
+ true
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs b/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs
new file mode 100644
index 000000000..20f32564c
--- /dev/null
+++ b/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs
@@ -0,0 +1,86 @@
+using AssetRipper.Text.SourceGeneration;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using SGF;
+using System.CodeDom.Compiler;
+using System.Collections.Immutable;
+
+namespace AssetRipper.GUI.Licensing.SourceGenerator;
+
+[IncrementalGenerator]
+public sealed class LicensesGenerator() : IncrementalGenerator(nameof(LicensesGenerator))
+{
+ public override void OnInitialize(SgfInitializationContext context)
+ {
+ IncrementalValueProvider> pipeline = context.AdditionalTextsProvider
+ .Where(static (text) => text.Path.EndsWith(".md"))
+ .Select(static (text, cancellationToken) =>
+ {
+ string name = Path.GetFileNameWithoutExtension(text.Path);
+ string? license = text.GetText(cancellationToken)?.ToString();
+ return (name, license);
+ })
+ .Where(static (pair) => pair.license != null)
+ .Collect()!;
+
+ context.RegisterSourceOutput(pipeline, GenerateCode);
+ }
+
+ private static void GenerateCode(SgfSourceProductionContext context, ImmutableArray<(string Name, string License)> array)
+ {
+ using StringWriter stringWriter = new();
+ using IndentedTextWriter writer = IndentedTextWriterFactory.Create(stringWriter);
+
+ writer.WriteGeneratedCodeWarning();
+ writer.WriteLineNoTabs();
+
+ writer.WriteLine("#nullable enable");
+ writer.WriteLineNoTabs();
+ writer.WriteFileScopedNamespace("AssetRipper.GUI.Licensing");
+ writer.WriteLineNoTabs();
+ writer.WriteLine("partial class Licenses");
+ using (new CurlyBrackets(writer))
+ {
+ List<(string, string)> sourceNames = new(array.Length);
+ foreach ((string name, string license) in array.OrderBy(pair => pair.Name))
+ {
+ string sourceName = name.Replace('.', '_').Replace('-', '_');
+ sourceNames.Add((name, sourceName));
+ writer.WriteLine($"private static global::System.ReadOnlySpan {sourceName}_span => {SymbolDisplay.FormatLiteral(license, true)}u8;");
+ writer.WriteLine($"private static string? {sourceName}_field;");
+ if (name != sourceName)
+ {
+ writer.WriteSummaryDocumentation(name);
+ }
+ writer.WriteLine($"public static string {sourceName} => {sourceName}_field ??= global::System.Text.Encoding.UTF8.GetString({sourceName}_span);");
+ writer.WriteLineNoTabs();
+ }
+
+ // Names list
+ writer.WriteLine("public static global::System.Collections.Generic.IReadOnlyList Names { get; } =");
+ writer.WriteLine('[');
+ using (new Indented(writer))
+ {
+ foreach ((string name, _) in sourceNames)
+ {
+ writer.WriteLine($"{SymbolDisplay.FormatLiteral(name, true)},");
+ }
+ }
+ writer.WriteLine("];");
+ writer.WriteLineNoTabs();
+
+ // Get method
+ writer.WriteLine("public static string? TryLoad(string name) => name switch");
+ using (new CurlyBracketsWithSemicolon(writer))
+ {
+ foreach ((string name, string sourceName) in sourceNames)
+ {
+ writer.WriteLine($"{SymbolDisplay.FormatLiteral(name, true)} => {sourceName},");
+ }
+ writer.WriteLine($"_ => null,");
+ }
+ }
+
+ context.AddSource("Licenses.g.cs", stringWriter.ToString());
+ }
+}
diff --git a/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj b/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj
index 40e6b131c..a4b5b52ac 100644
--- a/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj
+++ b/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj
@@ -4,11 +4,16 @@
true
..\0Bins\Other\AssetRipper.GUI.Licensing\$(Configuration)\
..\0Bins\obj\AssetRipper.GUI.Licensing\$(Configuration)\
+ true
-
+
+
+
+
+
diff --git a/Source/AssetRipper.GUI.Licensing/Licenses.cs b/Source/AssetRipper.GUI.Licensing/Licenses.cs
index c362d8339..3d2f9edcd 100644
--- a/Source/AssetRipper.GUI.Licensing/Licenses.cs
+++ b/Source/AssetRipper.GUI.Licensing/Licenses.cs
@@ -1,42 +1,24 @@
-using System.Reflection;
+namespace AssetRipper.GUI.Licensing;
-namespace AssetRipper.GUI.Licensing;
-
-public static class Licenses
+public static partial class Licenses
{
- private const string FilePrefix = "AssetRipper.GUI.Licensing.";
-
- private static Assembly Assembly => typeof(Licenses).Assembly;
-
- public static IReadOnlyList Names { get; } = Assembly
- .GetManifestResourceNames()
- .Select(t => t.Substring(FilePrefix.Length, t.Length - FilePrefix.Length - 3))
- .ToArray();
-
///
- /// Load a license file from the embedded resources.
+ /// Load a license file.
///
- /// The name of the file without any extension.
+ /// The name of the license (without any extension).
/// The loaded text.
- public static string Load(string fileName)
+ public static string Load(string name)
{
- if (TryLoad(fileName, out string? license))
+ if (TryLoad(name, out string? license))
{
return license;
}
- throw new LicenseNotFoundException(fileName);
+ throw new LicenseNotFoundException(name);
}
- public static bool TryLoad(string fileName, [NotNullWhen(true)] out string? license)
+ public static bool TryLoad(string name, [NotNullWhen(true)] out string? license)
{
- using Stream? stream = Assembly.GetManifestResourceStream(FilePrefix + fileName + ".md");
- if (stream is null)
- {
- license = null;
- return false;
- }
-
- license = new StreamReader(stream).ReadToEnd();
- return true;
+ license = TryLoad(name);
+ return license is not null;
}
}