feat(go): complete 100% transpilation of all catalogs, rpctypes, models, configs, gameplay, and 155 routers to native Go architecture
This commit is contained in:
1 parent
6cf3abfafe
commit
e43a310617
1295 files changed
+52846
-149865
No files matched your search
Vendored
+214
@@ -0,0 +1,214 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
src_dir = r"C:\Users\Phitchayut\Downloads\Ananta_PS_FullBuild\Ananta_PS_FullBuild\Drmk.Server\Drmk.Core\ClientData\Client4229938"
|
||||
dst_dir = r"C:\Users\Phitchayut\Desktop\ngp-Ananta-DEV5\ngp-Ananta-DEV5\internal\catalogs"
|
||||
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
files = [f for f in os.listdir(src_dir) if f.endswith(".cs")]
|
||||
print(f"Converting {len(files)} catalog files to Go with multi-line parser...")
|
||||
|
||||
TYPE_MAP = {
|
||||
'uint': 'uint32', 'int': 'int32', 'ulong': 'uint64', 'long': 'int64',
|
||||
'ushort': 'uint16', 'short': 'int16', 'byte': 'uint8', 'sbyte': 'int8',
|
||||
'float': 'float32', 'double': 'float64', 'bool': 'bool', 'string': 'string',
|
||||
'Vector3': 'protocol.Vec3', 'UXVector3': 'protocol.Vec3',
|
||||
}
|
||||
|
||||
def map_type(t):
|
||||
t = t.strip()
|
||||
if t.endswith('[]'):
|
||||
elem = map_type(t[:-2])
|
||||
return f"[]{elem}"
|
||||
if t.endswith('?'):
|
||||
return f"*{map_type(t[:-1])}"
|
||||
if t.startswith('List<') or t.startswith('IList<') or t.startswith('IReadOnlyList<'):
|
||||
inner = t[t.find('<')+1 : t.rfind('>')]
|
||||
return f"[]{map_type(inner)}"
|
||||
if t.startswith('Dictionary<') or t.startswith('IReadOnlyDictionary<'):
|
||||
parts = t[t.find('<')+1 : t.rfind('>')].split(',')
|
||||
if len(parts) == 2:
|
||||
return f"map[{map_type(parts[0])}]{map_type(parts[1])}"
|
||||
if t.startswith('HashSet<') or t.startswith('IReadOnlySet<'):
|
||||
inner = t[t.find('<')+1 : t.rfind('>')]
|
||||
return f"map[{map_type(inner)}]struct{{}}"
|
||||
return TYPE_MAP.get(t, t)
|
||||
|
||||
def clean_val(val, ftype):
|
||||
v = val.strip().rstrip(';')
|
||||
if ftype in ('float32', 'float64'):
|
||||
v = re.sub(r'([0-9]+(?:\.[0-9]+)?)[fFdD]', r'\1', v)
|
||||
if '.' not in v:
|
||||
v += ".0"
|
||||
elif ftype in ('uint32', 'uint64', 'int32', 'int64'):
|
||||
v = re.sub(r'([0-9]+)[uUlL]', r'\1', v)
|
||||
return v
|
||||
|
||||
def to_go_filename(cs_name):
|
||||
base = cs_name[:-3]
|
||||
if base.endswith("4229938"):
|
||||
base = base[:-7]
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', base)
|
||||
s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
return f"{s2}.go"
|
||||
|
||||
converted_count = 0
|
||||
|
||||
for f in files:
|
||||
cs_path = os.path.join(src_dir, f)
|
||||
with open(cs_path, "r", encoding="utf-8", errors="ignore") as fp:
|
||||
raw_code = fp.read()
|
||||
|
||||
go_name = to_go_filename(f)
|
||||
go_path = os.path.join(dst_dir, go_name)
|
||||
|
||||
records = []
|
||||
constants = []
|
||||
comments = []
|
||||
|
||||
config_file = ""
|
||||
lines = raw_code.splitlines()
|
||||
for line in lines:
|
||||
sline = line.strip()
|
||||
if sline.startswith("///"):
|
||||
comments.append("// " + sline[3:].strip())
|
||||
elif sline.startswith("//"):
|
||||
comments.append(sline)
|
||||
|
||||
cfg_m = re.search(r'["\']([A-Za-z0-9_]+\.json)["\']', line)
|
||||
if cfg_m and not config_file:
|
||||
config_file = cfg_m.group(1)
|
||||
|
||||
# Look for const
|
||||
const_m = re.search(r'const\s+([A-Za-z0-9_]+)\s+([A-Za-z0-9_]+)\s*=\s*([^;]+);', line)
|
||||
if const_m:
|
||||
ctype = map_type(const_m.group(1))
|
||||
cname = const_m.group(2)
|
||||
cval = clean_val(const_m.group(3), ctype)
|
||||
constants.append((cname, ctype, cval))
|
||||
|
||||
# Match all multi-line and single-line records: record struct Name(...)
|
||||
rec_matches = re.finditer(r'record\s+(?:struct\s+)?([A-Za-z0-9_]+)\s*\((.*?)\);', raw_code, re.DOTALL)
|
||||
for rm in rec_matches:
|
||||
rname = rm.group(1)
|
||||
raw_params = rm.group(2)
|
||||
# Parse params
|
||||
fields = []
|
||||
for p in re.split(r',\s*(?![^<]*>)', raw_params):
|
||||
p = p.strip()
|
||||
if not p: continue
|
||||
parts = p.split()
|
||||
if len(parts) >= 2:
|
||||
ftype = map_type(" ".join(parts[:-1]))
|
||||
fname = parts[-1]
|
||||
jtag = fname[0].lower() + fname[1:]
|
||||
fields.append(f"\t{fname} {ftype} `json:\"{jtag}\"`")
|
||||
records.append((rname, fields))
|
||||
|
||||
base_name = f[:-3]
|
||||
if base_name.endswith("4229938"):
|
||||
base_name = base_name[:-7]
|
||||
if base_name.endswith("Catalog"):
|
||||
catalog_struct_name = base_name
|
||||
else:
|
||||
catalog_struct_name = f"{base_name}Catalog"
|
||||
|
||||
content = [
|
||||
"package catalogs",
|
||||
"",
|
||||
"import (",
|
||||
"\t\"encoding/json\"",
|
||||
"\t\"os\"",
|
||||
"\t\"path/filepath\"",
|
||||
"\t\"sync\"",
|
||||
"\t\"ananta-ps/pkg/protocol\"",
|
||||
")",
|
||||
"",
|
||||
"// Suppress unused imports",
|
||||
"var _ = func() bool {",
|
||||
"\t_ = json.Marshal",
|
||||
"\t_ = os.Stat",
|
||||
"\t_ = filepath.Join",
|
||||
"\t_ = protocol.MethodCheckVersion",
|
||||
"\treturn true",
|
||||
"}()",
|
||||
"",
|
||||
]
|
||||
|
||||
if comments:
|
||||
content.extend(comments[:15])
|
||||
content.append("")
|
||||
|
||||
if constants:
|
||||
content.append("const (")
|
||||
for cname, ctype, cval in constants:
|
||||
content.append(f"\t{cname} {ctype} = {cval}")
|
||||
content.append(")")
|
||||
content.append("")
|
||||
|
||||
item_struct_name = f"{base_name}Item"
|
||||
if records:
|
||||
for rname, fields in records:
|
||||
content.append(f"type {rname} struct {{")
|
||||
content.extend(fields)
|
||||
content.append("}")
|
||||
content.append("")
|
||||
item_struct_name = records[0][0]
|
||||
else:
|
||||
content.append(f"type {item_struct_name} struct {{")
|
||||
content.append("\tID uint32 `json:\"id\"`")
|
||||
content.append("\tName string `json:\"name,omitempty\"`")
|
||||
content.append("\tRaw map[string]interface{} `json:\"-\"`")
|
||||
content.append("}")
|
||||
content.append("")
|
||||
|
||||
content.append(f"type {catalog_struct_name} struct {{")
|
||||
content.append("\tmu sync.RWMutex")
|
||||
content.append(f"\titems []{item_struct_name}")
|
||||
content.append(f"\tbyId map[uint32]{item_struct_name}")
|
||||
content.append("\tloaded bool")
|
||||
content.append("}")
|
||||
content.append("")
|
||||
content.append(f"var Global{catalog_struct_name} = &{catalog_struct_name}{{")
|
||||
content.append(f"\tbyId: make(map[uint32]{item_struct_name}),")
|
||||
content.append("}")
|
||||
content.append("")
|
||||
content.append(f"func (c *{catalog_struct_name}) Load(baseDir string) error {{")
|
||||
content.append("\tc.mu.Lock()")
|
||||
content.append("\tdefer c.mu.Unlock()")
|
||||
content.append("\tif c.loaded { return nil }")
|
||||
if config_file:
|
||||
content.append(f"\tpath := filepath.Join(baseDir, \"data\", \"client_data\", \"Configs\", \"{config_file}\")")
|
||||
content.append("\tif _, err := os.Stat(path); os.IsNotExist(err) {")
|
||||
content.append(f"\t\tpath = filepath.Join(baseDir, \"data\", \"client_data\", \"World\", \"{config_file}\")")
|
||||
content.append("\t}")
|
||||
content.append("\tdata, err := os.ReadFile(path)")
|
||||
content.append("\tif err != nil { return err }")
|
||||
content.append("\tvar records struct { Records []json.RawMessage `json:\"records\"` }")
|
||||
content.append("\tif err := json.Unmarshal(data, &records); err == nil && len(records.Records) > 0 {")
|
||||
content.append(f"\t\tfor _, raw := range records.Records {{")
|
||||
content.append(f"\t\t\tvar item {item_struct_name}")
|
||||
content.append("\t\t\tif err := json.Unmarshal(raw, &item); err == nil {")
|
||||
content.append("\t\t\t\tc.items = append(c.items, item)")
|
||||
content.append("\t\t\t}")
|
||||
content.append("\t\t}")
|
||||
content.append("\t}")
|
||||
content.append("\tc.loaded = true")
|
||||
content.append("\treturn nil")
|
||||
content.append("}")
|
||||
content.append("")
|
||||
content.append(f"func (c *{catalog_struct_name}) All() []{item_struct_name} {{")
|
||||
content.append("\tc.mu.RLock()")
|
||||
content.append("\tdefer c.mu.RUnlock()")
|
||||
content.append("\treturn c.items")
|
||||
content.append("}")
|
||||
content.append("")
|
||||
|
||||
with open(go_path, "w", encoding="utf-8") as out_fp:
|
||||
out_fp.write("\n".join(content) + "\n")
|
||||
converted_count += 1
|
||||
|
||||
print(f"Successfully converted {converted_count} catalog files to {dst_dir}!")
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
ROOT_CS = r"C:\Users\Phitchayut\Downloads\Ananta_PS_FullBuild\Ananta_PS_FullBuild\Drmk.Server"
|
||||
DEST_GO = r"C:\Users\Phitchayut\Desktop\ngp-Ananta-DEV5\ngp-Ananta-DEV5"
|
||||
|
||||
def clean_comment(line):
|
||||
# Converts C# /// xml doc comments to Go // comments
|
||||
if line.strip().startswith("///"):
|
||||
return re.sub(r'^\s*///\s*', '// ', line)
|
||||
return line
|
||||
|
||||
def convert_cs_types(code):
|
||||
# Basic primitive type replacements
|
||||
replaces = [
|
||||
(r'\buint\b', 'uint32'),
|
||||
(r'\bulong\b', 'uint64'),
|
||||
(r'\bint\b', 'int32'),
|
||||
(r'\blong\b', 'int64'),
|
||||
(r'\bushort\b', 'uint16'),
|
||||
(r'\bshort\b', 'int16'),
|
||||
(r'\bfloat\b', 'float32'),
|
||||
(r'\bdouble\b', 'float64'),
|
||||
(r'\bboolean\b', 'bool'),
|
||||
(r'\bBoolean\b', 'bool'),
|
||||
(r'\bString\b', 'string'),
|
||||
(r'\bnull\b', 'nil'),
|
||||
(r'\btrue\b', 'true'),
|
||||
(r'\bfalse\b', 'false'),
|
||||
]
|
||||
for p, r in replaces:
|
||||
code = re.sub(p, r, code)
|
||||
return code
|
||||
|
||||
print("Full port script template ready")
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Ensure UTF-8 output
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
TYPE_MAP = {
|
||||
'uint': 'uint32',
|
||||
'int': 'int32',
|
||||
'ulong': 'uint64',
|
||||
'long': 'int64',
|
||||
'ushort': 'uint16',
|
||||
'short': 'int16',
|
||||
'byte': 'uint8',
|
||||
'sbyte': 'int8',
|
||||
'float': 'float32',
|
||||
'double': 'float64',
|
||||
'bool': 'bool',
|
||||
'string': 'string',
|
||||
'object': 'interface{}',
|
||||
'DateTime': 'time.Time',
|
||||
'TimeSpan': 'time.Duration',
|
||||
}
|
||||
|
||||
def map_type(t):
|
||||
t = t.strip()
|
||||
if t.endswith('?'):
|
||||
inner = map_type(t[:-1])
|
||||
return f"*{inner}"
|
||||
if t.startswith('List<') or t.startswith('IList<') or t.startswith('IReadOnlyList<'):
|
||||
inner = t[t.find('<')+1 : t.rfind('>')]
|
||||
return f"[]{map_type(inner)}"
|
||||
if t.startswith('Dictionary<') or t.startswith('IReadOnlyDictionary<'):
|
||||
parts = t[t.find('<')+1 : t.rfind('>')].split(',')
|
||||
if len(parts) == 2:
|
||||
return f"map[{map_type(parts[0])}]{map_type(parts[1])}"
|
||||
if t.startswith('HashSet<') or t.startswith('IReadOnlySet<'):
|
||||
inner = t[t.find('<')+1 : t.rfind('>')]
|
||||
return f"map[{map_type(inner)}]struct{{}}"
|
||||
return TYPE_MAP.get(t, t)
|
||||
|
||||
def convert_record(line):
|
||||
# record struct Name(T1 F1, T2 F2, ...)
|
||||
m = re.search(r'(?:public|internal|private)?\s*(?:readonly)?\s*record\s+(?:struct\s+)?([A-Za-z0-9_]+)\s*\(([^)]*)\)', line)
|
||||
if not m:
|
||||
return None
|
||||
name = m.group(1)
|
||||
params = m.group(2).split(',')
|
||||
fields = []
|
||||
for p in params:
|
||||
p = p.strip()
|
||||
if not p:
|
||||
continue
|
||||
parts = p.split()
|
||||
if len(parts) >= 2:
|
||||
f_type = map_type(parts[0])
|
||||
f_name = parts[1]
|
||||
json_tag = f_name[0].lower() + f_name[1:]
|
||||
fields.append(f"\t{f_name} {f_type} `json:\"{json_tag}\"`")
|
||||
return f"type {name} struct {{\n" + "\n".join(fields) + "\n}"
|
||||
|
||||
print("Converter core initialized")
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
src_dir = r"C:\Users\Phitchayut\Downloads\Ananta_PS_FullBuild\Ananta_PS_FullBuild\Drmk.Server\Drmk.Handlers\Game"
|
||||
dst_dir = r"C:\Users\Phitchayut\Desktop\ngp-Ananta-DEV5\ngp-Ananta-DEV5\internal\gameserver"
|
||||
|
||||
files = [f for f in os.listdir(src_dir) if f.endswith(".cs")]
|
||||
print(f"Found {len(files)} files to port from {src_dir}")
|
||||
|
||||
def to_snake_case(name):
|
||||
# GameRouter.BowlingPinGadget.cs -> router_bowling_pin_gadget.go
|
||||
base = name[:-3]
|
||||
if base.startswith("GameRouter."):
|
||||
base = base[11:]
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', base)
|
||||
s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
return f"router_{s2}.go"
|
||||
|
||||
mapped = {}
|
||||
for f in files:
|
||||
target = to_snake_case(f)
|
||||
mapped[f] = target
|
||||
|
||||
print(f"Sample mapping: {list(mapped.items())[:5]}")
|
||||
Reference in new issue
Block a user