diff --git a/src/dump/index.js b/src/dump/index.js index b1bba3a..42097f8 100644 --- a/src/dump/index.js +++ b/src/dump/index.js @@ -164,7 +164,7 @@ function dumpADSScripts(filepath, resindex) { } } -const filepath = path.join(__dirname, '../../data'); +const filepath = path.join(__dirname, '../../data/castway'); const fc = fs.readFileSync(path.join(filepath, 'RESOURCE.MAP')); const buffer = fc.buffer.slice(fc.byteOffset, fc.byteOffset + fc.byteLength); diff --git a/src/resources/bmp.js b/src/resources/bmp.js index 3c03d75..f837fef 100644 --- a/src/resources/bmp.js +++ b/src/resources/bmp.js @@ -37,38 +37,37 @@ export function loadBMPResourceEntry(entry) { images[i].height = h; } block = getString(entry.data, offset, 3); - if (block !== 'BIN') { - throw `Invalid Type ${block}: expecting block type BIN`; - } - let blockSize = entry.data.getUint32(offset + 4, true); - const compressionType = entry.data.getUint8(offset + 8, true); - /* const uncompressedSize = */ entry.data.getUint32(offset + 9, true); - offset += 13; - blockSize -= 5; // take type and size out of the block - const compressedData = new DataView(entry.buffer.slice(offset, offset + blockSize)); - const data = decompress(compressionType, compressedData, 0, compressedData.byteLength); - let dataIndex = 0; - let pixelIndex = 0; - for (let i = 0; i < numImages; i += 1) { - const image = images[i]; - for (let h = 0; h < image.height; h += 1) { - for (let w = 0; w < image.width; w += 1) { - let c = data[dataIndex]; - if (pixelIndex % 2 === 0) { - c >>= 4; - } else { - c &= 0x0f; - dataIndex += 1; + if (block === 'BIN') { + let blockSize = entry.data.getUint32(offset + 4, true); + const compressionType = entry.data.getUint8(offset + 8, true); + /* const uncompressedSize = */ entry.data.getUint32(offset + 9, true); + offset += 13; + blockSize -= 5; // take type and size out of the block + const compressedData = new DataView(entry.buffer.slice(offset, offset + blockSize)); + const data = decompress(compressionType, compressedData, 0, compressedData.byteLength); + let dataIndex = 0; + let pixelIndex = 0; + for (let i = 0; i < numImages; i += 1) { + const image = images[i]; + for (let h = 0; h < image.height; h += 1) { + for (let w = 0; w < image.width; w += 1) { + let c = data[dataIndex]; + if (pixelIndex % 2 === 0) { + c >>= 4; + } else { + c &= 0x0f; + dataIndex += 1; + } + image.buffer[w + image.width * h] = c; + image.pixels[w + image.width * h] = { + index: c, + a: PALETTE[c].a, + r: PALETTE[c].r, + g: PALETTE[c].g, + b: PALETTE[c].b, + }; + pixelIndex += 1; } - image.buffer[w + image.width * h] = c; - image.pixels[w + image.width * h] = { - index: c, - a: PALETTE[c].a, - r: PALETTE[c].r, - g: PALETTE[c].g, - b: PALETTE[c].b, - }; - pixelIndex += 1; } } } diff --git a/src/resources/data/scripting.js b/src/resources/data/scripting.js index cb211ec..4fbcb09 100644 --- a/src/resources/data/scripting.js +++ b/src/resources/data/scripting.js @@ -1,34 +1,34 @@ export const TTMCommandType = [ - { opcode: 0x0020, command: 'SAVE_BACKGROUND' }, // not used + { opcode: 0x0020, command: 'SAVE_BACKGROUND' }, { opcode: 0x0080, command: 'DRAW_BACKGROUND' }, { opcode: 0x0110, command: 'PURGE' }, { opcode: 0x0FF0, command: 'UPDATE' }, { opcode: 0x1020, command: 'SET_DELAY' }, { opcode: 0x1050, command: 'SLOT_IMAGE' }, { opcode: 0x1060, command: 'SLOT_PALETTE' }, - { opcode: 0x1100, command: 'UNKNOWN_0' }, // Scene related? + { opcode: 0x1100, command: 'UNKNOWN_0' }, { opcode: 0x1110, command: 'SET_SCENE' }, { opcode: 0x1120, command: 'SET_BACKGROUND' }, { opcode: 0x1200, command: 'GOTO' }, { opcode: 0x2000, command: 'SET_COLORS' }, - { opcode: 0x2010, command: 'SET_FRAME1' }, - { opcode: 0x2020, command: 'UNKNOWN_3' }, // SET_FRAME2 ??? + { opcode: 0x2010, command: 'SET_FRAME' }, + { opcode: 0x2020, command: 'SET_TIMER' }, { opcode: 0x4000, command: 'SET_CLIP_REGION' }, { opcode: 0x4110, command: 'FADE_OUT' }, { opcode: 0x4120, command: 'FADE_IN' }, - { opcode: 0x4200, command: 'SAVE_IMAGE0' }, - { opcode: 0x4210, command: 'SAVE_IMAGE1' }, - { opcode: 0xA000, command: 'UNKNOWN_4' }, // Draw Line related? - { opcode: 0xA050, command: 'UNKNOWN_5' }, // Draw Line related? - { opcode: 0xA060, command: 'UNKNOWN_6' }, // Draw Line related? + { opcode: 0x4200, command: 'DRAW_BACKGROUND_REGION' }, + { opcode: 0x4210, command: 'SAVE_IMAGE_REGION' }, + { opcode: 0xA000, command: 'UNKNOWN_4' }, + { opcode: 0xA050, command: 'SAVE_REGION' }, + { opcode: 0xA060, command: 'RESTORE_REGION' }, { opcode: 0xA0A0, command: 'DRAW_LINE' }, { opcode: 0xA100, command: 'DRAW_RECT' }, { opcode: 0xA400, command: 'DRAW_BUBBLE' }, { opcode: 0xA500, command: 'DRAW_SPRITE' }, - { opcode: 0xA510, command: 'DRAW_SPRITE1' }, // not used + { opcode: 0xA510, command: 'DRAW_SPRITE1' }, { opcode: 0xA520, command: 'DRAW_SPRITE_FLIP' }, - { opcode: 0xA530, command: 'DRAW_SPRITE3' }, // not used + { opcode: 0xA530, command: 'DRAW_SPRITE3' }, { opcode: 0xA600, command: 'CLEAR_SCREEN' }, { opcode: 0xB600, command: 'DRAW_SCREEN' }, { opcode: 0xC020, command: 'LOAD_SAMPLE' }, diff --git a/src/resources/data/types.js b/src/resources/data/types.js deleted file mode 100644 index 324aa6d..0000000 --- a/src/resources/data/types.js +++ /dev/null @@ -1,14 +0,0 @@ -import { loadADSResourceEntry } from '../ads'; -import { loadBMPResourceEntry } from '../bmp'; -import { loadPALResourceEntry } from '../pal'; -import { loadSCRResourceEntry } from '../scr'; -import { loadTTMResourceEntry } from '../ttm'; - - -export const ResourceType = [ - { type: 'ADS', callback: loadADSResourceEntry }, // Animation sequences - { type: 'BMP', callback: loadBMPResourceEntry }, // Various raw images - { type: 'PAL', callback: loadPALResourceEntry }, // VGA palette - { type: 'SCR', callback: loadSCRResourceEntry }, // Background raw images - { type: 'TTM', callback: loadTTMResourceEntry }, // Scripting macros -]; diff --git a/src/resources/index.js b/src/resources/index.js index dcc6c99..2f31ffa 100644 --- a/src/resources/index.js +++ b/src/resources/index.js @@ -1,7 +1,38 @@ import { INDEX_STRING_SIZE } from '../constants'; import { getString } from '../utils/string'; -import { ResourceType } from './data/types'; +import { loadADSResourceEntry } from './ads'; +import { loadBMPResourceEntry } from './bmp'; +import { loadPALResourceEntry } from './pal'; +import { loadSCRResourceEntry } from './scr'; +import { loadTTMResourceEntry } from './ttm'; + +const NOP = () => {}; + +export const ResourceType = [ + { type: 'ADS', callback: loadADSResourceEntry }, // Animation sequences + { type: 'BMP', callback: loadBMPResourceEntry }, // Various raw images + { type: 'PAL', callback: loadPALResourceEntry }, // VGA palette + { type: 'SCR', callback: loadSCRResourceEntry }, // Background raw images + { type: 'TTM', callback: loadTTMResourceEntry }, // Scripting macros + { type: 'VIN', callback: NOP }, // + { type: 'SDS', callback: NOP }, // + { type: 'FNT', callback: NOP }, // + { type: 'DAT', callback: NOP }, // + { type: 'DDS', callback: NOP }, // + { type: 'TDS', callback: NOP }, // + { type: 'REQ', callback: NOP }, // + { type: 'WLD', callback: NOP }, // + { type: 'SNG', callback: NOP }, // + { type: 'ADL', callback: NOP }, // + { type: 'ADH', callback: NOP }, // + { type: 'RST', callback: NOP }, // + { type: 'OVL', callback: NOP }, // + { type: 'GDS', callback: NOP }, // + { type: 'RST', callback: NOP }, // + { type: 'SX', callback: NOP }, // + { type: 'RES', callback: NOP }, // +]; const INDEX_HEADER_SIZE = 6; @@ -10,7 +41,7 @@ const INDEX_HEADER_SIZE = 6; * @param {*} filepath Full path of the file * @param {*} filename File name */ -export const loadResources = (buffer) => { +export const loadResourceMap = (buffer) => { let offset = 0; // current resource offest const resources = []; // list of resource files const data = new DataView(buffer, offset, buffer.byteLength); @@ -60,75 +91,103 @@ export const loadResources = (buffer) => { }; }; -export function loadResourceEntry(entry) { +/** + * Load all Resource details based on index resource file + * @param {*} filepath Full path of the file + * @param {*} filename File name + */ +export const loadResourcebyName = (resource, resbuffer) => { + const res = { ...resource }; + res.size = resbuffer.byteLength; + const resData = new DataView(resbuffer, 0, res.size); + + for (let e = 0; e < res.numEntries; e += 1) { + const entry = res.entries[e]; + const name = getString(resData, entry.offset, INDEX_STRING_SIZE); + const entryCompressedSize = resData.getUint32(entry.offset + 13, true); + const startOffset = entry.offset + 17; + const endOffset = startOffset + entryCompressedSize; + + entry.name = name; + entry.type = name.split('.')[1]; + entry.compressedSize = entryCompressedSize; + entry.buffer = resbuffer.slice(startOffset, endOffset); + entry.data = new DataView(resbuffer, startOffset, entryCompressedSize); + + res.entries.push({ ...entry }); + } + + return res; +}; + +export const loadResourceEntry = (entry) => { const resType = ResourceType.find((r) => r.type === entry.type); return resType.callback(entry); -} +}; + +/** + * Load all Resource details based on index resource file + * @param {*} filepath Full path of the file + * @param {*} filename File name + */ +export function loadResources(buffer, resbuffer) { + let offset = 0; // current resource offest + const resources = []; // list of resource files + const data = new DataView(buffer, offset, buffer.byteLength); + + const header = { + unk0: data.getUint8(offset, true), + unk1: data.getUint8(offset + 1, true), // number of entries? + unk2: data.getUint8(offset + 2, true), + unk3: data.getUint8(offset + 3, true), + numResources: data.getUint8(offset + 4, true), + unk5: data.getUint8(offset + 5, true), + }; + offset += INDEX_HEADER_SIZE; + + // Read resource files and entries + // Read number of resource files (castaway only uses a single one) + for (let r = 0; r < header.numResources; r += 1) { + let innerOffset = offset; + const res = { + name: getString(data, innerOffset, INDEX_STRING_SIZE), + numEntries: data.getUint16(innerOffset + 13, true), + size: 0, + entries: [], + }; + resources.push(res); + innerOffset += 15; + res.size = resbuffer.byteLength; + const resData = new DataView(resbuffer, 0, res.size); + + for (let e = 0; e < res.numEntries; e += 1) { + // from index + const entrySize = data.getUint16(innerOffset, true); // uncompressed size + const entryOffset = data.getUint32(innerOffset + 4, true); + // from resource + const name = getString(resData, entryOffset, INDEX_STRING_SIZE); + const entryCompressedSize = resData.getUint32(entryOffset + 13, true); + const startOffset = entryOffset + 17; + const endOffset = startOffset + entryCompressedSize; -// /** -// * Load all Resource details based on index resource file -// * @param {*} filepath Full path of the file -// * @param {*} filename File name -// */ -// export function loadResources(buffer, resbuffer) { -// let offset = 0; // current resource offest -// const resources = []; // list of resource files -// const data = new DataView(buffer, offset, buffer.byteLength); - -// const header = { -// unk0: data.getUint8(offset, true), -// unk1: data.getUint8(offset + 1, true), // number of entries? -// unk2: data.getUint8(offset + 2, true), -// unk3: data.getUint8(offset + 3, true), -// numResources: data.getUint8(offset + 4, true), -// unk5: data.getUint8(offset + 5, true), -// }; -// offset += INDEX_HEADER_SIZE; - -// // Read resource files and entries -// // Read number of resource files (castaway only uses a single one) -// for (let r = 0; r < header.numResources; r += 1) { -// let innerOffset = offset; -// const res = { -// name: getString(data, innerOffset, INDEX_STRING_SIZE), -// numEntries: data.getUint16(innerOffset + 13, true), -// size: 0, -// entries: [], -// }; -// resources.push(res); -// innerOffset += 15; - -// res.size = resbuffer.byteLength; -// const resData = new DataView(resbuffer, 0, res.size); - -// for (let e = 0; e < res.numEntries; e += 1) { -// // from index -// const entrySize = data.getUint16(innerOffset, true); // uncompressed size -// const entryOffset = data.getUint32(innerOffset + 4, true); -// // from resource -// const name = getString(resData, entryOffset, INDEX_STRING_SIZE); -// const entryCompressedSize = resData.getUint32(entryOffset + 13, true); -// const startOffset = entryOffset + 17; -// const endOffset = startOffset + entryCompressedSize; - -// const entry = { -// name, -// type: name.split('.')[1], -// size: entrySize, // uncompressed size -// offset: entryOffset, -// compressedSize: entryCompressedSize, -// buffer: resbuffer.slice(startOffset, endOffset), -// data: new DataView(resbuffer, startOffset, entryCompressedSize), -// }; -// innerOffset += 8; - -// res.entries.push(entry); -// } -// } - -// return { -// header, -// resources -// }; -// } + const entry = { + name, + type: name.split('.')[1], + size: entrySize, // uncompressed size + offset: entryOffset, + compressedSize: entryCompressedSize, + buffer: resbuffer.slice(startOffset, endOffset), + data: new DataView(resbuffer, startOffset, entryCompressedSize), + }; + innerOffset += 8; + + res.entries.push(entry); + } + } + + return { + header, + resources + }; +} diff --git a/src/resources/scr.js b/src/resources/scr.js index 1530fa6..2cc8540 100644 --- a/src/resources/scr.js +++ b/src/resources/scr.js @@ -14,67 +14,67 @@ export function loadSCRResourceEntry(entry) { offset += 8; let block = getString(entry.data, offset, 3); - if (block !== 'DIM') { - throw `Invalid Type ${block}: expecting block type DIM`; - } - let blockSize = entry.data.getUint32(offset + 4, true); - const width = entry.data.getUint16(offset + 8, true); - const height = entry.data.getUint16(offset + 10, true); - offset += 12; + if (block === 'DIM') { + let blockSize = entry.data.getUint32(offset + 4, true); + const width = entry.data.getUint16(offset + 8, true); + const height = entry.data.getUint16(offset + 10, true); + offset += 12; - block = getString(entry.data, offset, 3); - if (block !== 'BIN') { - throw `Invalid Type ${block}: expecting block type BIN`; - } - blockSize = entry.data.getUint32(offset + 4, true); - const compressionType = entry.data.getUint8(offset + 8, true); - /* const uncompressedSize = */ entry.data.getUint32(offset + 9, true); - offset += 13; - blockSize -= 5; // take type and size out of the block + block = getString(entry.data, offset, 3); + if (block !== 'BIN') { + throw `Invalid Type ${block}: expecting block type BIN`; + } + blockSize = entry.data.getUint32(offset + 4, true); + const compressionType = entry.data.getUint8(offset + 8, true); + /* const uncompressedSize = */ entry.data.getUint32(offset + 9, true); + offset += 13; + blockSize -= 5; // take type and size out of the block - const compressedData = new DataView(entry.buffer.slice(offset, offset + blockSize)); - const data = decompress(compressionType, compressedData, 0, compressedData.byteLength); + const compressedData = new DataView(entry.buffer.slice(offset, offset + blockSize)); + const data = decompress(compressionType, compressedData, 0, compressedData.byteLength); - const numImages = 1; - const images = [{ - width, - height, - buffer: [], - pixels: [] - }]; - const image = images[0]; - let dataIndex = 0; - let pixelIndex = 0; - for (let h = 0; h < height; h += 1) { - for (let w = 0; w < width; w += 1) { - let c = data[dataIndex]; - if (pixelIndex % 2 === 0) { - c >>= 4; - } else { - c &= 0x0f; - dataIndex += 1; + const numImages = 1; + const images = [{ + width, + height, + buffer: [], + pixels: [] + }]; + const image = images[0]; + let dataIndex = 0; + let pixelIndex = 0; + for (let h = 0; h < height; h += 1) { + for (let w = 0; w < width; w += 1) { + let c = data[dataIndex]; + if (pixelIndex % 2 === 0) { + c >>= 4; + } else { + c &= 0x0f; + dataIndex += 1; + } + const pal = PALETTE[c]; + image.buffer[w + image.width * h] = c; + image.pixels[w + image.width * h] = { + index: c, + a: pal.a, + r: pal.r, + g: pal.g, + b: pal.b, + }; + pixelIndex += 1; } - const pal = PALETTE[c]; - image.buffer[w + image.width * h] = c; - image.pixels[w + image.width * h] = { - index: c, - a: pal.a, - r: pal.r, - g: pal.g, - b: pal.b, - }; - pixelIndex += 1; } + return { + name: entry.name, + type, + totalSize, + flags, + width, + height, + numImages, + images, + }; } - return { - name: entry.name, - type, - totalSize, - flags, - width, - height, - numImages, - images, - }; + return null; } diff --git a/src/ui/ViewerApp.jsx b/src/ui/ViewerApp.jsx index 6e4d659..c1e6f25 100644 --- a/src/ui/ViewerApp.jsx +++ b/src/ui/ViewerApp.jsx @@ -7,7 +7,7 @@ import ResourceList from './components/ResourceList'; import ResourceContent from './components/ResourceContent'; import { preloadFileAsync } from '../utils/preload'; -import { loadResources } from '../resources'; +import { loadResourceMap } from '../resources'; const GameResources = { castaway: 'RESOURCE.MAP', @@ -24,7 +24,7 @@ const ViewerApp = ({ game }) => { async.auto({ resindex: preloadFileAsync(`data/${game}/${GameResources[game]}`), }, (err, files) => { - setResindex(loadResources(files.resindex)); + setResindex(loadResourceMap(files.resindex)); }); return () => {}; }, [game]); @@ -32,7 +32,7 @@ const ViewerApp = ({ game }) => { return (
- {resindex && } + {resindex && }
{ overflowY: 'scroll' }} > - {resindex && } + {resindex && }
); diff --git a/src/ui/components/PlayView.jsx b/src/ui/components/PlayView.jsx deleted file mode 100644 index a6f790e..0000000 --- a/src/ui/components/PlayView.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useRef, useEffect } from 'react'; - -import { loadResourceEntry } from '../../resources'; -import { startProcess, stopProcess } from '../../scripting/process'; - -const PlayView = ({ entries }) => { - const canvasRef = useRef(); - const mainCanvasRef = useRef(); - - useEffect( - () => { - if (entries !== undefined) { - stopProcess(); - - const context = canvasRef.current.getContext('2d'); - context.clearRect(0, 0, 640, 480); - - const mainContext = mainCanvasRef.current.getContext('2d'); - context.clearRect(0, 0, 640, 480); - - const entry = entries.find((e) => e.name === 'BUILDING.ADS'); - const data = loadResourceEntry(entry); - - startProcess({ - type: 'ADS', - context, - mainContext, - data, - entries, - }); - } - - return () => { stopProcess(); }; - }, - [entries] - ); - - return ( -
- - -
- ); -}; - -export default PlayView; diff --git a/src/ui/components/ResourceContent.jsx b/src/ui/components/ResourceContent.jsx index d15d045..da32718 100644 --- a/src/ui/components/ResourceContent.jsx +++ b/src/ui/components/ResourceContent.jsx @@ -2,17 +2,24 @@ import React, { useState, useEffect } from 'react'; import { loadResourceEntry } from '../../resources'; -import PlayView from './PlayView'; +import { RESOURCES } from '../global'; + import ResourceView from './ResourceView'; const ResourceContent = ({ res }) => { const [data, setData] = useState(); const [entries, setEntries] = useState(); - const [name, setName] = useState(window.location.hash.split('=')[1]); - const isPlayMode = name === 'PLAY'; + const [game, setGame] = useState(); + const [resource, setResource] = useState(); + const [name, setName] = useState(); const onHashChanged = () => { - setName(window.location.hash.split('=')[1]); + const hash = window.location.hash; + if (hash) { + setGame(hash.split('=')[1].split(',')[0]); + setResource(hash.split('=')[1].split(',')[1]); + setName(hash.split('=')[1].split(',')[2]); + } }; useEffect(() => { @@ -24,23 +31,21 @@ const ResourceContent = ({ res }) => { useEffect(() => { if (name) { - const e = res.resources[0].entries; + const e = RESOURCES[`${game}-${resource}`].entries; setEntries(e); - if (!isPlayMode) { - const entry = res.resources[0].entries.find((f) => f.name === name); - setData(loadResourceEntry(entry)); - } + const entry = RESOURCES[`${game}-${resource}`].entries.find((f) => f.name === name); + console.log(entry); + setData(loadResourceEntry(entry)); } return () => {}; - }, [res, name]); + }, [res, game, name, resource]); return (
{name}
- {data && isPlayMode && } - {data && !isPlayMode && } + {data && } {!name && 'No resource loaded. Please select one of the resources from the left menu.'}
); diff --git a/src/ui/components/ResourceItems.jsx b/src/ui/components/ResourceItems.jsx new file mode 100644 index 0000000..b3ff7a6 --- /dev/null +++ b/src/ui/components/ResourceItems.jsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; +import { map } from 'lodash'; +import async from 'async'; + +import { preloadFileAsync } from '../../utils/preload'; +import { loadResourcebyName } from '../../resources'; + +import { RESOURCES } from '../global'; + +const ResourceItems = ({ game, res }) => { + const [resource, setResource] = useState(null); + + useEffect(() => { + async.auto({ + res: preloadFileAsync(`data/${game}/${res.name}`), + }, (err, files) => { + if (!RESOURCES[`${game}-${res.name}`]) { + RESOURCES[`${game}-${res.name}`] = loadResourcebyName(res, files.res); + } + setResource(RESOURCES[`${game}-${res.name}`]); + }); + return () => {}; + }, [name]); + + return resource && resource.entries && ( + map(resource.entries, + (entry, i) => ( + + {entry.name} + + ) + ) + ); +}; + +export default ResourceItems; diff --git a/src/ui/components/ResourceList.jsx b/src/ui/components/ResourceList.jsx index 1ef3594..addecdd 100644 --- a/src/ui/components/ResourceList.jsx +++ b/src/ui/components/ResourceList.jsx @@ -1,49 +1,37 @@ import React, { useState } from 'react'; import { map } from 'lodash'; +import ResourceItems from './ResourceItems'; -const ResourceList = ({ res }) => { - const [resName, setResName] = useState(res.resources[0].name); +const ResourceList = ({ game, res }) => { + const [name, setName] = useState(null); // res.resources[0].name return ( <> {map(res.resources, (r) => (
setResName(r.name)} + onClick={() => setName(r.name)} style={{ cursor: 'pointer' }} > {r.name}
- - {/* - {r.name} - */} - {/*
- {map(r.entries, - (entry) => { - if (entry.type === 'VIN') { - return null; - } - return ( - - {entry.name} - - ); - })} -
*/} + {r.name === name && ( +
+ rr.name === name)} + /> +
+ )}
) )} diff --git a/src/ui/global.js b/src/ui/global.js new file mode 100644 index 0000000..0f08245 --- /dev/null +++ b/src/ui/global.js @@ -0,0 +1,2 @@ + +export const RESOURCES = {};