reverseport 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +71 -26
  2. package/THIRD_PARTY_NOTICES.md +1 -0
  3. package/bin/reverseport.mjs +13 -1
  4. package/dist/generatedPackageMetadata.js +1 -1
  5. package/licenses/README.md +1 -0
  6. package/licenses/godot-engine-MIT.txt +20 -0
  7. package/native/windows/build/manifest.json +1 -1
  8. package/package.json +1 -1
  9. package/port/cli.mjs +6 -4
  10. package/port/knowledge/apis/godot-3-to-4-renames.json +1 -0
  11. package/port/knowledge/apis/godot-3.json +1 -0
  12. package/port/knowledge/apis/godot-4.3.json +1 -0
  13. package/port/knowledge/apis/godot-4.7.json +1 -0
  14. package/port/src/agent.mjs +1 -1
  15. package/port/src/agentdisk.mjs +83 -0
  16. package/port/src/apicheck.mjs +280 -0
  17. package/port/src/archive.mjs +34 -4
  18. package/port/src/convert.mjs +3 -1
  19. package/port/src/dormant.mjs +159 -0
  20. package/port/src/formats.mjs +1 -0
  21. package/port/src/gamearchives.mjs +476 -0
  22. package/port/src/godotbin.mjs +544 -0
  23. package/port/src/il2cpp.mjs +148 -0
  24. package/port/src/jsworkspace.mjs +2 -2
  25. package/port/src/knowledge.mjs +3 -1
  26. package/port/src/physics.mjs +135 -0
  27. package/port/src/plan.mjs +94 -11
  28. package/port/src/porters.mjs +87 -5
  29. package/port/src/refs.mjs +120 -0
  30. package/port/src/rpgmaker.mjs +416 -0
  31. package/port/src/save.mjs +326 -0
  32. package/port/src/scene.mjs +37 -3
  33. package/port/src/shader.mjs +385 -0
  34. package/port/src/strings.mjs +327 -0
  35. package/port/src/tools.mjs +48 -8
  36. package/port/src/trace.mjs +302 -0
  37. package/port/src/unity-anim.mjs +229 -0
  38. package/port/src/upgrade.mjs +219 -0
  39. package/skills/build-inspector/SKILL.md +8 -0
  40. package/skills/godot-port/SKILL.md +2 -0
  41. package/skills/port-verify/SKILL.md +20 -9
  42. package/skills/reverse-engineer-anything/SKILL.md +18 -2
  43. package/skills/reverse-engineer-anything/references/porting-and-rebuilds.md +35 -4
  44. package/skills/unity-to-godot/SKILL.md +14 -2
@@ -0,0 +1,219 @@
1
+ // port_upgrade: Godot 3 → Godot 4 for a whole project, as a converted COPY plus a list of what still needs a human.
2
+ // It applies the official rename table (classes, builtin types, functions, properties, signals, enums, colors,
3
+ // theme overrides, project settings, shaders) and the syntax changes an agent gets wrong most (export/onready/tool
4
+ // annotations, yield → await, instance → instantiate, string connect/emit_signal → signal calls, Pool*Array,
5
+ // OS time functions → Time). What cannot be converted mechanically (setget, funcref, move_and_slide with
6
+ // arguments, Tween nodes, yield on a coroutine, format-2 scenes that Godot re-saves itself) is reported with
7
+ // file and line. Sources are never modified. Follow with re_apicheck on the copy.
8
+ import fs from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { RPError, requireDir, walk, limit, toPosix } from './util.mjs';
11
+ import { workspaceDir, isInside } from './workspace.mjs';
12
+ import { renameTables } from './apicheck.mjs';
13
+
14
+ const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+
16
+ const MANUAL = [
17
+ [/\bsetget\b/, 'setget', 'Godot 4 uses property blocks: var x: T:\n\tset(v): …\n\tget: …'],
18
+ [/\bfuncref\s*\(/, 'funcref', 'use a Callable: Callable(obj, "method") or obj.method'],
19
+ [/\bmove_and_slide\s*\([^)]/, 'move_and_slide(velocity, …)', 'Godot 4: set velocity (a CharacterBody property) then call move_and_slide() without arguments; it returns bool, the velocity stays in velocity'],
20
+ [/\bmove_and_slide_with_snap\s*\(/, 'move_and_slide_with_snap', 'set floor_snap_length and call move_and_slide()'],
21
+ [/\bmove_and_collide\s*\([^)]*,\s*\w+\s*,/, 'move_and_collide(vel, infinite_inertia, …)', 'Godot 4 signature: move_and_collide(motion, test_only=false, safe_margin, recovery_as_collision)'],
22
+ [/\byield\s*\(\s*[^,)]+\s*\)/, 'yield() without a signal', 'a coroutine pause: Godot 4 functions are coroutines when they await; replace with await on the signal or coroutine you wait for'],
23
+ [/\bTween\.new\s*\(|extends\s+Tween\b|type="Tween"/, 'Tween node', 'Godot 4 tweens are created with create_tween(); interpolate_property → tween_property, start() is implicit'],
24
+ [/\binterpolate_property\s*\(/, 'interpolate_property', 'var t := create_tween(); t.tween_property(obj, "prop", final, duration)'],
25
+ [/\bget_node\s*\(\s*"[^"]*"\s*\)\s*\.\s*connect\s*\(\s*"[^"]*"\s*,\s*[^,]+\s*,\s*"[^"]*"\s*,\s*\[/, 'connect with binds', 'bind with Callable.bind(): sig.connect(method.bind(args))'],
26
+ [/\bNavigation2D\b|\bNavigation\b(?!Server|Agent|Region|Polygon|Mesh|Obstacle|Link|Layer|Path)/, 'Navigation node', 'Godot 4 uses NavigationServer + NavigationAgent2D/3D and NavigationRegion2D/3D'],
27
+ [/\bVisualServer\b|\bPhysics2DServer\b|\bPhysicsServer\b(?!2D|3D)/, 'server singletons', 'RenderingServer / PhysicsServer2D / PhysicsServer3D: names converted, but many methods changed'],
28
+ [/\bget_overlapping_(bodies|areas)\s*\(\s*\)/, 'overlap queries', 'converted; in Godot 4 these need monitoring=true and update one physics frame later'],
29
+ [/\bOS\.(window_|set_window_|get_window_|center_window|is_window_|get_screen_)/, 'OS window functions', 'get_window() / DisplayServer in Godot 4'],
30
+ [/\brandomize\s*\(\s*\)/, 'randomize()', 'converted call is fine; Godot 4 seeds the RNG automatically, so remove it or keep it'],
31
+ [/\bexport\s*\(\s*(?:PackedScene|Resource|NodePath|Texture)\b/, 'export(Resource)', 'converted to @export var x: Type where the type is clear; check the type and hints (@export_file, @export_node_path)'],
32
+ [/\bget_tree\(\)\.(set_network_peer|network_peer|is_network_server|get_network_unique_id|network_connected|connected_to_server|server_disconnected)\b/, 'SceneTree networking', 'multiplayer API moved: get_tree().get_multiplayer() / multiplayer.multiplayer_peer, multiplayer.is_server()'],
33
+ [/\brpc_(unreliable|id|unreliable_id)\s*\(|\bremote\s+func|\bmaster\s+func|\bpuppet\s+func/, 'RPC keywords', 'Godot 4: @rpc("any_peer", "unreliable") annotations and rpc()/rpc_id()'],
34
+ [/\bget_tree\(\)\.change_scene\s*\(/, 'change_scene(path)', 'converted to change_scene_to_file; for a PackedScene use change_scene_to_packed'],
35
+ [/\bString\s*\(\s*[\w.]+\s*\)\s*\.format|\.percent_encode\(|\.http_escape\(/, 'string helpers', 'http_escape → uri_encode, percent_encode → uri_encode'],
36
+ [/^\s*var\s+velocity\b/, 'var velocity in a CharacterBody script', 'CharacterBody2D/3D already has velocity: delete this declaration and use the built-in property'],
37
+ [/\bSCREEN_TEXTURE\b/, 'SCREEN_TEXTURE', 'Godot 4 shaders declare it: uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;'],
38
+ [/\bDEPTH_TEXTURE\b/, 'DEPTH_TEXTURE', 'Godot 4 shaders declare it: uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;'],
39
+ [/\bNode\.pause_mode\b|\bpause_mode\s*=\s*(?:PAUSE_MODE_|\d)/, 'pause_mode', 'process_mode with PROCESS_MODE_PAUSABLE/WHEN_PAUSED/ALWAYS/DISABLED/INHERIT'],
40
+ ];
41
+
42
+ function convertGdscript(text, R, localFuncs) {
43
+ let out = text;
44
+ const notes = [];
45
+ const rep = (re, to) => { out = out.replace(re, to); };
46
+ // Annotations.
47
+ rep(/^(\s*)tool\s*$/gm, '$1@tool');
48
+ rep(/^(\s*)onready\s+var\b/gm, '$1@onready var');
49
+ rep(/^(\s*)export\s*\(\s*(int|float|bool|String|Vector2|Vector3|Color|NodePath|Array|Dictionary|PackedScene|Resource|Texture2D|Texture|AudioStream|Font|Curve|Gradient|StyleBox|Shader|Material|Mesh)\s*\)\s+var\s+(\w+)\s*(?:=\s*(.+))?$/gm,
50
+ (m, ind, type, name, val) => `${ind}@export var ${name}: ${R.classes.get(type) ?? type}${val !== undefined ? ` = ${val}` : ''}`);
51
+ rep(/^(\s*)export\s*\(\s*(?:int|float)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)(?:\s*,\s*([-\d.]+))?\s*\)\s+var\b/gm, (m, ind, a, b, step) => `${ind}@export_range(${a}, ${b}${step ? `, ${step}` : ''}) var`);
52
+ rep(/^(\s*)export\s*\(\s*String\s*,\s*FILE(?:\s*,\s*("[^"]*"))?\s*\)\s+var\b/gm, (m, ind, f) => `${ind}@export_file${f ? `(${f})` : ''} var`);
53
+ rep(/^(\s*)export\s*\(\s*String\s*,\s*MULTILINE\s*\)\s+var\b/gm, '$1@export_multiline var');
54
+ rep(/^(\s*)export\s*\(\s*(?:int|String)\s*,\s*"([^"]*(?:"\s*,\s*"[^"]*)*)"\s*\)\s+var\b/gm, (m, ind, names) => `${ind}@export_enum("${names}") var`);
55
+ rep(/^(\s*)export\s*\(\s*int\s*,\s*FLAGS\s*(?:,\s*("[^"]*(?:"\s*,\s*"[^"]*)*"))?\s*\)\s+var\b/gm, (m, ind, names) => `${ind}@export_flags(${names ?? ''}) var`);
56
+ rep(/^(\s*)export\s*\(\s*int\s*,\s*LAYERS_2D_PHYSICS\s*\)\s+var\b/gm, '$1@export_flags_2d_physics var');
57
+ rep(/^(\s*)export\s*\(\s*int\s*,\s*LAYERS_3D_PHYSICS\s*\)\s+var\b/gm, '$1@export_flags_3d_physics var');
58
+ rep(/^(\s*)export\s*\(\s*Array\s*,\s*(\w+)\s*\)\s+var\s+(\w+)/gm, (m, ind, t, name) => `${ind}@export var ${name}: Array[${R.classes.get(t) ?? t}]`);
59
+ rep(/^(\s*)export\s+var\b/gm, '$1@export var');
60
+ rep(/^(\s*)export\s*\([^)]*\)\s+var\b/gm, (m, ind) => { notes.push('export hint kept as plain @export; check the hint'); return `${ind}@export var`; });
61
+ // yield(obj, "signal") → await obj.signal ; yield(coroutine_call) → await coroutine_call
62
+ const SIG = { idle_frame: 'process_frame', ...Object.fromEntries(R.signals) };
63
+ rep(/\byield\s*\(\s*((?:[^,()]|\((?:[^()]|\([^()]*\))*\))+?)\s*,\s*"(\w+)"\s*\)/g, (m, obj, sig) => `await ${obj.trim()}.${SIG[sig] ?? sig}`);
64
+ rep(/\byield\s*\(\s*([\w.]+\([^()]*\))\s*\)/g, 'await $1');
65
+ // emit_signal("sig", a, b) → sig.emit(a, b)
66
+ rep(/\bemit_signal\s*\(\s*"(\w+)"\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/g, (m, sig, args) => `${R.signals.get(sig) ?? sig}.emit(${args?.trim() ?? ''})`);
67
+ // connect("sig", obj, "method" [, binds]) → sig.connect(obj.method[.bind(...)]) ; obj.connect(...) → obj.sig.connect(...)
68
+ rep(/(?:(\$[\w/%]+|\w+(?:\([^()]*\))?(?:\.\w+(?:\([^()]*\))?)*)\.)?\bconnect\s*\(\s*"(\w+)"\s*,\s*([\w.$]+(?:\([^()]*\))?)\s*,\s*"(\w+)"\s*(?:,\s*(\[[^\]]*\]))?\s*(?:,\s*(\w+))?\s*\)/g,
69
+ (m, recv, sig, target, method, binds, flags) => {
70
+ const s = R.signals.get(sig) ?? sig;
71
+ const callable = `${target === 'self' ? '' : target + '.'}${method}${binds ? `.bind(${binds.slice(1, -1).trim()})` : ''}`;
72
+ return `${recv ? recv + '.' : ''}${s}.connect(${callable}${flags ? `, ${flags}` : ''})`;
73
+ });
74
+ rep(/\bdisconnect\s*\(\s*"(\w+)"\s*,\s*([\w.$]+)\s*,\s*"(\w+)"\s*\)/g, (m, sig, target, method) => `${R.signals.get(sig) ?? sig}.disconnect(${target === 'self' ? '' : target + '.'}${method})`);
75
+ rep(/\bis_connected\s*\(\s*"(\w+)"\s*,\s*([\w.$]+)\s*,\s*"(\w+)"\s*\)/g, (m, sig, target, method) => `${R.signals.get(sig) ?? sig}.is_connected(${target === 'self' ? '' : target + '.'}${method})`);
76
+ // instance() → instantiate(), OS time → Time, builtin types, classes.
77
+ rep(/\.instance\s*\(\s*\)/g, '.instantiate()');
78
+ rep(/\bOS\.get_ticks_msec\(\)/g, 'Time.get_ticks_msec()');
79
+ rep(/\bOS\.get_ticks_usec\(\)/g, 'Time.get_ticks_usec()');
80
+ rep(/\bOS\.get_unix_time\(\)/g, 'Time.get_unix_time_from_system()');
81
+ rep(/\bOS\.get_datetime\(\)/g, 'Time.get_datetime_dict_from_system()');
82
+ rep(/\bOS\.get_system_time_msecs\(\)/g, 'Time.get_ticks_msec()');
83
+ rep(/\bEngine\.editor_hint\b/g, 'Engine.is_editor_hint()');
84
+ rep(/\.empty\(\)/g, '.is_empty()');
85
+ rep(/\.invert\(\)/g, '.reverse()');
86
+ rep(/\bget_tree\(\)\.change_scene\s*\(/g, 'get_tree().change_scene_to_file(');
87
+ rep(/\bparse_json\s*\(/g, 'JSON.parse_string(');
88
+ rep(/\bto_json\s*\(/g, 'JSON.stringify(');
89
+ rep(/\bColor\.(\w+)\b/g, (m, c) => `Color.${R.colors.get(c) ?? c}`);
90
+ for (const [a, b] of R.builtins) rep(new RegExp(`\\b${esc(a)}\\b`, 'g'), b);
91
+ for (const [a, b] of R.classes) rep(new RegExp(`(?<![\\w."'/])${esc(a)}\\b(?!["'])`, 'g'), b);
92
+ // Functions: `name(` not preceded by a word char that would make it another identifier; skip locally defined funcs.
93
+ for (const [a, b] of R.functions) { if (a === 'yield' || localFuncs.has(a) || !/^\w+$/.test(b)) continue; rep(new RegExp(`(?<![\\w])${esc(a)}(?=\\s*\\()`, 'g'), b); }
94
+ // Properties: `.name` not followed by `(`; and bare `name =`/`name.` for self properties of common nodes.
95
+ for (const [a, b] of R.properties) rep(new RegExp(`\\.${esc(a)}\\b(?!\\s*\\()`, 'g'), `.${b}`);
96
+ for (const [a, b] of R.properties) rep(new RegExp(`^(\\s*)${esc(a)}(?=\\s*(?:=|\\.|\\+=|-=|\\*=|/=))`, 'gm'), `$1${b}`);
97
+ // Enums/constants.
98
+ for (const [a, b] of R.enums) rep(new RegExp(`\\b${esc(a)}\\b`, 'g'), b);
99
+ // Theme overrides in code: get("custom_colors/font_color") etc.
100
+ for (const [a, b] of R.themeOverrides) rep(new RegExp(esc(a), 'g'), b);
101
+ return { out, notes };
102
+ }
103
+
104
+ function convertScene(text, R) {
105
+ let out = text;
106
+ const rep = (re, to) => { out = out.replace(re, to); };
107
+ rep(/(\btype=")(\w+)(")/g, (m, a, t, c) => `${a}${R.classes.get(t) ?? t}${c}`);
108
+ rep(/(\[sub_resource type=")(\w+)/g, (m, a, t) => `${a}${R.classes.get(t) ?? t}`);
109
+ rep(/(\[ext_resource [^\]]*type=")(\w+)/g, (m, a, t) => `${a}${R.classes.get(t) ?? t}`);
110
+ for (const [a, b] of R.themeOverrides) rep(new RegExp(`^${esc(a)}`, 'gm'), b);
111
+ for (const [a, b] of R.properties) rep(new RegExp(`^${esc(a)}(?=\\s*=)`, 'gm'), b);
112
+ for (const [a, b] of R.builtins) rep(new RegExp(`\\b${esc(a)}\\s*\\(`, 'g'), `${b}(`);
113
+ rep(/\[connection signal="(\w+)" from="([^"]*)" to="([^"]*)" method="(\w+)"/g, (m, sig, from, to, method) => `[connection signal="${R.signals.get(sig) ?? sig}" from="${from}" to="${to}" method="${method}"`);
114
+ rep(/^(\[gd_scene[^\]]*?)format=2/gm, '$1format=3');
115
+ rep(/^(\[gd_resource[^\]]*?)format=2/gm, '$1format=3');
116
+ return out;
117
+ }
118
+
119
+ function convertProject(text, R) {
120
+ let out = text.replace(/^config_version=4[ \t]*$/m, 'config_version=5');
121
+ for (const [a, b] of R.projectGodot) out = out.replace(new RegExp(`^${esc(a)}=`, 'gm'), `${b}=`);
122
+ for (const [a, b] of R.projectSettings) out = out.replace(new RegExp(`^${esc(a)}=`, 'gm'), `${b}=`);
123
+ for (const [a, b] of R.inputMap) out = out.split(a).join(b);
124
+ out = out.replace(/,"command":(?:true|false)/g, '');
125
+ if (/^\[application\]/m.test(out) && !/config\/features=/.test(out)) out = out.replace(/^\[application\][ \t]*$/m, '[application]\n\nconfig/features=PackedStringArray("4.3", "Forward Plus")');
126
+ if (/^\[rendering\]/m.test(out)) out = out.replace(/^environment\/default_environment=/m, 'environment/defaults/default_environment=');
127
+ return out;
128
+ }
129
+
130
+ function convertShader(text, R) {
131
+ let out = text;
132
+ for (const [a, b] of R.shaders) out = out.replace(new RegExp(`\\b${esc(a)}\\b`, 'g'), b);
133
+ return out.replace(/\bhint_albedo\b/g, 'source_color').replace(/\bhint_color\b/g, 'source_color').replace(/\bhint_white\b/g, 'hint_default_white').replace(/\bhint_black\b/g, 'hint_default_black').replace(/\bhint_normal\b/g, 'hint_normal');
134
+ }
135
+
136
+ function diffCount(a, b) {
137
+ const la = a.split('\n'), lb = b.split('\n');
138
+ let n = 0;
139
+ for (let i = 0; i < Math.max(la.length, lb.length); i++) if (la[i] !== lb[i]) n++;
140
+ return n;
141
+ }
142
+
143
+ function manualFindings(text, rel) {
144
+ const found = [];
145
+ text.split('\n').forEach((l, i) => { for (const [re, what, how] of MANUAL) if (re.test(l)) found.push({ file: rel, line: i + 1, what, how }); });
146
+ return found;
147
+ }
148
+
149
+ export async function upgradeTool({ path: target, action = 'check', out, limit: max = 300 } = {}) {
150
+ if (!target) throw new RPError('INVALID_ARGUMENT', 'path is required: a Godot 3 project folder.');
151
+ const root = await requireDir(target);
152
+ const pg = await fs.readFile(path.join(root, 'project.godot'), 'utf8').catch(() => null);
153
+ if (pg === null) throw new RPError('NOT_GODOT', `${root} has no project.godot.`);
154
+ if (/config_version=5/.test(pg)) throw new RPError('INVALID_ARGUMENT', 'This is already a Godot 4 project (config_version=5). Use re_apicheck to find leftovers.');
155
+ const rt = renameTables();
156
+ const raw = JSON.parse(await fs.readFile(new URL('../knowledge/apis/godot-3-to-4-renames.json', import.meta.url), 'utf8')).tables;
157
+ const R = { ...rt, colors: new Map(raw.color_renames), themeOverrides: raw.theme_override_renames, projectGodot: raw.project_godot_renames, projectSettings: raw.project_settings_renames, inputMap: raw.input_map_renames };
158
+
159
+ let outAbs = null;
160
+ if (action === 'apply') {
161
+ outAbs = out ? path.resolve(out) : path.join(await workspaceDir('upgrades'), path.basename(root).replace(/[^A-Za-z0-9._-]+/g, '_') + '-godot4');
162
+ if (isInside(outAbs, root) || isInside(root, outAbs)) throw new RPError('INVALID_ARGUMENT', 'out must be outside the source project.');
163
+ const existing = await fs.readdir(outAbs).catch(() => []);
164
+ if (existing.length) throw new RPError('OUTPUT_NOT_EMPTY', `out is not empty: ${outAbs}`);
165
+ } else if (action !== 'check') throw new RPError('INVALID_ARGUMENT', 'action must be check or apply.');
166
+
167
+ const { files, truncated } = await walk(root, { maxFiles: 50000 });
168
+ const changed = [], manual = [], renamedFiles = [];
169
+ let converted = 0, copied = 0;
170
+ for (const f of files) {
171
+ const ext = f.ext;
172
+ const rel = f.rel;
173
+ let text = null, after = null, destRel = rel;
174
+ if (['.gd', '.tscn', '.tres', '.godot', '.gdshader', '.shader', '.cfg', '.import'].includes(ext) || path.basename(rel) === 'project.godot') {
175
+ text = await fs.readFile(f.abs, 'utf8').catch(() => null);
176
+ }
177
+ if (text !== null) {
178
+ if (ext === '.gd') {
179
+ const localFuncs = new Set([...text.matchAll(/^\s*(?:static\s+)?func\s+(\w+)/gm)].map(m => m[1]));
180
+ after = convertGdscript(text, R, localFuncs).out;
181
+ manual.push(...manualFindings(after, rel).filter(x => x.what !== 'var velocity in a CharacterBody script' || /extends\s+CharacterBody[23]D/.test(after)));
182
+ } else if (ext === '.tscn' || ext === '.tres') { after = convertScene(text, R); manual.push(...manualFindings(after, rel).filter(x => x.what === 'Tween node')); }
183
+ else if (path.basename(rel) === 'project.godot') after = convertProject(text, R);
184
+ else if (ext === '.gdshader' || ext === '.shader') { after = convertShader(text, R); manual.push(...manualFindings(after, rel).filter(x => /TEXTURE/.test(x.what))); if (ext === '.shader') { destRel = rel.slice(0, -7) + '.gdshader'; renamedFiles.push({ from: rel, to: destRel }); } }
185
+ else if (ext === '.import') { after = null; destRel = null; } // Godot 4 regenerates .import files.
186
+ else after = text;
187
+ }
188
+ if (destRel === null) continue;
189
+ const lines = after !== null && after !== text ? diffCount(text, after) : 0;
190
+ if (lines) { converted++; changed.push({ file: rel, lines }); }
191
+ if (action === 'apply') {
192
+ const dest = path.join(outAbs, destRel);
193
+ await fs.mkdir(path.dirname(dest), { recursive: true });
194
+ if (after !== null) await fs.writeFile(dest, after); else await fs.copyFile(f.abs, dest);
195
+ copied++;
196
+ }
197
+ }
198
+ // Rename references to renamed .shader files.
199
+ if (action === 'apply' && renamedFiles.length) {
200
+ for (const f of files) if (['.tscn', '.tres', '.gd'].includes(f.ext)) {
201
+ const dest = path.join(outAbs, f.rel);
202
+ let t = await fs.readFile(dest, 'utf8').catch(() => null);
203
+ if (t === null) continue;
204
+ let changedRef = false;
205
+ for (const r of renamedFiles) { const re = new RegExp(esc(toPosix(r.from)), 'g'); if (re.test(t)) { t = t.replace(re, toPosix(r.to)); changedRef = true; } }
206
+ if (changedRef) await fs.writeFile(dest, t);
207
+ }
208
+ }
209
+ changed.sort((a, b) => b.lines - a.lines);
210
+ const byWhat = {};
211
+ for (const m of manual) byWhat[m.what] = (byWhat[m.what] ?? 0) + 1;
212
+ return {
213
+ source: root, action, ...(outAbs ? { out: outAbs, filesWritten: copied } : {}), filesScanned: files.length, filesConverted: converted, truncated,
214
+ changed: limit(changed, max), renamedFiles: limit(renamedFiles, 100), manual: limit(manual, max), manualByKind: byWhat,
215
+ next: action === 'check'
216
+ ? `port_upgrade action:"apply" writes the converted copy (default under the workspace, or out). ${manual.length} place(s) need a human afterwards; they are listed in manual.`
217
+ : `Fix the ${manual.length} manual item(s) in ${outAbs}, run re_apicheck {path:"${toPosix(outAbs)}", engine:"godot@4.3"} and open the folder in Godot 4 (it re-saves scenes to format 3 and regenerates .import files). Renamed .shader files have been updated in scenes.`,
218
+ };
219
+ }
@@ -21,6 +21,14 @@ Ren'Py (`.rpa`), RPG Maker, Electron (`app.asar`), LÖVE.
21
21
  re_archive {file:"Game.pck"} → entries, sizes, types, Godot version, pack format
22
22
  re_archive {file:"Game.exe"} → same, when the pack is embedded in the executable
23
23
  re_archive {file, action:"extract", match:"*.gd"} → extract (MD5-checked) into the workspace or out
24
+ re_il2cpp {file:"…/Metadata/global-metadata.dat"} → Unity IL2CPP: assemblies, classes, method names, literals, C# skeleton
25
+ re_decode {path:"<extracted pack folder>"} → .gdc → .gd, .scn/.res → .tscn/.tres, project.binary → project.godot
26
+ re_archive {file:"data.win"} → GameMaker: game name/version, sprite/object/room names, textures + audio
27
+ re_archive {file:"archive.rpa"} → Ren'Py: scripts/images/audio (.rpyc = compiled AST, no source)
28
+ re_archive {file:"Master.bank"} / {file:"Init.bnk"} → FMOD events + FSB5 samples / Wwise media (.wem) + object counts
29
+ re_archive {file:"Game.pak"} → Unreal: entries, compression, stored/Zlib extract; encrypted = listed only
30
+ re_archive {file:"Build/game.data.br"} → Unity WebGL: data files, StreamingAssets, IL2CPP metadata; Emscripten .data via its loader .js
31
+ re_archive {file:"game.swf"} → Flash: stage, AS2/AS3, classes, symbols, images/sounds/ABC extract
24
32
  re_binary {file:"<Game>_Data/Managed/Assembly-CSharp.dll"} → Unity Mono build: game classes and methods
25
33
  re_binary {file:"Game.exe"} → arch, imports (graphics API, Steam, FMOD/Wwise…), hints
26
34
  ```
@@ -9,6 +9,8 @@ Use the port-project skill for the loop; this skill holds the Godot-specific jud
9
9
 
10
10
  ## Before planning
11
11
 
12
+ - Godot 3 source (`config_version=4` in project.godot)? Upgrade first: `port_upgrade {path, action:"apply"}`
13
+ writes a Godot 4 copy and lists what needs a human; fix those, `re_apicheck` the copy, then port from the copy.
12
14
  - `re_scene {file:"project.godot"}` → main scene, autoloads (singletons), input actions, physics tick rate.
13
15
  Autoloads become singletons (Unity: DontDestroyOnLoad MonoBehaviour; Unreal: GameInstanceSubsystem; web: module
14
16
  singleton). Input actions must be recreated by name in the target.
@@ -11,21 +11,32 @@ port_verify {planId}
11
11
 
12
12
  `passed:true` requires: every unit done or deliberately skipped, no missing output files, no source-engine API
13
13
  left in ported code (`leftovers`), every source function and signal present in the port (`missingSymbols`; lifecycle
14
- renames like `_ready` → `Awake` are understood), and no units marked partial.
14
+ renames like `_ready` → `Awake` are understood), no units marked partial, and no dormant logic (`dormant`): a name
15
+ that exists but has an empty or stub body, a function the source called that nothing in the port calls, a signal the
16
+ port only declares, or a suppression (`as any`, `@warning_ignore`, `#pragma warning disable`) the port added.
17
+
18
+ Lost your context? `port_verify` and `port_status` return `resume`: the plan, where it stands, the next unit with its
19
+ ported dependencies, and the open findings. Read it before continuing.
15
20
 
16
21
  ## Fix loop
17
22
 
18
- | Finding | Fix |
19
- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
20
- | `not ported yet` | `port_unit {planId, unit}` and port it, or `port_write {status:"skipped", notes}` with a reason the user accepts. |
21
- | `source-engine API left in the port` | Replace each listed pattern with the `use` it names, `port_write` again. |
22
- | `functions/signals not found` | Port them, or rename to the matching idiom. If the function truly became unnecessary, say why in notes. |
23
- | `lifecycle callbacks without a counterpart` | Fine when the work moved (e.g. connections replaced by OnTriggerEnter) — check it did. |
24
- | `marked partial` | Finish it or explain what is missing and who needs to do it. |
25
- | `PORT:` markers | Each is an open item: resolve it or list it in the hand-over. |
23
+ | Finding | Fix |
24
+ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
25
+ | `not ported yet` | `port_unit {planId, unit}` and port it, or `port_write {status:"skipped", notes}` with a reason the user accepts. |
26
+ | `source-engine API left in the port` | Replace each listed pattern with the `use` it names, `port_write` again. |
27
+ | `functions/signals not found` | Port them, or rename to the matching idiom. If the function truly became unnecessary, say why in notes. |
28
+ | `lifecycle callbacks without a counterpart` | Fine when the work moved (e.g. connections replaced by OnTriggerEnter) — check it did. |
29
+ | `marked partial` | Finish it or explain what is missing and who needs to do it. |
30
+ | `PORT:` markers | Each is an open item: resolve it or list it in the hand-over. |
31
+ | `behaviour parity` | `tick-moved`: put motion back in the physics callback. `delta-dropped/added`: match the source's delta use. `seed-missing`: seed the same way. `order-dropped`: map execution order/deferral. `timescale-dropped`: set the equivalent setting. Prove it with `port_trace`. |
32
+ | `dormant logic` | `empty-body`/`thin-body`: port the statements. `unwired`: call or connect it where the source did. `suppression`: remove it and port the real fix. |
26
33
 
27
34
  Run `port_verify` again until it passes (or the remaining items are explained to the user).
28
35
 
36
+ Godot output: also `re_apicheck {path: <output folder>, engine: "godot@4.x"}`. Every `renamed-*` is a Godot 3 name
37
+ (replace with the given Godot 4 name), every `unknown-*` an invented or misspelled API (use the suggestion or the
38
+ real API), `too-new` a class the target version does not have yet.
39
+
29
40
  ## Hand-over (short and factual)
30
41
 
31
42
  - Where the output is and how to open/run it in the target engine.
@@ -34,7 +34,7 @@ original task after repair. Client approval, shell PATH, registration and engine
34
34
  availability have different remedies; avoid repeating setup for all of them.
35
35
 
36
36
  Prefer an existing `reverseport` CLI. When none is installed and ReversePort package acquisition
37
- is within the authorized scope, use `npx -y reverseport@1.6.0` in its place.
37
+ is within the authorized scope, use `npx -y reverseport@1.8.0` in its place.
38
38
  Configuration repair starts with a read-only setup plan; show paths, backups and
39
39
  skill replacement before applying authorized changes. Preserve existing approval
40
40
  and target-execution boundaries. ReversePort setup never installs or upgrades unrelated
@@ -49,6 +49,21 @@ These tools run without an analysis engine or provider:
49
49
  - Quick look at an executable, pack or unknown file: `re_binary`, `re_archive`
50
50
  (Godot `.pck` also inside an `.exe`, zip/apk/jar, tar.gz, asar), `re_probe`,
51
51
  `re_extract`. Two builds: `re_compare`. Write-up: `re_report`.
52
+ - Unity project with pink materials, "missing script" components or GUID
53
+ trouble after moving files: `re_refs` (reports only; fix in Unity).
54
+ - Localisation: `re_strings` extracts user-facing text (Godot, Unity, RPG
55
+ Maker, Ren'Py, i18n JSON) to .pot/.csv with stable ids; `action:"apply"`
56
+ writes a translated copy and checks placeholders. Never edit strings by hand.
57
+ - Godot code you or a model wrote: `re_apicheck {path, engine:"godot@4.3"}`
58
+ before opening Godot. Fix every renamed-* with its replacement and every
59
+ unknown-* with a real API; never invent members.
60
+ - Godot 3 project → Godot 4: `port_upgrade {path, action:"apply"}` writes a
61
+ converted copy and lists what needs a human (setget, Tween nodes,
62
+ move_and_slide arguments…); fix those, then `re_apicheck` on the copy.
63
+ - Save files: `re_save {file, compare}` peels gzip/base64/LZ-String/JSON/
64
+ INI/store_var, names the fields by role, verifies checksums and diffs two
65
+ saves; `action:"schema"` writes SaveData.gd/.cs to port the format.
66
+ Encrypted or obfuscated saves are reported, never decoded.
52
67
  - No files, only a name, store page, website or public repository: `re_lookup`,
53
68
  then `re_blueprint` for a clean-room rebuild spec measured against the original.
54
69
  - Port between engines or languages: `port_map` → `port_plan` → `port_unit` /
@@ -67,7 +82,8 @@ explicit path or endpoint and do not need it.
67
82
 
68
83
  - ASAR or extracted JavaScript/Electron tree:
69
84
  `analyze_javascript_application`.
70
- - Archive or pack members (zip/apk/jar/asar, Godot .pck): `re_archive` to list
85
+ - Archive or pack members (zip/apk/jar/asar, Godot .pck, Ren'Py .rpa, GameMaker
86
+ data.win, FMOD/Wwise banks, Unreal .pak, Flash .swf): `re_archive` to list
71
87
  or extract. IPA/MSIX/AppX/DMG, or when you need Evidence IDs and integrity
72
88
  findings: `open_binary` with the local path, then `inspect_artifact`.
73
89
  - Android APK code, classes, methods, or incoming references:
@@ -32,11 +32,26 @@ routes in the main skill and cite their Evidence.
32
32
 
33
33
  - Godot packs can sit inside the `.exe`: `re_archive {file}` lists them; add
34
34
  `action: "extract", match: "*.gd"` to extract. Encrypted packs are reported,
35
- never opened.
35
+ never opened. Exported packs hold .gdc, binary .scn/.res and project.binary:
36
+ `re_decode {path: <extracted folder>}` writes an editable project back.
36
37
  - `re_binary` reads PE (with .NET types and methods), ELF, Mach-O, wasm, Java
37
38
  class and dex files; `Assembly-CSharp.dll` shows a Unity game's scripts.
38
39
  - `re_compare {a, b}` diffs folders, packs or binaries. Report changes grouped
39
40
  (scripts, scenes, textures, audio, engine binary), biggest first.
41
+ - `re_refs {path}` audits a Unity project's GUID references: dangling (missing
42
+ scripts, pink materials), duplicate `.meta` GUIDs, assets without `.meta`,
43
+ `.meta` without asset. Run it before `port_plan` from Unity: a dangling
44
+ reference is a component the port would silently lose.
45
+
46
+ ## Save files
47
+
48
+ `re_save {file}` lists the container layers (gzip, base64, LZ-String, JSON,
49
+ XML, INI, Godot store_var), every field with a role guess, and whether a
50
+ checksum field is a plain hash of the rest. `compare` with a second save shows
51
+ which fields the action in between changed. `action: "schema"` writes a JSON
52
+ schema and SaveData.gd/.cs with the same field names so a port keeps loading
53
+ old saves; reproduce the layer order when writing. Encrypted, XOR-obfuscated,
54
+ BinaryFormatter and pickle saves are reported, not decoded.
40
55
 
41
56
  ## Recon without files
42
57
 
@@ -75,15 +90,31 @@ Unity → Unreal, or any language.
75
90
  PORT-PLAN.md. `port_copy_assets {planId}` copies the assets.
76
91
  3. Loop in dependency order: `port_unit {planId}` returns the source, API hints
77
92
  with line numbers, conventions and the ported names of dependencies.
78
- Scenes: `port_scene {planId, unit}`. Code: write the port, then
93
+ Scenes, uGUI, prefab variants, `.anim` and `.controller`: `port_scene {planId, unit}`. Code: write the port, then
79
94
  `port_write {planId, unit, files}` and fix every leftover and
80
95
  missing function it reports before moving on.
96
+ Shaders: `port_shader {file, to}` gives a marked first draft (uniforms,
97
+ intrinsics, built-ins); resolve its leftovers and `PORT:` markers, then
98
+ `port_write` it as the unit's file.
81
99
  4. Translate behaviour, not syntax: events instead of signals, serialized
82
100
  references instead of node paths, timers or coroutines instead of awaits.
83
101
  Convert every position, direction, rotation, speed and distance with
84
102
  `port_convert` (Unreal: centimeters, Z-up; Unity mirrors Z; three.js matches
85
- Godot). Leave `PORT:` markers instead of silent gaps.
86
- 5. `port_verify {planId}` until it passes; `port_status` shows progress.
103
+ Godot). Physics: `port_convert {from, to, physics: {gravity, fixedStep,
104
+ linearDamp, friction, bounce, jumpVelocity, layers}}` gives the target
105
+ settings, converted numbers and the rules that differ (fixed step, damping
106
+ defaults, friction/bounce combination, per-body masks vs a matrix). Leave
107
+ `PORT:` markers instead of silent gaps.
108
+ 5. `port_verify {planId}` until it passes; `port_status` shows progress. Both
109
+ return `resume`: read it first after a context reset. Verify also flags
110
+ dormant logic (empty or thin bodies, unwired functions and signals, added
111
+ suppressions) and behaviour parity (motion moved between frame and physics
112
+ tick, delta dropped, RNG unseeded, execution order lost). To prove behaviour:
113
+ `port_trace {action:"harness", engine}` in both projects, then
114
+ `port_trace {action:"compare", a, b}` shows the first divergence per
115
+ property and whether the error grows. Godot targets: also
116
+ `re_apicheck {path: <output>, engine}` so no Godot 3 names or invented
117
+ members survive.
87
118
  6. Tell the user how to open the result. Unity: copy `Assets/` in, then Tools ▸
88
119
  ReversePort ▸ Build prefabs and scenes from specs. Godot: open the folder.
89
120
  three.js: `npm install && npm run dev`. Unreal: Generate project files, build.
@@ -11,7 +11,8 @@ Use the port-project skill for the loop; this skill holds the Unity-specific jud
11
11
 
12
12
  - Scenes and prefabs must be text: Unity → Project Settings → Editor → Asset Serialization = **Force Text**. Without
13
13
  it `re_scene` cannot read them.
14
- - Keep the `.meta` files: they map GUIDs to scripts and prefabs.
14
+ - Keep the `.meta` files: they map GUIDs to scripts and prefabs. Run `re_refs` on the project first: a dangling
15
+ GUID (missing script, pink material) is a component the port would silently lose; fix it in Unity before planning.
15
16
  - GDScript or C#? Large, idiomatic C# codebases port faster to **godot-csharp** (only engine API calls change);
16
17
  smaller or designer-facing projects read better as GDScript (`to:"godot"`).
17
18
 
@@ -21,10 +22,21 @@ Use the port-project skill for the loop; this skill holds the Unity-specific jud
21
22
  AnimatableBody3D), CharacterController → CharacterBody3D, trigger colliders → Area3D, other colliders →
22
23
  StaticBody3D, Box/Sphere/Capsule colliders → CollisionShape3D, built-in primitives → BoxMesh/SphereMesh/…, Camera,
23
24
  Light, AudioSource, tags → groups, prefab instances → instanced scenes, scripts → the ported script. Transforms are
24
- mirrored on Z. Model meshes, materials, animators and UI are flagged as `metadata/reverseport_todo` in the scene.
25
+ mirrored on Z. Prefab variants become inherited scenes (`instance=`) with `m_IsActive` applied and other
26
+ overrides listed. uGUI becomes Control nodes: Canvas → full-rect Control, Image/RawImage → TextureRect, Text and
27
+ TextMeshPro → Label (size, colour, alignment), Button → Button (label text, OnClick listed), Slider/Toggle/
28
+ ScrollRect/InputField/layout groups → HSlider/CheckBox/ScrollContainer/LineEdit/Box- and GridContainer, with
29
+ RectTransform anchors and offsets converted (Y flipped). Animator → an `AnimationPlayer` whose library holds the
30
+ ported clips plus an `Animator` child running the ported controller script. `.anim` files port to Godot
31
+ `Animation` resources (position/rotation/scale/property/sprite-swap/event tracks); `.controller` files port to a
32
+ GDScript state machine with the same states, parameters and transition conditions (blend trees and extra layers
33
+ are listed for an AnimationTree by hand). Model meshes and materials stay flagged as `metadata/reverseport_todo`.
25
34
 
26
35
  ## Code: the usual translations
27
36
 
37
+ After writing each script, `re_apicheck {path, engine: "godot@4.x"}`: models mix Godot 3 (`KinematicBody2D`,
38
+ `instance()`, `yield`, string `connect`) into Godot 4 code and invent members; the tool names the replacement.
39
+
28
40
  - One GameObject with several MonoBehaviours → one node per behaviour (child nodes) or merge them into one script.
29
41
  - `GetComponent<T>()` → the node itself or a child (`$Child`); components are nodes in Godot.
30
42
  - `[SerializeField]` → `@export`; prefab fields (`GameObject`) → `PackedScene`.