Skip to content

Commit

Permalink
Add a CLI to export to JSON 💾.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmarty committed Apr 26, 2024
1 parent af594df commit e321dff
Show file tree
Hide file tree
Showing 6 changed files with 828 additions and 649 deletions.
146 changes: 146 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env node --experimental-json-modules

import { parseArgs } from 'node:util';
import { basename, extname } from 'node:path';
import { open, readFile, writeFile } from 'node:fs/promises';
import pkg from './package.json' with { type: 'json' };
import crc32 from './src/lib/crc32.js';
import {
isJapaneseVersion,
getResFromCrc32,
} from './src/lib/getResFromCrc32.js';
import parseRom from './src/lib/parseRom.js';
import { stringify } from './src/lib/cliUtils.js';

const options = {
version: {
type: 'boolean',
short: 'v',
default: false,
},
help: {
type: 'boolean',
short: 'h',
default: false,
},
input: {
type: 'string',
short: 'i',
},
output: {
type: 'string',
short: 'o',
},
};

const { values } = parseArgs({
options,
strict: true,
});

if (values.version) {
const { version } = pkg;
// See https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html
console.log(`scumm-nes ${version}`);
process.exit(0);
}

if (values.help) {
const { description, version, bugs, homepage } = pkg;

// See https://www.gnu.org/prep/standards/html_node/_002d_002dhelp.html
console.log(`\x1b[1mDescription\x1b[0m
${description} [version ${version}]
\x1b[1mSynopsis\x1b[0m
This CLI exports the content of a ROM or PRG to JSON.
\x1b[1mUsage\x1b[0m
node ${basename(import.meta.url)} --input path/to/rom --output resources.json
\x1b[1mOptions\x1b[0m
--input, -i Path to a ROM or PRG file.
--output, -o Path to a JSON filename to be created.
--version, -v Print the version number.
Report bugs to: <\x1b[1m${bugs.url || bugs}\x1b[0m>
Home page: <\x1b[1m${homepage}\x1b[0m>`);
process.exit(0);
}

if (!values.input && !values.output) {
console.error(
'The \x1b[1m--input\x1b[0m and \x1b[1m--output\x1b[0m arguments are required.',
);
process.exit(1);
}
if (!values.input) {
console.error('The \x1b[1m--input\x1b[0m argument is required.');
process.exit(1);
}
if (!values.output) {
console.error('The \x1b[1m--output\x1b[0m argument is required.');
process.exit(1);
}

const inputExtname = extname(values.input).toLowerCase();
if (inputExtname !== '.prg' && inputExtname !== '.nes') {
console.error('Only PRG and NES files are accepted.');
process.exit(1);
}
const outputExtname = extname(values.output).toLowerCase();
if (outputExtname !== '.json') {
console.error(
'Specify a \x1b[1m.json\x1b[0m file for the \x1b[1m--output\x1b[0m argument.',
);
process.exit(1);
}

// Check that the output file does not already exist.
let outputFileExists = null;
try {
await open(values.output);
outputFileExists = true;
} catch (e) {
outputFileExists = false;
}
if (outputFileExists) {
console.error(`File '${values.output}' already exists.`);
process.exit(1);
}

// Load the input file.
const romPath = values.input;
let rom;
try {
rom = await readFile(romPath);
} catch (e) {
console.error(`Error opening input file '${romPath}'.`);
process.exit(1);
}

const dataView = new DataView(rom.buffer);
const signature = crc32(dataView);

if (isJapaneseVersion(signature)) {
console.error('The Japanese Famicom version is not supported.');
process.exit(1);
}

const res = getResFromCrc32(signature);
const resources = parseRom(rom.buffer, res);

if (!resources) {
console.error('Upload one of the ROMs of Maniac Mansion on NES.');
process.exit(1);
}

// Process the result of a parsing.
const content = stringify(signature, rom.buffer.byteLength, resources, res);

try {
await writeFile(values.output, content, { encoding: 'utf-8' });
console.log('Output file successfully written.');
} catch (e) {
console.error(`Error writing output file '${values.output}'.`);
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scumm-nes",
"version": "0.1.0",
"version": "0.1.1",
"description": "An app to explore the content of the Maniac Mansion game on NES.",
"author": "[email protected]",
"license": "BlueOak-1.0.0",
Expand All @@ -20,6 +20,7 @@
"staticFiles": {
"staticPath": "public"
},
"main": "index.js",
"scripts": {
"test": "node --test",
"dev": "parcel",
Expand Down
24 changes: 24 additions & 0 deletions src/lib/cliUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { hex } from './utils.js';

const stringify = (signature, size, resources, res) => {
resources.rooms.forEach((item) => {
delete item.buffer;
delete item.map;
});
resources.roomgfx.forEach((item) => delete item.buffer);
resources.preps.forEach((item) => delete item.buffer);

const data = {
metadata: {
crc32: `0x${hex(signature)}`,
size,
version: res.version,
lang: res.lang,
},
resources,
};

return JSON.stringify(data, null, ' ');
};

export { stringify };
2 changes: 1 addition & 1 deletion src/lib/getResFromCrc32.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import res from './resources';
import res from './resources.js';

const isJapaneseVersion = (c) => c === 0x3da2085e || c === 0xf526cea8;
const getResFromCrc32 = (c) =>
Expand Down
6 changes: 3 additions & 3 deletions src/lib/parseRom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import parseRooms from '../lib/parseRooms';
import parseRoomGfx from '../lib/parseRoomGfx';
import parsePreps from './parsePreps';
import parseRooms from '../lib/parseRooms.js';
import parseRoomGfx from '../lib/parseRoomGfx.js';
import parsePreps from './parsePreps.js';

const parseRom = (arrayBuffer, res) => {
const rooms = [];
Expand Down
Loading

0 comments on commit e321dff

Please sign in to comment.