roomie 1.1.1 → 1.1.2

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 (2) hide show
  1. package/dist/roomie.js +39 -45
  2. package/package.json +6 -7
package/dist/roomie.js CHANGED
@@ -2,9 +2,7 @@ import path from "node:path";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { createHash } from "node:crypto";
4
4
  import { promises as fs } from "node:fs";
5
- import * as AdmZip from "adm-zip";
6
- // Handle ESM/CJS Interop for AdmZip
7
- const Zip = AdmZip.default || AdmZip;
5
+ import JSZip from "jszip";
8
6
  import { regions } from "./tables/regions.js";
9
7
  import { specs } from "./tables/specs.js";
10
8
  import { isHiRomBuffer } from "./systems/snes.js";
@@ -453,42 +451,38 @@ export class Roomie extends EventEmitter {
453
451
  return this.computeRegion(this._system, this._gamecode());
454
452
  }
455
453
  async load(pathOrBuffer) {
456
- let b;
457
454
  if (typeof pathOrBuffer === "string") {
458
455
  this._path = pathOrBuffer;
459
- const fileBuffer = await fs.readFile(pathOrBuffer);
456
+ let fileBuffer = await fs.readFile(pathOrBuffer);
460
457
  // Check if it's a ZIP by extension or magic
461
458
  if (pathOrBuffer.toLowerCase().endsWith(".zip") || (fileBuffer.length > 4 && fileBuffer.readUInt32BE(0) === 0x504B0304)) {
462
- const zip = new Zip(fileBuffer);
463
- const entries = zip.getEntries();
464
- // Look for the first entry that doesn't look like junk
465
- const romEntry = entries.find((e) => !e.isDirectory && !e.entryName.match(/\.(txt|jpg|png|xml|db|url)$|^\./i));
466
- if (!romEntry)
459
+ const zip = await JSZip.loadAsync(fileBuffer);
460
+ const romFilename = Object.keys(zip.files).find(f => !zip.files[f].dir && !f.match(/\.(txt|jpg|png|xml|db|url|json)$|^\./i));
461
+ if (!romFilename)
467
462
  throw new Error("no_rom_in_zip");
468
- b = zip.readFile(romEntry);
463
+ fileBuffer = Buffer.from(await zip.files[romFilename].async("nodebuffer"));
464
+ pathOrBuffer = romFilename;
469
465
  }
470
- else {
471
- b = fileBuffer;
472
- }
473
- this._rom = b;
474
- const detected = this.detectSystemFromPath(pathOrBuffer);
475
- this._system = detected || "sfc"; // Fallback to be handled by buffer check if needed
466
+ this._rom = fileBuffer;
467
+ const detectedByPath = this.detectSystemFromPath(pathOrBuffer);
468
+ this._system = detectedByPath || "sfc";
476
469
  }
477
- else {
470
+ else if (Buffer.isBuffer(pathOrBuffer)) {
478
471
  this._path = "in-memory";
479
- // Check if buffer is a ZIP
472
+ // Check if buffer is a ZIP (0x504B0304)
480
473
  if (pathOrBuffer.length > 4 && pathOrBuffer.readUInt32BE(0) === 0x504B0304) {
481
- const zip = new Zip(pathOrBuffer);
482
- const entries = zip.getEntries();
483
- const romEntry = entries.find((e) => !e.isDirectory && !e.entryName.match(/\.(txt|jpg|png|xml|db|url)$|^\./i));
484
- if (!romEntry)
474
+ const zip = await JSZip.loadAsync(pathOrBuffer);
475
+ const romFilename = Object.keys(zip.files).find(f => !zip.files[f].dir && !f.match(/\.(txt|jpg|png|xml|db|url|json)$|^\./i));
476
+ if (!romFilename)
485
477
  throw new Error("no_rom_in_zip");
486
- b = zip.readFile(romEntry);
478
+ this._rom = Buffer.from(await zip.files[romFilename].async("nodebuffer"));
487
479
  }
488
480
  else {
489
- b = pathOrBuffer;
481
+ this._rom = pathOrBuffer;
490
482
  }
491
- this._rom = b;
483
+ }
484
+ else {
485
+ throw new Error("Invalid path or buffer provided to Roomie.load()");
492
486
  }
493
487
  const romBuffer = this._rom;
494
488
  let detected = undefined;
@@ -502,43 +496,43 @@ export class Roomie extends EventEmitter {
502
496
  }
503
497
  }
504
498
  // Check GBA: game code at 0xAC-0xB0 ASCII uppercase letters/digits
505
- if (!detected && b.length >= 0xB0) {
506
- const code = b.subarray(0xAC, 0xB0).toString("ascii");
499
+ if (!detected && romBuffer.length >= 0xB0) {
500
+ const code = romBuffer.subarray(0xAC, 0xB0).toString("ascii");
507
501
  if (/^[A-Z0-9]{4}$/.test(code)) {
508
502
  detected = "gba";
509
503
  }
510
504
  }
511
505
  // Check GB: game code at 0x0134-0x0143 ASCII valid characters
512
- if (!detected && b.length >= 0x0143) {
513
- const code = b.subarray(0x0134, 0x0143).toString("ascii");
506
+ if (!detected && romBuffer.length >= 0x0143) {
507
+ const code = romBuffer.subarray(0x0134, 0x0143).toString("ascii");
514
508
  if (/^[A-Z0-9]{4,9}$/.test(code)) {
515
509
  detected = "gb";
516
510
  }
517
511
  }
518
512
  // Check NES: starts with "NES\x1a"
519
- if (!detected && b.length >= 4) {
520
- if (b[0] === 0x4E && b[1] === 0x45 && b[2] === 0x53 && b[3] === 0x1A) {
513
+ if (!detected && romBuffer.length >= 4) {
514
+ if (romBuffer[0] === 0x4E && romBuffer[1] === 0x45 && romBuffer[2] === 0x53 && romBuffer[3] === 0x1A) {
521
515
  detected = "nes";
522
516
  }
523
517
  }
524
518
  // Check N64: ASCII text at 0x20-0x2E AND Magic Word at 0x00
525
- if (!detected && b.length >= 0x2F) {
526
- const magic = b.readUInt32BE(0);
519
+ if (!detected && romBuffer.length >= 0x2F) {
520
+ const magic = romBuffer.readUInt32BE(0);
527
521
  // Common N64 magic values (Big Endian, Byte Swapped, Little Endian)
528
522
  if (magic === 0x80371240 || magic === 0x37804012 || magic === 0x40123780) {
529
523
  detected = "n64";
530
524
  }
531
525
  }
532
526
  // Check SFC: verify checksum or markup before falling back
533
- if (!detected && b.length >= 0x8000) {
534
- if (isHiRomBuffer(b)) {
527
+ if (!detected && romBuffer.length >= 0x8000) {
528
+ if (isHiRomBuffer(romBuffer)) {
535
529
  detected = "sfc";
536
530
  }
537
531
  else {
538
532
  // LoROM check
539
533
  const titleOff = 0x7FC0;
540
- if (b.length > titleOff + 20) {
541
- const title = b.subarray(titleOff, titleOff + 20).toString('ascii');
534
+ if (romBuffer.length > titleOff + 20) {
535
+ const title = romBuffer.subarray(titleOff, titleOff + 20).toString('ascii');
542
536
  if (/^[\x20-\x7E\s]+$/.test(title) && title.trim().length > 0) {
543
537
  detected = "sfc";
544
538
  }
@@ -546,8 +540,8 @@ export class Roomie extends EventEmitter {
546
540
  }
547
541
  }
548
542
  // Check Genesis: "SEGA" at 0x100
549
- if (!detected && b.length >= 0x104) {
550
- const magic = b.subarray(0x100, 0x104).toString("ascii");
543
+ if (!detected && romBuffer.length >= 0x104) {
544
+ const magic = romBuffer.subarray(0x100, 0x104).toString("ascii");
551
545
  if (magic === "SEGA") {
552
546
  detected = "genesis";
553
547
  }
@@ -555,16 +549,16 @@ export class Roomie extends EventEmitter {
555
549
  // Check SMS/GG: "TMR SEGA" at 0x7FF0, 0x3FF0 or 0x1FF0
556
550
  if (!detected) {
557
551
  for (const off of [0x7FF0, 0x3FF0, 0x1FF0]) {
558
- if (b.length >= off + 8 && b.subarray(off, off + 8).toString("ascii") === "TMR SEGA") {
559
- detected = b.length >= 0x8000 ? "sms" : "gg"; // Heuristic
552
+ if (romBuffer.length >= off + 8 && romBuffer.subarray(off, off + 8).toString("ascii") === "TMR SEGA") {
553
+ detected = romBuffer.length >= 0x8000 ? "sms" : "gg"; // Heuristic
560
554
  break;
561
555
  }
562
556
  }
563
557
  }
564
558
  // Check WSC: check model byte near end
565
- if (!detected && b.length >= 0x8000) {
566
- const off = b.length - 10;
567
- if (b[off + 1] === 0 || b[off + 1] === 1) { // 0=WS, 1=WSC
559
+ if (!detected && romBuffer.length >= 0x8000) {
560
+ const off = romBuffer.length - 10;
561
+ if (romBuffer[off + 1] === 0 || romBuffer[off + 1] === 1) { // 0=WS, 1=WSC
568
562
  // WSC check is a bit weak without developer ID check, but let's use it as heuristic
569
563
  // maybe only if extension also matches?
570
564
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roomie",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "ROM metadata helper",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,9 +14,6 @@
14
14
  "default": "./dist/index.mjs"
15
15
  }
16
16
  },
17
- "dependencies": {
18
- "adm-zip": "^0.5.10"
19
- },
20
17
  "engines": {
21
18
  "node": ">=18"
22
19
  },
@@ -31,9 +28,11 @@
31
28
  "prepare": "npm run clean && npm run build"
32
29
  },
33
30
  "devDependencies": {
34
- "@types/adm-zip": "^0.5.8",
35
- "@types/node": "^20.10.0",
31
+ "@types/node": "^20.19.37",
36
32
  "rimraf": "^5.0.0",
37
33
  "typescript": "^5.6.0"
34
+ },
35
+ "dependencies": {
36
+ "jszip": "^3.10.1"
38
37
  }
39
- }
38
+ }