PSX Research
This commit is contained in:
parent
2f243976b6
commit
8d34c85c22
13 changed files with 1720 additions and 8 deletions
159
psx-map-exporter/tmp_dump_runtime_snapshot.mjs
Normal file
159
psx-map-exporter/tmp_dump_runtime_snapshot.mjs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const RAM_BASE = 0x80000000;
|
||||
const TYPE_TABLE_BASE = 0x800758c4;
|
||||
const TYPE_ROW_STRIDE = 0x18;
|
||||
const MAIN_VISIBLE_LIST = 0x8006ad5c;
|
||||
const MAIN_VISIBLE_READ_INDEX = 0x80067690;
|
||||
const MAIN_VISIBLE_WRITE_INDEX = 0x800676bc;
|
||||
const CURRENT_MAP_ID = 0x80067728;
|
||||
const NEXT_MAP_ID = 0x800678d0;
|
||||
|
||||
const projectRoot = path.resolve('..');
|
||||
const ramDumpPath = path.resolve(projectRoot, 'binary', 'Crusader - No Remorse (USA) Main Memory Dump.bin');
|
||||
const ram = fs.readFileSync(ramDumpPath);
|
||||
const outputPath = path.resolve(projectRoot, 'psx-map-exporter', '.cache', 'runtime-snapshot-report.json');
|
||||
|
||||
function vaToOffset(address) {
|
||||
const offset = address - RAM_BASE;
|
||||
if (offset < 0 || offset >= ram.length) {
|
||||
throw new RangeError(`Address out of dump range: 0x${address.toString(16)}`);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
function canRead(address, size = 4) {
|
||||
const offset = address - RAM_BASE;
|
||||
return offset >= 0 && offset + size <= ram.length;
|
||||
}
|
||||
|
||||
function readU32(address) {
|
||||
return ram.readUInt32LE(vaToOffset(address));
|
||||
}
|
||||
|
||||
function readS32(address) {
|
||||
return ram.readInt32LE(vaToOffset(address));
|
||||
}
|
||||
|
||||
function readU16(address) {
|
||||
return ram.readUInt16LE(vaToOffset(address));
|
||||
}
|
||||
|
||||
function readS16(address) {
|
||||
return ram.readInt16LE(vaToOffset(address));
|
||||
}
|
||||
|
||||
function readHeaderWords(address, wordCount = 10) {
|
||||
if (!canRead(address, wordCount * 4)) {
|
||||
return null;
|
||||
}
|
||||
return Array.from({ length: wordCount }, (_, index) => readU32(address + index * 4));
|
||||
}
|
||||
|
||||
function parseTypeRows(limit = 0x100) {
|
||||
const rows = [];
|
||||
for (let typeId = 0; typeId < limit; typeId += 1) {
|
||||
const rowBase = TYPE_TABLE_BASE + typeId * TYPE_ROW_STRIDE;
|
||||
const installCount = readU32(rowBase + 0x00);
|
||||
const builtResource = readU32(rowBase + 0x04);
|
||||
const stateScript = readU32(rowBase + 0x08);
|
||||
const simpleComponent = readU32(rowBase + 0x0c);
|
||||
const extents = readU32(rowBase + 0x10);
|
||||
const activeHeader = readU32(rowBase + 0x14);
|
||||
|
||||
if ((installCount | builtResource | stateScript | simpleComponent | extents | activeHeader) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const headerWords = activeHeader !== 0 ? readHeaderWords(activeHeader, 12) : null;
|
||||
rows.push({
|
||||
typeId,
|
||||
rowBase,
|
||||
installCount,
|
||||
builtResource,
|
||||
stateScript,
|
||||
simpleComponent,
|
||||
extents,
|
||||
activeHeader,
|
||||
headerWords,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function parseVisibleObjects(limit = 256) {
|
||||
const readIndex = readU32(MAIN_VISIBLE_READ_INDEX);
|
||||
const writeIndex = readU32(MAIN_VISIBLE_WRITE_INDEX);
|
||||
const count = Math.max(0, Math.min(limit, writeIndex));
|
||||
const objects = [];
|
||||
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const objectPtr = readU32(MAIN_VISIBLE_LIST + index * 4);
|
||||
if (!canRead(objectPtr, 0xa4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
objects.push({
|
||||
index,
|
||||
objectPtr,
|
||||
typeId: readU16(objectPtr + 0x18),
|
||||
routeWord: readU16(objectPtr + 0x1c),
|
||||
stateFlags: readU16(objectPtr + 0x1e),
|
||||
screenLeft: readS16(objectPtr + 0x20),
|
||||
screenTop: readS16(objectPtr + 0x22),
|
||||
screenRight: readS16(objectPtr + 0x24),
|
||||
screenBottom: readS16(objectPtr + 0x26),
|
||||
worldX: readS32(objectPtr + 0x3c),
|
||||
worldY: readS32(objectPtr + 0x40),
|
||||
worldZ: readS32(objectPtr + 0x44),
|
||||
velocityX: readS32(objectPtr + 0x60),
|
||||
velocityY: readS32(objectPtr + 0x64),
|
||||
velocityZ: readS32(objectPtr + 0x68),
|
||||
programPtr: readU32(objectPtr + 0x08),
|
||||
artResourcePtr: readU32(objectPtr + 0x10),
|
||||
companionExtentsPtr: readU32(objectPtr + 0x84),
|
||||
stateScriptPtr: readU32(objectPtr + 0x88),
|
||||
scriptBasePtr: readU32(objectPtr + 0x8c),
|
||||
scriptReadPtr: readU32(objectPtr + 0x90),
|
||||
latchedToken: readU16(objectPtr + 0x94),
|
||||
scriptCountdown: readU16(objectPtr + 0x96),
|
||||
selectorIndex: readU16(objectPtr + 0x9e),
|
||||
authoredRecordPtr: readU32(objectPtr + 0xa0),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
readIndex,
|
||||
writeIndex,
|
||||
count,
|
||||
objects,
|
||||
};
|
||||
}
|
||||
|
||||
const typeRows = parseTypeRows();
|
||||
const visible = parseVisibleObjects();
|
||||
const byType = new Map();
|
||||
for (const object of visible.objects) {
|
||||
byType.set(object.typeId, (byType.get(object.typeId) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const summary = {
|
||||
ramDumpPath,
|
||||
ramSize: ram.length,
|
||||
currentMapId: readU32(CURRENT_MAP_ID),
|
||||
nextMapId: readU32(NEXT_MAP_ID),
|
||||
mainVisibleReadIndex: visible.readIndex,
|
||||
mainVisibleWriteIndex: visible.writeIndex,
|
||||
visibleObjectCount: visible.objects.length,
|
||||
nonZeroTypeRowCount: typeRows.length,
|
||||
visibleTypeCounts: [...byType.entries()]
|
||||
.map(([typeId, count]) => ({ typeId, count }))
|
||||
.sort((left, right) => right.count - left.count || left.typeId - right.typeId),
|
||||
sampleTypeRows: typeRows.slice(0, 64),
|
||||
sampleVisibleObjects: visible.objects.slice(0, 128),
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, JSON.stringify(summary, null, 2));
|
||||
console.log(JSON.stringify({ outputPath }, null, 2));
|
||||
Loading…
Add table
Add a link
Reference in a new issue