1600 lines
60 KiB
C#
Vendored
1600 lines
60 KiB
C#
Vendored
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<string> _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<string> 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<string> 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<string> SplitGenericArgs(string args)
|
|
{
|
|
var result = new List<string>();
|
|
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, @"<summary>|</summary>|<remarks>|</remarks>|<see cref=""([^""]+)""\s*/>|<param[^>]*>|</param>|<returns>|</returns>|<c>|</c>|<b>|</b>", "$1");
|
|
sb.AppendLine("// " + l.Trim());
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
class Program
|
|
{
|
|
static readonly HashSet<string> 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<BaseTypeDeclarationSyntax>().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<string>? 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<string>(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<string>? 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<string>(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<string>? 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<FieldDeclarationSyntax>()
|
|
.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<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var method in cls.Members.OfType<MethodDeclarationSyntax>())
|
|
{
|
|
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<string>();
|
|
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<BaseTypeDeclarationSyntax>().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<string>? 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<string>(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<FieldDeclarationSyntax>().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<BaseTypeDeclarationSyntax>().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<FieldDeclarationSyntax>().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<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var m in cls.Members.OfType<MethodDeclarationSyntax>())
|
|
{
|
|
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<string>();
|
|
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<MethodDeclarationSyntax>())
|
|
{
|
|
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<string>();
|
|
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<BaseTypeDeclarationSyntax>().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<BaseTypeDeclarationSyntax>().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<BaseTypeDeclarationSyntax>().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<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
var typeDeclarations = root.DescendantNodes().OfType<BaseTypeDeclarationSyntax>().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<MethodDeclarationSyntax>().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<string, int> fileMethodNames)
|
|
{
|
|
string routerPrefix = CSharpToGoConverter.ExportName(moduleName);
|
|
|
|
foreach (var m in cls.Members.OfType<MethodDeclarationSyntax>())
|
|
{
|
|
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<string>();
|
|
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);
|
|
}
|
|
}
|