roomie 1.0.2 → 1.0.4
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/LICENSE +21 -0
- package/README.md +90 -13
- package/dist/index.cjs +4 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.mjs +4 -3
- package/dist/roomie.d.ts +16 -1
- package/dist/roomie.js +228 -20
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nicolás Contreras
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
# roomie
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
 
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
**roomie** is a lightweight library for extracting metadata from ROM files of classic gaming consoles. It supports multiple systems and provides detailed information such as game title, region, game code, ROM and RAM sizes, version, and other console-specific data. Designed for simplicity and accuracy, roomie aids developers and enthusiasts in analyzing ROM files programmatically.
|
|
10
|
+
|
|
11
|
+
---
|
|
4
12
|
|
|
5
13
|
## Installation
|
|
6
14
|
|
|
15
|
+
Install via npm:
|
|
16
|
+
|
|
7
17
|
```bash
|
|
8
18
|
npm install roomie
|
|
9
19
|
```
|
|
10
20
|
|
|
21
|
+
---
|
|
22
|
+
|
|
11
23
|
## Usage
|
|
12
24
|
|
|
25
|
+
roomie supports both CommonJS (CJS) and ES Modules (ESM) import styles.
|
|
26
|
+
|
|
27
|
+
### CommonJS (CJS)
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const Roomie = require("roomie");
|
|
31
|
+
|
|
32
|
+
const romPath = "/path/to/game.sfc";
|
|
33
|
+
const roomie = new Roomie(romPath);
|
|
34
|
+
|
|
35
|
+
roomie.on("loaded", (info) => {
|
|
36
|
+
console.log("ROM Information:", info);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### ES Modules (ESM)
|
|
41
|
+
|
|
13
42
|
```ts
|
|
14
43
|
import Roomie from "roomie";
|
|
15
44
|
|
|
@@ -28,24 +57,72 @@ await roomie.load(romBuffer);
|
|
|
28
57
|
console.log(roomie.info);
|
|
29
58
|
```
|
|
30
59
|
|
|
60
|
+
---
|
|
61
|
+
|
|
31
62
|
## Supported Consoles
|
|
32
63
|
|
|
33
|
-
|
|
64
|
+
| Console | Description |
|
|
65
|
+
|-----------------------------|---------------------------------------------------------------|
|
|
66
|
+
| **Nintendo DS (NDS)** | Extracts game name, region, game code, ROM/RAM size, version, and other metadata. |
|
|
67
|
+
| **Game Boy Advance (GBA)** | Provides title, game code, region, ROM/RAM size, version, and related info. |
|
|
68
|
+
| **Game Boy (GB)** | Retrieves title, cartridge type, ROM/RAM size, and additional metadata. |
|
|
69
|
+
| **Super Nintendo / Super Famicom (SNES/SFC)** | Detects ROM type (HiROM/LoROM), game name, region, code, ROM size, and console-specific fields. |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
### Constructor
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
new Roomie(pathOrBuffer: string | Buffer)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Creates a new Roomie instance and immediately loads the ROM from the given file path or Buffer. Emits the `'loaded'` event once metadata extraction is complete.
|
|
82
|
+
|
|
83
|
+
### Methods
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
await roomie.load(pathOrBuffer: string | Buffer): Promise<void>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Loads or reloads a ROM from a file path or Buffer, emitting `'loaded'` on success.
|
|
90
|
+
|
|
91
|
+
### Properties
|
|
92
|
+
|
|
93
|
+
- `roomie.info: RomInfo` – Object containing extracted ROM metadata.
|
|
94
|
+
- `roomie.rom: Buffer` – Raw bytes of the loaded ROM.
|
|
95
|
+
- `roomie.system: "nds" | "gba" | "gb" | "sfc"` – Detected console system.
|
|
96
|
+
|
|
97
|
+
### Events
|
|
98
|
+
|
|
99
|
+
- `'loaded'` – Emitted when ROM metadata is successfully loaded and parsed. The listener receives the `info` object.
|
|
100
|
+
|
|
101
|
+
### Example JSON Output
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"system": "gba",
|
|
106
|
+
"title": "METROID FUSION",
|
|
107
|
+
"gameCode": "AGB-AMME",
|
|
108
|
+
"region": "USA",
|
|
109
|
+
"romSize": 2097152,
|
|
110
|
+
"ramSize": 32768,
|
|
111
|
+
"version": 1,
|
|
112
|
+
"checksum": "0x1234"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
34
117
|
|
|
35
|
-
|
|
36
|
-
- **Game Boy Advance (GBA):** Extracts information about the title, game code, region, ROM and RAM size, version, etc.
|
|
37
|
-
- **Game Boy (GB):** Provides details about the title, cartridge type, ROM and RAM size, and other metadata.
|
|
38
|
-
- **Super Nintendo / Super Famicom (SNES/SFC):** Detects the ROM type (HiROM/LoROM), game name, region, code, ROM size, and other specific fields.
|
|
118
|
+
## Error Handling
|
|
39
119
|
|
|
40
|
-
|
|
120
|
+
If the ROM cannot be identified, the library throws errors with codes:
|
|
41
121
|
|
|
42
|
-
- `
|
|
43
|
-
- `
|
|
44
|
-
- `roomie.info: RomInfo` – Object with the analyzed ROM metadata, including system, size, game code, region, and other specific fields.
|
|
45
|
-
- `roomie.rom: Buffer` – Contains the raw bytes of the loaded ROM file.
|
|
46
|
-
- `roomie.system: "nds" | "gba" | "gb" | "sfc"` – Detected system based on the file extension and content.
|
|
122
|
+
- `unknown_file` – When loading from a file path and the system is unrecognized.
|
|
123
|
+
- `unknown_bytes` – When loading from a Buffer and the system is unrecognized.
|
|
47
124
|
|
|
48
|
-
|
|
125
|
+
---
|
|
49
126
|
|
|
50
127
|
## License
|
|
51
128
|
|
package/dist/index.cjs
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export * from "./
|
|
1
|
+
import Roomie from "./roomie.js";
|
|
2
|
+
export default Roomie;
|
|
3
|
+
export * from "./roomie.js";
|
|
4
|
+
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export * from "./
|
|
1
|
+
import Roomie from "./roomie.js";
|
|
2
|
+
export default Roomie;
|
|
3
|
+
export * from "./roomie.js";
|
|
4
|
+
export * from "./types.js";
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export * from "./
|
|
1
|
+
import Roomie from "./roomie.js";
|
|
2
|
+
export default Roomie;
|
|
3
|
+
export * from "./roomie.js";
|
|
4
|
+
export * from "./types.js";
|
package/dist/roomie.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import type { SupportedSystem } from "./types";
|
|
2
|
+
import type { SupportedSystem } from "./types.js";
|
|
3
3
|
export interface RomInfo {
|
|
4
4
|
path: string;
|
|
5
5
|
system: SupportedSystem;
|
|
@@ -19,17 +19,32 @@ export interface RomInfo {
|
|
|
19
19
|
ram?: number;
|
|
20
20
|
hardware?: Record<string, unknown>;
|
|
21
21
|
};
|
|
22
|
+
n64?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
country?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
};
|
|
22
27
|
}
|
|
23
28
|
export declare class Roomie extends EventEmitter {
|
|
24
29
|
private _path;
|
|
25
30
|
private _rom;
|
|
26
31
|
private _system;
|
|
27
32
|
private _info;
|
|
33
|
+
name?: string;
|
|
34
|
+
gameid?: string;
|
|
35
|
+
region?: string;
|
|
36
|
+
gamecode?: string;
|
|
37
|
+
cartridge?: Record<string, unknown>;
|
|
28
38
|
constructor(path: string);
|
|
29
39
|
private detectSystemFromPath;
|
|
30
40
|
private readGameCode;
|
|
31
41
|
private computeRegion;
|
|
32
42
|
private computeSfcInfo;
|
|
43
|
+
private _name;
|
|
44
|
+
private _gameid;
|
|
45
|
+
private _gamecode;
|
|
46
|
+
private _cartridge;
|
|
47
|
+
private _region;
|
|
33
48
|
load(pathOrBuffer: string | Buffer): Promise<void>;
|
|
34
49
|
get info(): RomInfo;
|
|
35
50
|
get system(): SupportedSystem;
|
package/dist/roomie.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { promises as fs } from "node:fs";
|
|
4
|
-
import { regions } from "./tables/regions";
|
|
5
|
-
import { specs } from "./tables/specs";
|
|
6
|
-
import { isHiRomBuffer } from "./systems/snes";
|
|
4
|
+
import { regions } from "./tables/regions.js";
|
|
5
|
+
import { specs } from "./tables/specs.js";
|
|
6
|
+
import { isHiRomBuffer } from "./systems/snes.js";
|
|
7
7
|
export class Roomie extends EventEmitter {
|
|
8
8
|
constructor(path) {
|
|
9
9
|
super();
|
|
@@ -19,6 +19,8 @@ export class Roomie extends EventEmitter {
|
|
|
19
19
|
return "gb";
|
|
20
20
|
if (ext === "sfc" || ext === "smc")
|
|
21
21
|
return "sfc";
|
|
22
|
+
if (ext === "z64" || ext === "n64")
|
|
23
|
+
return "n64";
|
|
22
24
|
// Default to sfc to keep compatibility with original intent; could be improved.
|
|
23
25
|
return "sfc";
|
|
24
26
|
}
|
|
@@ -34,6 +36,9 @@ export class Roomie extends EventEmitter {
|
|
|
34
36
|
if (system === "gb" && b.length >= 0x0143) {
|
|
35
37
|
return b.subarray(0x013F, 0x0143).toString("ascii");
|
|
36
38
|
}
|
|
39
|
+
if (system === "n64" && b.length >= 0x2F) {
|
|
40
|
+
return b.subarray(0x20, 0x2F).toString("ascii").trim();
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
43
|
catch { }
|
|
39
44
|
return undefined;
|
|
@@ -66,40 +71,224 @@ export class Roomie extends EventEmitter {
|
|
|
66
71
|
return regions.snes[key];
|
|
67
72
|
}
|
|
68
73
|
return undefined;
|
|
74
|
+
case "n64":
|
|
75
|
+
// N64 region code at offset 0x3E in some ROMs (common practice)
|
|
76
|
+
if (this._rom.length > 0x3E) {
|
|
77
|
+
const regionByte = this._rom[0x3E];
|
|
78
|
+
// Map region byte to region string (basic example)
|
|
79
|
+
const regionMap = {
|
|
80
|
+
0x44: "USA",
|
|
81
|
+
0x45: "Europe",
|
|
82
|
+
0x46: "France",
|
|
83
|
+
0x4A: "Japan",
|
|
84
|
+
0x50: "PAL",
|
|
85
|
+
0x55: "Australia",
|
|
86
|
+
0x58: "Germany",
|
|
87
|
+
0x59: "Europe",
|
|
88
|
+
0x5A: "Europe",
|
|
89
|
+
};
|
|
90
|
+
return regionMap[regionByte] || "Unknown";
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
69
93
|
}
|
|
70
94
|
}
|
|
71
95
|
computeSfcInfo() {
|
|
72
96
|
if (this._system !== "sfc")
|
|
73
97
|
return undefined;
|
|
74
98
|
const hi = isHiRomBuffer(this._rom);
|
|
75
|
-
const base = hi ? 0xFFD0 : 0x7FD0;
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
99
|
+
const base = hi ? 0xFFD0 : 0x7FD0;
|
|
100
|
+
const offD5 = base + 0x05; // map mode (for specs mapping)
|
|
101
|
+
const offD6 = base + 0x06; // hardware type
|
|
102
|
+
const offD7 = base + 0x07; // ROM size exponent
|
|
103
|
+
const offD8 = base + 0x08; // RAM size exponent
|
|
104
|
+
const offD9 = base + 0x09; // raw romSpeed byte
|
|
80
105
|
const out = {};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
out.romSpeed =
|
|
106
|
+
// romSpeed: raw D9 byte as string, like original JS
|
|
107
|
+
if (this._rom.length > offD9) {
|
|
108
|
+
out.romSpeed = this._rom[offD9].toString().trim();
|
|
109
|
+
}
|
|
110
|
+
// specs: map D5 (2-digit hex) to specs.sfc.romspeed
|
|
111
|
+
if (this._rom.length > offD5) {
|
|
112
|
+
const key = this._rom[offD5].toString(16).padStart(2, "0");
|
|
84
113
|
const spec = specs.sfc?.romspeed?.[key];
|
|
85
114
|
if (spec)
|
|
86
115
|
out.rom = { ...(out.rom || {}), type: spec.type, speed: spec.speed };
|
|
87
116
|
}
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
// rom size from D7 using original expression
|
|
118
|
+
if (this._rom.length > offD7) {
|
|
119
|
+
const exp = this._rom[offD7];
|
|
120
|
+
const size = 2 ** (2 ^ exp) * 1000;
|
|
121
|
+
out.rom = { ...(out.rom || {}), size };
|
|
122
|
+
}
|
|
123
|
+
// ram size from D8 using original expression
|
|
124
|
+
if (this._rom.length > offD8) {
|
|
125
|
+
const exp = this._rom[offD8];
|
|
126
|
+
out.ram = 2 ** (2 ^ exp) * 1000;
|
|
127
|
+
}
|
|
128
|
+
// hardware from D6 (2-digit hex)
|
|
129
|
+
if (this._rom.length > offD6) {
|
|
130
|
+
const hwKey = this._rom[offD6].toString(16).padStart(2, "0");
|
|
90
131
|
const hw = specs.sfc?.hardware?.[hwKey];
|
|
91
132
|
if (hw)
|
|
92
133
|
out.hardware = hw;
|
|
93
134
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
_name() {
|
|
138
|
+
const b = this._rom;
|
|
139
|
+
try {
|
|
140
|
+
switch (this._system) {
|
|
141
|
+
case "nds":
|
|
142
|
+
if (b.length >= 0x20) {
|
|
143
|
+
return b.subarray(0x0, 0x20).toString("ascii").replace(/\0/g, "").trim();
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case "gba":
|
|
147
|
+
if (b.length >= 0xAC) {
|
|
148
|
+
return b.subarray(0xA0, 0xAC).toString("ascii").replace(/\0/g, "").trim();
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
case "gb":
|
|
152
|
+
if (b.length >= 0x134) {
|
|
153
|
+
return b.subarray(0x134, 0x144).toString("ascii").replace(/\0/g, "").trim();
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
case "sfc":
|
|
157
|
+
// SNES title at 0x7FC0 or 0xFFC0 depending on LoROM/HiROM
|
|
158
|
+
const hi = isHiRomBuffer(b);
|
|
159
|
+
const base = hi ? 0xFFC0 : 0x7FC0;
|
|
160
|
+
if (b.length > base + 21) {
|
|
161
|
+
return b.subarray(base, base + 21).toString("ascii").replace(/\0/g, "").trim();
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case "n64":
|
|
165
|
+
if (b.length >= 0x20) {
|
|
166
|
+
return b.subarray(0x20, 0x34).toString("ascii").replace(/\0/g, "").trim();
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
97
170
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
171
|
+
catch { }
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
_gameid() {
|
|
175
|
+
const code = this._gamecode();
|
|
176
|
+
if (!code)
|
|
177
|
+
return undefined;
|
|
178
|
+
switch (this._system) {
|
|
179
|
+
case "nds":
|
|
180
|
+
return "NTR-" + code;
|
|
181
|
+
case "gba":
|
|
182
|
+
return "AGB-" + code;
|
|
183
|
+
default:
|
|
184
|
+
return undefined;
|
|
101
185
|
}
|
|
102
|
-
|
|
186
|
+
}
|
|
187
|
+
_gamecode() {
|
|
188
|
+
const b = this._rom;
|
|
189
|
+
try {
|
|
190
|
+
switch (this._system) {
|
|
191
|
+
case "nds":
|
|
192
|
+
if (b.length >= 0x10) {
|
|
193
|
+
return b.subarray(0x0C, 0x10).toString("ascii");
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
case "gba":
|
|
197
|
+
if (b.length >= 0xB0) {
|
|
198
|
+
return b.subarray(0xAC, 0xB0).toString("ascii");
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case "gb":
|
|
202
|
+
if (b.length >= 0x0143) {
|
|
203
|
+
return b.subarray(0x013F, 0x0143).toString("ascii");
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "n64":
|
|
207
|
+
if (b.length >= 0x2F) {
|
|
208
|
+
return b.subarray(0x20, 0x2F).toString("ascii").trim();
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch { }
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
_cartridge() {
|
|
217
|
+
// Build cartridge metadata depending on system
|
|
218
|
+
const b = this._rom;
|
|
219
|
+
switch (this._system) {
|
|
220
|
+
case "nds": {
|
|
221
|
+
const code = this._gamecode();
|
|
222
|
+
const region = this._region();
|
|
223
|
+
return {
|
|
224
|
+
system: "nds",
|
|
225
|
+
gameCode: code,
|
|
226
|
+
region,
|
|
227
|
+
size: b.length,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
case "gba": {
|
|
231
|
+
const code = this._gamecode();
|
|
232
|
+
const region = this._region();
|
|
233
|
+
return {
|
|
234
|
+
system: "gba",
|
|
235
|
+
gameCode: code,
|
|
236
|
+
region,
|
|
237
|
+
size: b.length,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
case "gb": {
|
|
241
|
+
const region = this._region();
|
|
242
|
+
return {
|
|
243
|
+
system: "gb",
|
|
244
|
+
region,
|
|
245
|
+
size: b.length,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
case "sfc": {
|
|
249
|
+
const sfcInfo = this.computeSfcInfo();
|
|
250
|
+
return {
|
|
251
|
+
system: "sfc",
|
|
252
|
+
...sfcInfo,
|
|
253
|
+
size: b.length,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
case "n64": {
|
|
257
|
+
// N64 cartridge info from header bytes
|
|
258
|
+
const countryByte = b.length > 0x3E ? b[0x3E] : undefined;
|
|
259
|
+
const versionByte = b.length > 0x3F ? b[0x3F] : undefined;
|
|
260
|
+
const countryMap = {
|
|
261
|
+
0x00: "Japan",
|
|
262
|
+
0x01: "USA",
|
|
263
|
+
0x02: "Europe",
|
|
264
|
+
0x03: "Germany",
|
|
265
|
+
0x04: "France",
|
|
266
|
+
0x05: "Spain",
|
|
267
|
+
0x06: "Italy",
|
|
268
|
+
0x07: "China",
|
|
269
|
+
0x08: "Australia",
|
|
270
|
+
0x09: "Unknown",
|
|
271
|
+
0x0A: "Unknown",
|
|
272
|
+
0x0B: "Unknown",
|
|
273
|
+
0x0C: "Unknown",
|
|
274
|
+
0x0D: "Unknown",
|
|
275
|
+
0x0E: "Unknown",
|
|
276
|
+
0x0F: "Unknown",
|
|
277
|
+
};
|
|
278
|
+
return {
|
|
279
|
+
system: "n64",
|
|
280
|
+
name: this._name(),
|
|
281
|
+
country: countryByte !== undefined ? countryMap[countryByte] || "Unknown" : undefined,
|
|
282
|
+
version: versionByte !== undefined ? versionByte.toString() : undefined,
|
|
283
|
+
size: b.length,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
default:
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
_region() {
|
|
291
|
+
return this.computeRegion(this._system, this._gamecode());
|
|
103
292
|
}
|
|
104
293
|
async load(pathOrBuffer) {
|
|
105
294
|
if (typeof pathOrBuffer === "string") {
|
|
@@ -136,6 +325,13 @@ export class Roomie extends EventEmitter {
|
|
|
136
325
|
detected = "gb";
|
|
137
326
|
}
|
|
138
327
|
}
|
|
328
|
+
// Check N64: ASCII text at 0x20-0x2E
|
|
329
|
+
if (!detected && b.length >= 0x2F) {
|
|
330
|
+
const code = b.subarray(0x20, 0x2F).toString("ascii");
|
|
331
|
+
if (/^[\x20-\x7E]+$/.test(code) && code.trim().length > 0) {
|
|
332
|
+
detected = "n64";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
139
335
|
// Check SFC: use isHiRomBuffer heuristic
|
|
140
336
|
if (!detected) {
|
|
141
337
|
if (b.length > 0x8000 && (isHiRomBuffer(b) || !isHiRomBuffer(b))) {
|
|
@@ -160,7 +356,19 @@ export class Roomie extends EventEmitter {
|
|
|
160
356
|
if (this._system === "sfc") {
|
|
161
357
|
info.sfc = this.computeSfcInfo();
|
|
162
358
|
}
|
|
359
|
+
if (this._system === "n64") {
|
|
360
|
+
info.n64 = {
|
|
361
|
+
name: this._name(),
|
|
362
|
+
country: this._region(),
|
|
363
|
+
version: this._rom.length > 0x3F ? this._rom[0x3F].toString() : undefined,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
163
366
|
this._info = info;
|
|
367
|
+
this.name = this._name();
|
|
368
|
+
this.gameid = this._gameid();
|
|
369
|
+
this.region = this._region();
|
|
370
|
+
this.gamecode = this._gamecode();
|
|
371
|
+
this.cartridge = this._cartridge();
|
|
164
372
|
this.emit("loaded", info);
|
|
165
373
|
}
|
|
166
374
|
get info() { return this._info; }
|
package/dist/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type SupportedSystem = "nds" | "gba" | "gb" | "sfc";
|
|
1
|
+
export type SupportedSystem = "nds" | "gba" | "gb" | "sfc" | "n64";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roomie",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "ROM metadata helper",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"require": "./dist/index.cjs",
|
|
13
13
|
"import": "./dist/index.mjs",
|
|
14
|
-
"
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|