diff --git a/src/lib/parser/parseRooms.js b/src/lib/parser/parseRooms.js index a022140..ed9d5c1 100644 --- a/src/lib/parser/parseRooms.js +++ b/src/lib/parser/parseRooms.js @@ -1,9 +1,9 @@ import Parser from './parser.js'; +import parseRoomHeader from './room/parseRoomHeader.js'; const assert = console.assert; const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { - const parser = new Parser(arrayBuffer); const metadata = { id: i, offset, @@ -14,50 +14,13 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { return { metadata }; } - const chunkSize = parser.getUint16(); // Room res size + const header = parseRoomHeader(arrayBuffer.slice(0, 0x1c)); assert( - chunkSize === arrayBuffer.byteLength, + header.chunkSize === arrayBuffer.byteLength, 'Room res size flag does not match chunk size.', ); - const unk1 = parser.getUint8(); - const unk2 = parser.getUint8(); - - assert(unk1 === 0, 'Unknown 1 is not 0.'); - - const width = parser.getUint16(); // Room width - const height = parser.getUint16(); // Room height - - assert(width === 28 || width === 60, 'Room width is not 28 or 60.'); - assert(height === 16, 'Room height is not 16.'); - - const unk3 = parser.getUint16(); // Number objects in room (unused?) - - assert(unk3 === 0, 'Unknown 3 is not 0.'); - - const nametableOffs = parser.getUint16(); // Gfx background tileset offset - const attrOffs = parser.getUint16(); // Gfx background attr offset - const maskOffs = parser.getUint16(); // Gfx mask offset - - const unk4 = parser.getUint16(); // charMap offset - const unk5 = parser.getUint16(); // picMap offset - - assert(unk4 === unk5, 'The values of unknown 4 and 5 do not match.'); - - const objectsNum = parser.getUint8(); - - assert(objectsNum < 57, 'There are more than 56 objects in room.'); - - const boxOffs = parser.getUint8(); - const soundsNum = parser.getUint8(); - - assert(soundsNum === 0, 'The number of sounds is not 0.'); - - const scriptsNum = parser.getUint8(); - const excdOffs = parser.getUint16(); // Exit script (EXCD) offset - const encdOffs = parser.getUint16(); // Entry script (ENCD) offset - let map = [ { type: 'header', @@ -66,25 +29,18 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { }, ]; - const header = { + const { chunkSize, - unk1, - unk2, width, - height, - unk3, nametableOffs, attrOffs, maskOffs, - unk4, - unk5, objectsNum, boxOffs, - soundsNum, - scriptsNum, excdOffs, encdOffs, - }; + } = header; + const objectImages = []; const objects = []; const boxes = []; diff --git a/src/lib/parser/room/parseRoomHeader.js b/src/lib/parser/room/parseRoomHeader.js new file mode 100644 index 0000000..fe5b86c --- /dev/null +++ b/src/lib/parser/room/parseRoomHeader.js @@ -0,0 +1,68 @@ +import Parser from '../parser.js'; + +const assert = console.assert; + +const parseRoomHeader = (arrayBuffer) => { + const parser = new Parser(arrayBuffer); + + const chunkSize = parser.getUint16(); // Room res size + + const unk1 = parser.getUint8(); + const unk2 = parser.getUint8(); + + assert(unk1 === 0, 'Unknown 1 is not 0.'); + + const width = parser.getUint16(); // Room width + const height = parser.getUint16(); // Room height + + assert(width === 28 || width === 60, 'Room width is not 28 or 60.'); + assert(height === 16, 'Room height is not 16.'); + + const unk3 = parser.getUint16(); // Number objects in room (unused?) + + assert(unk3 === 0, 'Unknown 3 is not 0.'); + + const nametableOffs = parser.getUint16(); // Gfx background tileset offset + const attrOffs = parser.getUint16(); // Gfx background attr offset + const maskOffs = parser.getUint16(); // Gfx mask offset + + const unk4 = parser.getUint16(); // charMap offset + const unk5 = parser.getUint16(); // picMap offset + + assert(unk4 === unk5, 'The values of unknown 4 and 5 do not match.'); + + const objectsNum = parser.getUint8(); + + assert(objectsNum < 57, 'There are more than 56 objects in room.'); + + const boxOffs = parser.getUint8(); + const soundsNum = parser.getUint8(); + + assert(soundsNum === 0, 'The number of sounds is not 0.'); + + const scriptsNum = parser.getUint8(); + const excdOffs = parser.getUint16(); // Exit script (EXCD) offset + const encdOffs = parser.getUint16(); // Entry script (ENCD) offset + + return { + chunkSize, + unk1, + unk2, + width, + height, + unk3, + nametableOffs, + attrOffs, + maskOffs, + unk4, + unk5, + objectsNum, + boxOffs, + soundsNum, + scriptsNum, + excdOffs, + encdOffs, + }; +}; + +export default parseRoomHeader; diff --git a/src/lib/serialiser/room/serialiseRoomHeader.js b/src/lib/serialiser/room/serialiseRoomHeader.js new file mode 100644 index 0000000..aabea8f --- /dev/null +++ b/src/lib/serialiser/room/serialiseRoomHeader.js @@ -0,0 +1,38 @@ +import Serialiser from '../serialiser.js'; + +const serialiseRoomHeader = (header) => { + const serialiser = new Serialiser(); + + // @todo Move the writing of chunk size outside of this code, + // to the end of the serialisation. + serialiser.setUint16(header.chunkSize); + + serialiser.setUint8(header.unk1); + serialiser.setUint8(header.unk2); + + serialiser.setUint16(header.width); + serialiser.setUint16(header.height); + + serialiser.setUint16(); // 0x00 + + serialiser.setUint16(header.nametableOffs); + serialiser.setUint16(header.attrOffs); + serialiser.setUint16(header.maskOffs); + + serialiser.setUint16(header.unk4); + serialiser.setUint16(header.unk5); + + // @todo Update the value of objectsNum after the serialisation. + serialiser.setUint8(header.objectsNum); + + serialiser.setUint8(header.boxOffs); + serialiser.setUint8(header.soundsNum); + + serialiser.setUint8(header.scriptsNum); + serialiser.setUint16(header.excdOffs); + serialiser.setUint16(header.encdOffs); + + return serialiser.buffer; +}; + +export default serialiseRoomHeader; diff --git a/src/lib/serialiser/serialiser.js b/src/lib/serialiser/serialiser.js index 3bb7a7f..4f553a7 100644 --- a/src/lib/serialiser/serialiser.js +++ b/src/lib/serialiser/serialiser.js @@ -19,6 +19,12 @@ class Serialiser { this.#view.setUint8(this.#ptr++, value); } + setUint16(value = 0x00) { + this.#view.buffer.resize(this.#ptr + 2); + this.#view.setUint16(this.#ptr, value, true); + this.#ptr += 2; + } + 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) { diff --git a/test/parseRoomHeader.test.js b/test/parseRoomHeader.test.js new file mode 100644 index 0000000..db1016b --- /dev/null +++ b/test/parseRoomHeader.test.js @@ -0,0 +1,33 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import parseRoomHeader from '../src/lib/parser/room/parseRoomHeader.js'; +import serialiseRoomHeader from '../src/lib/serialiser/room/serialiseRoomHeader.js'; + +const roomHeaderBuffer = () => { + const array = [ + 0x0c, 0x0d, 0x00, 0x24, 0x3c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x01, + 0xc5, 0x03, 0xfa, 0x03, 0x2d, 0x00, 0x2d, 0x00, 0x14, 0x6d, 0x00, 0x01, + 0xca, 0x0c, 0xfb, 0x0c, + ]; + const buffer = new ArrayBuffer(array.length); + const view = new DataView(buffer); + array.forEach((v, i) => view.setUint8(i, v)); + return buffer; +}; + +describe('parseRoomHeader', () => { + it('should return a non empty object.', () => { + const header = parseRoomHeader(roomHeaderBuffer()); + + assert.equal(typeof header, 'object'); + assert.ok(Object.keys(header).length > 0); + }); + + it('should be the inverse of serialiseRoomHeader.', () => { + const initialBuffer = roomHeaderBuffer(); + const header = parseRoomHeader(initialBuffer); + const buffer = serialiseRoomHeader(header); + + assert.deepEqual(initialBuffer, buffer); + }); +}); diff --git a/test/serialiseRoomHeader.test.js b/test/serialiseRoomHeader.test.js new file mode 100644 index 0000000..23390b1 --- /dev/null +++ b/test/serialiseRoomHeader.test.js @@ -0,0 +1,47 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import serialiseRoomHeader from '../src/lib/serialiser/room/serialiseRoomHeader.js'; +import parseRoomHeader from '../src/lib/parser/room/parseRoomHeader.js'; + +const room = { + header: { + chunkSize: 3340, + unk1: 0, + unk2: 36, + width: 60, + height: 16, + unk3: 0, + nametableOffs: 272, + attrOffs: 965, + maskOffs: 1018, + unk4: 45, + unk5: 45, + objectsNum: 20, + boxOffs: 109, + soundsNum: 0, + scriptsNum: 1, + excdOffs: 3274, + encdOffs: 3323, + }, +}; + +describe('serialiseRoomHeader', () => { + it('should return an instance of ArrayBuffer.', () => { + const buffer = serialiseRoomHeader(room.header); + assert.ok(buffer instanceof ArrayBuffer); + }); + + it('should serialise a room header.', () => { + const buffer = serialiseRoomHeader(room.header); + const view = new DataView(buffer); + + assert.equal(view.getUint8(2), 0x00); + }); + + it('should be the inverse of parseRoomHeader.', () => { + const buffer = serialiseRoomHeader(room.header); + const header = parseRoomHeader(buffer); + + assert.deepEqual(room.header, header); + }); +});