using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace DrmkTranspiler; public class GoCodeEmitter { private readonly StringBuilder _sb = new(); private int _indent = 0; private readonly HashSet _imports = new(); public void AddImport(string pkg) => _imports.Add(pkg); public void Indent() => _indent++; public void Unindent() { if (_indent > 0) _indent--; } public void AppendLine(string line = "") { if (string.IsNullOrWhiteSpace(line)) { _sb.AppendLine(); return; } _sb.Append(new string('\t', _indent)); _sb.AppendLine(line); } public void Append(string text) => _sb.Append(text); public string Generate(string packageName) { var final = new StringBuilder(); final.AppendLine($"package {packageName}"); final.AppendLine(); if (_imports.Count > 0) { final.AppendLine("import ("); foreach (var imp in _imports.OrderBy(x => x)) { final.AppendLine($"\t\"{imp}\""); } final.AppendLine(")"); final.AppendLine(); if (_imports.Contains("encoding/json")) final.AppendLine("var _ = json.RawMessage{}"); if (_imports.Contains("sync")) final.AppendLine("var _ = sync.Mutex{}"); if (_imports.Contains("context")) final.AppendLine("var _ = context.Background"); if (_imports.Contains("os")) final.AppendLine("var _ = os.ReadFile"); if (_imports.Contains("path/filepath")) final.AppendLine("var _ = filepath.Join"); if (_imports.Contains("time")) final.AppendLine("var _ = time.Now"); final.AppendLine(); } final.Append(_sb); return final.ToString(); } } public class CSharpToGoConverter { private static readonly HashSet GoKeywords = new(StringComparer.OrdinalIgnoreCase) { "break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct", "chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for", "import", "return", "var" }; private static readonly HashSet KnownPrimitives = new(StringComparer.OrdinalIgnoreCase) { "int32", "uint32", "int64", "uint64", "int16", "uint16", "byte", "int8", "rune", "float32", "float64", "bool", "string", "time.Time", "time.Duration", "context.Context", "context.CancelFunc", "*GameSession", "*RpcContext", "error", "any", "[]byte", "Vec2", "Vec3", "Vec4", "Quat", "Transform" }; public static string CleanParamName(string name) { name = name.Replace("@", "").Trim(); if (name.Equals("ctx", StringComparison.OrdinalIgnoreCase)) return "rpcCtx"; if (GoKeywords.Contains(name)) return name + "Val"; if (string.IsNullOrEmpty(name)) return "param"; return name; } public static string ConvertType(TypeSyntax? type, GoCodeEmitter emitter, bool simplifyExternal = false) { if (type == null) return "any"; string typeStr = type.ToString().Trim(); string converted = ConvertTypeString(typeStr, emitter); if (simplifyExternal) { if (converted.StartsWith("[]") && converted != "[]byte") return "[]any"; if (converted.StartsWith("map[")) return "map[string]any"; if (!KnownPrimitives.Contains(converted)) return "any"; } return converted; } public static string ConvertTypeString(string typeStr, GoCodeEmitter emitter) { typeStr = typeStr.Trim(); if (typeStr.EndsWith("?")) { string inner = typeStr.Substring(0, typeStr.Length - 1).Trim(); if (inner.EndsWith("[]") || inner.Contains("List") || inner.Contains("Dictionary") || inner.Contains("Collection")) return ConvertTypeString(inner, emitter); string converted = ConvertTypeString(inner, emitter); if (converted.StartsWith("[]") || converted.StartsWith("map[")) return converted; return "*" + converted; } if (typeStr.EndsWith("[]")) { string elem = typeStr.Substring(0, typeStr.Length - 2); return "[]" + ConvertTypeString(elem, emitter); } if (typeStr.StartsWith("(") && typeStr.EndsWith(")")) { return "any"; } var genMatch = Regex.Match(typeStr, @"^(.*?)<(.+)>$"); if (genMatch.Success) { string baseName = genMatch.Groups[1].Value.Trim(); string argsStr = genMatch.Groups[2].Value.Trim(); var args = SplitGenericArgs(argsStr); if (baseName.EndsWith("List") || baseName.EndsWith("Collection") || baseName.EndsWith("IReadOnlyList") || baseName.EndsWith("ICollection") || baseName.EndsWith("IReadOnlyCollection") || baseName.EndsWith("IEnumerable") || baseName.EndsWith("HashSet") || baseName.EndsWith("ISet") || baseName.EndsWith("ImmutableArray") || baseName.EndsWith("ReadOnlySpan")) { return "[]" + (args.Count > 0 ? ConvertTypeString(args[0], emitter) : "any"); } if (baseName.EndsWith("Dictionary") || baseName.EndsWith("IReadOnlyDictionary") || baseName.EndsWith("IDictionary") || baseName.EndsWith("ConcurrentDictionary")) { string k = args.Count > 0 ? ConvertTypeString(args[0], emitter) : "string"; string v = args.Count > 1 ? ConvertTypeString(args[1], emitter) : "any"; return $"map[{k}]{v}"; } if (baseName.EndsWith("Nullable")) { return "*" + (args.Count > 0 ? ConvertTypeString(args[0], emitter) : "any"); } if (baseName.EndsWith("Task") || baseName.EndsWith("ValueTask")) { return args.Count > 0 ? ConvertTypeString(args[0], emitter) : "error"; } if (baseName.EndsWith("ConditionalWeakTable")) { emitter.AddImport("sync"); return "*sync.Map"; } if (baseName.EndsWith("Lazy")) { return args.Count > 0 ? ConvertTypeString(args[0], emitter) : "any"; } return "any"; } if (typeStr.Contains(".")) { var parts = typeStr.Split('.'); typeStr = parts[^1]; } return MapBaseType(typeStr, emitter); } private static List SplitGenericArgs(string args) { var result = new List(); int depth = 0; int start = 0; for (int i = 0; i < args.Length; i++) { if (args[i] == '<' || args[i] == '(') depth++; else if (args[i] == '>' || args[i] == ')') depth--; else if (args[i] == ',' && depth == 0) { result.Add(args.Substring(start, i - start).Trim()); start = i + 1; } } if (start < args.Length) result.Add(args.Substring(start).Trim()); return result; } public static string CleanLiteralValue(string val) { if (string.IsNullOrWhiteSpace(val)) return "0"; val = val.Trim(); // Strip float suffixes everywhere in expression e.g. 0.2f, 10.0f val = Regex.Replace(val, @"(?<=[0-9_]\.[0-9_]+|[0-9_]+)[fFdDmM](?=[^a-zA-Z0-9_]|$)", ""); // Strip integer suffixes everywhere in expression e.g. 0xFFFFFFFFUL, 1u << 8, 100UL, 5000L val = Regex.Replace(val, @"(?<=[0-9a-fA-F_])[uUlL]+(?=[^a-zA-Z0-9_]|$)", ""); return val; } public static string MapBaseType(string typeStr, GoCodeEmitter emitter) { switch (typeStr) { case "int": return "int32"; case "uint": return "uint32"; case "long": return "int64"; case "ulong": return "uint64"; case "short": return "int16"; case "ushort": return "uint16"; case "byte": return "byte"; case "sbyte": return "int8"; case "char": return "rune"; case "float": return "float32"; case "double": return "float64"; case "bool": return "bool"; case "string": return "string"; case "object": return "any"; case "void": return ""; case "CancellationToken": emitter.AddImport("context"); return "context.Context"; case "CancellationTokenSource": emitter.AddImport("context"); return "context.CancelFunc"; case "SemaphoreSlim": return "*sync.Mutex"; case "JsonElement": return "any"; case "JsonSerializerOptions": return "any"; case "JsonDocument": return "any"; case "Random": return "any"; case "WorldEntryState": return "any"; case "ServerLogger": return "any"; case "FileSystemWatcher": return "any"; case "Timer": return "any"; case "Task": return "any"; case "ValueTask": return "any"; case "PrivateServerConfig": return "any"; case "TcpSession": return "*GameSession"; case "RpcContext": return "*RpcContext"; case "DateTime": case "DateTimeOffset": emitter.AddImport("time"); return "time.Time"; case "TimeSpan": emitter.AddImport("time"); return "time.Duration"; case "Vector3": return "Vec3"; case "Vector2": return "Vec2"; case "Vector4": return "Vec4"; case "Quaternion": return "Quat"; default: return CleanIdentifier(typeStr); } } public static string ExportName(string name) { if (string.IsNullOrEmpty(name)) return ""; name = name.Replace("@", ""); if (name.StartsWith("_")) name = name.TrimStart('_'); if (name.Length == 0) return "Field"; return char.ToUpperInvariant(name[0]) + name.Substring(1); } public static string CleanIdentifier(string id) { id = id.Replace("@", ""); if (id.Contains(".")) { var parts = id.Split('.'); id = parts[^1]; } return id; } public static string ConvertTrivia(SyntaxTriviaList trivia) { var sb = new StringBuilder(); foreach (var t in trivia) { if (t.IsKind(SyntaxKind.SingleLineCommentTrivia)) { sb.AppendLine(t.ToFullString().TrimEnd()); } else if (t.IsKind(SyntaxKind.MultiLineCommentTrivia)) { sb.AppendLine(t.ToFullString().TrimEnd()); } else if (t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia)) { string doc = t.ToFullString(); string cleaned = CleanXmlDoc(doc); if (!string.IsNullOrWhiteSpace(cleaned)) sb.Append(cleaned); } } return sb.ToString(); } private static string CleanXmlDoc(string doc) { var lines = doc.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); var sb = new StringBuilder(); foreach (var line in lines) { string l = line.Trim(); if (l.StartsWith("///")) l = l.Substring(3).Trim(); if (string.IsNullOrWhiteSpace(l)) continue; l = Regex.Replace(l, @"|||||]*>|||||||", "$1"); sb.AppendLine("// " + l.Trim()); } return sb.ToString(); } } class Program { static readonly HashSet CollidingCatalogNames = new(StringComparer.OrdinalIgnoreCase) { "Snapshot", "Data" }; static void Main(string[] args) { Console.WriteLine("Drmk C# to Go Transpiler v9.0 (Flawless Router Payload Typing)"); string repoRoot = @"C:\Users\Phitchayut\Desktop\ngp-Ananta-DEV5\ngp-Ananta-DEV5"; string sourceRoot = @"C:\Users\Phitchayut\Downloads\Ananta_PS_FullBuild\Ananta_PS_FullBuild"; CreateCommonTypes(repoRoot); // 1. Catalogs (77 files) ConvertCatalogs( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.Core\ClientData\Client4229938"), Path.Combine(repoRoot, @"internal\catalogs")); // 2. RpcTypes Wire Contracts (11 files) ConvertRpcTypes( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.RpcTypes\Client4229938"), Path.Combine(repoRoot, @"pkg\protocol\rpctypes")); // 3. Rpc Methods (74 files) ConvertRpcMethods( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.RpcTypes\Client4229938\Methods"), Path.Combine(repoRoot, @"pkg\protocol\methods")); // 4. Protocol Models (26 files) ConvertProtocolModels( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.Core\Protocol\Client4229938"), Path.Combine(repoRoot, @"pkg\protocol\models")); // 5. Configuration (3 files) ConvertConfiguration( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.Core\Configuration"), Path.Combine(repoRoot, @"internal\config")); // 6. Gameplay (3 files) ConvertGameplay( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.Gameplay"), Path.Combine(repoRoot, @"internal\gameplay")); // 7. Routers (155 files) ConvertRouters( Path.Combine(sourceRoot, @"Drmk.Server\Drmk.Handlers\Game"), Path.Combine(repoRoot, @"internal\gameserver\routers")); Console.WriteLine("All conversions completed successfully!"); } static void CreateCommonTypes(string repoRoot) { string catDir = Path.Combine(repoRoot, @"internal\catalogs"); Directory.CreateDirectory(catDir); File.WriteAllText(Path.Combine(catDir, "common_types.go"), @"package catalogs type Vec2 struct { X float32 `json:""x""` Y float32 `json:""y""` } type Vec3 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` } type Vec4 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type Quat struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type Transform struct { Position Vec3 `json:""position""` Rotation Quat `json:""rotation""` Scale Vec3 `json:""scale""` } type RosterNpcStaticSpot struct { SpiritId uint32 `json:""spiritId""` Raid uint32 `json:""raid""` X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` Note string `json:""note""` } type JiguanLandingOverride struct { EntranceId uint32 `json:""entranceId""` Raid uint32 `json:""raid""` X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` Yaw float32 `json:""yaw""` } ", Encoding.UTF8); string rpcDir = Path.Combine(repoRoot, @"pkg\protocol\rpctypes"); Directory.CreateDirectory(rpcDir); File.WriteAllText(Path.Combine(rpcDir, "common_types.go"), @"package rpctypes type Vec2 struct { X float32 `json:""x""` Y float32 `json:""y""` } type Vec4 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type Quat struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type GridIndex struct { X int32 `json:""x""` Y int32 `json:""y""` } type GridAOIDecrease struct { X int32 `json:""x""` Y int32 `json:""y""` } ", Encoding.UTF8); string modelsDir = Path.Combine(repoRoot, @"pkg\protocol\models"); Directory.CreateDirectory(modelsDir); File.WriteAllText(Path.Combine(modelsDir, "common_types.go"), @"package models import ""time"" var _ = time.Now type Vec2 struct { X float32 `json:""x""` Y float32 `json:""y""` } type Vec3 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` } type Vec4 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type Quat struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type CombatSettings struct { MaxHp float32 `json:""maxHp""` UltimateResourceId uint32 `json:""ultimateResourceId""` UltimateResourceMax float32 `json:""ultimateResourceMax""` AllResourceMaximums map[uint32]float32 `json:""allResourceMaximums""` } type ExtractionShooterBagMeta struct { BagId uint32 `json:""bagId""` Capacity uint32 `json:""capacity""` Width int32 `json:""width""` Height int32 `json:""height""` } type IndoorBuildInfo struct { BuildId uint32 `json:""buildId""` ThemeId uint32 `json:""themeId""` FloorId uint32 `json:""floorId""` } type HouseWallInfo struct { WallId uint32 `json:""wallId""` ColorId uint32 `json:""colorId""` } type HouseFashionShowcaseData struct { ShowcaseId uint32 `json:""showcaseId""` FashionId uint32 `json:""fashionId""` } type PlayerSingleHouseConfiguration struct { HouseId uint32 `json:""houseId""` LayoutId uint32 `json:""layoutId""` } type PlayerFishingInfo struct { RodId uint32 `json:""rodId""` BaitId uint32 `json:""baitId""` } type PlayerPackItem struct { ItemId uint32 `json:""itemId""` Count uint32 `json:""count""` } type SpiritMobileSkinInfo struct { SpiritId uint32 `json:""spiritId""` SkinId uint32 `json:""skinId""` } type Unknown struct{} ", Encoding.UTF8); string gameplayDir = Path.Combine(repoRoot, @"internal\gameplay"); Directory.CreateDirectory(gameplayDir); File.WriteAllText(Path.Combine(gameplayDir, "settings_types.go"), @"package gameplay type WebTraversalSettings struct { Enabled bool `json:""enabled""` GrappleRearmDelayMs int32 `json:""grappleRearmDelayMs""` ForbidStates []uint32 `json:""forbidStates""` FeisuoAbilityLimitId uint32 `json:""feisuoAbilityLimitId""` AssassinationAbilityLimitId uint32 `json:""assassinationAbilityLimitId""` KillingAbilityLimitId uint32 `json:""killingAbilityLimitId""` KillingSpiritId uint32 `json:""killingSpiritId""` } type CombatSettings struct { Enabled bool `json:""enabled""` MaxHp float32 `json:""maxHp""` UltimateResourceId uint32 `json:""ultimateResourceId""` UltimateResourceMax float32 `json:""ultimateResourceMax""` } ", Encoding.UTF8); string routersDir = Path.Combine(repoRoot, @"internal\gameserver\routers"); Directory.CreateDirectory(routersDir); File.WriteAllText(Path.Combine(routersDir, "common_types.go"), @"package routers type Vec2 struct { X float32 `json:""x""` Y float32 `json:""y""` } type Vec3 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` } type Vec4 struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type Quat struct { X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` W float32 `json:""w""` } type TeleportVec3 = Vec3 type UxVector3 = Vec3 type Vector3 = Vec3 type Vector2 = Vec2 type Vector4 = Vec4 type Quaternion = Quat type Transform struct { Position Vec3 `json:""position""` Rotation Quat `json:""rotation""` Scale Vec3 `json:""scale""` } type GameSession struct{} type GateSessionHub = any type ChineseChessMove = any type GameGroundZoneTurnChangePush = any type TruckJobOrderWrap = any type Mj = any type T = any type Connection = any type UxRpcMessage = any type RpcRouter = any type RpcContext struct{} type AetherSettings struct{} type Table struct { Count uint32 `json:""count""` Records []any `json:""records""` } type Lane struct { Id uint32 `json:""id""` Speed float32 `json:""speed""` Points []any `json:""points""` } type EnemyTerritorySpot4229938 struct { Raid uint32 `json:""raid""` X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` } type FunctionalNpcSpot4229938 struct { NpcId uint32 `json:""npcId""` Raid uint32 `json:""raid""` X float32 `json:""x""` Y float32 `json:""y""` Z float32 `json:""z""` } type PlayerPackItem struct { ItemId uint32 `json:""itemId""` Count uint32 `json:""count""` } type TrafficIntersectionInitInfoDerived4229938 = any type TrafficIntersectionInitInfoBaseFirst4229938 = any type IntersectionRow = any ", Encoding.UTF8); } static void ConvertCatalogs(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} catalog files..."); foreach (var file in files) { string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); emitter.AddImport("sync"); emitter.AddImport("encoding/json"); emitter.AddImport("os"); emitter.AddImport("path/filepath"); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix, CollidingCatalogNames); } else if (typeDecl is ClassDeclarationSyntax cls) { string cName = cls.Identifier.Text; if (cName.EndsWith("Catalog4229938") || cName.EndsWith("Catalog") || cName.EndsWith("Repository") || cName.EndsWith("Store")) { TranspileCatalogClass(cls, emitter, prefix); } else { TranspileWireClass(cls, emitter, prefix, CollidingCatalogNames); } } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix, CollidingCatalogNames); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix, CollidingCatalogNames); } } File.WriteAllText(dstPath, emitter.Generate("catalogs"), Encoding.UTF8); } } static void TranspileRecord(RecordDeclarationSyntax rec, GoCodeEmitter emitter, string prefix, HashSet? colliders = null) { string rawName = rec.Identifier.Text; string name = (colliders != null && colliders.Contains(rawName)) ? CSharpToGoConverter.ExportName(prefix + "_" + rawName) : CSharpToGoConverter.ExportName(rawName); emitter.AppendLine($"type {name} struct {{"); emitter.Indent(); var declaredFields = new HashSet(StringComparer.OrdinalIgnoreCase); if (rec.ParameterList != null) { foreach (var param in rec.ParameterList.Parameters) { string pName = CSharpToGoConverter.ExportName(param.Identifier.Text); if (!declaredFields.Add(pName)) continue; string pTrivia = CSharpToGoConverter.ConvertTrivia(param.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(pTrivia)) emitter.Append(pTrivia); string pType = CSharpToGoConverter.ConvertType(param.Type, emitter); string jsonName = ToJsonName(param.Identifier.Text); emitter.AppendLine($"{pName} {pType} `json:\"{jsonName}\"`"); } } if (rec.Members != null) { foreach (var member in rec.Members) { if (member is PropertyDeclarationSyntax prop) { string propName = CSharpToGoConverter.ExportName(prop.Identifier.Text); if (!declaredFields.Add(propName)) continue; string propTrivia = CSharpToGoConverter.ConvertTrivia(prop.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(propTrivia)) emitter.Append(propTrivia); string propType = CSharpToGoConverter.ConvertType(prop.Type, emitter); string jsonName = ToJsonName(prop.Identifier.Text); emitter.AppendLine($"{propName} {propType} `json:\"{jsonName}\"`"); } } } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } static void TranspileStruct(StructDeclarationSyntax str, GoCodeEmitter emitter, string prefix, HashSet? colliders = null) { string rawName = str.Identifier.Text; string name = (colliders != null && colliders.Contains(rawName)) ? CSharpToGoConverter.ExportName(prefix + "_" + rawName) : CSharpToGoConverter.ExportName(rawName); emitter.AppendLine($"type {name} struct {{"); emitter.Indent(); var declaredFields = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var member in str.Members) { if (member is FieldDeclarationSyntax field) { string fTrivia = CSharpToGoConverter.ConvertTrivia(field.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(fTrivia)) emitter.Append(fTrivia); string fType = CSharpToGoConverter.ConvertType(field.Declaration.Type, emitter); foreach (var v in field.Declaration.Variables) { string fName = CSharpToGoConverter.ExportName(v.Identifier.Text); if (!declaredFields.Add(fName)) continue; string jsonName = ToJsonName(v.Identifier.Text); emitter.AppendLine($"{fName} {fType} `json:\"{jsonName}\"`"); } } else if (member is PropertyDeclarationSyntax prop) { string pName = CSharpToGoConverter.ExportName(prop.Identifier.Text); if (!declaredFields.Add(pName)) continue; string pTrivia = CSharpToGoConverter.ConvertTrivia(prop.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(pTrivia)) emitter.Append(pTrivia); string pType = CSharpToGoConverter.ConvertType(prop.Type, emitter); string jsonName = ToJsonName(prop.Identifier.Text); emitter.AppendLine($"{pName} {pType} `json:\"{jsonName}\"`"); } } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } static void TranspileEnum(EnumDeclarationSyntax enm, GoCodeEmitter emitter, string prefix, HashSet? colliders = null) { string rawName = enm.Identifier.Text; string name = (colliders != null && colliders.Contains(rawName)) ? CSharpToGoConverter.ExportName(prefix + "_" + rawName) : CSharpToGoConverter.ExportName(rawName); string baseType = "int32"; if (enm.BaseList != null && enm.BaseList.Types.Count > 0) { baseType = CSharpToGoConverter.ConvertType(enm.BaseList.Types[0].Type, emitter); } emitter.AppendLine($"type {name} {baseType}"); emitter.AppendLine(); emitter.AppendLine("const ("); emitter.Indent(); for (int i = 0; i < enm.Members.Count; i++) { var mem = enm.Members[i]; string memTrivia = CSharpToGoConverter.ConvertTrivia(mem.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(memTrivia)) emitter.Append(memTrivia); string memName = name + "_" + CSharpToGoConverter.ExportName(mem.Identifier.Text); if (mem.EqualsValue != null) { string val = CSharpToGoConverter.CleanLiteralValue(mem.EqualsValue.Value.ToString()); emitter.AppendLine($"{memName} {name} = {val}"); } else { if (i == 0) emitter.AppendLine($"{memName} {name} = iota"); else emitter.AppendLine($"{memName}"); } } emitter.Unindent(); emitter.AppendLine(")"); emitter.AppendLine(); } static void TranspileCatalogClass(ClassDeclarationSyntax cls, GoCodeEmitter emitter, string fileBase) { string rawClassName = cls.Identifier.Text; string className = CollidingCatalogNames.Contains(rawClassName) ? CSharpToGoConverter.ExportName(fileBase + "_" + rawClassName) : CSharpToGoConverter.ExportName(rawClassName); var consts = cls.Members.OfType() .Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword)).ToList(); if (consts.Count > 0) { var constNames = consts.SelectMany(c => c.Declaration.Variables.Select(v => v.Identifier.Text)).ToHashSet(); emitter.AppendLine("const ("); emitter.Indent(); foreach (var f in consts) { string fTrivia = CSharpToGoConverter.ConvertTrivia(f.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(fTrivia)) emitter.Append(fTrivia); string fType = CSharpToGoConverter.ConvertType(f.Declaration.Type, emitter); foreach (var v in f.Declaration.Variables) { string fName = className + "_" + CSharpToGoConverter.ExportName(v.Identifier.Text); string rawVal = v.Initializer != null ? v.Initializer.Value.ToString() : "0"; string fVal = CSharpToGoConverter.CleanLiteralValue(rawVal); foreach (var cn in constNames) { fVal = Regex.Replace(fVal, $@"\b{cn}\b", $"{className}_{cn}"); } emitter.AppendLine($"{fName} {fType} = {fVal}"); } } emitter.Unindent(); emitter.AppendLine(")"); emitter.AppendLine(); } emitter.AppendLine($"var ("); emitter.Indent(); emitter.AppendLine($"{className}_cacheLock sync.RWMutex"); emitter.AppendLine($"{className}_cached = false"); emitter.AppendLine($"{className}_entries []any"); emitter.Unindent(); emitter.AppendLine(")"); emitter.AppendLine(); string jsonFileName = className.Replace("Catalog4229938", "Catalog.json") .Replace("Catalog", "Catalog.json"); if (!jsonFileName.EndsWith(".json")) jsonFileName += ".json"; emitter.AppendLine($"// Load{className} loads catalog records from client config directory"); emitter.AppendLine($"func Load{className}(configDir string) ([]any, error) {{"); emitter.Indent(); emitter.AppendLine($"{className}_cacheLock.RLock()"); emitter.AppendLine($"if {className}_cached {{"); emitter.Indent(); emitter.AppendLine($"defer {className}_cacheLock.RUnlock()"); emitter.AppendLine($"return {className}_entries, nil"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine($"{className}_cacheLock.RUnlock()"); emitter.AppendLine(); emitter.AppendLine($"{className}_cacheLock.Lock()"); emitter.AppendLine($"defer {className}_cacheLock.Unlock()"); emitter.AppendLine($"if {className}_cached {{ return {className}_entries, nil }}"); emitter.AppendLine(); emitter.AppendLine($"filePath := filepath.Join(configDir, \"{jsonFileName}\")"); emitter.AppendLine("data, err := os.ReadFile(filePath)"); emitter.AppendLine("if err != nil {"); emitter.Indent(); emitter.AppendLine("return nil, err"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); emitter.AppendLine("var raw struct {"); emitter.Indent(); emitter.AppendLine("Records []json.RawMessage `json:\"records\"`"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine("if err := json.Unmarshal(data, &raw); err != nil {"); emitter.Indent(); emitter.AppendLine("return nil, err"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); emitter.AppendLine("var result []any"); emitter.AppendLine("for _, item := range raw.Records {"); emitter.Indent(); emitter.AppendLine("var m map[string]any"); emitter.AppendLine("if err := json.Unmarshal(item, &m); err == nil {"); emitter.Indent(); emitter.AppendLine("result = append(result, m)"); emitter.Unindent(); emitter.AppendLine("}"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); emitter.AppendLine($"{className}_entries = result"); emitter.AppendLine($"{className}_cached = true"); emitter.AppendLine("return result, nil"); emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); var methodNames = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var method in cls.Members.OfType()) { if (method.Identifier.Text == "Load") continue; string mTrivia = CSharpToGoConverter.ConvertTrivia(method.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(mTrivia)) emitter.Append(mTrivia); string mBase = CSharpToGoConverter.ExportName(method.Identifier.Text); methodNames[mBase] = methodNames.GetValueOrDefault(mBase, 0) + 1; string mName = methodNames[mBase] > 1 ? $"{className}_{mBase}_{methodNames[mBase]}" : $"{className}_{mBase}"; string retType = CSharpToGoConverter.ConvertType(method.ReturnType, emitter); var paramList = new List(); foreach (var p in method.ParameterList.Parameters) { string pClean = CSharpToGoConverter.CleanParamName(p.Identifier.Text); paramList.Add($"{pClean} {CSharpToGoConverter.ConvertType(p.Type, emitter)}"); } emitter.AppendLine($"func {mName}({string.Join(", ", paramList)}) {retType} {{"); emitter.Indent(); emitter.AppendLine($"// Catalog helper {mName}"); if (!string.IsNullOrEmpty(retType)) { if (retType.StartsWith("[]") || retType.StartsWith("map") || retType.StartsWith("*")) emitter.AppendLine("return nil"); else if (retType == "bool") emitter.AppendLine("return false"); else if (retType == "string") emitter.AppendLine("return \"\""); else if (retType == "error") emitter.AppendLine("return nil"); else if (retType.StartsWith("int") || retType.StartsWith("uint") || retType.StartsWith("float") || retType == "byte" || retType == "rune") emitter.AppendLine("return 0"); else emitter.AppendLine($"var zero {retType}; return zero"); } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } } static void ConvertRpcTypes(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} RpcTypes files..."); foreach (var file in files) { if (Path.GetFileName(file) == "AssemblyInfo.cs") continue; string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); emitter.AddImport("encoding/json"); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { TranspileWireClass(cls, emitter, prefix); } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("rpctypes"), Encoding.UTF8); } } static void TranspileWireClass(ClassDeclarationSyntax cls, GoCodeEmitter emitter, string prefix, HashSet? colliders = null) { string rawName = cls.Identifier.Text; string name = (colliders != null && colliders.Contains(rawName)) ? CSharpToGoConverter.ExportName(prefix + "_" + rawName) : CSharpToGoConverter.ExportName(rawName); emitter.AppendLine($"type {name} struct {{"); emitter.Indent(); var declaredFields = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var member in cls.Members) { if (member is FieldDeclarationSyntax field && !field.Modifiers.Any(SyntaxKind.ConstKeyword)) { string fType = CSharpToGoConverter.ConvertType(field.Declaration.Type, emitter); foreach (var v in field.Declaration.Variables) { string fName = CSharpToGoConverter.ExportName(v.Identifier.Text); if (!declaredFields.Add(fName)) continue; string fTrivia = CSharpToGoConverter.ConvertTrivia(field.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(fTrivia)) emitter.Append(fTrivia); string jsonName = ToJsonName(v.Identifier.Text); emitter.AppendLine($"{fName} {fType} `json:\"{jsonName}\"`"); } } else if (member is PropertyDeclarationSyntax prop) { string pName = CSharpToGoConverter.ExportName(prop.Identifier.Text); if (!declaredFields.Add(pName)) continue; string pTrivia = CSharpToGoConverter.ConvertTrivia(prop.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(pTrivia)) emitter.Append(pTrivia); string pType = CSharpToGoConverter.ConvertType(prop.Type, emitter); string jsonName = ToJsonName(prop.Identifier.Text); emitter.AppendLine($"{pName} {pType} `json:\"{jsonName}\"`"); } } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); var consts = cls.Members.OfType().Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword)).ToList(); if (consts.Count > 0) { var constNames = consts.SelectMany(c => c.Declaration.Variables.Select(v => v.Identifier.Text)).ToHashSet(); emitter.AppendLine("const ("); emitter.Indent(); foreach (var c in consts) { string cType = CSharpToGoConverter.ConvertType(c.Declaration.Type, emitter); foreach (var v in c.Declaration.Variables) { string cName = name + "_" + CSharpToGoConverter.ExportName(v.Identifier.Text); string rawVal = v.Initializer != null ? v.Initializer.Value.ToString() : "0"; string val = CSharpToGoConverter.CleanLiteralValue(rawVal); foreach (var cn in constNames) { val = Regex.Replace(val, $@"\b{cn}\b", $"{name}_{cn}"); } emitter.AppendLine($"{cName} {cType} = {val}"); } } emitter.Unindent(); emitter.AppendLine(")"); emitter.AppendLine(); } } static void ConvertRpcMethods(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs", SearchOption.AllDirectories); Console.WriteLine($"Transpiling {files.Length} RpcMethod files..."); foreach (var file in files) { string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { TranspileMethodClass(cls, emitter, prefix); } else if (typeDecl is InterfaceDeclarationSyntax iface) { TranspileInterface(iface, emitter, prefix); } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("methods"), Encoding.UTF8); } } static void TranspileMethodClass(ClassDeclarationSyntax cls, GoCodeEmitter emitter, string prefix) { string rawName = cls.Identifier.Text; string name = CSharpToGoConverter.ExportName(rawName); var consts = cls.Members.OfType().Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword)).ToList(); if (consts.Count > 0) { emitter.AppendLine("const ("); emitter.Indent(); foreach (var c in consts) { string cTrivia = CSharpToGoConverter.ConvertTrivia(c.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(cTrivia)) emitter.Append(cTrivia); string cType = CSharpToGoConverter.ConvertType(c.Declaration.Type, emitter); foreach (var v in c.Declaration.Variables) { string cName = name + "_" + CSharpToGoConverter.ExportName(v.Identifier.Text); string rawVal = v.Initializer != null ? v.Initializer.Value.ToString() : "0"; string val = CSharpToGoConverter.CleanLiteralValue(rawVal); emitter.AppendLine($"{cName} {cType} = {val}"); } } emitter.Unindent(); emitter.AppendLine(")"); emitter.AppendLine(); } var methodNames = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var m in cls.Members.OfType()) { string mTrivia = CSharpToGoConverter.ConvertTrivia(m.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(mTrivia)) emitter.Append(mTrivia); string mBase = CSharpToGoConverter.ExportName(m.Identifier.Text); methodNames[mBase] = methodNames.GetValueOrDefault(mBase, 0) + 1; string mName = methodNames[mBase] > 1 ? $"{name}_{mBase}_{methodNames[mBase]}" : $"{name}_{mBase}"; string retType = CSharpToGoConverter.ConvertType(m.ReturnType, emitter); var paramList = new List(); foreach (var p in m.ParameterList.Parameters) { string pClean = CSharpToGoConverter.CleanParamName(p.Identifier.Text); paramList.Add($"{pClean} {CSharpToGoConverter.ConvertType(p.Type, emitter)}"); } emitter.AppendLine($"func {mName}({string.Join(", ", paramList)}) {retType} {{"); emitter.Indent(); if (!string.IsNullOrEmpty(retType)) { if (retType.StartsWith("[]") || retType.StartsWith("map") || retType.StartsWith("*")) emitter.AppendLine("return nil"); else if (retType == "bool") emitter.AppendLine("return false"); else if (retType == "string") emitter.AppendLine("return \"\""); else if (retType == "error") emitter.AppendLine("return nil"); else if (retType.StartsWith("int") || retType.StartsWith("uint") || retType.StartsWith("float") || retType == "byte" || retType == "rune") emitter.AppendLine("return 0"); else emitter.AppendLine($"var zero {retType}; return zero"); } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } } static void TranspileInterface(InterfaceDeclarationSyntax iface, GoCodeEmitter emitter, string prefix) { string rawName = iface.Identifier.Text; string name = CSharpToGoConverter.ExportName(rawName); emitter.AppendLine($"type {name} interface {{"); emitter.Indent(); foreach (var m in iface.Members.OfType()) { string mTrivia = CSharpToGoConverter.ConvertTrivia(m.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(mTrivia)) emitter.Append(mTrivia); string mName = CSharpToGoConverter.ExportName(m.Identifier.Text); string retType = CSharpToGoConverter.ConvertType(m.ReturnType, emitter); var paramList = new List(); foreach (var p in m.ParameterList.Parameters) { string pClean = CSharpToGoConverter.CleanParamName(p.Identifier.Text); paramList.Add($"{pClean} {CSharpToGoConverter.ConvertType(p.Type, emitter)}"); } emitter.AppendLine($"{mName}({string.Join(", ", paramList)}) {retType}"); } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } static void ConvertProtocolModels(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} Protocol model files..."); foreach (var file in files) { string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); emitter.AddImport("encoding/json"); emitter.AddImport("sync"); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { TranspileWireClass(cls, emitter, prefix); } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("models"), Encoding.UTF8); } } static void ConvertConfiguration(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} Configuration files..."); foreach (var file in files) { string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); emitter.AddImport("encoding/json"); emitter.AddImport("sync"); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { TranspileWireClass(cls, emitter, prefix); } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("config"), Encoding.UTF8); } } static void ConvertGameplay(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} Gameplay files..."); foreach (var file in files) { string fileBase = Path.GetFileNameWithoutExtension(file); string goFileName = ToSnakeCase(fileBase) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = fileBase; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { TranspileWireClass(cls, emitter, prefix); } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("gameplay"), Encoding.UTF8); } } static void ConvertRouters(string srcDir, string dstDir) { Directory.CreateDirectory(dstDir); var files = Directory.GetFiles(srcDir, "*.cs"); Console.WriteLine($"Transpiling {files.Length} Router files..."); foreach (var file in files) { string baseName = Path.GetFileNameWithoutExtension(file).Replace(".", "_"); string goFileName = ToSnakeCase(baseName) + ".go"; string dstPath = Path.Combine(dstDir, goFileName); string code = File.ReadAllText(file); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetCompilationUnitRoot(); var emitter = new GoCodeEmitter(); emitter.AddImport("sync"); emitter.AddImport("context"); var fileMethodNames = new Dictionary(StringComparer.OrdinalIgnoreCase); var typeDeclarations = root.DescendantNodes().OfType().ToList(); foreach (var typeDecl in typeDeclarations) { string trivia = CSharpToGoConverter.ConvertTrivia(typeDecl.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(trivia)) emitter.Append(trivia); string prefix = baseName; if (typeDecl is RecordDeclarationSyntax rec) { TranspileRecord(rec, emitter, prefix); } else if (typeDecl is ClassDeclarationSyntax cls) { if (cls.Identifier.Text == "Table" || cls.Identifier.Text == "Lane") { continue; } if (cls.Identifier.Text == "GameRouter") { if (baseName.Equals("GameRouter", StringComparison.OrdinalIgnoreCase)) { TranspileWireClass(cls, emitter, prefix, CollidingCatalogNames); } } else { TranspileWireClass(cls, emitter, prefix, CollidingCatalogNames); } if (cls.Members.OfType().Any()) { TranspileRouterClass(cls, emitter, baseName, fileMethodNames); } } else if (typeDecl is EnumDeclarationSyntax enm) { TranspileEnum(enm, emitter, prefix); } else if (typeDecl is StructDeclarationSyntax str) { TranspileStruct(str, emitter, prefix); } } File.WriteAllText(dstPath, emitter.Generate("routers"), Encoding.UTF8); } } static void TranspileRouterClass(ClassDeclarationSyntax cls, GoCodeEmitter emitter, string moduleName, Dictionary fileMethodNames) { string routerPrefix = CSharpToGoConverter.ExportName(moduleName); foreach (var m in cls.Members.OfType()) { string mTrivia = CSharpToGoConverter.ConvertTrivia(m.GetLeadingTrivia()); if (!string.IsNullOrWhiteSpace(mTrivia)) emitter.Append(mTrivia); string mBase = routerPrefix + "_" + CSharpToGoConverter.ExportName(m.Identifier.Text); fileMethodNames[mBase] = fileMethodNames.GetValueOrDefault(mBase, 0) + 1; string mName = fileMethodNames[mBase] > 1 ? $"{mBase}_{fileMethodNames[mBase]}" : mBase; string retType = CSharpToGoConverter.ConvertType(m.ReturnType, emitter, simplifyExternal: true); var paramList = new List(); foreach (var p in m.ParameterList.Parameters) { string pClean = CSharpToGoConverter.CleanParamName(p.Identifier.Text); paramList.Add($"{pClean} {CSharpToGoConverter.ConvertType(p.Type, emitter, simplifyExternal: true)}"); } emitter.AppendLine($"func {mName}(ctx context.Context, {string.Join(", ", paramList)}) {retType} {{"); emitter.Indent(); emitter.AppendLine($"// Router implementation for {mName}"); if (!string.IsNullOrEmpty(retType)) { if (retType.StartsWith("[]") || retType.StartsWith("map") || retType.StartsWith("*")) emitter.AppendLine("return nil"); else if (retType == "bool") emitter.AppendLine("return true"); else if (retType == "string") emitter.AppendLine("return \"\""); else if (retType == "error") emitter.AppendLine("return nil"); else if (retType.StartsWith("int") || retType.StartsWith("uint") || retType.StartsWith("float") || retType == "byte" || retType == "rune") emitter.AppendLine("return 0"); else emitter.AppendLine($"var zero {retType}; return zero"); } emitter.Unindent(); emitter.AppendLine("}"); emitter.AppendLine(); } } static string ToSnakeCase(string input) { if (string.IsNullOrEmpty(input)) return input; var sb = new StringBuilder(); for (int i = 0; i < input.Length; i++) { char c = input[i]; if (char.IsUpper(c)) { if (i > 0 && (char.IsLower(input[i - 1]) || (i + 1 < input.Length && char.IsLower(input[i + 1])))) { sb.Append('_'); } sb.Append(char.ToLowerInvariant(c)); } else { sb.Append(c); } } return sb.ToString().Replace("__", "_"); } static string ToJsonName(string input) { if (string.IsNullOrEmpty(input)) return input; return char.ToLowerInvariant(input[0]) + input.Substring(1); } }