romdevtools 0.44.0 → 0.70.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.
- package/CHANGELOG.md +572 -0
- package/README.md +9 -7
- package/examples/dreamcast/hello/main.c +24 -0
- package/examples/n64/platformer/main.c +158 -0
- package/examples/n64/puzzle/main.c +117 -0
- package/examples/n64/racing/main.c +147 -0
- package/examples/n64/shmup/main.c +122 -0
- package/examples/n64/sports/main.c +127 -0
- package/examples/ps1/platformer/main.c +158 -0
- package/examples/ps1/puzzle/main.c +125 -0
- package/examples/ps1/racing/main.c +147 -0
- package/examples/ps1/shmup/main.c +192 -0
- package/examples/ps1/sports/main.c +127 -0
- package/examples/ps1/sprite_move/main.c +38 -0
- package/package.json +11 -2
- package/src/analysis/analyze.js +224 -29
- package/src/analysis/decompile.js +7 -0
- package/src/analysis/decompiler/sleigh/mips.ldefs +25 -0
- package/src/analysis/decompiler/sleigh/mips32.pspec +78 -0
- package/src/analysis/decompiler/sleigh/mips32be.cspec +107 -0
- package/src/analysis/decompiler/sleigh/mips32be.sla +211162 -0
- package/src/analysis/decompiler/sleigh/mips32le.cspec +106 -0
- package/src/analysis/decompiler/sleigh/mips32le.sla +210624 -0
- package/src/analysis/rizin.js +24 -1
- package/src/cores/capabilities.js +122 -2
- package/src/cores/registry.js +17 -0
- package/src/host/LibretroGL.js +273 -0
- package/src/host/LibretroGLBridge.js +836 -0
- package/src/host/LibretroHost.js +203 -3
- package/src/host/callbacks.js +22 -2
- package/src/host/coreLoader.js +65 -5
- package/src/host/cpu-state.js +27 -0
- package/src/host/framebuffer.js +14 -1
- package/src/host/glOptionalDep.js +60 -0
- package/src/host/n64-ai-state.js +43 -0
- package/src/host/ps1-spu-state.js +65 -0
- package/src/host/retroConstants.js +14 -0
- package/src/mcp/tools/disasm.js +2 -0
- package/src/mcp/tools/frame.js +18 -9
- package/src/mcp/tools/lifecycle.js +1 -1
- package/src/mcp/tools/platform-tools.js +20 -4
- package/src/mcp/tools/platforms.js +38 -4
- package/src/mcp/tools/project.js +116 -0
- package/src/mcp/tools/rendering-context.js +2 -1
- package/src/mcp/tools/toolchain.js +1 -1
- package/src/platforms/n64/lib/c/n64.c +196 -0
- package/src/platforms/n64/lib/c/n64.h +68 -0
- package/src/platforms/ps1/lib/c/psx.c +200 -0
- package/src/platforms/ps1/lib/c/psx.h +83 -0
- package/src/toolchains/index.js +65 -0
- package/src/toolchains/mips-c/lib/be/libc.a +0 -0
- package/src/toolchains/mips-c/lib/be/libgcc.a +0 -0
- package/src/toolchains/mips-c/lib/be/libm.a +0 -0
- package/src/toolchains/mips-c/lib/el/libc.a +0 -0
- package/src/toolchains/mips-c/lib/el/libm.a +0 -0
- package/src/toolchains/mips-c/lib/n64-crt0.s +21 -0
- package/src/toolchains/mips-c/lib/n64-ipl3.s +30 -0
- package/src/toolchains/mips-c/lib/n64.ld +15 -0
- package/src/toolchains/mips-c/lib/ps1-crt0.s +20 -0
- package/src/toolchains/mips-c/lib/ps1.ld +15 -0
- package/src/toolchains/mips-c/lib/softint.c +37 -0
- package/src/toolchains/mips-c/mips-c.js +155 -0
- package/src/toolchains/mips-elf-gcc/gcc.js +130 -0
- package/src/toolchains/sh-c/lib/dc-crt0.s +23 -0
- package/src/toolchains/sh-c/lib/dc.h +102 -0
- package/src/toolchains/sh-c/lib/dc.ld +11 -0
- package/src/toolchains/sh-c/lib/libc.a +0 -0
- package/src/toolchains/sh-c/lib/libgcc.a +0 -0
- package/src/toolchains/sh-c/lib/libm.a +0 -0
- package/src/toolchains/sh-c/sh-c.js +101 -0
- package/src/toolchains/sh-elf-gcc/gcc.js +122 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* sports/main.c — SLAM COURT: a 3D PlayStation sports game (air-hockey / pong).
|
|
3
|
+
*
|
|
4
|
+
* A 3D playfield seen in perspective: you control the near paddle, the CPU the far
|
|
5
|
+
* one. A ball bounces down the court in 3D (X across, Z into the screen); rally it
|
|
6
|
+
* past the CPU to score, miss and the CPU scores. First to 7. Title -> match ->
|
|
7
|
+
* game-over. The whole court is drawn with the perspective pipeline; the ball's
|
|
8
|
+
* size changes with depth, selling the 3D.
|
|
9
|
+
*
|
|
10
|
+
* Build: build({ platform:"ps1", language:"c" }). Controls: LEFT/RIGHT move your
|
|
11
|
+
* paddle, START begin/restart.
|
|
12
|
+
*
|
|
13
|
+
* 3D technique: the court floor is a big no-cull quad; paddles + ball are cubes at
|
|
14
|
+
* world positions. The CPU paddle tracks the ball's X with a capped speed. 16.16 fp.
|
|
15
|
+
*/
|
|
16
|
+
#include "psx.h"
|
|
17
|
+
|
|
18
|
+
enum { TITLE, PLAY, OVER };
|
|
19
|
+
|
|
20
|
+
static fix px; /* player paddle X (near, z = small) */
|
|
21
|
+
static fix cx; /* cpu paddle X (far, z = big) */
|
|
22
|
+
static fix bx, bz, bvx, bvz; /* ball position + velocity */
|
|
23
|
+
static int you, cpu, state;
|
|
24
|
+
static unsigned int prev_pad;
|
|
25
|
+
|
|
26
|
+
#define COURT_HW FIX(5) /* court half-width */
|
|
27
|
+
#define NEAR_Z FIX(3)
|
|
28
|
+
#define FAR_Z FIX(22)
|
|
29
|
+
|
|
30
|
+
static void draw_cube_at(fix x, fix y, fix z, fix s, unsigned int col)
|
|
31
|
+
{
|
|
32
|
+
Vec3 v[8]; int i;
|
|
33
|
+
static const int sx[8]={-1,1,1,-1,-1,1,1,-1},sy[8]={-1,-1,1,1,-1,-1,1,1},sz[8]={-1,-1,-1,-1,1,1,1,1};
|
|
34
|
+
psx_model(x,y,z,0);
|
|
35
|
+
for(i=0;i<8;i++){v[i].x=sx[i]*s;v[i].y=sy[i]*s;v[i].z=sz[i]*s;}
|
|
36
|
+
psx_quad3d(v[0],v[1],v[2],v[3],col); psx_quad3d(v[5],v[4],v[7],v[6],col);
|
|
37
|
+
psx_quad3d(v[4],v[0],v[3],v[7],col); psx_quad3d(v[1],v[5],v[6],v[2],col);
|
|
38
|
+
psx_quad3d(v[4],v[5],v[1],v[0],col); psx_quad3d(v[3],v[2],v[6],v[7],col);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static void serve(int toward_cpu)
|
|
42
|
+
{
|
|
43
|
+
bx = 0; bz = (NEAR_Z + FAR_Z) >> 1;
|
|
44
|
+
bvx = (psx_rand() & 1) ? FIXF(0.12f) : FIXF(-0.12f);
|
|
45
|
+
bvz = toward_cpu ? FIXF(0.45f) : FIXF(-0.45f);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static void reset_game(void) { px = 0; cx = 0; you = 0; cpu = 0; serve(1); }
|
|
49
|
+
|
|
50
|
+
static void update(void)
|
|
51
|
+
{
|
|
52
|
+
unsigned int pad = psx_pad();
|
|
53
|
+
if (pad & PAD_LEFT) px -= FIXF(0.22f);
|
|
54
|
+
if (pad & PAD_RIGHT) px += FIXF(0.22f);
|
|
55
|
+
if (px < -COURT_HW) px = -COURT_HW; if (px > COURT_HW) px = COURT_HW;
|
|
56
|
+
|
|
57
|
+
/* cpu tracks the ball, capped */
|
|
58
|
+
{ fix d = bx - cx; if (d > FIXF(0.16f)) d = FIXF(0.16f); if (d < -FIXF(0.16f)) d = -FIXF(0.16f); cx += d; }
|
|
59
|
+
|
|
60
|
+
bx += bvx; bz += bvz;
|
|
61
|
+
if (bx < -COURT_HW || bx > COURT_HW) bvx = -bvx; /* side walls */
|
|
62
|
+
|
|
63
|
+
/* near paddle (player) */
|
|
64
|
+
if (bz <= NEAR_Z) {
|
|
65
|
+
fix d = bx - px; if (d < 0) d = -d;
|
|
66
|
+
if (d < FIX(2)) { bvz = -bvz; bz = NEAR_Z; bvx += (bx - px) >> 4; }
|
|
67
|
+
else { cpu++; if (cpu >= 7) { state = OVER; } else serve(1); }
|
|
68
|
+
}
|
|
69
|
+
/* far paddle (cpu) */
|
|
70
|
+
if (bz >= FAR_Z) {
|
|
71
|
+
fix d = bx - cx; if (d < 0) d = -d;
|
|
72
|
+
if (d < FIX(2)) { bvz = -bvz; bz = FAR_Z; }
|
|
73
|
+
else { you++; if (you >= 7) { state = OVER; } else serve(0); }
|
|
74
|
+
}
|
|
75
|
+
prev_pad = pad;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static void render(void)
|
|
79
|
+
{
|
|
80
|
+
Vec3 a,b,c,d;
|
|
81
|
+
psx_clear(RGB(20, 40, 30));
|
|
82
|
+
psx_camera(0, FIX(4), FIX(-1), 0, FIXF(-0.30f)); /* look down the court */
|
|
83
|
+
|
|
84
|
+
/* court floor */
|
|
85
|
+
a.x=-COURT_HW; a.y=0; a.z=NEAR_Z; b.x=COURT_HW; b.y=0; b.z=NEAR_Z;
|
|
86
|
+
c.x=COURT_HW; c.y=0; c.z=FAR_Z; d.x=-COURT_HW; d.y=0; d.z=FAR_Z;
|
|
87
|
+
psx_quad3d_nc(a,b,c,d, RGB(40, 90, 60));
|
|
88
|
+
/* center line */
|
|
89
|
+
a.x=-COURT_HW; a.z=(NEAR_Z+FAR_Z)>>1; b.x=COURT_HW; b.z=a.z;
|
|
90
|
+
c.x=COURT_HW; c.z=a.z+FIXF(0.3f); d.x=-COURT_HW; d.z=c.z;
|
|
91
|
+
a.y=FIXF(0.02f);b.y=a.y;c.y=a.y;d.y=a.y;
|
|
92
|
+
psx_quad3d_nc(a,b,c,d, RGB(220,220,220));
|
|
93
|
+
|
|
94
|
+
draw_cube_at(px, FIXF(0.5f), NEAR_Z, FIXF(0.9f), RGB(80, 180, 255)); /* you */
|
|
95
|
+
draw_cube_at(cx, FIXF(0.5f), FAR_Z, FIXF(0.9f), RGB(255, 100, 80)); /* cpu */
|
|
96
|
+
draw_cube_at(bx, FIXF(0.5f), bz, FIXF(0.4f), RGB(255, 240, 120)); /* ball */
|
|
97
|
+
|
|
98
|
+
psx_number(40, 8, (unsigned)you, RGB(80, 180, 255));
|
|
99
|
+
psx_number(240, 8, (unsigned)cpu, RGB(255, 100, 80));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
int main(void)
|
|
103
|
+
{
|
|
104
|
+
psx_init();
|
|
105
|
+
psx_srand(0x5A11);
|
|
106
|
+
state = TITLE; prev_pad = 0;
|
|
107
|
+
for (;;) {
|
|
108
|
+
unsigned int pad = psx_pad();
|
|
109
|
+
if (state == TITLE) {
|
|
110
|
+
static fix t; t += FIX(3);
|
|
111
|
+
psx_clear(RGB(15, 30, 25));
|
|
112
|
+
psx_camera(0, 0, FIX(-1), 0, 0);
|
|
113
|
+
draw_cube_at(0, 0, FIX(6), FIX(1), RGB(80, 180, 255));
|
|
114
|
+
if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
|
|
115
|
+
prev_pad = pad;
|
|
116
|
+
} else if (state == PLAY) { update(); render(); }
|
|
117
|
+
else {
|
|
118
|
+
psx_clear(RGB(8, 20, 16));
|
|
119
|
+
psx_number(120, 100, (unsigned)you, RGB(80, 180, 255));
|
|
120
|
+
psx_number(160, 100, (unsigned)cpu, RGB(255, 100, 80));
|
|
121
|
+
if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
|
|
122
|
+
prev_pad = pad;
|
|
123
|
+
}
|
|
124
|
+
psx_vsync();
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* sprite_move/main.c — PS1 starter (mips-elf-gcc + the bundled psx.h helper).
|
|
3
|
+
*
|
|
4
|
+
* Boots a visible 320x240 display and animates a colored box bouncing around the
|
|
5
|
+
* screen — the canonical "GPU is alive, here's a moving primitive" PS1 starter.
|
|
6
|
+
* Exercises the whole helper lib: psx_init (GPU bring-up), psx_clear + psx_rect
|
|
7
|
+
* (flat polygons), psx_vsync (pacing).
|
|
8
|
+
*
|
|
9
|
+
* Build with: build({ platform:"ps1", language:"c" }) — language defaults to C.
|
|
10
|
+
* Output is a PS-EXE the HLE BIOS loads at 0x80010000; main() loops forever
|
|
11
|
+
* (no OS to return to).
|
|
12
|
+
*
|
|
13
|
+
* PS1 NOTE: there is no tile/sprite/nametable hardware — the GPU draws polygons
|
|
14
|
+
* into a framebuffer. A "sprite" here is a textured/flat quad you draw each frame.
|
|
15
|
+
*/
|
|
16
|
+
#include "psx.h"
|
|
17
|
+
|
|
18
|
+
int main(void)
|
|
19
|
+
{
|
|
20
|
+
int x = 40, y = 40, dx = 2, dy = 2;
|
|
21
|
+
const int W = 32, H = 32;
|
|
22
|
+
|
|
23
|
+
psx_init();
|
|
24
|
+
|
|
25
|
+
for (;;) {
|
|
26
|
+
/* move + bounce */
|
|
27
|
+
x += dx; y += dy;
|
|
28
|
+
if (x < 0 || x + W > 320) dx = -dx;
|
|
29
|
+
if (y < 0 || y + H > 240) dy = -dy;
|
|
30
|
+
if (x < 0) x = 0; if (x + W > 320) x = 320 - W;
|
|
31
|
+
if (y < 0) y = 0; if (y + H > 240) y = 240 - H;
|
|
32
|
+
|
|
33
|
+
psx_clear(RGB(20, 30, 60)); /* deep-blue background */
|
|
34
|
+
psx_rect(x, y, W, H, RGB(255, 180, 40)); /* the bouncing box */
|
|
35
|
+
psx_vsync();
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "romdevtools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across
|
|
3
|
+
"version": "0.70.0",
|
|
4
|
+
"description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 17 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, PlayStation, N64, Dreamcast, ...) via WASM toolchains + emulator cores. Use over plain HTTP, as an Agent Skill, or as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
|
7
7
|
"bin": {
|
|
@@ -58,6 +58,9 @@
|
|
|
58
58
|
"romdev-core-geargrafx": "0.7.0",
|
|
59
59
|
"romdev-core-gpgx": "0.12.0",
|
|
60
60
|
"romdev-core-handy": "0.7.0",
|
|
61
|
+
"romdev-core-flycast": "0.1.0",
|
|
62
|
+
"romdev-core-parallel-n64": "0.1.0",
|
|
63
|
+
"romdev-core-beetle-psx-hw": "0.1.0",
|
|
61
64
|
"romdev-core-prosystem": "0.8.0",
|
|
62
65
|
"romdev-core-vice": "0.9.0",
|
|
63
66
|
"romdev-famitone": "0.1.0",
|
|
@@ -67,8 +70,10 @@
|
|
|
67
70
|
"romdev-platform-snes": "0.8.0",
|
|
68
71
|
"romdev-toolchain-cc65": "0.1.1",
|
|
69
72
|
"romdev-toolchain-m68k-gcc": "0.2.0",
|
|
73
|
+
"romdev-toolchain-mips-gcc": "0.1.0",
|
|
70
74
|
"romdev-toolchain-rgbds": "0.1.0",
|
|
71
75
|
"romdev-toolchain-sdcc": "0.2.0",
|
|
76
|
+
"romdev-toolchain-sh-gcc": "0.1.0",
|
|
72
77
|
"romdev-toolchain-vasm": "0.1.0",
|
|
73
78
|
"romdev-xgm2": "0.1.0",
|
|
74
79
|
"romdev_game_codes": "0.1.0",
|
|
@@ -77,6 +82,10 @@
|
|
|
77
82
|
"yauzl": "^3.3.1",
|
|
78
83
|
"zod": "^4.4.3"
|
|
79
84
|
},
|
|
85
|
+
"optionalDependencies": {
|
|
86
|
+
"native-gles": "^0.5.0",
|
|
87
|
+
"webgl-node": "^1.2.0"
|
|
88
|
+
},
|
|
80
89
|
"files": [
|
|
81
90
|
"src",
|
|
82
91
|
"!src/**/*.test.js",
|
package/src/analysis/analyze.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// resolved by the existing disasm mappers — analysis here is whole-file.
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
|
-
import { runRizinJson, RIZIN_ARCH } from "./rizin.js";
|
|
15
|
+
import { runRizinJson, RIZIN_ARCH, RIZIN_ENDIAN } from "./rizin.js";
|
|
16
16
|
import { decompileFunction, SLEIGH_LANGID } from "./decompile.js";
|
|
17
17
|
import { registersForPlatform } from "../platforms/common/registers.js";
|
|
18
18
|
|
|
@@ -133,13 +133,80 @@ export function sniffPlatform(p) {
|
|
|
133
133
|
if (/\.(lnx|lyx)$/i.test(p)) return "lynx";
|
|
134
134
|
if (/\.gba$/i.test(p)) return "gba";
|
|
135
135
|
if (/\.pce$/i.test(p)) return "pce";
|
|
136
|
+
if (/\.(z64|n64|v64)$/i.test(p)) return "n64";
|
|
137
|
+
if (/\.(psexe|psx)$/i.test(p)) return "ps1"; // .exe/.bin ambiguous — pass platform explicitly
|
|
138
|
+
if (/\.(cdi|gdi)$/i.test(p)) return "dreamcast"; // DC disc images; .elf is cross-platform — pass platform explicitly
|
|
136
139
|
if (/\.(gen|md|bin)$/i.test(p)) return "genesis";
|
|
137
140
|
return null;
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/** rizin asm.bits per arch (analysis defaults; rizin's loader usually sets
|
|
141
144
|
* these for recognized formats, but raw blobs need a hint). */
|
|
142
|
-
const BITS = { arm: 32, m68k: 32, snes: 16 };
|
|
145
|
+
const BITS = { arm: 32, m68k: 32, snes: 16, mips: 32, sh: 32 };
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The rizin analysis-SEED command for a context. For the 8/16-bit platforms
|
|
149
|
+
* rizin's `aaa` recognizes the bin format / vectors and seeds itself. For a RAW
|
|
150
|
+
* MIPS image (N64/PS1) there's no recognized entry, so `aaa` finds nothing —
|
|
151
|
+
* instead define a function at the code start and recursively analyze its call
|
|
152
|
+
* graph (`af` + `aac`), which surfaces the real function tree (verified: 251
|
|
153
|
+
* functions on a real libdragon N64 ROM where `aaa` found 0).
|
|
154
|
+
* @param {{arch:string, codeStart:number}} ctx
|
|
155
|
+
* @returns {string} the seed command (no trailing semicolon)
|
|
156
|
+
*/
|
|
157
|
+
function analysisSeed({ arch, codeStart }) {
|
|
158
|
+
// MIPS (N64/PS1) and SH-4 (Dreamcast) are raw code images with no rizin-recognized
|
|
159
|
+
// entry, so `aaa` finds nothing — seed a function at the code start + recursively
|
|
160
|
+
// analyze its call graph (`af` + `aac`). The 8/16-bit bin formats self-seed via aaa.
|
|
161
|
+
if (arch === "mips" || arch === "sh") {
|
|
162
|
+
const at = "0x" + (codeStart || 0).toString(16);
|
|
163
|
+
return `af @ ${at}; aac @ ${at}`;
|
|
164
|
+
}
|
|
165
|
+
return "aaa";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** MIPS analysis runs FLAT (rizin ignores -B on a raw malloc:// buffer), so rizin
|
|
169
|
+
* reports FILE-OFFSET addresses. For PS1 loadContext left-pads the .text so flat
|
|
170
|
+
* offset == the VA's low 20 bits (see there); we add the VA's HIGH bits back as
|
|
171
|
+
* `rebase` so callers get real VAs that round-trip through the decompile
|
|
172
|
+
* VA→fileOffset math. N64 (.z64) keeps its absolute VAs baked into the code and is
|
|
173
|
+
* analyzed flat from codeStart, so no rebase. */
|
|
174
|
+
function mipsAnalysisBase({ platform, loadBase, codeStart }) {
|
|
175
|
+
// PS1 (left-padded to the VA's low 20 bits) and Dreamcast (same, loadBase
|
|
176
|
+
// 0x8c010000) need the VA's HIGH bits added back so addresses round-trip. N64 runs
|
|
177
|
+
// flat with VAs already baked in (no rebase).
|
|
178
|
+
const padded = (platform === "ps1" || platform === "dreamcast") && loadBase;
|
|
179
|
+
const rebase = padded ? (loadBase & 0xfff00000) >>> 0 : 0;
|
|
180
|
+
return { baddr: undefined, seedAt: codeStart, rebase };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Normalize an N64 ROM to BIG-ENDIAN (.z64) byte order. Dumps come in three
|
|
185
|
+
* orders, distinguished by the first 4 bytes of the header:
|
|
186
|
+
* .z64 (big) 80 37 12 40 — native; no change
|
|
187
|
+
* .v64 (byteswap)37 80 40 12 — swap every 2 bytes
|
|
188
|
+
* .n64 (little) 40 12 37 80 — swap every 4 bytes
|
|
189
|
+
* MIPS analysis wants the big-endian image so addresses + instruction words line up.
|
|
190
|
+
* @param {Uint8Array} bytes
|
|
191
|
+
* @returns {{ bytes: Uint8Array, reordered: string|null }}
|
|
192
|
+
*/
|
|
193
|
+
function normalizeN64ByteOrder(bytes) {
|
|
194
|
+
if (bytes.length < 4) return { bytes, reordered: null };
|
|
195
|
+
const b = bytes;
|
|
196
|
+
const is = (a, c, d, e) => b[0] === a && b[1] === c && b[2] === d && b[3] === e;
|
|
197
|
+
if (is(0x80, 0x37, 0x12, 0x40)) return { bytes, reordered: null }; // .z64 big — native
|
|
198
|
+
if (is(0x37, 0x80, 0x40, 0x12)) { // .v64 byteswapped — swap pairs
|
|
199
|
+
const out = new Uint8Array(b.length & ~1);
|
|
200
|
+
for (let i = 0; i + 1 < b.length; i += 2) { out[i] = b[i + 1]; out[i + 1] = b[i]; }
|
|
201
|
+
return { bytes: out, reordered: "v64 (byteswapped) → z64" };
|
|
202
|
+
}
|
|
203
|
+
if (is(0x40, 0x12, 0x37, 0x80)) { // .n64 little — swap dwords
|
|
204
|
+
const out = new Uint8Array(b.length & ~3);
|
|
205
|
+
for (let i = 0; i + 3 < b.length; i += 4) { out[i] = b[i + 3]; out[i + 1] = b[i + 2]; out[i + 2] = b[i + 1]; out[i + 3] = b[i]; }
|
|
206
|
+
return { bytes: out, reordered: "n64 (little-endian dwords) → z64" };
|
|
207
|
+
}
|
|
208
|
+
return { bytes, reordered: null }; // unknown header — leave as-is
|
|
209
|
+
}
|
|
143
210
|
|
|
144
211
|
/** Build the common rizin invocation context for a ROM + platform. Returns
|
|
145
212
|
* { romBytes, arch, bits, note } — arch null means let rizin sniff. */
|
|
@@ -175,11 +242,86 @@ async function loadContext(romPath, platformOverride) {
|
|
|
175
242
|
loadBase = romBytes[0] | (romBytes[1] << 8);
|
|
176
243
|
romBytes = romBytes.subarray(2);
|
|
177
244
|
}
|
|
245
|
+
const warningsEarly = [];
|
|
246
|
+
// N64: normalize to big-endian (.z64) byte order, then the ROM header's entry
|
|
247
|
+
// point (big-endian word at 0x08) is the CPU load base. The 0x1000-byte header
|
|
248
|
+
// (IPL3 bootcode) precedes the game; rizin analyzes the whole image but the
|
|
249
|
+
// entry tells functions where code starts.
|
|
250
|
+
let codeStart = 0; // file offset where executable code begins (post-header)
|
|
251
|
+
if (platform === "n64") {
|
|
252
|
+
const norm = normalizeN64ByteOrder(romBytes);
|
|
253
|
+
romBytes = norm.bytes;
|
|
254
|
+
if (norm.reordered) warningsEarly.push(`N64 ROM was ${norm.reordered} byte order — normalized to z64 (big-endian) for analysis.`);
|
|
255
|
+
if (romBytes.length >= 0x0c) {
|
|
256
|
+
// entry point: big-endian 32-bit at offset 0x08.
|
|
257
|
+
loadBase = (romBytes[0x08] << 24) | (romBytes[0x09] << 16) | (romBytes[0x0a] << 8) | romBytes[0x0b];
|
|
258
|
+
}
|
|
259
|
+
// The IPL3 bootcode occupies the first 0x1000 bytes; game code starts after it.
|
|
260
|
+
// rizin's `aaa` doesn't recognize a raw N64 ROM's entry, so analysis is seeded
|
|
261
|
+
// here (see ANALYSIS_SEED).
|
|
262
|
+
codeStart = 0x1000;
|
|
263
|
+
}
|
|
264
|
+
// PS1 PS-EXE: a 2048-byte header; the load (t_addr) is a little-endian word at
|
|
265
|
+
// 0x18, and the code follows the header. Strip the header so rizin sees code at
|
|
266
|
+
// the load address. A raw .bin (no PS-EXE magic) is left flat.
|
|
267
|
+
if (platform === "ps1" && romBytes.length >= 0x800 &&
|
|
268
|
+
romBytes[0] === 0x50 && romBytes[1] === 0x53 && romBytes[2] === 0x2d && romBytes[3] === 0x58) { // "PS-X"
|
|
269
|
+
loadBase = (romBytes[0x18] | (romBytes[0x19] << 8) | (romBytes[0x1a] << 16) | (romBytes[0x1b] << 24)) >>> 0;
|
|
270
|
+
const text = romBytes.subarray(0x800);
|
|
271
|
+
// rizin ignores -B on a raw malloc:// buffer, so it addresses flat from 0. PS1
|
|
272
|
+
// jal targets are ABSOLUTE VAs (e.g. jal 0x80010518) — to let rizin follow them,
|
|
273
|
+
// left-pad the .text by the load address's low 20 bits so flat offset N == the
|
|
274
|
+
// VA's low bits (jal masks to a 28-bit region). The high bits (0x80000000) are
|
|
275
|
+
// added back as `rebase`. Without this, every cross-function call dangles and
|
|
276
|
+
// only the entry blob is discovered. (Cap the pad so a weird t_addr can't OOM.)
|
|
277
|
+
const lowPad = loadBase & 0x000fffff;
|
|
278
|
+
if (lowPad > 0 && lowPad <= 0x200000) {
|
|
279
|
+
const padded = new Uint8Array(lowPad + text.length);
|
|
280
|
+
padded.set(text, lowPad);
|
|
281
|
+
romBytes = padded;
|
|
282
|
+
codeStart = lowPad;
|
|
283
|
+
} else {
|
|
284
|
+
romBytes = text;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Dreamcast SH-4: KOS produces an ELF (load base 0x8c010000) or a stripped flat
|
|
288
|
+
// binary. For an ELF, strip to the first PT_LOAD segment's bytes + take its vaddr
|
|
289
|
+
// as loadBase. Like PS1, SH-4 uses PC-relative + absolute addressing, so left-pad
|
|
290
|
+
// so flat offset == the VA's low bits and add the high bits back as rebase.
|
|
291
|
+
if (platform === "dreamcast") {
|
|
292
|
+
let text = romBytes, loadVa = 0x8c010000;
|
|
293
|
+
if (romBytes.length >= 0x34 && romBytes[0] === 0x7f && romBytes[1] === 0x45 &&
|
|
294
|
+
romBytes[2] === 0x4c && romBytes[3] === 0x46) { // "\x7fELF"
|
|
295
|
+
// ELF32 LE: e_phoff @0x1c, e_phentsize @0x2a, e_phnum @0x2c. Find first PT_LOAD.
|
|
296
|
+
const u32 = (o) => (romBytes[o] | (romBytes[o + 1] << 8) | (romBytes[o + 2] << 16) | (romBytes[o + 3] << 24)) >>> 0;
|
|
297
|
+
const u16 = (o) => romBytes[o] | (romBytes[o + 1] << 8);
|
|
298
|
+
const phoff = u32(0x1c), phentsize = u16(0x2a), phnum = u16(0x2c);
|
|
299
|
+
for (let i = 0; i < phnum; i++) {
|
|
300
|
+
const ph = phoff + i * phentsize;
|
|
301
|
+
if (u32(ph) === 1) { // PT_LOAD
|
|
302
|
+
const p_offset = u32(ph + 4), p_vaddr = u32(ph + 8), p_filesz = u32(ph + 16);
|
|
303
|
+
loadVa = p_vaddr >>> 0;
|
|
304
|
+
text = romBytes.subarray(p_offset, p_offset + p_filesz);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
loadBase = loadVa >>> 0;
|
|
310
|
+
const lowPad = loadBase & 0x000fffff;
|
|
311
|
+
if (lowPad > 0 && lowPad <= 0x200000) {
|
|
312
|
+
const padded = new Uint8Array(lowPad + text.length);
|
|
313
|
+
padded.set(text, lowPad);
|
|
314
|
+
romBytes = padded;
|
|
315
|
+
codeStart = lowPad;
|
|
316
|
+
} else {
|
|
317
|
+
romBytes = text;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
178
320
|
|
|
179
321
|
// A6: container/format sniff. Some dumps are interleaved/headered such that a
|
|
180
322
|
// FLAT read scrambles every byte → fake "bad instruction" noise everywhere.
|
|
181
323
|
// Detect + auto-correct, and warn so a flat disasm isn't silently wrong.
|
|
182
|
-
const warnings = [];
|
|
324
|
+
const warnings = [...warningsEarly];
|
|
183
325
|
if (platform === "genesis") {
|
|
184
326
|
const smd = deinterleaveSmd(romBytes);
|
|
185
327
|
if (smd) {
|
|
@@ -188,7 +330,7 @@ async function loadContext(romPath, platformOverride) {
|
|
|
188
330
|
"auto-deinterleaved before analysis. A flat read of the original would scramble every instruction.");
|
|
189
331
|
}
|
|
190
332
|
}
|
|
191
|
-
return { platform, romBytes, arch, bits: BITS[arch], approx, loadBase, warnings };
|
|
333
|
+
return { platform, romBytes, arch, bits: BITS[arch], endian: RIZIN_ENDIAN[platform], approx, loadBase, codeStart, warnings };
|
|
192
334
|
}
|
|
193
335
|
|
|
194
336
|
/** Detect + reverse Sega Mega Drive SMD interleaving. An .smd dump is a 512-byte
|
|
@@ -227,11 +369,12 @@ function hx(n) { return "0x" + (n >>> 0).toString(16); }
|
|
|
227
369
|
* @returns {{platform, count, functions: Array<{address, name, size, nbbs, cc, callers, callees}>}}
|
|
228
370
|
*/
|
|
229
371
|
export async function analyzeFunctions(romPath, platformOverride) {
|
|
230
|
-
const { platform, romBytes, arch, bits, loadBase, warnings } = await loadContext(romPath, platformOverride);
|
|
231
|
-
const
|
|
372
|
+
const { platform, romBytes, arch, bits, endian, loadBase, codeStart, warnings } = await loadContext(romPath, platformOverride);
|
|
373
|
+
const { baddr, seedAt, rebase } = mipsAnalysisBase({ arch, platform, loadBase, codeStart });
|
|
374
|
+
const fns = await runRizinJson({ romBytes, arch, bits, endian, baddr, commands: `${analysisSeed({ arch, codeStart: seedAt })}; aflj` });
|
|
232
375
|
const functions = fns.map((f) => ({
|
|
233
|
-
address: f.offset,
|
|
234
|
-
addressHex: hx(f.offset),
|
|
376
|
+
address: (f.offset + rebase) >>> 0,
|
|
377
|
+
addressHex: hx((f.offset + rebase) >>> 0),
|
|
235
378
|
name: f.name,
|
|
236
379
|
size: f.size,
|
|
237
380
|
nbbs: f.nbbs, // basic-block count
|
|
@@ -269,30 +412,35 @@ export async function analyzeFunctions(romPath, platformOverride) {
|
|
|
269
412
|
*/
|
|
270
413
|
export async function analyzeCfg(romPath, address, platformOverride) {
|
|
271
414
|
if (address == null) throw new Error("analyze cfg: address required");
|
|
272
|
-
const { platform, romBytes, arch, bits, loadBase } = await loadContext(romPath, platformOverride);
|
|
415
|
+
const { platform, romBytes, arch, bits, endian, loadBase, codeStart } = await loadContext(romPath, platformOverride);
|
|
273
416
|
// afbj = basic blocks of the function as JSON: each block has addr/size/jump/
|
|
274
417
|
// fail/ninstr. `jump` is the taken edge; `fail` (present only on conditional
|
|
275
418
|
// blocks) is the fall-through. This is the structured CFG source — `agf json`
|
|
276
419
|
// only gives a text body blob with untyped out_nodes.
|
|
420
|
+
// MIPS: seed the call graph first (the address is a vaddr from `functions`,
|
|
421
|
+
// which rizin recovers during `aac`). PS1 rebases to loadBase so the vaddr lines up.
|
|
422
|
+
const { baddr, seedAt, rebase } = mipsAnalysisBase({ arch, platform, loadBase, codeStart });
|
|
423
|
+
const flat = ((address >>> 0) - rebase) >>> 0; // VA → flat offset rizin uses
|
|
424
|
+
const rb = (a) => (a == null ? a : (a + rebase) >>> 0);
|
|
277
425
|
const blocks = await runRizinJson({
|
|
278
|
-
romBytes, arch, bits, baddr
|
|
279
|
-
commands:
|
|
426
|
+
romBytes, arch, bits, endian, baddr,
|
|
427
|
+
commands: `${analysisSeed({ arch, codeStart: seedAt })}; af @ ${hx(flat)}; afbj @ ${hx(flat)}`,
|
|
280
428
|
});
|
|
281
429
|
if (!Array.isArray(blocks) || blocks.length === 0) {
|
|
282
430
|
return { platform, arch, address, addressHex: hx(address), nodes: [], edges: [], note: "no function/blocks at address" };
|
|
283
431
|
}
|
|
284
432
|
const nodes = blocks.map((b) => ({
|
|
285
|
-
id: b.addr,
|
|
286
|
-
address: b.addr,
|
|
287
|
-
addressHex: hx(b.addr),
|
|
433
|
+
id: rb(b.addr),
|
|
434
|
+
address: rb(b.addr),
|
|
435
|
+
addressHex: hx(rb(b.addr)),
|
|
288
436
|
size: b.size,
|
|
289
437
|
ninstr: b.ninstr,
|
|
290
438
|
}));
|
|
291
439
|
const edges = [];
|
|
292
440
|
for (const b of blocks) {
|
|
293
441
|
const conditional = b.fail != null;
|
|
294
|
-
if (b.jump != null) edges.push({ from: b.addr, to: b.jump, type: conditional ? "branch_true" : "jump_or_fall" });
|
|
295
|
-
if (b.fail != null) edges.push({ from: b.addr, to: b.fail, type: "branch_false" });
|
|
442
|
+
if (b.jump != null) edges.push({ from: rb(b.addr), to: rb(b.jump), type: conditional ? "branch_true" : "jump_or_fall" });
|
|
443
|
+
if (b.fail != null) edges.push({ from: rb(b.addr), to: rb(b.fail), type: "branch_false" });
|
|
296
444
|
}
|
|
297
445
|
return {
|
|
298
446
|
platform, arch,
|
|
@@ -308,10 +456,13 @@ export async function analyzeCfg(romPath, address, platformOverride) {
|
|
|
308
456
|
*/
|
|
309
457
|
export async function analyzeXrefs(romPath, address, platformOverride) {
|
|
310
458
|
if (address == null) throw new Error("analyze xrefs: address required");
|
|
311
|
-
const { platform, romBytes, arch, bits, loadBase } = await loadContext(romPath, platformOverride);
|
|
459
|
+
const { platform, romBytes, arch, bits, endian, loadBase, codeStart } = await loadContext(romPath, platformOverride);
|
|
460
|
+
const { baddr, seedAt, rebase } = mipsAnalysisBase({ arch, platform, loadBase, codeStart });
|
|
461
|
+
const flat = ((address >>> 0) - rebase) >>> 0;
|
|
462
|
+
const rb = (a) => (a == null ? a : (a + rebase) >>> 0);
|
|
312
463
|
let refs;
|
|
313
464
|
try {
|
|
314
|
-
refs = await runRizinJson({ romBytes, arch, bits, baddr:
|
|
465
|
+
refs = await runRizinJson({ romBytes, arch, bits, endian, baddr, commands: `${analysisSeed({ arch, codeStart: seedAt })}; axtj @ ${hx(flat)}` });
|
|
315
466
|
} catch (e) {
|
|
316
467
|
// axtj prints nothing (not even `[]`) when there are zero refs → our JSON
|
|
317
468
|
// guard throws. Treat "no JSON" as "no refs".
|
|
@@ -319,9 +470,9 @@ export async function analyzeXrefs(romPath, address, platformOverride) {
|
|
|
319
470
|
else throw e;
|
|
320
471
|
}
|
|
321
472
|
const out = (refs ?? []).map((r) => ({
|
|
322
|
-
from: r.from,
|
|
323
|
-
fromHex: hx(r.from),
|
|
324
|
-
to: r.to,
|
|
473
|
+
from: rb(r.from),
|
|
474
|
+
fromHex: hx(rb(r.from)),
|
|
475
|
+
to: rb(r.to),
|
|
325
476
|
type: (r.type || "").toLowerCase(), // CALL / CODE / DATA / STRING
|
|
326
477
|
opcode: r.opcode,
|
|
327
478
|
}));
|
|
@@ -333,23 +484,25 @@ export async function analyzeXrefs(romPath, address, platformOverride) {
|
|
|
333
484
|
* analysis pass. The "give me the shape of this ROM" call.
|
|
334
485
|
*/
|
|
335
486
|
export async function analyzeStructure(romPath, platformOverride) {
|
|
336
|
-
const { platform, romBytes, arch, bits, loadBase } = await loadContext(romPath, platformOverride);
|
|
337
|
-
const baddr = loadBase
|
|
487
|
+
const { platform, romBytes, arch, bits, endian, loadBase, codeStart } = await loadContext(romPath, platformOverride);
|
|
488
|
+
const { baddr, seedAt, rebase } = mipsAnalysisBase({ arch, platform, loadBase, codeStart });
|
|
489
|
+
const seed = analysisSeed({ arch, codeStart: seedAt });
|
|
338
490
|
const [fns, strings, entries] = await Promise.all([
|
|
339
|
-
runRizinJson({ romBytes, arch, bits, baddr, commands:
|
|
340
|
-
runRizinJson({ romBytes, arch, bits, baddr, commands:
|
|
341
|
-
runRizinJson({ romBytes, arch, bits, baddr, commands:
|
|
491
|
+
runRizinJson({ romBytes, arch, bits, endian, baddr, commands: `${seed}; aflj` }).catch(() => []),
|
|
492
|
+
runRizinJson({ romBytes, arch, bits, endian, baddr, commands: `${seed}; izj` }).catch(() => []),
|
|
493
|
+
runRizinJson({ romBytes, arch, bits, baddr, commands: `${seed}; iej` }).catch(() => []),
|
|
342
494
|
]);
|
|
495
|
+
const rb = (a) => (a == null ? a : (a + rebase) >>> 0);
|
|
343
496
|
return {
|
|
344
497
|
platform, arch,
|
|
345
498
|
functionCount: Array.isArray(fns) ? fns.length : 0,
|
|
346
499
|
stringCount: Array.isArray(strings) ? strings.length : 0,
|
|
347
|
-
entrypoints: (Array.isArray(entries) ? entries : []).map((e) => ({ address: e.vaddr, addressHex: hx(e.vaddr) })),
|
|
500
|
+
entrypoints: (Array.isArray(entries) ? entries : []).map((e) => ({ address: rb(e.vaddr), addressHex: hx(rb(e.vaddr)) })),
|
|
348
501
|
functions: (Array.isArray(fns) ? fns : []).slice(0, 512).map((f) => ({
|
|
349
|
-
address: f.offset, addressHex: hx(f.offset), name: f.name, size: f.size, callers: f.indegree ?? 0,
|
|
502
|
+
address: rb(f.offset), addressHex: hx(rb(f.offset)), name: f.name, size: f.size, callers: f.indegree ?? 0,
|
|
350
503
|
})),
|
|
351
504
|
strings: (Array.isArray(strings) ? strings : []).slice(0, 256).map((s) => ({
|
|
352
|
-
address: s.vaddr, addressHex: hx(s.vaddr), value: s.string,
|
|
505
|
+
address: rb(s.vaddr), addressHex: hx(rb(s.vaddr)), value: s.string,
|
|
353
506
|
})),
|
|
354
507
|
};
|
|
355
508
|
}
|
|
@@ -507,6 +660,48 @@ export async function analyzeDecompile(romPath, address, platformOverride) {
|
|
|
507
660
|
// to pure garbage.
|
|
508
661
|
if (platform === "genesis") romBytes = deinterleaveSmd(romBytes) ?? romBytes;
|
|
509
662
|
|
|
663
|
+
// MIPS (N64/PS1): `address` is a vaddr from target='functions'. Map it to a file
|
|
664
|
+
// offset and decompile via the MIPS SLEIGH spec (MIPS:BE:32 for N64, MIPS:LE:32
|
|
665
|
+
// for PS1). N64: normalize byte order, fileOff = vaddr - entryVaddr + 0x1000 (post
|
|
666
|
+
// IPL3). PS1: strip PS-EXE, fileOff = vaddr - loadAddr.
|
|
667
|
+
if (platform === "n64" || platform === "ps1" || platform === "dreamcast") {
|
|
668
|
+
let fileOff;
|
|
669
|
+
if (platform === "n64") {
|
|
670
|
+
romBytes = normalizeN64ByteOrder(romBytes).bytes;
|
|
671
|
+
const entry = ((romBytes[0x08] << 24) | (romBytes[0x09] << 16) | (romBytes[0x0a] << 8) | romBytes[0x0b]) >>> 0;
|
|
672
|
+
fileOff = ((address >>> 0) - entry + 0x1000) >>> 0;
|
|
673
|
+
} else if (platform === "dreamcast") {
|
|
674
|
+
// SH-4: strip the ELF to its first PT_LOAD segment (vaddr = loadBase), or treat
|
|
675
|
+
// a raw image as flat at 0x8c010000. fileOff = address - segment vaddr.
|
|
676
|
+
let loadVa = 0x8c010000;
|
|
677
|
+
if (romBytes.length >= 0x34 && romBytes[0] === 0x7f && romBytes[1] === 0x45 && romBytes[2] === 0x4c && romBytes[3] === 0x46) {
|
|
678
|
+
const u32 = (o) => (romBytes[o] | (romBytes[o + 1] << 8) | (romBytes[o + 2] << 16) | (romBytes[o + 3] << 24)) >>> 0;
|
|
679
|
+
const u16 = (o) => romBytes[o] | (romBytes[o + 1] << 8);
|
|
680
|
+
const phoff = u32(0x1c), phentsize = u16(0x2a), phnum = u16(0x2c);
|
|
681
|
+
for (let i = 0; i < phnum; i++) {
|
|
682
|
+
const ph = phoff + i * phentsize;
|
|
683
|
+
if (u32(ph) === 1) { loadVa = u32(ph + 8) >>> 0; romBytes = romBytes.subarray(u32(ph + 4), u32(ph + 4) + u32(ph + 16)); break; }
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
fileOff = ((address >>> 0) - loadVa) >>> 0;
|
|
687
|
+
} else {
|
|
688
|
+
let loadAddr = 0;
|
|
689
|
+
if (romBytes.length >= 0x800 && romBytes[0] === 0x50 && romBytes[1] === 0x53 && romBytes[2] === 0x2d && romBytes[3] === 0x58) {
|
|
690
|
+
loadAddr = (romBytes[0x18] | (romBytes[0x19] << 8) | (romBytes[0x1a] << 16) | (romBytes[0x1b] << 24)) >>> 0;
|
|
691
|
+
romBytes = romBytes.subarray(0x800);
|
|
692
|
+
}
|
|
693
|
+
fileOff = ((address >>> 0) - loadAddr) >>> 0;
|
|
694
|
+
}
|
|
695
|
+
if (fileOff >= romBytes.length) {
|
|
696
|
+
throw new Error(`decompile: ${platform} address ${hx(address)} maps to file offset ${hx(fileOff)}, outside the ${romBytes.length}-byte image. Use an address from target='functions'.`);
|
|
697
|
+
}
|
|
698
|
+
const rm = await decompileFunction({ platform, romBytes, fileOffset: fileOff });
|
|
699
|
+
return {
|
|
700
|
+
platform, langid: rm.langid, address, addressHex: hx(address),
|
|
701
|
+
code: prettyDecompile(rm.code, platform), warnings: rm.warnings,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
510
705
|
// SNES: banked 24-bit space. `address` is a LoROM/HiROM CPU address (what
|
|
511
706
|
// target='functions'/'cfg' report). Lay the cart out by CPU address so BOTH
|
|
512
707
|
// the function address AND its in-bank/JSL operands resolve, then decompile at
|
|
@@ -47,6 +47,13 @@ export const SLEIGH_LANGID = {
|
|
|
47
47
|
genesis: "68000:BE:32:default",
|
|
48
48
|
snes: "65816:LE:24:snes",
|
|
49
49
|
pce: "HuC6280:LE:16:default",
|
|
50
|
+
// 32-bit MIPS tier. PS1 R3000 = little-endian; N64 R4300 = big-endian (games run
|
|
51
|
+
// 32-bit MIPS III code). Ghidra ships both MIPS variants in its stock SLEIGH.
|
|
52
|
+
ps1: "MIPS:LE:32:default",
|
|
53
|
+
n64: "MIPS:BE:32:default",
|
|
54
|
+
// Dreamcast = SH-4 (SuperH), little-endian. Ghidra ships SuperH4 in its stock
|
|
55
|
+
// SLEIGH; we compile SuperH4_le.sla (see scripts/build-decompiler.sh).
|
|
56
|
+
dreamcast: "SuperH4:LE:32:default",
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<language_definitions>
|
|
3
|
+
<language processor="MIPS"
|
|
4
|
+
endian="big"
|
|
5
|
+
size="32"
|
|
6
|
+
variant="default"
|
|
7
|
+
version="1.7"
|
|
8
|
+
slafile="mips32be.sla"
|
|
9
|
+
processorspec="mips32.pspec"
|
|
10
|
+
id="MIPS:BE:32:default">
|
|
11
|
+
<description>MIPS32 big endian (N64 R4300, 32-bit code)</description>
|
|
12
|
+
<compiler name="default" spec="mips32be.cspec" id="default"/>
|
|
13
|
+
</language>
|
|
14
|
+
<language processor="MIPS"
|
|
15
|
+
endian="little"
|
|
16
|
+
size="32"
|
|
17
|
+
variant="default"
|
|
18
|
+
version="1.7"
|
|
19
|
+
slafile="mips32le.sla"
|
|
20
|
+
processorspec="mips32.pspec"
|
|
21
|
+
id="MIPS:LE:32:default">
|
|
22
|
+
<description>MIPS32 little endian (PS1 R3000)</description>
|
|
23
|
+
<compiler name="default" spec="mips32le.cspec" id="default"/>
|
|
24
|
+
</language>
|
|
25
|
+
</language_definitions>
|