Skip to content

Commit

Permalink
Allow passing base ROM to CLI commands 📡.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmarty committed May 15, 2024
1 parent c81fb78 commit f2457ed
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 27 deletions.
8 changes: 6 additions & 2 deletions experiments/expandRom.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const { values } = parseArgs({
type: 'string',
short: 'i',
},
'base-rom': {
type: 'string',
short: 'b',
},
},
strict: true,
});
Expand All @@ -27,14 +31,14 @@ if (!values.input) {
It creates a new ROM file called '${output}' in current path.
\x1b[1mUsage\x1b[0m
node ${basename(import.meta.url)} --input path/to/rom`);
node ${basename(import.meta.url)} --input path/to/rom [--base-rom xxx]`);
process.exit(0);
}

// Load the input file.
let rom;
try {
({ rom } = await loadRom(values.input));
({ rom } = await loadRom(values.input, values['base-rom']));
} catch (err) {
console.error(`Input ROM file could not be opened.`);
console.error(err);
Expand Down
8 changes: 6 additions & 2 deletions experiments/updatePreps.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const { values } = parseArgs({
type: 'string',
short: 'i',
},
'base-rom': {
type: 'string',
short: 'b',
},
},
strict: true,
});
Expand All @@ -28,14 +32,14 @@ if (!values.input) {
It creates a new ROM file called '${output}' in current path.
\x1b[1mUsage\x1b[0m
node ${basename(import.meta.url)} --input path/to/rom`);
node ${basename(import.meta.url)} --input path/to/rom [--base-rom xxx]`);
process.exit(0);
}

// Load the input file.
let rom, res;
try {
({ rom, res } = await loadRom(values.input));
({ rom, res } = await loadRom(values.input, values['base-rom']));
} catch (err) {
console.error(`Input ROM file could not be opened.`);
console.error(err);
Expand Down
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const options = {
type: 'string',
short: 'i',
},
'base-rom': {
type: 'string',
short: 'b',
},
output: {
type: 'string',
short: 'o',
Expand Down Expand Up @@ -56,6 +60,7 @@ if (values.help || (!values.input && !values.output)) {
\x1b[1mOptions\x1b[0m
--input, -i Path to a ROM or PRG file.
--base-rom, -b The base ROM version of the input ROM.
--output, -o Path to a JSON filename to be created.
--version, -v Print the version number.
Expand Down Expand Up @@ -103,7 +108,7 @@ if (outputFileExists) {
// Load the input file.
let rom, res, hash;
try {
({ rom, res, hash } = await loadRom(values.input));
({ rom, res, hash } = await loadRom(values.input, values['base-rom']));
} catch (err) {
console.error(`Input ROM file could not be opened.`);
console.error(err);
Expand Down
8 changes: 3 additions & 5 deletions src/components/BaseRomDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import {
TransitionChild,
} from '@headlessui/react';
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline';
import resources from '../lib/resources';
import { BASE_ROMS } from '../lib/getResFromCrc32';

const BASE_ROMS = resources.map(({ metadata: { name } }) => name);
BASE_ROMS.sort();
const defaultValue = 'USA'; // The USA version is the most commonly used base ROM.

const BaseRomDialog = ({ open, setOpen, setBaseRom }) => {
Expand Down Expand Up @@ -101,8 +99,8 @@ const BaseRomSelector = ({ setBaseRom }) => {
className="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-primary-600 sm:text-sm sm:leading-6"
defaultValue={defaultValue}
onChange={({ target }) => setBaseRom(target.value)}>
{BASE_ROMS.map((baseRom) => (
<option key={baseRom}>{baseRom}</option>
{BASE_ROMS.map(({ name }) => (
<option key={name}>{name}</option>
))}
</select>
</>
Expand Down
31 changes: 27 additions & 4 deletions src/lib/cliUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { extname } from 'node:path';
import { readFile, writeFile } from 'node:fs/promises';
import crc32 from './crc32.js';
import { isJapaneseVersion, getResFromCrc32 } from './getResFromCrc32.js';
import {
BASE_ROMS,
isJapaneseVersion,
getResFromCrc32,
getResFromBaseRom,
} from './getResFromCrc32.js';
import { hex, hasNesHeader } from './utils.js';

const BANK_SIZE = 0x4000;
Expand All @@ -13,7 +18,7 @@ const NES_HEADER = new Uint8Array([
0x00, 0x00, 0x00, 0x00,
]);

const loadRom = async (romPath = '') => {
const loadRom = async (romPath = '', baseRom = null) => {
const inputExtname = extname(romPath).toLowerCase();
if (inputExtname !== '.prg' && inputExtname !== '.nes') {
throw new Error('Only PRG and NES files are accepted.');
Expand All @@ -39,11 +44,29 @@ const loadRom = async (romPath = '') => {
throw new Error('The Japanese Famicom version is not supported.');
}

const res = getResFromCrc32(hash);
let res;
if (baseRom) {
res = getResFromBaseRom(baseRom);
if (typeof baseRom !== 'string' || !res) {
const baseRomList = BASE_ROMS.map(
({ name, alpha2code }) => `${alpha2code} (${name})`,
);
baseRomList.sort();
const formatter = new Intl.ListFormat('en-GB', {
style: 'long',
type: 'conjunction',
});
throw new Error(
`Base ROM only accepts the following values: ${formatter.format(baseRomList)}.`,
);
}
} else {
res = getResFromCrc32(hash);
}

if (!res) {
throw new Error(
`The file at '${romPath}' is not a valid ROM of Maniac Mansion on NES.`,
`The file '${romPath}' is not a valid ROM of Maniac Mansion on NES. If it's a ROM hack, pass the base ROM version to --base-rom.`,
);
}

Expand Down
18 changes: 13 additions & 5 deletions src/lib/getResFromCrc32.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import res from './resources.js';

const BASE_ROMS = res.map(({ metadata }) => metadata);
BASE_ROMS.sort((a, b) => a.name.localeCompare(b.name));

const isJapaneseVersion = (c) => c === 0x3da2085e || c === 0xf526cea8;
const getResFromCrc32 = (c) =>
res.find(({ crc32, crc32Rom }) => crc32 === c || crc32Rom === c) ?? null;
const getResFromBaseRom = (b) =>
res.find(
({ metadata: { name } }) => name.toLowerCase() === b.toLowerCase(),
) ?? null;
const getResFromBaseRom = (baseRom) =>
res.find(({ metadata: { name, alpha2code, alpha3code } }) => {
baseRom = baseRom.toLowerCase();
return (
baseRom === name.toLowerCase() ||
baseRom === alpha2code.toLowerCase() ||
baseRom === alpha3code.toLowerCase()
);
}) ?? null;

export { isJapaneseVersion, getResFromCrc32, getResFromBaseRom };
export { BASE_ROMS, isJapaneseVersion, getResFromCrc32, getResFromBaseRom };
16 changes: 8 additions & 8 deletions src/lib/resources.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// prettier-ignore
const usa = {
metadata: { name: 'USA' },
metadata: { name: 'USA', alpha2code: 'us', alpha3code: 'usa' },
roomgfx: [
[0x04001, 0x03c9], [0x043ca, 0x069e], [0x04a68, 0x0327], [0x04d8f, 0x053b], [0x052ca, 0x06be],
[0x05988, 0x0682], [0x0600a, 0x0778], [0x06782, 0x0517], [0x06c99, 0x07fb], [0x07494, 0x07be],
Expand Down Expand Up @@ -110,7 +110,7 @@ const usa = {
};
// prettier-ignore
const eur = {
metadata: { name: 'Europe' },
metadata: { name: 'Europe', alpha2code: 'eu', alpha3code: 'eur' },
roomgfx: [
[0x04001, 0x03b9], [0x043ba, 0x069e], [0x04a58, 0x0327], [0x04d7f, 0x053b], [0x052ba, 0x06be],
[0x05978, 0x0682], [0x05ffa, 0x0778], [0x06772, 0x0517], [0x06c89, 0x07fb], [0x07484, 0x07be],
Expand Down Expand Up @@ -220,7 +220,7 @@ const eur = {
};
// prettier-ignore
const swe = {
metadata: { name: 'Sweden' },
metadata: { name: 'Sweden', alpha2code: 'se', alpha3code: 'swe' },
roomgfx: [
[0x04001, 0x03f0], [0x043f1, 0x069e], [0x04a8f, 0x0327], [0x04db6, 0x053b], [0x052f1, 0x06be],
[0x059af, 0x0682], [0x06031, 0x0778], [0x067a9, 0x0517], [0x06cc0, 0x07fb], [0x074bb, 0x07be],
Expand Down Expand Up @@ -329,7 +329,7 @@ const swe = {
};
// prettier-ignore
const fra = {
metadata: { name: 'France' },
metadata: { name: 'France', alpha2code: 'fr', alpha3code: 'fra' },
roomgfx: [
[0x04001, 0x0426], [0x04427, 0x069e], [0x04ac5, 0x0327], [0x04dec, 0x053b], [0x05327, 0x06be],
[0x059e5, 0x0682], [0x06067, 0x0778], [0x067df, 0x0517], [0x06cf6, 0x07fb], [0x074f1, 0x07be],
Expand Down Expand Up @@ -437,7 +437,7 @@ const fra = {
};
// prettier-ignore
const ger = {
metadata: { name: 'Germany' },
metadata: { name: 'Germany', alpha2code: 'de', alpha3code: 'deu' },
roomgfx: [
[0x04001, 0x0406], [0x04407, 0x069e], [0x04aa5, 0x0327], [0x04dcc, 0x053b], [0x05307, 0x06be],
[0x059c5, 0x0682], [0x06047, 0x0778], [0x067bf, 0x0517], [0x06cd6, 0x07fb], [0x074d1, 0x07be],
Expand Down Expand Up @@ -546,7 +546,7 @@ const ger = {
};
// prettier-ignore
const esp = {
metadata: { name: 'Spain' },
metadata: { name: 'Spain', alpha2code: 'es', alpha3code: 'esp' },
roomgfx: [
[0x04001, 0x041b], [0x0441c, 0x069e], [0x04aba, 0x0327], [0x04de1, 0x053b], [0x0531c, 0x06be],
[0x059da, 0x0682], [0x0605c, 0x0778], [0x067d4, 0x0517], [0x06ceb, 0x07fb], [0x074e6, 0x07be],
Expand Down Expand Up @@ -654,7 +654,7 @@ const esp = {
};
// prettier-ignore
const ita = {
metadata: { name: 'Italy' },
metadata: { name: 'Italy', alpha2code: 'it', alpha3code: 'ita' },
roomgfx: [
[0x04001, 0x03ef], [0x043f0, 0x069e], [0x04a8e, 0x0327], [0x04db5, 0x053b], [0x052f0, 0x06be],
[0x059ae, 0x0682], [0x06030, 0x0778], [0x067a8, 0x0517], [0x06cbf, 0x07fb], [0x074ba, 0x07be],
Expand Down Expand Up @@ -761,7 +761,7 @@ const ita = {
};
// prettier-ignore
const proto = {
metadata: { name: 'Prototype' },
metadata: { name: 'Prototype', alpha2code: 'proto', alpha3code: 'proto' },
roomgfx: [
[0x04001, 0x03c9], [0x043ca, 0x069e], [0x04a68, 0x0327], [0x04d8f, 0x053b], [0x052ca, 0x06be],
[0x05988, 0x0682], [0x0600a, 0x0778], [0x06782, 0x0517], [0x06c99, 0x07fb], [0x07494, 0x07be],
Expand Down

0 comments on commit f2457ed

Please sign in to comment.