Files
bd2/plugins/GameSdk/Generation/AssemblyNames.cs
T

180 lines
11 KiB
C#

using BD2.GameNames.Internal;
using Mono.Cecil;
using Mono.Cecil.Cil;
using static BD2.GameSdk.Generation.ShellGenerator;
namespace BD2.GameSdk.Generation;
internal static class AssemblyNames
{
internal static IEnumerable<TypeDefinition> Types(ModuleDefinition module) => module.GetTypes();
internal static string TypeKey(TypeReference type) => type switch
{
GenericParameter p => (p.Type == GenericParameterType.Method ? "!!" : "!") + p.Position,
GenericInstanceType g => TypeKey(g.ElementType) + "<" + string.Join(",", g.GenericArguments.Select(TypeKey)) + ">",
ArrayType a => TypeKey(a.ElementType) + "[" + new string(',', a.Rank - 1) + "]",
ByReferenceType b => TypeKey(b.ElementType) + "&",
PointerType p => TypeKey(p.ElementType) + "*",
OptionalModifierType m => TypeKey(m.ElementType) + " modopt(" + TypeKey(m.ModifierType) + ")",
RequiredModifierType m => TypeKey(m.ElementType) + " modreq(" + TypeKey(m.ModifierType) + ")",
FunctionPointerType f => "fnptr(" + TypeKey(f.ReturnType) + ":" + string.Join(",", f.Parameters.Select(p => TypeKey(p.ParameterType))) + ")",
_ => type.FullName.Replace('/', '+')
};
internal static string Signature(MemberReference member) => member switch
{
MethodReference m => $"{m.GenericParameters.Count}:{TypeKey(m.ReturnType)}({string.Join(",", m.Parameters.Select(p => TypeKey(p.ParameterType)))})",
FieldReference f => TypeKey(f.FieldType),
_ => throw new ArgumentException("Unsupported member " + member)
};
internal static ModuleDefinition ReadModule(string path, params string[] searchDirectories)
{
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
foreach (string directory in searchDirectories) resolver.AddSearchDirectory(directory);
return ModuleDefinition.ReadModule(path, new ReaderParameters { InMemory = true, AssemblyResolver = resolver });
}
internal static void SetTypeName(TypeReference type, string full)
{
if (type.DeclaringType != null) { type.Name = full[(full.LastIndexOf('+') + 1)..]; return; }
int dot = full.LastIndexOf('.');
type.Namespace = dot < 0 ? "" : full[..dot];
type.Name = full[(dot + 1)..];
}
internal static void Rewrite(ModuleDefinition module, NameTable table, bool toReadable)
{
var types = table.types.ToDictionary(t => toReadable ? t.original : t.readable);
var readableByOriginal = table.types.ToDictionary(t => t.original, t => t.readable);
string TranslateSignature(string sig)
{
// Type references in signatures are tokenized, so substring collisions are avoided.
return System.Text.RegularExpressions.Regex.Replace(sig, @"[^,:()\[\]&*<>\s]+", m => readableByOriginal.TryGetValue(m.Value, out string v) ? v : m.Value);
}
var members = table.members.Where(m => m.kind is "method" or "field").ToDictionary(
m => (toReadable ? m.declaring_type : readableByOriginal[m.declaring_type]) + "|" + m.kind + "|" + (toReadable ? m.original : m.readable) + "|" + (toReadable ? m.signature : TranslateSignature(m.signature)));
var namedMembers = table.members.Where(m => m.original != m.readable && m.kind is "method" or "field")
.Select(m => (toReadable ? m.declaring_type : readableByOriginal[m.declaring_type]) + "|" + m.kind + "|" + (toReadable ? m.original : m.readable)).ToHashSet(StringComparer.Ordinal);
bool IsGame(TypeReference type)
{
while (type is TypeSpecification s) type = s.ElementType;
while (type.DeclaringType != null) type = type.DeclaringType;
return type.Scope is AssemblyNameReference a ? a.Name == (toReadable ? table.assembly_name : ShellName) : toReadable && type.Scope == module;
}
var memberRefs = module.GetMemberReferences().ToList();
var attributes = new List<CustomAttribute>();
void Attributes(ICustomAttributeProvider provider)
{
if (provider.HasCustomAttributes) attributes.AddRange(provider.CustomAttributes);
}
Attributes(module); Attributes(module.Assembly);
foreach (TypeDefinition t in Types(module))
{
Attributes(t);
foreach (GenericParameter gp in t.GenericParameters) Attributes(gp);
foreach (InterfaceImplementation i in t.Interfaces) Attributes(i);
foreach (FieldDefinition f in t.Fields) Attributes(f);
foreach (PropertyDefinition p in t.Properties) Attributes(p);
foreach (EventDefinition e in t.Events) Attributes(e);
foreach (MethodDefinition m in t.Methods)
{
Attributes(m); Attributes(m.MethodReturnType);
foreach (ParameterDefinition p in m.Parameters) Attributes(p);
foreach (GenericParameter gp in m.GenericParameters) Attributes(gp);
}
}
foreach (CustomAttribute attribute in attributes) memberRefs.Add(attribute.Constructor);
foreach (TypeDefinition type in Types(module))
foreach (MethodDefinition method in type.Methods)
{
foreach (MethodReference ov in method.Overrides) memberRefs.Add(ov);
if (method.HasBody) foreach (Instruction instruction in method.Body.Instructions)
if (instruction.Operand is MemberReference mr) memberRefs.Add(mr);
}
foreach (MemberReference reference in memberRefs.Distinct())
{
MemberReference member = reference is GenericInstanceMethod gm ? gm.ElementMethod : reference;
if (member is not MethodReference && member is not FieldReference || member is IMemberDefinition || !IsGame(member.DeclaringType)) continue;
TypeReference declaring = member.DeclaringType is GenericInstanceType gi ? gi.ElementType : member.DeclaringType;
// CLR array Get/Set/Address pseudo-methods have no metadata definitions.
if (declaring is ArrayType) continue;
string key = TypeKey(declaring) + "|" + (member is MethodReference ? "method" : "field") + "|" + member.Name + "|" + Signature(member);
if (members.TryGetValue(key, out MemberName entry)) member.Name = toReadable ? entry.readable : entry.original;
else if (namedMembers.Contains(key[..key.LastIndexOf('|')])) throw new InvalidDataException("Game member signature does not match the names table: " + key);
}
// Snapshot names before renaming a parent of a nested type.
var renames = new Dictionary<TypeReference, string>();
void Visit(TypeReference type)
{
if (type == null || type is GenericParameter) return;
if (type is FunctionPointerType fp) { Visit(fp.ReturnType); foreach (ParameterDefinition p in fp.Parameters) Visit(p.ParameterType); return; }
if (type is TypeSpecification spec)
{
Visit(spec.ElementType);
if (type is GenericInstanceType gi) foreach (TypeReference a in gi.GenericArguments) Visit(a);
if (type is IModifierType modifier) Visit(modifier.ModifierType);
return;
}
if (!renames.ContainsKey(type) && IsGame(type))
{
if (!types.TryGetValue(TypeKey(type), out TypeName entry)) throw new InvalidDataException("Unmapped game type: " + TypeKey(type));
renames[type] = toReadable ? entry.readable : entry.original;
}
Visit(type.DeclaringType);
}
void VisitArgument(CustomAttributeArgument argument)
{
Visit(argument.Type);
if (argument.Value is TypeReference type) Visit(type);
if (argument.Value is CustomAttributeArgument boxed) VisitArgument(boxed);
if (argument.Value is CustomAttributeArgument[] array) foreach (CustomAttributeArgument item in array) VisitArgument(item);
}
foreach (CustomAttribute attribute in attributes)
{
// Decode blobs before changing assembly scopes. System.Type arguments are typed
// metadata even though the ECMA-335 blob stores assembly-qualified text.
foreach (CustomAttributeArgument argument in attribute.ConstructorArguments) VisitArgument(argument);
void NamedArguments(Mono.Collections.Generic.Collection<CustomAttributeNamedArgument> arguments, string kind)
{
for (int i = 0; i < arguments.Count; i++)
{
CustomAttributeNamedArgument argument = arguments[i]; VisitArgument(argument.Argument);
if (!IsGame(attribute.AttributeType)) continue;
string declaring = TypeKey(attribute.AttributeType);
MemberName entry = table.members.SingleOrDefault(m => m.kind == kind &&
(toReadable ? m.declaring_type : readableByOriginal[m.declaring_type]) == declaring &&
(toReadable ? m.original : m.readable) == argument.Name);
if (entry != null) arguments[i] = new CustomAttributeNamedArgument(toReadable ? entry.readable : entry.original, argument.Argument);
}
}
NamedArguments(attribute.Fields, "field"); NamedArguments(attribute.Properties, "property");
}
foreach (TypeReference t in module.GetTypeReferences()) Visit(t);
foreach (MemberReference member in memberRefs)
{
Visit(member.DeclaringType);
if (member is MethodReference m) { Visit(m.ReturnType); foreach (ParameterDefinition p in m.Parameters) Visit(p.ParameterType); if (m is GenericInstanceMethod gm) foreach (TypeReference a in gm.GenericArguments) Visit(a); }
if (member is FieldReference f) Visit(f.FieldType);
}
foreach (TypeDefinition type in Types(module))
{
Visit(type.BaseType);
foreach (InterfaceImplementation i in type.Interfaces) Visit(i.InterfaceType);
foreach (FieldDefinition f in type.Fields) Visit(f.FieldType);
foreach (PropertyDefinition p in type.Properties) { Visit(p.PropertyType); foreach (ParameterDefinition a in p.Parameters) Visit(a.ParameterType); }
foreach (EventDefinition e in type.Events) Visit(e.EventType);
foreach (GenericParameter gp in type.GenericParameters) foreach (GenericParameterConstraint c in gp.Constraints) Visit(c.ConstraintType);
foreach (MethodDefinition m in type.Methods)
{
Visit(m.ReturnType);
foreach (ParameterDefinition p in m.Parameters) Visit(p.ParameterType);
foreach (GenericParameter gp in m.GenericParameters) foreach (GenericParameterConstraint c in gp.Constraints) Visit(c.ConstraintType);
if (m.HasBody) { foreach (VariableDefinition v in m.Body.Variables) Visit(v.VariableType); foreach (ExceptionHandler h in m.Body.ExceptionHandlers) Visit(h.CatchType); foreach (Instruction i in m.Body.Instructions) if (i.Operand is TypeReference t) Visit(t); }
}
}
foreach (KeyValuePair<TypeReference, string> pair in renames) SetTypeName(pair.Key, pair.Value);
}
}