Skip to content

Commit d4d8667

Browse files
committed
Added signature checker so I'll be able to track new methods in SubModule
Added the ability for the loader to be optional, depending on a module being present
1 parent a578600 commit d4d8667

13 files changed

Lines changed: 4270 additions & 791 deletions

File tree

‎build/common.props‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<!--BUTRShared Version-->
1313
<BUTRSharedVersion>3.0.0.142</BUTRSharedVersion>
1414
<!--ModuleManager Version-->
15-
<BUTRModuleManagerVersion>6.0.246</BUTRModuleManagerVersion>
15+
<BUTRModuleManagerVersion>6.0.247</BUTRModuleManagerVersion>
1616
<!--Harmony Extensions Version-->
1717
<HarmonyExtensionsVersion>3.2.0.77</HarmonyExtensionsVersion>
1818
</PropertyGroup>

‎src/Bannerlord.ModuleLoader.Injector/InjectorGenerator.cs‎

Lines changed: 135 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -9,162 +9,161 @@
99
using System.Text;
1010
using System.Text.RegularExpressions;
1111

12-
namespace Bannerlord.ModuleLoader.Injector
12+
namespace Bannerlord.ModuleLoader.Injector;
13+
14+
[Generator]
15+
public class InjectorGenerator : IIncrementalGenerator
1316
{
14-
[Generator]
15-
public class InjectorGenerator : IIncrementalGenerator
17+
private readonly record struct BuildProperties(string? OutputPath, string? CsProjPath, string? ModuleId);
18+
19+
private static readonly DiagnosticDescriptor MissingRequiredBuildProperties = new(
20+
"INJ001",
21+
"Missing Required Build Properties",
22+
"OutputPath, MSBuildProjectFullPath, or ModuleId not provided. Skipping generation.",
23+
"Generator",
24+
DiagnosticSeverity.Warning,
25+
true);
26+
27+
private static readonly DiagnosticDescriptor StartingModuleGeneration = new(
28+
"INJ002",
29+
"Starting Module Generation",
30+
"Generating module files for {0}...",
31+
"Generator",
32+
DiagnosticSeverity.Info,
33+
true);
34+
35+
private static readonly DiagnosticDescriptor ModuleGenerationSuccessful = new(
36+
"INJ003",
37+
"Module Generation Successful",
38+
"Successfully generated module files for {0}.",
39+
"Generator",
40+
DiagnosticSeverity.Info,
41+
true);
42+
43+
private static readonly DiagnosticDescriptor ModuleGenerationFailed = new(
44+
"INJ004",
45+
"Module Generation Failed",
46+
"An error occurred while generating module files for {0}: {1}",
47+
"Generator",
48+
DiagnosticSeverity.Error,
49+
true);
50+
51+
private static readonly Regex InvalidIdentifierCharacters = new("[^a-zA-Z0-9]", RegexOptions.Compiled);
52+
53+
public void Initialize(IncrementalGeneratorInitializationContext context)
1654
{
17-
private readonly record struct BuildProperties(string? OutputPath, string? CsProjPath, string? ModuleId);
18-
19-
private static readonly DiagnosticDescriptor MissingRequiredBuildProperties = new(
20-
"INJ001",
21-
"Missing Required Build Properties",
22-
"OutputPath, MSBuildProjectFullPath, or ModuleId not provided. Skipping generation.",
23-
"Generator",
24-
DiagnosticSeverity.Warning,
25-
true);
26-
27-
private static readonly DiagnosticDescriptor StartingModuleGeneration = new(
28-
"INJ002",
29-
"Starting Module Generation",
30-
"Generating module files for {0}...",
31-
"Generator",
32-
DiagnosticSeverity.Info,
33-
true);
34-
35-
private static readonly DiagnosticDescriptor ModuleGenerationSuccessful = new(
36-
"INJ003",
37-
"Module Generation Successful",
38-
"Successfully generated module files for {0}.",
39-
"Generator",
40-
DiagnosticSeverity.Info,
41-
true);
42-
43-
private static readonly DiagnosticDescriptor ModuleGenerationFailed = new(
44-
"INJ004",
45-
"Module Generation Failed",
46-
"An error occurred while generating module files for {0}: {1}",
47-
"Generator",
48-
DiagnosticSeverity.Error,
49-
true);
50-
51-
private static readonly Regex InvalidIdentifierCharacters = new("[^a-zA-Z0-9]", RegexOptions.Compiled);
52-
53-
public void Initialize(IncrementalGeneratorInitializationContext context)
55+
var buildProps = context.AnalyzerConfigOptionsProvider.Select((provider, _) => new BuildProperties
5456
{
55-
var buildProps = context.AnalyzerConfigOptionsProvider.Select((provider, _) => new BuildProperties
56-
{
57-
OutputPath = provider.GlobalOptions.TryGetValue("build_property.outputpath", out var path) ? path : null,
58-
CsProjPath = provider.GlobalOptions.TryGetValue("build_property.msbuildprojectfullpath", out var csPath) ? csPath : null,
59-
ModuleId = provider.GlobalOptions.TryGetValue("build_property.moduleid", out var moduleId) ? moduleId :
60-
provider.GlobalOptions.TryGetValue("build_property.modulename", out var moduleName) ? moduleName : null,
61-
});
62-
63-
// Generate output only if all required properties are available
64-
context.RegisterImplementationSourceOutput(buildProps, (spc, props) =>
65-
{
66-
if (props.OutputPath is null || props.CsProjPath is null || props.ModuleId is null)
67-
{
68-
spc.ReportDiagnostic(Diagnostic.Create(MissingRequiredBuildProperties, Location.None));
69-
70-
return;
71-
}
72-
73-
spc.ReportDiagnostic(Diagnostic.Create(StartingModuleGeneration, Location.None, props.ModuleId));
74-
75-
try
76-
{
77-
var fullPath = Path.Combine(Path.GetDirectoryName(props.CsProjPath)!, props.OutputPath);
78-
GenerateAssembly(fullPath, props.ModuleId);
79-
spc.ReportDiagnostic(Diagnostic.Create(ModuleGenerationSuccessful, Location.None, props.ModuleId));
80-
}
81-
catch (Exception ex)
82-
{
83-
spc.ReportDiagnostic(Diagnostic.Create(ModuleGenerationFailed, Location.None, props.ModuleId, $"{ex.GetType().Name}: {ex.Message}"));
84-
}
85-
});
86-
}
87-
88-
private static void GenerateAssembly(string fullPath, string moduleId)
57+
OutputPath = provider.GlobalOptions.TryGetValue("build_property.outputpath", out var path) ? path : null,
58+
CsProjPath = provider.GlobalOptions.TryGetValue("build_property.msbuildprojectfullpath", out var csPath) ? csPath : null,
59+
ModuleId = provider.GlobalOptions.TryGetValue("build_property.moduleid", out var moduleId) ? moduleId :
60+
provider.GlobalOptions.TryGetValue("build_property.modulename", out var moduleName) ? moduleName : null,
61+
});
62+
63+
// Generate output only if all required properties are available
64+
context.RegisterImplementationSourceOutput(buildProps, (spc, props) =>
8965
{
90-
CopyEmbeddedResourceToFile("Bannerlord.ModuleLoader.dll", Path.Combine(fullPath, $"Bannerlord.ModuleLoader.{moduleId}.dll"), stream => SetNames(moduleId, stream));
91-
CopyEmbeddedResourceToFile("Bannerlord.ModuleLoader.pdb", Path.Combine(fullPath, $"Bannerlord.ModuleLoader.{moduleId}.pdb"));
92-
}
66+
if (props.OutputPath is null || props.CsProjPath is null || props.ModuleId is null)
67+
{
68+
spc.ReportDiagnostic(Diagnostic.Create(MissingRequiredBuildProperties, Location.None));
9369

94-
private static void CopyEmbeddedResourceToFile(string resourceName, string destinationPath, Func<Stream, Stream>? transform = null)
95-
{
96-
using var resourceStream = typeof(InjectorGenerator).Assembly.GetManifestResourceStream(resourceName);
97-
if (resourceStream is null) return;
70+
return;
71+
}
9872

99-
using var outputStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write);
100-
using var finalStream = transform is not null ? transform(resourceStream) : resourceStream;
73+
spc.ReportDiagnostic(Diagnostic.Create(StartingModuleGeneration, Location.None, props.ModuleId));
10174

102-
finalStream.CopyTo(outputStream);
103-
}
75+
try
76+
{
77+
var fullPath = Path.Combine(Path.GetDirectoryName(props.CsProjPath)!, props.OutputPath);
78+
GenerateAssembly(fullPath, props.ModuleId);
79+
spc.ReportDiagnostic(Diagnostic.Create(ModuleGenerationSuccessful, Location.None, props.ModuleId));
80+
}
81+
catch (Exception ex)
82+
{
83+
spc.ReportDiagnostic(Diagnostic.Create(ModuleGenerationFailed, Location.None, props.ModuleId, $"{ex.GetType().Name}: {ex.Message}"));
84+
}
85+
});
86+
}
10487

105-
private static Stream SetNames(string moduleName, Stream? assemblyStream)
106-
{
107-
if (assemblyStream is null)
108-
throw new ArgumentNullException(nameof(assemblyStream));
88+
private static void GenerateAssembly(string fullPath, string moduleId)
89+
{
90+
CopyEmbeddedResourceToFile("Bannerlord.ModuleLoader.dll", Path.Combine(fullPath, $"Bannerlord.ModuleLoader.{moduleId}.dll"), stream => SetNames(moduleId, stream));
91+
CopyEmbeddedResourceToFile("Bannerlord.ModuleLoader.pdb", Path.Combine(fullPath, $"Bannerlord.ModuleLoader.{moduleId}.pdb"));
92+
}
10993

110-
using var modifiedAss = AssemblyDefinition.ReadAssembly(assemblyStream);
111-
modifiedAss.Name.Name = moduleName;
94+
private static void CopyEmbeddedResourceToFile(string resourceName, string destinationPath, Func<Stream, Stream>? transform = null)
95+
{
96+
using var resourceStream = typeof(InjectorGenerator).Assembly.GetManifestResourceStream(resourceName);
97+
if (resourceStream is null) return;
11298

113-
var type = modifiedAss.MainModule.GetType("Bannerlord.ModuleLoader.SubModule");
114-
type.Name = FlatName(moduleName);
99+
using var outputStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write);
100+
using var finalStream = transform is not null ? transform(resourceStream) : resourceStream;
115101

116-
var ms = new MemoryStream();
117-
modifiedAss.Write(ms, new WriterParameters { DeterministicMvid = true });
118-
ms.Seek(0, SeekOrigin.Begin);
119-
return ms;
120-
}
102+
finalStream.CopyTo(outputStream);
103+
}
121104

122-
private static string FlatNameRegex(string str) => InvalidIdentifierCharacters.Replace(str, "_");
105+
private static Stream SetNames(string moduleName, Stream? assemblyStream)
106+
{
107+
if (assemblyStream is null)
108+
throw new ArgumentNullException(nameof(assemblyStream));
123109

124-
private static string FlatName(string str)
125-
{
126-
if (string.IsNullOrWhiteSpace(str))
127-
return "_";
110+
using var modifiedAss = AssemblyDefinition.ReadAssembly(assemblyStream);
111+
modifiedAss.Name.Name = moduleName;
128112

129-
var sb = new StringBuilder(str.Length);
130-
var firstChar = true;
113+
var type = modifiedAss.MainModule.GetType("Bannerlord.ModuleLoader.SubModule");
114+
type.Name = FlatName(moduleName);
131115

132-
foreach (var c in str)
133-
{
134-
sb.Append(IsValidInIdentifier(c, firstChar) ? c : '_');
135-
firstChar = false;
136-
}
116+
var ms = new MemoryStream();
117+
modifiedAss.Write(ms, new WriterParameters { DeterministicMvid = true });
118+
ms.Seek(0, SeekOrigin.Begin);
119+
return ms;
120+
}
137121

138-
if (char.IsDigit(sb[0]))
139-
sb.Insert(0, '_');
122+
private static string FlatNameRegex(string str) => InvalidIdentifierCharacters.Replace(str, "_");
140123

141-
var result = sb.ToString();
124+
private static string FlatName(string str)
125+
{
126+
if (string.IsNullOrWhiteSpace(str))
127+
return "_";
142128

143-
// Ensure it's not a C# keyword
144-
if (SyntaxFacts.IsKeywordKind(SyntaxFacts.GetKeywordKind(result)))
145-
result = $"{result}_";
129+
var sb = new StringBuilder(str.Length);
130+
var firstChar = true;
146131

147-
return result;
132+
foreach (var c in str)
133+
{
134+
sb.Append(IsValidInIdentifier(c, firstChar) ? c : '_');
135+
firstChar = false;
148136
}
149137

150-
private static bool IsValidInIdentifier(char c, bool firstChar = true) => char.GetUnicodeCategory(c) switch
151-
{
152-
// Always allowed in C# identifiers
153-
UnicodeCategory.UppercaseLetter => true,
154-
UnicodeCategory.LowercaseLetter => true,
155-
UnicodeCategory.TitlecaseLetter => true,
156-
UnicodeCategory.ModifierLetter => true,
157-
UnicodeCategory.OtherLetter => true,
158-
159-
// Only allowed after first char
160-
UnicodeCategory.LetterNumber => !firstChar,
161-
UnicodeCategory.NonSpacingMark => !firstChar,
162-
UnicodeCategory.SpacingCombiningMark => !firstChar,
163-
UnicodeCategory.DecimalDigitNumber => !firstChar,
164-
UnicodeCategory.ConnectorPunctuation => !firstChar,
165-
UnicodeCategory.Format => !firstChar,
166-
167-
_ => false,
168-
};
138+
if (char.IsDigit(sb[0]))
139+
sb.Insert(0, '_');
140+
141+
var result = sb.ToString();
142+
143+
// Ensure it's not a C# keyword
144+
if (SyntaxFacts.IsKeywordKind(SyntaxFacts.GetKeywordKind(result)))
145+
result = $"{result}_";
146+
147+
return result;
169148
}
149+
150+
private static bool IsValidInIdentifier(char c, bool firstChar = true) => char.GetUnicodeCategory(c) switch
151+
{
152+
// Always allowed in C# identifiers
153+
UnicodeCategory.UppercaseLetter => true,
154+
UnicodeCategory.LowercaseLetter => true,
155+
UnicodeCategory.TitlecaseLetter => true,
156+
UnicodeCategory.ModifierLetter => true,
157+
UnicodeCategory.OtherLetter => true,
158+
159+
// Only allowed after the first char
160+
UnicodeCategory.LetterNumber => !firstChar,
161+
UnicodeCategory.NonSpacingMark => !firstChar,
162+
UnicodeCategory.SpacingCombiningMark => !firstChar,
163+
UnicodeCategory.DecimalDigitNumber => !firstChar,
164+
UnicodeCategory.ConnectorPunctuation => !firstChar,
165+
UnicodeCategory.Format => !firstChar,
166+
167+
_ => false,
168+
};
170169
}

‎src/Bannerlord.ModuleLoader.sln‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
2525
EndProject
2626
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bannerlord.ModuleLoader.Injector", "Bannerlord.ModuleLoader.Injector\Bannerlord.ModuleLoader.Injector.csproj", "{2B993E72-4587-416B-A13A-C1943C9FE1E5}"
2727
EndProject
28+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E129176A-EBD5-40D0-A358-672F6B97DFFE}"
29+
EndProject
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetMethodExtractor", "..\tools\NuGetMethodExtractor\NuGetMethodExtractor.csproj", "{79AB10F0-EEF0-4288-9261-175BA320F584}"
31+
EndProject
2832
Global
2933
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3034
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +43,10 @@ Global
3943
{2B993E72-4587-416B-A13A-C1943C9FE1E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
4044
{2B993E72-4587-416B-A13A-C1943C9FE1E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
4145
{2B993E72-4587-416B-A13A-C1943C9FE1E5}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{79AB10F0-EEF0-4288-9261-175BA320F584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47+
{79AB10F0-EEF0-4288-9261-175BA320F584}.Debug|Any CPU.Build.0 = Debug|Any CPU
48+
{79AB10F0-EEF0-4288-9261-175BA320F584}.Release|Any CPU.ActiveCfg = Release|Any CPU
49+
{79AB10F0-EEF0-4288-9261-175BA320F584}.Release|Any CPU.Build.0 = Release|Any CPU
4250
EndGlobalSection
4351
GlobalSection(SolutionProperties) = preSolution
4452
HideSolutionNode = FALSE
@@ -49,6 +57,8 @@ Global
4957
{F6746F72-C8F6-4E5A-BAD7-4465E7768A39} = {0A880434-9A10-41FB-AFD1-6AF39032617A}
5058
{AE4FD8A9-31B7-4E9E-A3FA-68B4787DCFC9} = {374A43C2-209F-47C2-8FD3-81A3FDD7D69F}
5159
{2B993E72-4587-416B-A13A-C1943C9FE1E5} = {0A880434-9A10-41FB-AFD1-6AF39032617A}
60+
{E129176A-EBD5-40D0-A358-672F6B97DFFE} = {374A43C2-209F-47C2-8FD3-81A3FDD7D69F}
61+
{79AB10F0-EEF0-4288-9261-175BA320F584} = {E129176A-EBD5-40D0-A358-672F6B97DFFE}
5262
EndGlobalSection
5363
GlobalSection(ExtensibilityGlobals) = postSolution
5464
SolutionGuid = {827F528D-7EB7-4B7C-8188-1F6A6D9DF9CD}

‎src/Bannerlord.ModuleLoader/Bannerlord.ModuleLoader.csproj‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net472</TargetFramework>
4+
<TargetFramework>netstandard2.0</TargetFramework>
55
<LangVersion>12.0</LangVersion>
66
<Nullable>enable</Nullable>
77
<Authors>BUTR</Authors>
@@ -34,16 +34,16 @@
3434
</ItemGroup>
3535

3636
<ItemGroup>
37-
<PackageReference Include="System.Reflection.Metadata" Version="9.0.2" Pack="false" />
37+
<PackageReference Include="System.Reflection.Metadata" Version="10.0.0" Pack="false" />
3838
</ItemGroup>
3939

4040
<ItemGroup>
4141
<PackageReference Include="NuGetizer" Version="0.9.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
4242
</ItemGroup>
4343

4444
<ItemGroup>
45-
<PackageReference Include="ILRepack" Version="2.0.37" PrivateAssets="all" />
46-
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.37" PrivateAssets="All" />
45+
<PackageReference Include="ILRepack" Version="2.0.44" PrivateAssets="all" />
46+
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.44.1" PrivateAssets="all" />
4747
</ItemGroup>
4848

4949
</Project>

0 commit comments

Comments
 (0)