diff --git a/Common/Utility/Platform.cs b/Common/Utility/Platform.cs new file mode 100644 index 0000000..37efcfc --- /dev/null +++ b/Common/Utility/Platform.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.Common.Utility { + public enum PlatformType { + WinClient, + WinServer, + WinAny + } + + public static class PlatformUtility { + public static bool TryParse(string str, out PlatformType type) { + type = PlatformType.WinClient; + switch (str) { + case "win-client" or "wc": + type = PlatformType.WinClient; + return true; + case "win-server" or "ws": + type = PlatformType.WinServer; + return true; + case "win-any" or "wa": + type = PlatformType.WinAny; + return true; + default: + return false; + } + } + } +} diff --git a/SymbolGenerator/Commands/MainCommand.cs b/SymbolGenerator/Commands/MainCommand.cs index b51a4d8..851ac7c 100644 --- a/SymbolGenerator/Commands/MainCommand.cs +++ b/SymbolGenerator/Commands/MainCommand.cs @@ -25,10 +25,18 @@ public partial class MainCommand : ICommand [CommandOption("filters", 'f', Description = "List of filters to apply when generating symbols.")] public IReadOnlyList Filters { get; set; } = null!; + [CommandOption("platform", 'p', Description = "Target platform for symbol generation (e.g., win-client, win-server).", IsRequired = false)] + public string Platform { get; set; } = "win-client"; + public ValueTask ExecuteAsync(IConsole console) { DirectoryInfo Input = new(InputPath); DirectoryInfo Output = new(OutputPath); + if (Platform == "win-any") + Logger.Fatal("Platform 'win-any' is not supported for symbol generation. Please specify either 'win-client' or 'win-server'."); + + if (!PlatformUtility.TryParse(Platform, out PlatformType PlatformType)) + Logger.Fatal($"Invalid platform '{Platform}'. Supported platforms are: win-client, win-server."); // Ensure input directory exists if (Input.Exists is false) @@ -43,7 +51,7 @@ public ValueTask ExecuteAsync(IConsole console) { headerTracker = new( inputDirectory: Input, - checksumFile: new FileInfo(Path.Combine(Output.FullName, "header_checksums.json")), + checksumFile: new FileInfo(Path.Combine(Output.FullName, $"{Platform}_header_checksums.json")), searchPatterns: ["*.h", "*.hpp", "*.hh", "*.hxx"], filters: [.. Filters] ); @@ -108,11 +116,7 @@ public ValueTask ExecuteAsync(IConsole console) { if (variable.RawComment is null || variable.Location is null || !willBeParsed.Contains(variable.Location.File)) continue; - RawAnnotation[] anns = CommentParser.ParseAnnotations(variable.RawComment, variable.Location).ToArray(); - for (int i = 0; i < anns.Length; i++) - { - anns[i].Target = variable; - } + RawAnnotation[] anns = CommentParser.ParseAnnotations(variable, variable.RawComment, variable.Location).ToArray(); annotations.AddRange(anns); } @@ -121,11 +125,7 @@ public ValueTask ExecuteAsync(IConsole console) { if (cls.RawComment is null || cls.Location is null) continue; - RawAnnotation[] anns = CommentParser.ParseAnnotations(cls.RawComment, cls.Location).ToArray(); - for (int i = 0; i < anns.Length; i++) - { - anns[i].Target = cls; - } + RawAnnotation[] anns = CommentParser.ParseAnnotations(cls, cls.RawComment, cls.Location).ToArray(); annotations.AddRange(anns); } @@ -134,11 +134,7 @@ public ValueTask ExecuteAsync(IConsole console) { if (method.RawComment is null || method.Location is null) continue; - RawAnnotation[] anns = CommentParser.ParseAnnotations(method.RawComment, method.Location).ToArray(); - for (int i = 0; i < anns.Length; i++) - { - anns[i].Target = method; - } + RawAnnotation[] anns = CommentParser.ParseAnnotations(method, method.RawComment, method.Location).ToArray(); annotations.AddRange(anns); } }); @@ -146,7 +142,7 @@ public ValueTask ExecuteAsync(IConsole console) Utils.Benchmark("Process annotations", () => { // Process extracted annotations - var processor = new AnnotationProcessor(); + var processor = new AnnotationProcessor(PlatformType); Utils.Benchmark("Process and resolve annotations", () => processor.ProcessAndResolve(annotations)); foreach (var processed in processor.ProcessedAnnotations) { diff --git a/SymbolGenerator/Parsing/Annotations/AbstractAnnotationHandler.cs b/SymbolGenerator/Parsing/Annotations/AbstractAnnotationHandler.cs index 8088a60..fce519b 100644 --- a/SymbolGenerator/Parsing/Annotations/AbstractAnnotationHandler.cs +++ b/SymbolGenerator/Parsing/Annotations/AbstractAnnotationHandler.cs @@ -2,11 +2,17 @@ namespace Amethyst.SymbolGenerator.Parsing.Annotations { - public abstract class AbstractAnnotationHandler(AnnotationProcessor processor) + public enum HandlerAction { + Handle, + SilentlySkip + } + + public abstract class AbstractAnnotationHandler(AnnotationProcessor processor, RawAnnotation annotation) { public readonly AnnotationProcessor Processor = processor; + public readonly RawAnnotation Annotation = annotation; - public abstract void CanHandle(RawAnnotation annotation); + public abstract HandlerAction CanHandle(RawAnnotation annotation); public abstract ProcessedAnnotation Handle(RawAnnotation annotation); } } diff --git a/SymbolGenerator/Parsing/Annotations/AbstractAnnotationTarget.cs b/SymbolGenerator/Parsing/Annotations/AbstractAnnotationTarget.cs index dfaee7c..33edaf3 100644 --- a/SymbolGenerator/Parsing/Annotations/AbstractAnnotationTarget.cs +++ b/SymbolGenerator/Parsing/Annotations/AbstractAnnotationTarget.cs @@ -6,17 +6,16 @@ public abstract class AbstractAnnotationTarget public HashSet Annotations { get; set; } = []; - public bool HasAnnotation(string tag) + public bool HasAnnotation(AnnotationID id) { - string officialTag = AnnotationProcessor.GetOfficialTagForAlias(tag); - return Annotations.Any(a => a.Annotation.Tag.Equals(officialTag, StringComparison.OrdinalIgnoreCase)); + return Annotations.Any(a => a.ID == id); } - public bool HasAnyOfAnnotations(IEnumerable tags) + public bool HasAnyOfAnnotations(IEnumerable ids) { - foreach (var tag in tags) + foreach (var id in ids) { - if (HasAnnotation(tag)) + if (HasAnnotation(id)) return true; } return false; diff --git a/SymbolGenerator/Parsing/Annotations/AbstractParameterPack.cs b/SymbolGenerator/Parsing/Annotations/AbstractParameterPack.cs new file mode 100644 index 0000000..94c251e --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/AbstractParameterPack.cs @@ -0,0 +1,14 @@ +using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations { + public abstract class AbstractParameterPack(RawAnnotation annotation) { + public RawAnnotation Annotation { get; } = annotation; + + public abstract T Parse(); + } +} diff --git a/SymbolGenerator/Parsing/Annotations/AnnotationID.cs b/SymbolGenerator/Parsing/Annotations/AnnotationID.cs new file mode 100644 index 0000000..d8d6a75 --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/AnnotationID.cs @@ -0,0 +1,42 @@ +using Amethyst.Common.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations { + public class AnnotationID(string tag, PlatformType platform) { + public string Tag { get; } = tag; + public PlatformType Platform { get; } = platform; + + public override int GetHashCode() { + var canonicalTag = AnnotationProcessor.GetCanonicalTagForAlias(Tag); + var platformHash = Platform == PlatformType.WinAny ? 0 : (int)Platform; + return HashCode.Combine(canonicalTag, platformHash); + } + + public override bool Equals(object? otherV) + { + if (otherV is null || otherV is not AnnotationID other) + return false; + var thisCanonicalTag = AnnotationProcessor.GetCanonicalTagForAlias(Tag); + var thatCanonicalTag = AnnotationProcessor.GetCanonicalTagForAlias(other.Tag); + + if (thisCanonicalTag != thatCanonicalTag) + return false; + + if (Platform != PlatformType.WinAny && other.Platform != PlatformType.WinAny && Platform != other.Platform) + return false; + return true; + } + + public static bool operator==(AnnotationID lhs, AnnotationID rhs) { + return lhs.Equals(rhs); + } + + public static bool operator!=(AnnotationID lhs, AnnotationID rhs) { + return !lhs.Equals(rhs); + } + } +} diff --git a/SymbolGenerator/Parsing/Annotations/AnnotationProcessor.cs b/SymbolGenerator/Parsing/Annotations/AnnotationProcessor.cs index 0895906..7026127 100644 --- a/SymbolGenerator/Parsing/Annotations/AnnotationProcessor.cs +++ b/SymbolGenerator/Parsing/Annotations/AnnotationProcessor.cs @@ -1,14 +1,16 @@ using Amethyst.Common.Diagnostics; +using Amethyst.Common.Utility; using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; using System.Reflection; namespace Amethyst.SymbolGenerator.Parsing.Annotations { - public class AnnotationProcessor + public class AnnotationProcessor(PlatformType type) { public static Dictionary Handlers { get; private set; } public HashSet ProcessedAnnotations { get; } = []; public HashSet ResolvedAnnotations { get; } = []; + public PlatformType PlatformType { get; private set; } = type; static AnnotationProcessor() { @@ -23,7 +25,7 @@ static AnnotationProcessor() .ToDictionary(t => t.attr, t => t.type); } - public static string GetOfficialTagForAlias(string tag) + public static string GetCanonicalTagForAlias(string tag) { string tagLower = tag.ToLowerInvariant(); var handler = Handlers.FirstOrDefault(h => h.Key.Tags.Select(t => t.ToLowerInvariant()).Contains(tagLower)); @@ -59,25 +61,28 @@ private void Process(RawAnnotation annotation) return; } - AbstractAnnotationHandler handler = (AbstractAnnotationHandler)Activator.CreateInstance(handlerType, [this])!; ProcessedAnnotation processed; try { - handler.CanHandle(annotation); + AbstractAnnotationHandler handler = (AbstractAnnotationHandler)Activator.CreateInstance(handlerType, [this, annotation])!; + if (handler.CanHandle(annotation) == HandlerAction.SilentlySkip) + return; processed = handler.Handle(annotation); - annotation.Target.Annotations.Add(processed); } - catch (UnhandledAnnotationException ex) + catch (UnhandledAnnotationException ex) { Logger.Warn($"Skipping annotation '{annotation}' at {annotation.Location}: {ex.Message}"); return; } - catch + catch (Exception ex) { + Logger.Warn($"Skipping annotation '{annotation}' at {annotation.Location}: {ex.InnerException?.Message}"); return; } + ProcessedAnnotations.Add(processed); + annotation.Target.Annotations.Add(processed); } } } diff --git a/SymbolGenerator/Parsing/Annotations/Comments/CommentParser.cs b/SymbolGenerator/Parsing/Annotations/Comments/CommentParser.cs index e1670a7..8674fa6 100644 --- a/SymbolGenerator/Parsing/Annotations/Comments/CommentParser.cs +++ b/SymbolGenerator/Parsing/Annotations/Comments/CommentParser.cs @@ -5,7 +5,7 @@ namespace Amethyst.SymbolGenerator.Parsing.Annotations { public static partial class CommentParser { - public static IEnumerable ParseAnnotations(string comment, ASTCursorLocation location) + public static IEnumerable ParseAnnotations(AbstractAnnotationTarget target, string comment, ASTCursorLocation location) { using var sr = new StringReader(comment); string? line; @@ -21,7 +21,7 @@ public static IEnumerable ParseAnnotations(string comment, ASTCur string name = match.Groups[1].Value; string? parameters = match.Groups[2].Success ? match.Groups[2].Value : null; IEnumerable parts = (parameters?.Split(',') ?? []).Select(a => a.Trim().TrimStart('"').TrimEnd('"')); - yield return new RawAnnotation(name, parts, location); + yield return new RawAnnotation(name, parts, location, target); } } diff --git a/SymbolGenerator/Parsing/Annotations/Comments/RawAnnotation.cs b/SymbolGenerator/Parsing/Annotations/Comments/RawAnnotation.cs index a75c14a..0bfce13 100644 --- a/SymbolGenerator/Parsing/Annotations/Comments/RawAnnotation.cs +++ b/SymbolGenerator/Parsing/Annotations/Comments/RawAnnotation.cs @@ -1,9 +1,7 @@ namespace Amethyst.SymbolGenerator.Parsing.Annotations.Comments { - public record RawAnnotation(string Tag, IEnumerable Arguments, ASTCursorLocation Location) + public record RawAnnotation(string Tag, IEnumerable Arguments, ASTCursorLocation Location, AbstractAnnotationTarget Target) { - public AbstractAnnotationTarget Target { get; set; } = null!; - public override string ToString() { return $"[{Tag}(...)]"; diff --git a/SymbolGenerator/Parsing/Annotations/Handlers/AddressAnnotationHandler.cs b/SymbolGenerator/Parsing/Annotations/Handlers/AddressAnnotationHandler.cs index 6804eb1..3e540ee 100644 --- a/SymbolGenerator/Parsing/Annotations/Handlers/AddressAnnotationHandler.cs +++ b/SymbolGenerator/Parsing/Annotations/Handlers/AddressAnnotationHandler.cs @@ -1,13 +1,19 @@ using Amethyst.Common.Models; using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks; namespace Amethyst.SymbolGenerator.Parsing.Annotations.Handlers { [AnnotationHandler("address", ["addr", "absolute", "abs", "at"])] - public class AddressAnnotationHandler(AnnotationProcessor processor) : AbstractAnnotationHandler(processor) + public class AddressAnnotationHandler(AnnotationProcessor processor, RawAnnotation annotation) : AbstractAnnotationHandler(processor, annotation) { - public override void CanHandle(RawAnnotation annotation) + public AddressAnnotationParameterPack ParameterPack { get; } = new AddressAnnotationParameterPack(annotation).Parse(); + + public override HandlerAction CanHandle(RawAnnotation annotation) { + if (ParameterPack.Platform != Processor.PlatformType) + return HandlerAction.SilentlySkip; + if (annotation.Target is ASTMethod method) { if (!method.IsImported) @@ -23,28 +29,25 @@ public override void CanHandle(RawAnnotation annotation) throw new UnhandledAnnotationException($"Address annotation can only be applied to methods or variables. Applied to {annotation.Target.GetType().Name} instead.", annotation); } - if (annotation.Target.HasAnyOfAnnotations([annotation.Tag, "signature"])) + if (annotation.Target.HasAnyOfAnnotations([ + new(annotation.Tag, ParameterPack.Platform), + new("signature", ParameterPack.Platform) + ])) throw new UnhandledAnnotationException($"Multiple address or signature annotations applied to the same target {annotation.Target}.", annotation); - - string[] args = [.. annotation.Arguments]; - if (args.Length != 1) - throw new UnhandledAnnotationException($"Address annotation requires exactly one argument. Received {args.Length}", annotation); - - if (!ulong.TryParse(args[0].Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out _)) - throw new UnhandledAnnotationException($"Address annotation argument must be a valid hexadecimal number. Received {args[0]}", annotation); + return HandlerAction.Handle; } public override ProcessedAnnotation Handle(RawAnnotation annotation) { - string[] args = [.. annotation.Arguments]; if (annotation.Target is ASTVariable variable) { return new ProcessedAnnotation( annotation, + new(annotation.Tag, ParameterPack.Platform), new VariableSymbolModel { Name = variable.MangledName, - Address = args[0] + Address = $"0x{ParameterPack.Address:x}" } ); } @@ -52,10 +55,11 @@ public override ProcessedAnnotation Handle(RawAnnotation annotation) { return new ProcessedAnnotation( annotation, + new(annotation.Tag, ParameterPack.Platform), new FunctionSymbolModel { Name = method.MangledName, - Address = args[0] + Address = $"0x{ParameterPack.Address:x}" } ); } diff --git a/SymbolGenerator/Parsing/Annotations/Handlers/SignatureAnnotationHandler.cs b/SymbolGenerator/Parsing/Annotations/Handlers/SignatureAnnotationHandler.cs index 84698ff..1d2350c 100644 --- a/SymbolGenerator/Parsing/Annotations/Handlers/SignatureAnnotationHandler.cs +++ b/SymbolGenerator/Parsing/Annotations/Handlers/SignatureAnnotationHandler.cs @@ -1,46 +1,45 @@ using Amethyst.Common.Models; using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks; using System.Text.RegularExpressions; namespace Amethyst.SymbolGenerator.Parsing.Annotations.Handlers { [AnnotationHandler("signature", ["sig", "pattern"])] - public partial class SignatureAnnotationHandler(AnnotationProcessor processor) : AbstractAnnotationHandler(processor) + public partial class SignatureAnnotationHandler(AnnotationProcessor processor, RawAnnotation annotation) : AbstractAnnotationHandler(processor, annotation) { - public override void CanHandle(RawAnnotation annotation) + public SignatureAnnotationParameterPack ParameterPack { get; } = new SignatureAnnotationParameterPack(annotation).Parse(); + + public override HandlerAction CanHandle(RawAnnotation annotation) { + if (ParameterPack.Platform != Processor.PlatformType) + return HandlerAction.SilentlySkip; + if (annotation.Target is not ASTMethod method) throw new UnhandledAnnotationException($"Signature annotation can only be applied to methods. Applied to {annotation.Target.GetType().Name} instead.", annotation); - - if (annotation.Target.HasAnyOfAnnotations([annotation.Tag, "address"])) - throw new UnhandledAnnotationException($"Multiple signature or address annotations applied to the same target {annotation.Target}.", annotation); - if (!method.IsImported) throw new UnhandledAnnotationException("Signature annotation can only be applied to imported methods.", annotation); - - string[] args = [.. annotation.Arguments]; - if (args.Length != 1) - throw new UnhandledAnnotationException($"Signature annotation requires exactly one argument. Received {args.Length}", annotation); - - if (!IDASignatureRegex().IsMatch(args[0])) - throw new UnhandledAnnotationException($"Signature annotation argument must be a valid IDA-style signature. Received {args[0]}", annotation); + + if (annotation.Target.HasAnyOfAnnotations([ + new(annotation.Tag, ParameterPack.Platform), + new("address", ParameterPack.Platform) + ])) + throw new UnhandledAnnotationException($"Multiple signature or address annotations applied to the same target {annotation.Target}.", annotation); + return HandlerAction.Handle; } public override ProcessedAnnotation Handle(RawAnnotation annotation) { ASTMethod target = (annotation.Target as ASTMethod)!; - string[] args = [.. annotation.Arguments]; return new ProcessedAnnotation( annotation, + new(annotation.Tag, ParameterPack.Platform), new FunctionSymbolModel { Name = target.MangledName, - Signature = args[0] + Signature = ParameterPack.Signature } ); } - - [GeneratedRegex(@"^(?:[0-9A-Fa-f]{2}|\?)(?:\s+(?:[0-9A-Fa-f]{2}|\?))*$")] - private static partial Regex IDASignatureRegex(); } } diff --git a/SymbolGenerator/Parsing/Annotations/Handlers/VirtualIndexAnnotationHandler.cs b/SymbolGenerator/Parsing/Annotations/Handlers/VirtualIndexAnnotationHandler.cs index 644db0c..db67a1c 100644 --- a/SymbolGenerator/Parsing/Annotations/Handlers/VirtualIndexAnnotationHandler.cs +++ b/SymbolGenerator/Parsing/Annotations/Handlers/VirtualIndexAnnotationHandler.cs @@ -1,6 +1,7 @@ using Amethyst.Common.Models; using Amethyst.Common.Utility; using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks; using System; using System.Collections.Generic; using System.Linq; @@ -10,49 +11,44 @@ namespace Amethyst.SymbolGenerator.Parsing.Annotations.Handlers { [AnnotationHandler("vidx", ["virtualindex", "vindex"])] - public class VirtualIndexAnnotationHandler(AnnotationProcessor processor) : AbstractAnnotationHandler(processor) + public class VirtualIndexAnnotationHandler(AnnotationProcessor processor, RawAnnotation annotation) : AbstractAnnotationHandler(processor, annotation) { - public override void CanHandle(RawAnnotation annotation) + public VirtualIndexAnnotationParameterPack ParameterPack { get; } = new VirtualIndexAnnotationParameterPack(annotation).Parse(); + + public override HandlerAction CanHandle(RawAnnotation annotation) { - if (annotation.Target is not ASTMethod method) - throw new UnhandledAnnotationException($"Virtual index annotation can only be applied to methods. Applied to {annotation.Target.GetType().Name} instead.", annotation); + if (ParameterPack.Platform != processor.PlatformType) + return HandlerAction.SilentlySkip; - if (!method.IsVirtual) - throw new UnhandledAnnotationException("Virtual index annotation can only be applied to virtual methods.", annotation); + // Validated by the Parameter Pack + ASTMethod method = (annotation.Target as ASTMethod)!; if (!method.IsImported) throw new UnhandledAnnotationException("Virtual index annotation can only be applied to imported methods.", annotation); - if (annotation.Target.HasAnyOfAnnotations([annotation.Tag, "address", "signature"])) + if (annotation.Target.HasAnyOfAnnotations([ + new(annotation.Tag, ParameterPack.Platform), + new("address", ParameterPack.Platform), + new("signature", ParameterPack.Platform) + ])) throw new UnhandledAnnotationException($"Multiple virtual index, address or signature annotations applied to the same target {annotation.Target}.", annotation); - - string[] args = [.. annotation.Arguments]; - if (args.Length < 1 || args.Length >= 3) - throw new UnhandledAnnotationException($"Virtual index annotation requires one or two arguments. Received {args.Length}", annotation); - - bool inherit = args[0] == "inherit" || args[0] == "i"; - if (inherit && method.OverrideOf is null) - throw new UnhandledAnnotationException("Virtual index annotation 'inherit' argument can only be used on methods that override a base method.", annotation); - - if (!inherit && !uint.TryParse(args[0], out _)) - throw new UnhandledAnnotationException($"Virtual index annotation first argument must be a unsigned integer. Received {args[0]}", annotation); + return HandlerAction.Handle; } public override ProcessedAnnotation Handle(RawAnnotation annotation) { + // Validated by the Parameter Pack ASTMethod target = (annotation.Target as ASTMethod)!; - string[] args = [.. annotation.Arguments]; - bool inherit = args[0] == "inherit" || args[0] == "i"; - string vtableName = args.Length > 1 ? args[1] : "this"; return new ProcessedAnnotation( annotation, + new(annotation.Tag, ParameterPack.Platform), new VirtualFunctionSymbolModel { Name = target.MangledName, - Index = inherit ? 0 : uint.Parse(args[0]), - VirtualTable = $"{target.DeclaringClass!.FullName}::vtable::'{vtableName}'", - Inherit = inherit, - Overrides = inherit ? target.OverrideOf!.MangledName : null + Index = ParameterPack.Index, + VirtualTable = $"{target.DeclaringClass!.FullName}::vtable::'{ParameterPack.TargetVirtualTable}'", + Inherit = ParameterPack.ShouldInherit, + Overrides = ParameterPack.ShouldInherit ? target.OverrideOf!.MangledName : null }, Resolve ); diff --git a/SymbolGenerator/Parsing/Annotations/Handlers/VirtualPointerAnnotationHandler.cs b/SymbolGenerator/Parsing/Annotations/Handlers/VirtualPointerAnnotationHandler.cs index 65550c0..17bab53 100644 --- a/SymbolGenerator/Parsing/Annotations/Handlers/VirtualPointerAnnotationHandler.cs +++ b/SymbolGenerator/Parsing/Annotations/Handlers/VirtualPointerAnnotationHandler.cs @@ -1,45 +1,47 @@ using Amethyst.Common.Models; using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks; namespace Amethyst.SymbolGenerator.Parsing.Annotations.Handlers { [AnnotationHandler("vtable", ["virtualpointer", "vtableptr", "vtablepointer", "vptr"])] - public class VirtualPointerAnnotation(AnnotationProcessor processor) : AbstractAnnotationHandler(processor) + public class VirtualPointerAnnotation(AnnotationProcessor processor, RawAnnotation annotation) : AbstractAnnotationHandler(processor, annotation) { - public override void CanHandle(RawAnnotation annotation) - { - if (annotation.Target is not ASTClass cls) - throw new UnhandledAnnotationException($"Virtual table annotation can only be applied to classes. Applied to {annotation.Target.GetType().Name} instead.", annotation); + public VirtualPointerAnnotationParameterPack ParameterPack { get; } = new VirtualPointerAnnotationParameterPack(annotation).Parse(); - string[] args = [.. annotation.Arguments]; - if (args.Length < 1 || args.Length >= 4) - throw new UnhandledAnnotationException($"Virtual table annotation requires exactly one, two or three arguments. Received {args.Length}", annotation); + public override HandlerAction CanHandle(RawAnnotation annotation) + { + if (ParameterPack.Platform != processor.PlatformType) + return HandlerAction.SilentlySkip; - var targetAnnotations = annotation.Target.Annotations.Where(a => AnnotationProcessor.GetOfficialTagForAlias(a.Annotation.Tag) == "vtable"); + var targetAnnotations = annotation.Target.Annotations.Where(a => AnnotationProcessor.GetCanonicalTagForAlias(a.Annotation.Tag) == "vtable"); foreach (var existing in targetAnnotations) { - if (existing.Annotation.Arguments.ElementAt(1) == args[1]) - throw new UnhandledAnnotationException($"Multiple virtual table annotations with the same label '{args[1]}' applied to the same target {annotation.Target}.", annotation); + if (existing.ID.Platform != ParameterPack.Platform) + continue; + VirtualPointerAnnotationParameterPack existingParameterPack = new VirtualPointerAnnotationParameterPack(existing.Annotation) + .Parse(); + + if (existingParameterPack.TargetVirtualTable == ParameterPack.TargetVirtualTable) + throw new UnhandledAnnotationException($"Multiple virtual table annotations with the same label '{ParameterPack.TargetVirtualTable}' applied to the same target {annotation.Target}.", annotation); } - if (!ulong.TryParse(args[0].Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out _)) - throw new UnhandledAnnotationException($"Virtual table annotation first argument must be a valid hexadecimal number. Received {args[0]}", annotation); + return HandlerAction.Handle; } public override ProcessedAnnotation Handle(RawAnnotation annotation) { + // Validated by the Parameter Pack ASTClass target = (annotation.Target as ASTClass)!; - string[] args = [.. annotation.Arguments]; - string label = args.Length > 1 ? args[1] : "this"; - string? vtableMangledLabel = args.Length > 2 ? args[2] : null; return new ProcessedAnnotation( annotation, + new(annotation.Tag, ParameterPack.Platform), new VirtualTableSymbolModel { - Name = $"{target.FullName}::vtable::'{label}'", - Address = args[0], - ForWhat = label, - VtableMangledLabel = vtableMangledLabel + Name = $"{target.FullName}::vtable::'{ParameterPack.TargetVirtualTable}'", + Address = $"0x{ParameterPack.Address:x}", + ForWhat = ParameterPack.TargetVirtualTable, + VtableMangledLabel = ParameterPack.VirtualTableMangledName } ); } diff --git a/SymbolGenerator/Parsing/Annotations/ParameterPacks/AddressAnnotationParameterPack.cs b/SymbolGenerator/Parsing/Annotations/ParameterPacks/AddressAnnotationParameterPack.cs new file mode 100644 index 0000000..bd3ef16 --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/ParameterPacks/AddressAnnotationParameterPack.cs @@ -0,0 +1,27 @@ +using Amethyst.Common.Utility; +using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks { + public class AddressAnnotationParameterPack(RawAnnotation annotation) : AbstractParameterPack(annotation) { + public ulong Address { get; private set; } + public PlatformType Platform { get; private set; } = PlatformType.WinClient; + + public override AddressAnnotationParameterPack Parse() { + string[] args = [..Annotation.Arguments]; + if (args.Length < 1) + throw new UnhandledAnnotationException("Address annotation requires at least one argument.", Annotation); + if (!ulong.TryParse(args[0].Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out ulong address)) + throw new UnhandledAnnotationException($"Address annotation first argument must be a valid hexadecimal number. Received {args[0]}", Annotation); + Address = address; + if (args.Length > 1) + if (PlatformUtility.TryParse(args[1], out var platformType)) + Platform = platformType; + return this; + } + } +} diff --git a/SymbolGenerator/Parsing/Annotations/ParameterPacks/SignatureAnnotationParameterPack.cs b/SymbolGenerator/Parsing/Annotations/ParameterPacks/SignatureAnnotationParameterPack.cs new file mode 100644 index 0000000..b7afbd2 --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/ParameterPacks/SignatureAnnotationParameterPack.cs @@ -0,0 +1,31 @@ +using Amethyst.Common.Utility; +using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks { + public partial class SignatureAnnotationParameterPack(RawAnnotation annotation) : AbstractParameterPack(annotation) { + public string Signature { get; private set; } = string.Empty; + public PlatformType Platform { get; private set; } = PlatformType.WinClient; + + public override SignatureAnnotationParameterPack Parse() { + string[] args = [.. Annotation.Arguments]; + if (args.Length < 1) + throw new UnhandledAnnotationException("Signature annotation requires at least one argument.", Annotation); + if (!IDASignatureRegex().IsMatch(args[0])) + throw new UnhandledAnnotationException($"Signature annotation first argument must be a valid IDA-style signature. Received {args[0]}", annotation); + Signature = args[0]; + if (args.Length > 1) + if (PlatformUtility.TryParse(args[1], out var platformType)) + Platform = platformType; + return this; + } + + [GeneratedRegex(@"^(?:[0-9A-Fa-f]{2}|\?)(?:\s+(?:[0-9A-Fa-f]{2}|\?))*$")] + private static partial Regex IDASignatureRegex(); + } +} diff --git a/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualIndexAnnotationParameterPack.cs b/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualIndexAnnotationParameterPack.cs new file mode 100644 index 0000000..d74d5e3 --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualIndexAnnotationParameterPack.cs @@ -0,0 +1,54 @@ +using Amethyst.Common.Utility; +using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks { + public class VirtualIndexAnnotationParameterPack(RawAnnotation annotation) : AbstractParameterPack(annotation) { + public uint Index { get; private set; } = 0; + public bool ShouldInherit { get; private set; } = false; + public string TargetVirtualTable { get; private set; } = string.Empty; + public PlatformType Platform { get; private set; } = PlatformType.WinClient; + + public override VirtualIndexAnnotationParameterPack Parse() { + if (annotation.Target is not ASTMethod method) + throw new UnhandledAnnotationException($"Virtual index annotation can only be applied to methods. Applied to {annotation.Target.GetType().Name} instead.", annotation); + + if (!method.IsVirtual) + throw new UnhandledAnnotationException("Virtual index annotation can only be applied to virtual methods.", annotation); + + string[] args = [.. Annotation.Arguments]; + if (args.Length < 1) + throw new UnhandledAnnotationException($"Virtual index annotation requires at least one argument. Received {args.Length}.", Annotation); + var indexArg = args[0]; + if (indexArg == "inherit" || indexArg == "i") { + ShouldInherit = true; + Index = 0; + } + else { + NumberStyles styles; + if (args[0].StartsWith("0x")) { + indexArg = indexArg.Replace("0x", ""); + styles = NumberStyles.HexNumber; + } + else + styles = NumberStyles.Integer; + if (!uint.TryParse(indexArg, styles, null, out uint index)) + throw new UnhandledAnnotationException($"Virtual index annotation first argument must be a valid hexadecimal or decimal number or \"inherit\"/\"i\". Received {args[0]}", Annotation); + Index = index; + } + + if (ShouldInherit && method.OverrideOf is null) + throw new UnhandledAnnotationException("Virtual index annotation 'inherit' argument can only be used on methods that override a base method.", annotation); + TargetVirtualTable = args.Length > 1 ? args[1] : "this"; + if (args.Length > 2) + if (PlatformUtility.TryParse(args[2], out var platformType)) + Platform = platformType; + return this; + } + } +} diff --git a/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualPointerAnnotationParameterPack.cs b/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualPointerAnnotationParameterPack.cs new file mode 100644 index 0000000..5eb4f02 --- /dev/null +++ b/SymbolGenerator/Parsing/Annotations/ParameterPacks/VirtualPointerAnnotationParameterPack.cs @@ -0,0 +1,47 @@ +using Amethyst.Common.Utility; +using Amethyst.SymbolGenerator.Parsing.Annotations.Comments; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Amethyst.SymbolGenerator.Parsing.Annotations.ParameterPacks { + public class VirtualPointerAnnotationParameterPack(RawAnnotation annotation) : AbstractParameterPack(annotation) { + public ulong Address { get; private set; } = 0; + public string TargetVirtualTable { get; private set; } = string.Empty; + public string? VirtualTableMangledName { get; set; } = null; + public PlatformType Platform { get; private set; } = PlatformType.WinClient; + + public override VirtualPointerAnnotationParameterPack Parse() { + if (Annotation.Target is not ASTClass cls) + throw new UnhandledAnnotationException($"Virtual pointer annotation can only be applied to classes or structs. Applied to {annotation.Target.GetType().Name} instead.", annotation); + + string[] args = [.. Annotation.Arguments]; + if (args.Length < 1) + throw new UnhandledAnnotationException($"Virtual pointer annotation requires at least one argument. Received {args.Length}.", Annotation); + + var addressArg = args[0]; + NumberStyles styles; + if (args[0].StartsWith("0x")) { + addressArg = addressArg.Replace("0x", ""); + styles = NumberStyles.HexNumber; + } + else + styles = NumberStyles.Integer; + if (!ulong.TryParse(addressArg, styles, null, out ulong addr)) + throw new UnhandledAnnotationException($"Virtual pointer annotation first argument must be a valid hexadecimal or decimal number. Received {args[0]}", Annotation); + Address = addr; + TargetVirtualTable = args.Length > 1 ? args[1] : "this"; + + if (args.Length > 2) + if (PlatformUtility.TryParse(args[2], out var platformType)) + Platform = platformType; + + if (args.Length > 3) + VirtualTableMangledName = args[3]; + return this; + } + } +} diff --git a/SymbolGenerator/Parsing/Annotations/ProcessedAnnotation.cs b/SymbolGenerator/Parsing/Annotations/ProcessedAnnotation.cs index 3727131..6c92dae 100644 --- a/SymbolGenerator/Parsing/Annotations/ProcessedAnnotation.cs +++ b/SymbolGenerator/Parsing/Annotations/ProcessedAnnotation.cs @@ -3,7 +3,7 @@ namespace Amethyst.SymbolGenerator.Parsing.Annotations { - public record ProcessedAnnotation(RawAnnotation Annotation, object Data, Action, ProcessedAnnotation, AnnotationProcessor>? ResolveReferences = null) + public record ProcessedAnnotation(RawAnnotation Annotation, AnnotationID ID, object Data, Action, ProcessedAnnotation, AnnotationProcessor>? ResolveReferences = null) { public AbstractAnnotationTarget Target => Annotation.Target; public bool Resolved { get; private set; } = false; diff --git a/SymbolGenerator/Properties/launchSettings.json b/SymbolGenerator/Properties/launchSettings.json index 5a5ffbe..4ccf029 100644 --- a/SymbolGenerator/Properties/launchSettings.json +++ b/SymbolGenerator/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Generate Symbols": { "commandName": "Project", - "commandLineArgs": "-i \"./input/src\"\r\n-o \"./output\"\r\n-f \"mc\"\r\n--\r\n-x c++\r\n-std=c++23\r\n-include-pch \"./input/src/pch.hpp.pch\"\r\n-fms-extensions\r\n-fms-compatibility\r\n-fms-define-stdc\r\n-I\"./input/include\"\r\n-I\"./input/src\"" + "commandLineArgs": "-i \"./input/src\"\r\n-o \"./output\"\r\n-f \"mc\"\r\n-p \"win-client\"\r\n--\r\n-x c++\r\n-std=c++23\r\n-include-pch \"./input/src/pch.hpp.pch\"\r\n-fms-extensions\r\n-fms-compatibility\r\n-fms-define-stdc\r\n-I\"./input/include\"\r\n-I\"./input/src\"" } } } \ No newline at end of file