mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 15:08:47 -07:00
166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Decode a DorkNet ProtoDumper manifest into .proto text.
|
|
|
|
Usage:
|
|
python decode_dump.py manifest.tsv [out_dir]
|
|
|
|
manifest.tsv has one "<proto path>\\t<base64 FileDescriptorProto>" line per
|
|
file (as written by the ProtoDumper MelonLoader mod). This emits one .proto
|
|
file per descriptor under out_dir/ (default: ./proto_out), plus a combined
|
|
recroom_2023.proto with every rec_room/circuits message in one file for an
|
|
easy diff against DorkNet.Server/Protos/recroom_2020.proto.
|
|
|
|
Requires: pip install protobuf
|
|
"""
|
|
import sys, os, base64, re
|
|
from google.protobuf import descriptor_pb2 as D
|
|
|
|
FDP = D.FileDescriptorProto
|
|
|
|
# proto3 scalar type names keyed by FieldDescriptorProto.Type
|
|
SCALAR = {
|
|
1: "double", 2: "float", 3: "int64", 4: "uint64", 5: "int32",
|
|
6: "fixed64", 7: "fixed32", 8: "bool", 9: "string", 12: "bytes",
|
|
13: "uint32", 15: "sfixed32", 16: "sfixed64", 17: "sint32", 18: "sint64",
|
|
}
|
|
LABEL_REPEATED = 3
|
|
|
|
|
|
def type_name(f):
|
|
if f.type in (11, 14): # message / enum -> use type_name (strip leading dot)
|
|
return f.type_name.lstrip(".")
|
|
return SCALAR.get(f.type, f"type{f.type}")
|
|
|
|
|
|
def emit_enum(e, indent):
|
|
pad = " " * indent
|
|
out = [f"{pad}enum {e.name} {{"]
|
|
for v in e.value:
|
|
out.append(f"{pad} {v.name} = {v.number};")
|
|
out.append(f"{pad}}}")
|
|
return out
|
|
|
|
|
|
def emit_message(m, indent=0):
|
|
pad = " " * indent
|
|
out = [f"{pad}message {m.name} {{"]
|
|
|
|
# map<> detection: synthetic nested *Entry types with map_entry option
|
|
map_entries = {}
|
|
for nt in m.nested_type:
|
|
if nt.options.map_entry:
|
|
kt = type_name(nt.field[0])
|
|
vt = type_name(nt.field[1])
|
|
map_entries[nt.name] = (kt, vt)
|
|
|
|
# which oneofs are real (not proto3 optional synthetic)
|
|
for nt in m.nested_type:
|
|
if nt.name in map_entries:
|
|
continue
|
|
out += emit_message(nt, indent + 1)
|
|
for en in m.enum_type:
|
|
out += emit_enum(en, indent + 1)
|
|
|
|
# group fields by oneof
|
|
printed_oneof = set()
|
|
for f in m.field:
|
|
# map field?
|
|
if f.type == 11 and f.type_name.split(".")[-1] in map_entries:
|
|
kt, vt = map_entries[f.type_name.split(".")[-1]]
|
|
out.append(f"{pad} map<{kt}, {vt}> {f.name} = {f.number};")
|
|
continue
|
|
|
|
if f.HasField("oneof_index") and not f.proto3_optional:
|
|
idx = f.oneof_index
|
|
if idx not in printed_oneof:
|
|
printed_oneof.add(idx)
|
|
out.append(f"{pad} oneof {m.oneof_decl[idx].name} {{")
|
|
for g in m.field:
|
|
if g.HasField("oneof_index") and g.oneof_index == idx and not g.proto3_optional:
|
|
out.append(f"{pad} {type_name(g)} {g.name} = {g.number};")
|
|
out.append(f"{pad} }}")
|
|
continue
|
|
|
|
label = "repeated " if f.label == LABEL_REPEATED else ""
|
|
opt = "optional " if f.proto3_optional else ""
|
|
out.append(f"{pad} {label}{opt}{type_name(f)} {f.name} = {f.number};")
|
|
|
|
out.append(f"{pad}}}")
|
|
return out
|
|
|
|
|
|
def render_file(fdp):
|
|
out = []
|
|
syntax = fdp.syntax or "proto2"
|
|
out.append(f'syntax = "{syntax}";')
|
|
out.append("")
|
|
if fdp.package:
|
|
out.append(f"package {fdp.package};")
|
|
for dep in fdp.dependency:
|
|
out.append(f'import "{dep}";')
|
|
if fdp.package or fdp.dependency:
|
|
out.append("")
|
|
for en in fdp.enum_type:
|
|
out += emit_enum(en, 0)
|
|
out.append("")
|
|
for m in fdp.message_type:
|
|
out += emit_message(m, 0)
|
|
out.append("")
|
|
return "\n".join(out).rstrip() + "\n"
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__); sys.exit(1)
|
|
manifest = sys.argv[1]
|
|
out_dir = sys.argv[2] if len(sys.argv) > 2 else "proto_out"
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
|
|
files = []
|
|
with open(manifest, "r", encoding="utf-8") as fh:
|
|
for ln in fh:
|
|
ln = ln.rstrip("\n")
|
|
if not ln or "\t" not in ln:
|
|
continue
|
|
name, b64 = ln.split("\t", 1)
|
|
try:
|
|
raw = base64.b64decode(b64)
|
|
fdp = FDP(); fdp.ParseFromString(raw)
|
|
except Exception as e:
|
|
print(f" ! failed to parse {name}: {e}")
|
|
continue
|
|
files.append((name, fdp))
|
|
|
|
print(f"parsed {len(files)} descriptors")
|
|
|
|
# per-file .proto
|
|
for name, fdp in files:
|
|
safe = re.sub(r"[^A-Za-z0-9_.\-]", "_", name)
|
|
with open(os.path.join(out_dir, safe), "w", encoding="utf-8") as w:
|
|
w.write(render_file(fdp))
|
|
|
|
# combined rec_room/circuits view (skip google/* well-known imports)
|
|
rr = [ (n,f) for n,f in files if not n.startswith("google/") ]
|
|
seen = set(); combo = ['syntax = "proto3";', "", "package recroom;", ""]
|
|
total_msgs = 0
|
|
for name, fdp in sorted(rr, key=lambda x: x[0]):
|
|
combo.append(f"// ===== {name} (package {fdp.package or '-'}) =====")
|
|
for m in fdp.message_type:
|
|
if m.name in seen:
|
|
continue
|
|
seen.add(m.name)
|
|
combo += emit_message(m, 0)
|
|
combo.append("")
|
|
total_msgs += 1
|
|
for en in fdp.enum_type:
|
|
combo += emit_enum(en, 0)
|
|
combo.append("")
|
|
with open(os.path.join(out_dir, "recroom_2023.proto"), "w", encoding="utf-8") as w:
|
|
w.write("\n".join(combo).rstrip() + "\n")
|
|
print(f"wrote {out_dir}/recroom_2023.proto with {total_msgs} unique messages "
|
|
f"from {len(rr)} rec_room/circuits descriptors")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|