diff --git a/README.md b/README.md index e22a792..24dff1f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ It currently support: - Rooms (partially) - Room graphics +- Costumes (not yet exposed to the UI) - Prepositions - Title screens diff --git a/src/lib/parser/Parser.js b/src/lib/parser/Parser.js index ba039c2..6dc4275 100644 --- a/src/lib/parser/Parser.js +++ b/src/lib/parser/Parser.js @@ -12,6 +12,10 @@ class Parser { return this.#view.getUint8(this.#ptr++); } + getInt8() { + return this.#view.getInt8(this.#ptr++); + } + getUint16() { const val = this.#view.getUint16(this.#ptr, true); this.#ptr += 2; diff --git a/src/lib/parser/costumes/parseCostumeGfx.js b/src/lib/parser/costumes/parseCostumeGfx.js new file mode 100644 index 0000000..737be77 --- /dev/null +++ b/src/lib/parser/costumes/parseCostumeGfx.js @@ -0,0 +1,45 @@ +import Parser from '../Parser.js'; + +const assert = console.assert; + +const parseCostumeGfx = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + decompressedSize: 0, + }; + + const numberOfTiles = parser.getUint8(); + + const gfx = []; + while (parser.pointer < arrayBuffer.byteLength) { + const loop = parser.getUint8(); + if (loop & 0x80) { + for (let j = 0; j < (loop & 0x7f); j++) { + gfx.push(parser.getUint8()); + } + } else { + const data = parser.getUint8(); + for (let j = 0; j < (loop & 0x7f); j++) { + gfx.push(data); + } + } + } + + assert( + numberOfTiles === gfx.length / 8 / 2, + 'Number of tiles byte does not match number of tiles decoded.', + ); + + metadata.decompressedSize = gfx.length; + + return { + metadata, + numberOfTiles, + gfx, + }; +}; + +export default parseCostumeGfx; diff --git a/src/lib/parser/costumes/parseCostumes.js b/src/lib/parser/costumes/parseCostumes.js new file mode 100644 index 0000000..d1aacc2 --- /dev/null +++ b/src/lib/parser/costumes/parseCostumes.js @@ -0,0 +1,43 @@ +import Parser from '../Parser.js'; + +const parseCostumes = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + decompressedSize: 0, + }; + + const costumes = []; + let to = 0; + for (let i = 0; i < 24; i++) { + const offset = parser.getUint8(); + const length = parser.getUint8(); + const costumeParser = new Parser( + arrayBuffer.slice(offset, offset + length), + ); + to = Math.max(to, offset + length); + const costume = []; + for (let j = 0; j < length; j++) { + costume.push(costumeParser.getUint8()); + } + costumes.push(costume); + } + + metadata.decompressedSize = costumes.length; + + const map = { + type: 'costumes', + from: offset, + to: offset + to, + }; + + return { + metadata, + costumes, + map, + }; +}; + +export default parseCostumes; diff --git a/src/lib/parser/costumes/parseSprdata.js b/src/lib/parser/costumes/parseSprdata.js new file mode 100644 index 0000000..704c26a --- /dev/null +++ b/src/lib/parser/costumes/parseSprdata.js @@ -0,0 +1,48 @@ +import Parser from '../Parser.js'; + +const parseSprdata = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + decompressedSize: 0, + }; + + const sprdata = []; + for (let i = 0; i < arrayBuffer.byteLength; i += 3) { + const sprdata0 = parser.getInt8(); + const tile = parser.getUint8(); + const sprdata2 = parser.getInt8(); + + const mask = sprdata0 & 0x80 ? 0x01 : 0x80; + let y = (sprdata0 << 1) >> 1; + + const sprpal = (sprdata2 & 0x03) << 2; + let x = sprdata2 >> 2; + + sprdata.push({ + x, + y, + tile, + mask, + sprpal, + }); + } + + metadata.decompressedSize = sprdata.length; + + const map = { + type: 'sprdata', + from: offset, + to: offset + parser.pointer, + }; + + return { + metadata, + sprdata, + map, + }; +}; + +export default parseSprdata; diff --git a/src/lib/parser/costumes/parseSprdesc.js b/src/lib/parser/costumes/parseSprdesc.js new file mode 100644 index 0000000..6e4af57 --- /dev/null +++ b/src/lib/parser/costumes/parseSprdesc.js @@ -0,0 +1,32 @@ +import Parser from '../Parser.js'; + +const parseSprdesc = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + }; + + const sprdesc = []; + for (let i = 0; i < arrayBuffer.byteLength - 1; i += 2) { + sprdesc.push(parser.getUint16()); + } + + // For some reason, the last element is a Uint8, probably unused. + sprdesc.push(parser.getUint8()); + + const map = { + type: 'sprdesc', + from: offset, + to: offset + parser.pointer, + }; + + return { + metadata, + sprdesc, + map, + }; +}; + +export default parseSprdesc; diff --git a/src/lib/parser/costumes/parseSprlens.js b/src/lib/parser/costumes/parseSprlens.js new file mode 100644 index 0000000..af95228 --- /dev/null +++ b/src/lib/parser/costumes/parseSprlens.js @@ -0,0 +1,29 @@ +import Parser from '../Parser.js'; + +const parseSprlens = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + }; + + const sprlens = []; + for (let i = 0; i < arrayBuffer.byteLength; i++) { + sprlens.push(parser.getUint8() + 1); + } + + const map = { + type: 'sprlens', + from: offset, + to: offset + parser.pointer, + }; + + return { + metadata, + sprlens, + map, + }; +}; + +export default parseSprlens; diff --git a/src/lib/parser/costumes/parseSproffs.js b/src/lib/parser/costumes/parseSproffs.js new file mode 100644 index 0000000..0e83667 --- /dev/null +++ b/src/lib/parser/costumes/parseSproffs.js @@ -0,0 +1,29 @@ +import Parser from '../Parser.js'; + +const parseSproffs = (arrayBuffer, i = 0, offset = 0) => { + const parser = new Parser(arrayBuffer); + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + }; + + const sproffs = []; + for (let i = 0; i < arrayBuffer.byteLength; i += 2) { + sproffs.push(parser.getUint16()); + } + + const map = { + type: 'sproffs', + from: offset, + to: offset + parser.pointer, + }; + + return { + metadata, + sproffs, + map, + }; +}; + +export default parseSproffs; diff --git a/src/lib/parser/costumes/parseSprpals.js b/src/lib/parser/costumes/parseSprpals.js new file mode 100644 index 0000000..d5fb1ba --- /dev/null +++ b/src/lib/parser/costumes/parseSprpals.js @@ -0,0 +1,22 @@ +import parsePalette from '../parsePalette.js'; + +const parseSprpals = (arrayBuffer, i = 0, offset = 0) => { + const metadata = { + id: i, + offset, + size: arrayBuffer.byteLength, + }; + const map = []; + + const { palette, paletteMap } = parsePalette(arrayBuffer, offset); + + map.push(paletteMap); + + return { + metadata, + palette, + map, + }; +}; + +export default parseSprpals; diff --git a/src/lib/parser/parseRom.js b/src/lib/parser/parseRom.js index 2a5d059..9792f92 100644 --- a/src/lib/parser/parseRom.js +++ b/src/lib/parser/parseRom.js @@ -1,8 +1,15 @@ import parseRooms from './parseRooms.js'; import parseRoomGfx from './parseRoomGfx.js'; import parseGlobdata from './parseGlobdata.js'; -import parsePreps from './parsePreps.js'; import parseScript from './parseScript.js'; +import parseCostumeGfx from './costumes/parseCostumeGfx.js'; +import parseCostumes from './costumes/parseCostumes.js'; +import parseSprpals from './costumes/parseSprpals.js'; +import parseSprdesc from './costumes/parseSprdesc.js'; +import parseSprlens from './costumes/parseSprlens.js'; +import parseSproffs from './costumes/parseSproffs.js'; +import parseSprdata from './costumes/parseSprdata.js'; +import parsePreps from './parsePreps.js'; import parseTitles from './parseTitles.js'; const parseRom = (arrayBuffer, res) => { @@ -10,6 +17,13 @@ const parseRom = (arrayBuffer, res) => { const roomgfx = []; const globdata = []; const scripts = []; + const costumegfx = []; + const costumes = []; + const sprpals = []; + const sprdesc = []; + const sprlens = []; + const sproffs = []; + const sprdata = []; const preps = []; const titles = []; @@ -43,11 +57,80 @@ const parseRom = (arrayBuffer, res) => { for (let i = 0; i < res.scripts.length; i++) { const [offset, length] = res.scripts[i]; - const resBuffer = arrayBuffer.slice(offset, offset + length); - const script = parseScript(resBuffer, i, offset, res.characters); - scripts.push(script); + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseScript(buffer, i, offset, res.characters); + item.buffer = buffer; + scripts.push(item); + } + + for (let i = 0; i < res.costumes.length; i++) { + const [offset, length] = res.costumes[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseCostumes(buffer, i, offset); + item.buffer = buffer; + costumes.push(item); + } + + for (let i = 0; i < res.costumegfx.length; i++) { + const [offset, length] = res.costumegfx[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseCostumeGfx(buffer, i, offset); + item.buffer = buffer; + costumegfx.push(item); } + for (let i = 0; i < res.sprpals.length; i++) { + const [offset, length] = res.sprpals[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseSprpals(buffer, i, offset); + item.buffer = buffer; + sprpals.push(item); + } + + for (let i = 0; i < res.sprdesc.length; i++) { + const [offset, length] = res.sprdesc[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseSprdesc(buffer, i, offset); + item.buffer = buffer; + sprdesc.push(item); + } + + for (let i = 0; i < res.sprlens.length; i++) { + const [offset, length] = res.sprlens[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseSprlens(buffer, i, offset); + item.buffer = buffer; + sprlens.push(item); + } + + for (let i = 0; i < res.sproffs.length; i++) { + const [offset, length] = res.sproffs[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseSproffs(buffer, i, offset); + item.buffer = buffer; + sproffs.push(item); + } + + // @todo Assert that the highest value of sprdesc is within sprlens and sproffs. + // @todo Assert that sprlens and sproffs have the same length. + + for (let i = 0; i < res.sprdata.length; i++) { + const [offset, length] = res.sprdata[i]; + + const buffer = arrayBuffer.slice(offset, offset + length); + const item = parseSprdata(buffer, i, offset); + item.buffer = buffer; + sprdata.push(item); + } + + // @todo Assert that the highest value of sproffs is within sprdata. + for (let i = 0; i < res?.preplist?.length; i++) { const [offset, length] = res.preplist[i]; @@ -72,8 +155,14 @@ const parseRom = (arrayBuffer, res) => { rooms, roomgfx, globdata, - preps, scripts, + costumes, + sprpals, + sprdesc, + sprlens, + sproffs, + sprdata, + preps, titles, }; };