fixed (while improving) offset issues with untested psv code, added more throw-outs for model parsing

This commit is contained in:
yellows111 2023-10-12 21:59:39 +01:00
parent d68e2f913a
commit 4d1b790a7c
3 changed files with 62 additions and 24 deletions

74
icon.js
View File

@ -1,7 +1,7 @@
//todo: Make this a module/mjs file. C6 compatibility can stay, if needed. //todo: Make this a module/mjs file. C6 compatibility can stay, if needed.
ICONJS_DEBUG = false; ICONJS_DEBUG = false;
ICONJS_STRICT = true; ICONJS_STRICT = true;
ICONJS_VERSION = "0.3.3"; ICONJS_VERSION = "0.3.4";
function setDebug(value) { function setDebug(value) {
ICONJS_DEBUG = !!value; ICONJS_DEBUG = !!value;
@ -114,11 +114,16 @@ function readIconFile(input) {
throw `Not a PS2 icon file (was ${magic}, expected ${0x010000})`; throw `Not a PS2 icon file (was ${magic}, expected ${0x010000})`;
} }
const numberOfShapes = u32le(4); const numberOfShapes = u32le(4);
if(numberOfShapes > 16) {
throw `Too many defined shapes! Is this a valid file? (file reports ${numberOfShapes} shapes)`;
}
const textureType = u32le(8); const textureType = u32le(8);
const textureFormat = getTextureFormat(textureType); const textureFormat = getTextureFormat(textureType);
//:skip 4 //:skip 4
const numberOfVertexes = u32le(16); const numberOfVertexes = u32le(16);
// now we're entering a bunch of chunks... oh baby, now it's painful. if(!!(numberOfVertexes % 3)){
throw `Not enough vertices to define a triangle (${numberOfVertexes % 3} vertices remained).`;
}
// format: [xxyyzzaa * numberOfShapes][xxyyzzaa][uuvvrgba], ((8 * numberOfShapes) + 16) [per chunk] // format: [xxyyzzaa * numberOfShapes][xxyyzzaa][uuvvrgba], ((8 * numberOfShapes) + 16) [per chunk]
let offset = 20; let offset = 20;
let vertices = new Array(); let vertices = new Array();
@ -148,13 +153,12 @@ function readIconFile(input) {
}; };
let color = u32_rgba8(u32le(offset+(chunkLength*index)+((numberOfShapes * 8))+12)); let color = u32_rgba8(u32le(offset+(chunkLength*index)+((numberOfShapes * 8))+12));
// keep original u32le color? // keep original u32le color?
vertices.push({shapes, normal, uv, color}); vertices.push({shapes, normal, uv, color});
} }
offset = (20+(numberOfVertexes * chunkLength)); offset = (20+(numberOfVertexes * chunkLength));
animationHeader = {id: u32le(offset), length: u32le(offset+4), speed: f32le(offset+8), "offset": u32le(offset+12), keyframes: u32le(offset+16)}; animationHeader = {id: u32le(offset), length: u32le(offset+4), speed: f32le(offset+8), "offset": u32le(offset+12), keyframes: u32le(offset+16)};
let animData = new Array(); let animData = new Array();
// now we have to do stuff dynamically // now we have to enumerate values, so now we introduce an offset value.
// format for a keyframe: sssskkkk[ffffvvvv] where [ffffvvvv] repeat based on the value that kkkk(eys) has. // format for a keyframe: sssskkkk[ffffvvvv] where [ffffvvvv] repeat based on the value that kkkk(eys) has.
// sssskkkk[ffffvvvv] is repeated based on animationHeader.keyframes value. // sssskkkk[ffffvvvv] is repeated based on animationHeader.keyframes value.
offset += 20; offset += 20;
@ -177,11 +181,26 @@ function readIconFile(input) {
case 'U': { case 'U': {
//where every 16-bit entry is a BGR5A1 color 0b[bbbbbgggggrrrrra] //where every 16-bit entry is a BGR5A1 color 0b[bbbbbgggggrrrrra]
texture = new Uint16Array(input.slice(offset, (offset+0x8000))); texture = new Uint16Array(input.slice(offset, (offset+0x8000)));
//texture.forEach(function(indice){console.log(BGR5A1(indice))});
break; break;
} }
case 'C': { case 'C': {
// compression format unknown, but all in type use same header format // compression format is RLE-based, where first u32 is size, and format is defined as:
/**
* u16 rleType;
* if (rleType >= 0xff00) {
* //do a raw copy
* let length = (0xffff - rleType);
* byte data[length];
* } else {
* //repeat next byte rleType times
* data = new Uint16Array(rleType);
* for (let index = 0; index < rleType; index++) {
* data[index] = u16 repeater @ +4;
* }
* }
**/
//output of this will be another u16[0x4000] of the decompressed texture
//after that just parse output as-if it was uncompressed, I think.
size = u32le(offset); size = u32le(offset);
texture = {size, data: input.slice(offset+4, offset+(4+size))}; texture = {size, data: input.slice(offset+4, offset+(4+size))};
} }
@ -273,6 +292,14 @@ function readEmsPsuFile(input){
function readPsvFile(input){ function readPsvFile(input){
const view = new DataView(input); const view = new DataView(input);
const u32le = function(i){return view.getUint32(i, 1)}; const u32le = function(i){return view.getUint32(i, 1)};
const t64le = function(i){return {
seconds: view.getUint8(i+1),
minutes: view.getUint8(i+2),
hours: view.getUint8(i+3),
day: view.getUint8(i+4),
month: view.getUint8(i+5),
year: view.getUint16(i+6, 1)
}};
//!pattern psv_file.hexpat //!pattern psv_file.hexpat
const magic = u32le(0); const magic = u32le(0);
if (magic !== 0x50535600) { if (magic !== 0x50535600) {
@ -280,25 +307,28 @@ function readPsvFile(input){
} }
//:skip 4 //:skip 4
//:skip 20 // key seed, console ignores this //:skip 20 // key seed, console ignores this
//:skip 20 // sha1 hmac digest, useful for... something //:skip 20 // sha1 hmac digest, useful for verifying that this is, indeed, save data.
//:skip 8 //:skip 8
const type1 = u32le(52); const type1 = u32le(56);
const type2 = u32le(56); const type2 = u32le(60);
if(type1 !== 0x2c && type2 !== 2) { if(type1 !== 0x2c && type2 !== 2) {
throw `Not parsing, this is not a PS2 save export (was ${type1}:${type2}, expected 44:2)`; throw `Not parsing, this is not a PS2 save export (was ${type1}:${type2}, expected 44:2)`;
} }
const displayedSize = u32le(60); const displayedSize = u32le(64);
const ps2dOffset = u32le(64); const ps2dOffset = u32le(68);
const ps2dSize = u32le(68); // don't know why this is included if its always 964 const ps2dSize = u32le(72); // don't know why this is included if its always 964
const nModelOffset = u32le(72); const nModelOffset = u32le(76);
const nModelSize = u32le(76); const nModelSize = u32le(80);
const cModelOffset = u32le(80); const cModelOffset = u32le(84);
const cModelSize = u32le(84); const cModelSize = u32le(88);
const dModelOffset = u32le(88); const dModelOffset = u32le(92);
const dModelSize = u32le(92); const dModelSize = u32le(96);
const numberOfFiles = u32le(96); // in-case this library changes stance on other files const numberOfFiles = u32le(100); // in-case this library changes stance on other files
const rootDirectoryData = input.slice(100, 158); // file = {t64le created, t64le modified, u32 size, u32 permissions, byte[32] title}
let offset = 158; // and if it's not the root directory, add another u32 for offset/location
const rootDirectoryData = input.slice(104, 162);
const timestamps = {created: t64le(104), modified: t64le(112)};
let offset = 162;
let fileData = new Array(); let fileData = new Array();
for (let index = 0; index < numberOfFiles; index++) { for (let index = 0; index < numberOfFiles; index++) {
fileData.push(input.slice(offset,offset+0x3c)); fileData.push(input.slice(offset,offset+0x3c));
@ -313,7 +343,7 @@ function readPsvFile(input){
if (ICONJS_DEBUG) { if (ICONJS_DEBUG) {
console.log({magic, type1, type2, displayedSize, ps2dOffset, ps2dSize, nModelOffset, nModelSize, cModelOffset, cModelSize, dModelOffset, dModelSize, numberOfFiles, rootDirectoryData, fileData}) console.log({magic, type1, type2, displayedSize, ps2dOffset, ps2dSize, nModelOffset, nModelSize, cModelOffset, cModelSize, dModelOffset, dModelSize, numberOfFiles, rootDirectoryData, fileData})
} }
return {icons, "icon.sys": input.slice(ps2dOffset, ps2dOffset+ps2dSize)}; return {icons, "icon.sys": input.slice(ps2dOffset, ps2dOffset+ps2dSize), timestamps};
} }
if(typeof module !== "undefined") { if(typeof module !== "undefined") {

View File

@ -16,15 +16,18 @@ if(processObj.argv[2] === "psu") {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu"); let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu");
const parsed = iconjs.readEmsPsuFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength)); const parsed = iconjs.readEmsPsuFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data); const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data);
output = {parsed, PS2D} let output = {parsed, PS2D}
Object.keys(PS2D.filenames).forEach(function(file) { Object.keys(PS2D.filenames).forEach(function(file) {
output[file] = iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames[file]].data); output[file] = iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames[file]].data);
}); });
console.log(output); console.log(output);
} else if(processObj.argv[2] === "psv") { } else if(processObj.argv[2] === "psv") {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu"); let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psv");
const parsed = iconjs.readPsvFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength)); const parsed = iconjs.readPsvFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
console.log(parsed); console.log(parsed);
const PS2D = iconjs.readPS2D(parsed["icon.sys"]);
let output = {parsed, PS2D};
console.log(output);
} else { } else {
let inputFile = filesystem.readFileSync(processObj.argv[2] ? processObj.argv[2] : "icon.sys"); let inputFile = filesystem.readFileSync(processObj.argv[2] ? processObj.argv[2] : "icon.sys");
const metadata = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength)); const metadata = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));

View File

@ -186,6 +186,11 @@
let inputData = readPsvFile(d); let inputData = readPsvFile(d);
let output = readPS2D(inputData["icon.sys"]); let output = readPS2D(inputData["icon.sys"]);
updateDisplay(output); updateDisplay(output);
let cTime = inputData.timestamps.created;
let mTime = inputData.timestamps.modified;
//TODO: use Time() to align JST times to user-local timezone
document.getElementById("dateCreated").textContent = `${cTime.hours.toString().padStart("2","0")}:${cTime.minutes.toString().padStart("2","0")}:${cTime.seconds.toString().padStart("2","0")} ${cTime.day.toString().padStart("2","0")}/${cTime.month.toString().padStart("2","0")}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${mTime.hours.toString().padStart("2","0")}:${mTime.minutes.toString().padStart("2","0")}:${mTime.seconds.toString().padStart("2","0")} ${mTime.day.toString().padStart("2","0")}/${mTime.month.toString().padStart("2","0")}/${mTime.year}`;
} catch(e) { } catch(e) {
if(glContext!==null){glContext.clear(glContext.COLOR_BUFFER_BIT);} if(glContext!==null){glContext.clear(glContext.COLOR_BUFFER_BIT);}
alert(e); alert(e);