diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e1b41e3..2e3b21e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,9 +20,12 @@ jobs: steps: - name: Checkout the repository using git uses: actions/checkout@v4 - - run: node --version + - name: Set node version + uses: actions/setup-node@v4 + with: + node-version: '>=20' - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} diff --git a/README.md b/README.md index 1a2732f..c080431 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ It does not modify the ROM, it is only an explorer of the resources present in t ## Contribute -To run it locally, clone the repo and install the dependencies: +To run it locally, make sure that node v20 or higher is installed, clone the repo and install the dependencies: ```sh npm install diff --git a/index.js b/index.js index 0a03954..e6761df 100755 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ import { isJapaneseVersion, getResFromCrc32, } from './src/lib/getResFromCrc32.js'; -import parseRom from './src/lib/parseRom.js'; +import parseRom from './src/lib/parser/parseRom.js'; import { stringify } from './src/lib/cliUtils.js'; const options = { diff --git a/package.json b/package.json index 739c7e9..460a3ef 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,12 @@ "bin": { "scumm-nes": "index.js" }, + "engines": { + "node": ">=20" + }, "scripts": { "test": "node --test", + "coverage": "node --experimental-test-coverage --test", "dev": "parcel", "start": "parcel", "build": "parcel build --public-url /scumm-nes" diff --git a/src/App.js b/src/App.js index 50853de..f671ba9 100644 --- a/src/App.js +++ b/src/App.js @@ -13,7 +13,7 @@ const App = () => { const navigate = useNavigate(); const onFile = async (rom, res) => { - const { default: parseRom } = await import('./lib/parseRom'); + const { default: parseRom } = await import('./lib/parser/parseRom'); setRom(rom); setRes(res); setResources(parseRom(rom, res)); diff --git a/src/lib/parsePreps.js b/src/lib/parser/parsePreps.js similarity index 100% rename from src/lib/parsePreps.js rename to src/lib/parser/parsePreps.js diff --git a/src/lib/parseRom.js b/src/lib/parser/parseRom.js similarity index 91% rename from src/lib/parseRom.js rename to src/lib/parser/parseRom.js index 2301cee..12e3f4a 100644 --- a/src/lib/parseRom.js +++ b/src/lib/parser/parseRom.js @@ -1,5 +1,5 @@ -import parseRooms from '../lib/parseRooms.js'; -import parseRoomGfx from '../lib/parseRoomGfx.js'; +import parseRooms from './parseRooms.js'; +import parseRoomGfx from './parseRoomGfx.js'; import parsePreps from './parsePreps.js'; const parseRom = (arrayBuffer, res) => { diff --git a/src/lib/parseRoomGfx.js b/src/lib/parser/parseRoomGfx.js similarity index 100% rename from src/lib/parseRoomGfx.js rename to src/lib/parser/parseRoomGfx.js diff --git a/src/lib/parseRooms.js b/src/lib/parser/parseRooms.js similarity index 100% rename from src/lib/parseRooms.js rename to src/lib/parser/parseRooms.js diff --git a/src/lib/parser.js b/src/lib/parser/parser.js similarity index 84% rename from src/lib/parser.js rename to src/lib/parser/parser.js index f389c78..eccc570 100644 --- a/src/lib/parser.js +++ b/src/lib/parser/parser.js @@ -3,8 +3,8 @@ class Parser { #ptr = 0; #characters = {}; - constructor(arrayBuffer, characters = {}) { - this.#view = new DataView(arrayBuffer); + constructor(buffer, characters = {}) { + this.#view = new DataView(buffer); this.#characters = characters; } @@ -25,7 +25,7 @@ class Parser { return 0x00; } - const char = String.fromCharCode(charCode); + const char = String.fromCodePoint(charCode); if (typeof this.#characters[char] !== 'undefined') { return this.#characters[char]; diff --git a/src/lib/serialiser/serialisePreps.js b/src/lib/serialiser/serialisePreps.js new file mode 100644 index 0000000..a6449a4 --- /dev/null +++ b/src/lib/serialiser/serialisePreps.js @@ -0,0 +1,14 @@ +import Serialiser from './serialiser.js'; + +const serialisePreps = (preps = [], characters = {}) => { + const serialiser = new Serialiser(characters); + + for (let i = 0; i < preps.length; i++) { + serialiser.setString(preps[i]); + serialiser.setUint8(); // 0x00 is the default value. + } + + return serialiser.buffer; +}; + +export default serialisePreps; diff --git a/src/lib/serialiser/serialiser.js b/src/lib/serialiser/serialiser.js new file mode 100644 index 0000000..3bb7a7f --- /dev/null +++ b/src/lib/serialiser/serialiser.js @@ -0,0 +1,37 @@ +const maxByteLength = 16384; // The largest resource is sprdata. + +class Serialiser { + #view; + #ptr = 0; + #characters = {}; + + constructor(characters = {}) { + const buffer = new ArrayBuffer(0, { maxByteLength }); + this.#view = new DataView(buffer); + // Swap keys for values. + this.#characters = Object.fromEntries( + Object.entries(characters).map(([key, value]) => [value, key]), + ); + } + + setUint8(value = 0x00) { + this.#view.buffer.resize(this.#ptr + 1); + this.#view.setUint8(this.#ptr++, value); + } + + setString(str) { + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt#looping_with_codepointat + for (let codePoint of str) { + if (this.#characters[codePoint] !== undefined) { + codePoint = this.#characters[codePoint]; + } + this.setUint8(codePoint.codePointAt(0)); + } + } + + get buffer() { + return this.#view.buffer; + } +} + +export default Serialiser; diff --git a/test/parsePreps.test.js b/test/parsePreps.test.js index 7e55d66..3f36684 100644 --- a/test/parsePreps.test.js +++ b/test/parsePreps.test.js @@ -1,6 +1,6 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import parsePreps from '../src/lib/parsePreps.js'; +import parsePreps from '../src/lib/parser/parsePreps.js'; describe('parsePreps', () => { it('should set the offset value in metadata.', () => { @@ -39,7 +39,7 @@ describe('parsePreps', () => { view.setUint8(4, 'c'.codePointAt(0)); view.setUint8(5, 0x00); - const characters = { a: '0', b: 1, c: '2' }; + const characters = { a: '0', b: '1', c: '2' }; const { preps } = parsePreps(buffer, 0, 0, characters); assert.deepEqual(preps, ['0', '1', '2']); }); diff --git a/test/serialisePreps.test.js b/test/serialisePreps.test.js new file mode 100644 index 0000000..1eff6c6 --- /dev/null +++ b/test/serialisePreps.test.js @@ -0,0 +1,37 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import serialisePreps from '../src/lib/serialiser/serialisePreps.js'; + +describe('serialisePreps', () => { + it('should return an instance of ArrayBuffer.', () => { + const buffer = serialisePreps(); + assert.ok(buffer instanceof ArrayBuffer); + }); + + it('should serialise prepositions.', () => { + const preps = ['a', 'b', 'c']; + const buffer = serialisePreps(preps); + const view = new DataView(buffer); + + assert.equal(view.getUint8(0), 0x61); + assert.equal(view.getUint8(1), 0x00); + assert.equal(view.getUint8(2), 0x62); + assert.equal(view.getUint8(3), 0x00); + assert.equal(view.getUint8(4), 0x63); + assert.equal(view.getUint8(5), 0x00); + }); + + it('should serialise characters according to the character mapping.', () => { + const preps = ['0', '1', '2']; + const characters = { a: '0', b: '1', c: '2' }; + const buffer = serialisePreps(preps, characters); + + const view = new DataView(buffer); + assert.equal(view.getUint8(0), 0x61); + assert.equal(view.getUint8(1), 0x00); + assert.equal(view.getUint8(2), 0x62); + assert.equal(view.getUint8(3), 0x00); + assert.equal(view.getUint8(4), 0x63); + assert.equal(view.getUint8(5), 0x00); + }); +});