Compare commits

..

No commits in common. "f05ad6fac4407095f4048148fe8b75c8b79a298f" and "1b5f483df86ed9fe015cab40f8d35278e704bac2" have entirely different histories.

9 changed files with 190 additions and 1073 deletions

View File

@ -14,7 +14,7 @@ jobs:
- name: Install node.js
uses: actions/setup-node@main
- name: Compile documentation with JSDoc
run: npx jsdoc ./icon.js ./lzari.js -d ./documentation -R ./README.md
run: npx jsdoc ./icon.js -d ./documentation -R ./README.md
- name: Upload Artifacts
uses: actions/upload-artifact@main
with:

2
.gitignore vendored
View File

@ -13,8 +13,6 @@ icon.sys
*.p2m
*.md
*.cbs
*.max
*.pws
# Unused data

View File

@ -6,11 +6,9 @@ A set of vertices with may or may not include a texture while defining colours f
## Why?
Current implementations had some issues with rendering some icons. These were mostly:
* Not rendering any color for texture types 0-3.
* Failing to decompress some specific RLE-compressed icons. (types with bit 4 enabled)
* Not rendering any color for texture type 3.
* Failing to decompress some specific RLE-compressed icons. (types above 8)
* Requires writing/reading a specific format for successful output of data.
* Incorrect analysis of texture types leading to assuming texture type 1 isn't the same group as texture type 3.
* Further incorrect analysis revealing it's that the texture type is a bitmask.
As of writing, there was no exporter that exists for the format that exhibited one of these problems.
@ -22,7 +20,6 @@ As of writing, there was no exporter that exists for the format that exhibited o
* SharkPort export files (.sps)
* X-Port export files (.xps)
* CodeBreaker Save export files (.cbs)
* Max Drive "PowerSave"/export files (.max)
* PS2 icons (.ico, .icn)
* PS2D format (icon.sys)
@ -33,7 +30,6 @@ As of writing, there was no exporter that exists for the format that exhibited o
* CommonJS (that includes node!) module exporting while still being compatible with other JavaScript implementations.
* Convert a 128x128x16 BGR5A1 bitmap to a standard RGB5A1 format.
* Convert an icon or a set of icons to glTF 2, with textures saved as PNG.
* Decompress any LZARI-formatted data.
## What it doesn't do:
* Create, manipulate or otherwise taint save files.
@ -41,9 +37,9 @@ As of writing, there was no exporter that exists for the format that exhibited o
* Use any implementation-specific features.
## Client compatibility:
The library currently requires use of `const`, `let` and `class` declarations, template literals, and destructuring assignment for variables.
The library requires use of `const`, `let` and `class` declarations.
Any JavaScript implementation should work if they support all of the required features.
Any JavaScript implementation should work if they support all three of these declarations.
### Tested clients:
* Chrome (or Blink-based browser) 49 (or higher) - HTML reference client
@ -55,13 +51,11 @@ Because it replaced what *was* left of icondumper (1).
## Included files:
| File | Description |
| ------------------- | ----------------------------------------------- |
| ---------------- | ----------------------------------------- |
| icon.js | The library itself. |
| index.js | Node.js example client. |
| gltf-exporter.js | Node.js client to export icons to glTF 2. |
| index.htm | HTML reference client. |
| lzari.js | A LZARI decompression-only library. |
| tests/iconwriter.js | Node.js. Creates icons with texture types 0-31. |
## Included example files:
| Directory | Description | Formats |

View File

@ -99,22 +99,13 @@ function imf2gltf(icon = null, filename = "untitled") {
}]; // no indices because who needs indexing when you're transcoding?
gltfOutput.materials = [{
"name": `Material (${filename}#${index})`,
"pbrMetallicRoughness": null,
"pbrMetallicRoughness": {
"baseColorTexture": {"index":0, "texCoord": 0}
},
"extensions": { // or we get annoying PBR and specular stuff we don't need
"KHR_materials_unlit": {}
}
}];
if(icon.textureFormat !== "N") {
gltfOutput.materials[0].pbrMetallicRoughness = {
"baseColorTexture": {"index":0, "texCoord": 0}
};
} else {
gltfOutput.materials[0].pbrMetallicRoughness = {
"baseColorFactor": [1.0, 1.0, 1.0, 1.0],
"metallicFactor": 0.0,
"roughnessFactor": 1.0
}
}
gltfOutput.buffers = [{"uri": `${filename}.bin`, "byteLength": outputFloatArray.byteLength}];
gltfOutput.bufferViews = [
{
@ -182,14 +173,16 @@ function imf2gltf(icon = null, filename = "untitled") {
];
gltfOutput.asset = {"version": "2.0", "generator": `icondumper2/${icondumper2.version}`}
gltfOutput.extensionsUsed = ["KHR_materials_unlit"];
if(icon.textureFormat !== "N") {
gltfOutput.textures = [{"source": 0}];
gltfOutput.images = [{"name": `Texture (${filename}#${index})`, "uri": `${filename}.png`}]
}
gltfOutputArray[index] = (gltfOutput);
}
let texture16 = null; // Uint16Array(16384)
switch(icon.textureFormat) {
case "N": {
texture16 = (new Uint16Array(16384)).fill(0xffff);
break;
}
case "C": {
texture16 = icondumper2.helpers.uncompressTexture(icon.texture.data);
break;
@ -199,7 +192,6 @@ function imf2gltf(icon = null, filename = "untitled") {
break;
}
}
if(texture16 !== null) {
let texture24 = new Uint8Array(49983);
texture24.set([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
@ -251,9 +243,6 @@ function imf2gltf(icon = null, filename = "untitled") {
0xae, 0x42, 0x60, 0x82
], textureOffset);
return {objects: gltfOutputArray, buffer: outputFloatArray, texture: texture24};
} else {
return {objects: gltfOutputArray, buffer: outputFloatArray, texture: null};
}
}
function loadAndConvertIcon(inputData, attemptedFilename = "-") {
@ -268,11 +257,10 @@ function loadAndConvertIcon(inputData, attemptedFilename = "-") {
}
(require("fs")).writeFileSync(`${filename}.bin`, glTF_output.buffer);
console.info(`Saved glTF buffer as "${filename}.bin".`);
if(glTF_output.texture !== null) {
(require("fs")).writeFileSync(`${filename}.png`, glTF_output.texture);
console.info(`Saved texture as "${filename}.png".\n`);
}
}
// can anything de-dupe this code somehow? (index.js)
console.info(`icon.js version ${icondumper2.version}, ${(new Date()).getFullYear().toString()} (c) yellows111`);
@ -334,23 +322,6 @@ switch(processObj.argv[2]) {
}
break;
}
case "max":
case "pws": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.max");
function myUnlzari(inputBuffer) {
return (require("./lzari.js").decodeLzari(inputBuffer)).buffer;
}
const parsed = iconjs.readMaxPwsFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength), myUnlzari);
const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data);
loadAndConvertIcon(iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames.n].data), PS2D.filenames.n);
if(PS2D.filenames.n !== PS2D.filenames.c) {
loadAndConvertIcon(iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames.c].data), PS2D.filenames.c);
}
if(PS2D.filenames.n !== PS2D.filenames.d) {
loadAndConvertIcon(iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames.d].data), PS2D.filenames.d);
}
break;
}
case "sys": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "icon.sys");
const PS2D = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
@ -381,8 +352,6 @@ psv: Read a PS3 export file.
sps: Read a SharkPort export file.
xps: Read a X-Port export file.
cbs: Read a CodeBreaker Save export file.
max: Read a Max Drive export file.
pws: Read a PowerSave export file.
sys: Read a icon.sys (964 bytes) file, and attempt
to read icon files from the current directory.

184
icon.js
View File

@ -1,5 +1,5 @@
//To swap between mjs/esm and c6js, go to the end of this file, and (un)comment your wanted module mode.
/* jshint bitwise: false, esversion: 6, -W009, -W010 */ // not doing this makes linters scream about BWOs, es6 features, and using new Primitive() instead of said primitives
//LOOKING FOR: LZARI implementation (for MAX and PWS files. THIS WILL COMPLETE ICONDUMPER2.)
var ICONJS_DEBUG = false;
var ICONJS_STRICT = true;
@ -8,7 +8,7 @@ var ICONJS_STRICT = true;
* @constant {string}
* @default
*/
const ICONJS_VERSION = "0.8.4";
const ICONJS_VERSION = "0.7.0";
/**
* The RC4 key used for ciphering CodeBreaker Saves.
@ -63,22 +63,22 @@ class yellowDataReader extends DataView {
* @param {number} i Indice offset.
* @returns {number}
*/
u16le(i){return super.getUint16(i, 1);}
u16le(i){return super.getUint16(i, 1)};
/** Fixed-point 16-bit, Little Endian.
* @param {number} i Indice offset.
* @returns {number}
*/
f16le(i){return (super.getInt16(i, 1) / 4096);}
f16le(i){return (super.getInt16(i, 1) / 4096)};
/** Unsigned 32-bit, Little Endian.
* @param {number} i Indice offset.
* @returns {number}
*/
u32le(i){return super.getUint32(i, 1);}
u32le(i){return super.getUint32(i, 1)};
/** Floating-point 32-bit, Little Endian.
* @param {number} i Indice offset.
* @returns {number}
*/
f32le(i){return super.getFloat32(i, 1);}
f32le(i){return super.getFloat32(i, 1)};
/** 64-bit Timestamp structure, Little Endian.
* Time returned is set for JST (UTC+09:00) instead of UTC.
* Time returned is going to be offseted for JST (GMT+09:00).
@ -92,14 +92,13 @@ class yellowDataReader extends DataView {
* @property {number} year - Year.
*/
t64le(i){return {
"__proto__": null,
seconds: super.getUint8(i+1),
minutes: super.getUint8(i+2),
hours: super.getUint8(i+3),
day: super.getUint8(i+4),
month: super.getUint8(i+5),
year: super.getUint16(i+6, 1)
};}
}};
constructor(buffer) {
super(buffer);
return {
@ -108,7 +107,7 @@ class yellowDataReader extends DataView {
u32le: this.u32le.bind(this),
f32le: this.f32le.bind(this),
t64le: this.t64le.bind(this)
};
}
}
}
@ -153,7 +152,6 @@ function setDebug(value) {
* @public
*/
function setStrictness(value) {
console.info("setStrictness is deprecated!");
ICONJS_STRICT = !!value;
}
@ -164,18 +162,15 @@ function setStrictness(value) {
* @access protected
*/
function getTextureFormat(i) {
if(ICONJS_DEBUG) {
console.debug("Texture format: %i", i);
if (i<8) {
if(i==3) {
return 'N';
}
// bit 1: enable smooth shading (TODO)
// bit 2: ??? something weird with colours, check iconwriter.js output...
if(!!(i & 4)) { // if bit 3 (textured)...
if(!!(i & 8)) { // if bit 4 (compressed)...
return "C"; // Compressed.
}
return "U"; // Uncompressed.
} else { // if bit 3 isn't set...
return "N"; // No texture.
return 'U';
} else if (i>=8) {
return 'C';
} else {
return void(0);
}
}
@ -194,7 +189,7 @@ function uncompressTexture(texData) {
let uncompressed = new Uint16Array(16384);
let offset = 0;
for (let index = 0; index < 16384;) {
let currentValue = u16le(offset);
currentValue = u16le(offset);
if(currentValue === 0) {
// if this is specifically a katamari 1 or 2 icon, skip this byte
// because it's formatted like that for some reason
@ -202,7 +197,7 @@ function uncompressTexture(texData) {
currentValue = u16le(offset);
}
offset += 2;
if (currentValue >= 0xfe00) { // everyone says this is 0xff00 but gauntlet:DL tells me its lower
if (currentValue >= 0xff00) {
//do a raw copy of the next currentValue bytes
let length = ((0x10000 - currentValue));
for (let enumerator = 0; enumerator < length; enumerator++) {
@ -215,7 +210,7 @@ function uncompressTexture(texData) {
uncompressed[index] = u16le(offset);
for (let indey = 0; indey < currentValue; indey++) {
uncompressed[index] = u16le(offset);
index++;
index++
}
offset += 2;
}
@ -287,13 +282,13 @@ function readPS2D(input) {
{x: f32le(80), y: f32le(84), z: f32le(88)}, //:skip 4
{x: f32le(96), y: f32le(100), z: f32le(104)}, //:skip 4
{x: f32le(112), y: f32le(116), z: f32le(120)} //:skip 4
];
]
const lightColors = [
{r: f32le(128), g: f32le(132), b: f32le(136), a: f32le(140)},
{r: f32le(144), g: f32le(148), b: f32le(152), a: f32le(156)},
{r: f32le(160), g: f32le(164), b: f32le(168), a: f32le(172)},
{r: f32le(176), g: f32le(180), b: f32le(184), a: f32le(188)}
];
]
// P2SB says color 1 is ambient, 2-4 are for 3-point cameras
// official HDD icon.sys files (completely different PS2ICON text-based format) also say the same.
const int_title = input.slice(0xc0, 0x100);
@ -319,7 +314,7 @@ function readPS2D(input) {
n: stringScrubber((new TextDecoder("utf-8")).decode(int_filename_n)),
c: stringScrubber((new TextDecoder("utf-8")).decode(int_filename_c)),
d: stringScrubber((new TextDecoder("utf-8")).decode(int_filename_d))
};
}
if(ICONJS_DEBUG){
console.debug({header, titleOffset, bgAlpha, bgColors, lightIndices, lightColors, title, filenames});
}
@ -341,7 +336,7 @@ function readIconFile(input) {
b: ((i & 0xff0000) >> 16),
a: (i > 0x7fffffff ? 255 : (((i & 0xff000000) >>> 24) * 2)+1)
// I don't think alpha transparency is actually USED in icons?, rendering with it looks strange.
};};
}};
const magic = u32le(0);
if (magic !== 0x010000) {
// USER NOTICE: So far, I have yet to parse an icon that hasn't had 0x00010000 as it's magic.
@ -356,7 +351,7 @@ function readIconFile(input) {
const textureFormat = getTextureFormat(textureType);
//:skip 4
const numberOfVertexes = u32le(16);
if((numberOfVertexes % 3) > 0){
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]
@ -391,7 +386,7 @@ function readIconFile(input) {
vertices.push({shapes, normal, uv, color});
}
offset = (20+(numberOfVertexes * chunkLength));
const 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();
// 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.
@ -438,7 +433,7 @@ function readIconFile(input) {
//output of this will be another u16[0x4000] of the decompressed texture
//after that just parse output as-if it was uncompressed.
//see uncompressTexture() and convertBGR5A1toRGB5A1() for more info.
const size = u32le(offset);
size = u32le(offset);
texture = {size, data: input.slice(offset+4, offset+(4+size))};
}
}
@ -493,13 +488,13 @@ function readEntryBlock(input) {
function readEmsPsuFile(input){
const header = readEntryBlock(input.slice(0,0x1ff));
if(header.size > 0x7f) {
throw `Directory is too large! (maximum size: ${0x7f}, was ${header.size})`;
throw `Directory is too large! (maximum size: ${0x7f}, was ${header.size})`
}
let fsOut = {"__proto__": null, length: header.size, rootDirectory: header.filename, timestamps: header.timestamps};
let output = {"__proto__": null};
let fsOut = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps};
let output = new Object();
let offset = 512;
for (let index = 0; index < header.size; index++) {
const fdesc = readEntryBlock(input.slice(offset, offset + 512));
fdesc = readEntryBlock(input.slice(offset, offset + 512));
switch(fdesc.type) {
case "directory": {
offset += 512;
@ -571,21 +566,15 @@ function readPsvFile(input){
for (let index = 0; index < numberOfFiles; index++) {
fileData.push(input.slice(offset,offset+0x3c));
offset += 0x3c;
}
};
//then file data after this but we already have pointers to the files we care about
const icons = {
n: input.slice(nModelOffset, nModelOffset+nModelSize),
c: input.slice(cModelOffset, cModelOffset+cModelSize),
d: input.slice(dModelOffset, dModelOffset+dModelSize),
};
}
if (ICONJS_DEBUG) {
console.debug({magic, type1, type2, displayedSize,
ps2dOffset, ps2dSize,
nModelOffset, nModelSize,
cModelOffset, cModelSize,
dModelOffset, dModelSize,
numberOfFiles, rootDirectoryData, fileData
});
console.debug({magic, type1, type2, displayedSize, ps2dOffset, ps2dSize, nModelOffset, nModelSize, cModelOffset, cModelSize, dModelOffset, dModelSize, numberOfFiles, rootDirectoryData, fileData})
}
return {icons, "icon.sys": input.slice(ps2dOffset, ps2dOffset+ps2dSize), timestamps};
}
@ -648,7 +637,7 @@ function readSharkXPortSxpsFile(input) {
let offset = 4;
const ident = input.slice(offset, offset+identLength);
if((new TextDecoder("utf-8")).decode(ident) !== "SharkPortSave") {
throw `Unrecognized file identification string. Expected "SharkPortSave".`;
throw `Unrecognized file identification string. Expected "SharkPortSave".`
}
offset += (identLength + 4);
const titleLength = u32le(offset);
@ -668,19 +657,19 @@ function readSharkXPortSxpsFile(input) {
const comments = {
"game": stringScrubber((new TextDecoder("utf-8")).decode(title)),
"name": stringScrubber((new TextDecoder("utf-8")).decode(description))
};
}
if(description2Length !== 0) {
comments.desc = stringScrubber((new TextDecoder("utf-8")).decode(description2));
}
//const totalSize = u32le(offset); has data, unused in script
const totalSize = u32le(offset);
offset += 4;
const header = readSxpsDescriptor(input.slice(offset, offset + 250));
offset += 250;
// alright now lets parse some actual data
let fsOut = {"__proto__": null, length: header.size, rootDirectory: header.filename, timestamps: header.timestamps, comments};
let output = {"__proto__": null};
let fsOut = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps, comments};
let output = new Object();
for (let index = 0; index < (header.size - 2); index++) {
const fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
switch(fdesc.type) {
case "directory": {
offset += 250;
@ -714,7 +703,7 @@ function readSharkXPortSxpsFile(input) {
*/
function readCodeBreakerCbsDirectory(input) {
const {u32le, t64le} = new yellowDataReader(input);
const virtualFilesystem = {"__proto__": null};
const virtualFilesystem = new Object();
for (let offset = 0; offset < input.byteLength;) {
const timestamps = {created: t64le(offset), modified: t64le(offset+8)};
const dataSize = u32le(offset+16);
@ -739,7 +728,7 @@ function readCodeBreakerCbsDirectory(input) {
*/
function readCodeBreakerCbsFile(input, inflator = null) {
if(typeof inflator !== "function") {
throw "No inflator function passed. Skipping.";
throw `No inflator function passed. Skipping.`;
}
const {u32le, t64le} = new yellowDataReader(input);
const magic = u32le(0);
@ -765,105 +754,28 @@ function readCodeBreakerCbsFile(input, inflator = null) {
const compressedData = input.slice(dataOffset, dataOffset + compressedSize);
const decipheredData = rc4Cipher(ICONJS_CBS_RC4_KEY, new Uint8Array(compressedData));
const inflatedData = inflator(decipheredData);
const fsOut = {"__proto__": null, rootDirectory: dirName, timestamps};
const fsOut = {rootDirectory: dirName, timestamps};
fsOut[dirName] = readCodeBreakerCbsDirectory(inflatedData);
if(ICONJS_DEBUG) {
console.debug({magic, dataOffset, compressedSize, dirName, permissions, displayName});
}
return fsOut;
}
/**
* Read a Max Drive (MAX) or PowerSave (PWS) file's directory structure.
* @param {ArrayBuffer} input - Uncompressed input
* @returns {Object} (user didn't write a description)
* @protected
*/
function readMaxPwsDirectory(input, directorySize) {
const {u32le} = new yellowDataReader(input);
const virtualFilesystem = {"__proto__": null};
let offset = 0;
for (let index = 0; index < directorySize; index++) {
const dataSize = u32le(offset);
const _filename = input.slice(offset+4, offset+36);
const filename = stringScrubber((new TextDecoder("utf-8")).decode(_filename));
if(filename === "") { throw `Unexpected null filename at byte ${offset+4}.`; }
offset += 36;
const data = input.slice(offset, offset+dataSize);
offset += dataSize;
if(index !== directorySize - 1) {
while((offset & 15) !== 8) {
offset++;
}
}
virtualFilesystem[filename] = ({dataSize, data});
}
return virtualFilesystem;
}
/**
* Read a Max Drive (MAX) or PowerSave (PWS) file.
* @param {ArrayBuffer} input - MAX/PWS formatted file
* @param {function(Uint8Array): ArrayBuffer} unlzari - a function which provides a LZARI-compatible decompression function.
* @returns {Object} (user didn't write a description)
* @public
*/
function readMaxPwsFile(input, unlzari) {
if(typeof unlzari !== "function") {
throw "No decompresser function passed. Skipping.";
}
const {u32le} = new yellowDataReader(input);
const ident = input.slice(0, 12);
if((new TextDecoder("utf-8")).decode(ident) !== "Ps2PowerSave") {
throw "Unrecognized file identification string. Expected \"Ps2PowerSave\".";
}
//:skip 4 (u32 checksum)
const _dirName = input.slice(0x10, 0x30);
const dirName = stringScrubber((new TextDecoder("utf-8")).decode(_dirName));
const _displayName = input.slice(0x30, 0x50);
const displayName = stringScrubber((new TextDecoder("utf-8")).decode(_displayName));
const compressedSize = u32le(0x50);
if(compressedSize !== (input.byteLength - 88)) {
console.warn(`This file says it's larger then it actually is! (Given size: ${compressedSize}, actual size: ${input.byteLength - 88})`);
}
const size = u32le(0x54);
const compressedData = input.slice(88, input.byteLength);
const uncompressedData = unlzari(new Uint8Array(compressedData)); // read above why we can't trust given size
const fsOut = {"__proto__": null, rootDirectory: dirName};
fsOut[dirName] = readMaxPwsDirectory(uncompressedData, size);
// there's no... timestamps or permissions... this doesn't bode well.
if(ICONJS_DEBUG) {
console.debug({ident, compressedSize, dirName, displayName});
}
return fsOut;
}
/**
* Define (module.)exports with all public functions.
* @exports icondumper2/icon
*/ // start c6js#
/* globals exports: true */
if(typeof exports !== "object") {
*/ // start c6js
exports = {
readers: {"readIconFile": readIconFile, "readPS2D": readPS2D, "readEmsPsuFile": readEmsPsuFile, "readPsvFile": readPsvFile, "readSharkXPortSxpsFile": readSharkXPortSxpsFile, "readCodeBreakerCbsFile": readCodeBreakerCbsFile, "readMaxPwsFile": readMaxPwsFile},
helpers: {"uncompressTexture": uncompressTexture, "convertBGR5A1toRGB5A1": convertBGR5A1toRGB5A1},
options: {"setDebug": setDebug, "setStrictness": setStrictness},
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile},
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
options: {setDebug, setStrictness},
version: ICONJS_VERSION
};
} else {
exports.readers = {"readIconFile": readIconFile, "readPS2D": readPS2D, "readEmsPsuFile": readEmsPsuFile, "readPsvFile": readPsvFile, "readSharkXPortSxpsFile": readSharkXPortSxpsFile, "readCodeBreakerCbsFile": readCodeBreakerCbsFile, "readMaxPwsFile": readMaxPwsFile};
exports.helpers = {"uncompressTexture": uncompressTexture, "convertBGR5A1toRGB5A1": convertBGR5A1toRGB5A1};
exports.options = {"setDebug": setDebug, "setStrictness": setStrictness};
exports.version = ICONJS_VERSION;
}
/* globals module: true */
if(typeof module !== "undefined") {
module.exports = exports;
}
//end c6js
//start esm
/*export {
readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile, uncompressTexture, convertBGR5A1toRGB5A1, setDebug, ICONJS_VERSION
readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, uncompressTexture, convertBGR5A1toRGB5A1, setDebug, ICONJS_VERSION
};*/
//end esm

205
index.htm
View File

@ -4,12 +4,10 @@
<meta charset="utf-8"></meta>
<meta name="viewport" content="initial-scale=1.5"></meta>
<meta name="description" content="A HTML client for icondumper2"></meta>
<title>icondumper2 HTML reference client</title>
<title>icondumper2 - HTML reference client</title>
<script src="icon.js"></script>
<!-- Removing or commenting below will disable MAX reading... -->
<script src="lzari.js"></script>
<!-- If you need pako to be optional, remove/comment the line below. This will disable support for CBS reading, however. -->
<script src="https://cdn.jsdelivr.net/npm/pako/dist/pako_inflate.es5.min.js" integrity="sha512-tHdgbM+jAAm3zeGYP67IjUouqHYEMuT/Wg/xvTrfEE7zsSX2GJj0G26pyobvn8Hb2LVWGp+UwsLM2HvXwCY+og==" crossorigin="anonymous"></script>
<!-- If you need pako to be optional, remove/comment the bottom line. This will disable support for CBS reading, however -->
<script src="https://cdn.jsdelivr.net/npm/pako/dist/pako_inflate.min.js" integrity="sha512-mlnC6JeOvg9V4vBpWMxGKscsCdScB6yvGVCeFF2plnQMRmwH69s9F8SHPbC0oirqfePmRBhqx2s3Bx7WIvHfWg==" crossorigin="anonymous" id="inflator-installed"></script>
<style>
html {color: #ccc; background: black; font-family: sans-serif}
#title1, #title2 {
@ -25,31 +23,22 @@
#version {text-shadow: 1px 1px 2px black;}
a {color: #ccc;}
.inputbox {
display: table-cell;
display: inline-grid;
margin-right: 0.25em;
border: 1px gray solid;
padding: 0.175em 0.25em 0 0.25em;
margin-bottom: 4px;
border-right: 0;
}
.last-input {
border-right: 1px gray solid;
}
.inputbox > input {
width: 100%;
}
</style>
<meta data-comment="WebGL Shader: Icon">
<script type="text/plain" id="shader-icon-v">
attribute vec3 a_position;
attribute vec3 a_nextPosition;
attribute vec3 a_normal;
attribute vec2 a_textureCoords;
attribute vec4 a_color;
uniform float u_rotation;
uniform float u_scale;
uniform float u_interp;
uniform highp vec3 u_ambientLight;
//uniform highp vec3 u_lightColorA;
//uniform highp vec3 u_lightColorB;
@ -61,12 +50,11 @@
void main() {
float angle = radians(360.0) * u_rotation;
vec2 pos = vec2(cos(angle), sin(angle));
vec3 lv_interp = mix(a_position, a_nextPosition, u_interp);
// x, y, z, scale (w)
gl_Position = vec4(
(lv_interp.x * pos.x) + (lv_interp.z * pos.y), //transform the x position
(-lv_interp.y) - 2.75, // invert the y position and move down -2.75, which will center the model
(lv_interp.x * -pos.y) + (lv_interp.z * pos.x), //transform the z position
(a_position.x * pos.x) + (a_position.z * pos.y), //transform the x position
(0.0 - a_position.y) - 2.75, // invert the y position and move down -2.75, which will center the model
(a_position.x * -pos.y) + (a_position.z * pos.x), //transform the z position
u_scale
);
// flip it, scale it
@ -137,7 +125,7 @@
<h1 id="title1">&#xFF2E;&#xFF4F;&#x3000;&#xFF26;&#xFF49;&#xFF4C;&#xFF45;</h1>
<h1 id="title2">&#xFF2C;&#xFF4F;&#xFF41;&#xFF44;&#xFF45;&#xFF44;</h1>
</div>
<span>Background/icon preview (Keyboard controls: rotate: &larr;/&rarr;, scale: &uarr;/&darr;, step: &minus;/=, play: &lt;/&gt;, change icon: 1:N/2:C/3:D):</span><br>
<span>Background/icon preview (Keyboard controls: rotate: &larr;/&rarr;, scale: &uarr;/&darr;, frame-step: &minus;/&equals;, change icon: 1:N/2:C/3:D):</span><br>
<canvas id="bgcanvas" width="480" height="480"></canvas>
<canvas id="iconcanvas" width="480" height="480"></canvas>
<hr>
@ -145,35 +133,31 @@
<div id="advanced">
<hr>
<div class="inputbox">
<label for="input">icon.sys:</label>
<label for="input">icon.sys goes here:</label>
<input type="file" id="input" name="input" accept=".sys" />
</div>
<div class="inputbox last-input">
<label for="icon">raw icon file:</label>
<div class="inputbox">
<label for="icon">raw icon file goes here:</label>
<input type="file" id="icon" name="icon" accept=".icn, .ico" />
</div>
</div>
<hr>
<div class="inputbox">
<label for="psuinput">EMS Memory Adapter export file (.psu):</label>
<label for="psuinput">EMS Memory Adapter export file (.psu) goes here:</label>
<input type="file" id="psuinput" name="psuinput" accept=".psu" />
</div>
<div class="inputbox">
<label for="psvinput">PS3 export file (.psv):</label>
<label for="psvinput">PS3 export file (.psv) goes here:</label>
<input type="file" id="psvinput" name="psvinput" accept=".psv" />
</div>
<div class="inputbox">
<label for="spsinput">SharkPort/X-Port export file (.sps,&nbsp;.xps):</label>
<label for="spsinput">SharkPort/X-Port export file (.sps, .xps) goes here:</label>
<input type="file" id="spsinput" name="spsinput" accept=".sps, .xps" />
</div>
<div class="inputbox">
<label for="cbsinput">CodeBreaker Save export file (.cbs):</label>
<label for="cbsinput">CodeBreaker Save export file (.cbs) goes here:</label>
<input type="file" id="cbsinput" name="cbsinput" accept=".cbs" />
</div>
<div class="inputbox last-input">
<label for="maxinput">Max Drive/PowerSave export file (.max):</label>
<input type="file" id="maxinput" name="maxinput" accept=".max, .pws" />
</div>
<p>
<span>Date&nbsp;created: </span><span id="dateCreated">--:--:--&nbsp;--/--/----</span><span> UTC+09:00</span>
<wbr><span>&ndash;</span>
@ -183,12 +167,8 @@
<span>File comments: </span><span id="fileCommentGame">(no title)</span><span> - </span><span id="fileCommentName">(no description)</span><span> - </span><span id="fileCommentDesc">(no other text)</span>
</p>
<script>
// I usually don't do in-body <script>'s, but I didn't want to do an awaited onload() again
const GlobalState = {"__proto__": null, rotations: 2, scale: 0, interpfactor: 0, dataLength: 0,
uniforms: {"__proto__": null, rotation: null, scale: null},
iconState: {"__proto__": null, source: null, currentIcon: null, currentSubmodel: 0, cachedIconSys: null},
fileReader: (new FileReader)
};
// I usually don't do in-body <script>'s, but I didn't want to do an await onload() again
const GlobalState = {rotations: 2, dataLength: 0, uniforms: {rotation: null, scale: null}, iconState: {source: null, currentIcon: null, currentSubmodel: 0, cachedIconSys: null}, fileReader: (new FileReader)};
// I don't care HOW disgusting doing this is, I'm sick of pressing escape to clear these.
let allInputs = document.querySelectorAll('input[type="file"]');
Array.from(allInputs).forEach(
@ -199,15 +179,10 @@
}
);
function p0in(input) { // "prefix 0 if needed"
if(typeof input !== "string") {input = input.toString()};
return ((input.length>=2) ? input : `0${input}`);
};
function timeToString(t64leOutput) {
return `${p0in(t64leOutput.hours)}:${p0in(t64leOutput.minutes)}:${p0in(t64leOutput.seconds)} ${p0in(t64leOutput.day)}/${p0in(t64leOutput.month)}/${t64leOutput.year}`
}
// rotation stuff
var rotationDensity = 60;
var interpolationRate = 0.34;
const rotationDensity = 60;
document.body.onkeydown = function(ev) {
if(glBgContext === null || GlobalState.iconState.currentIcon === null) {return;}
if(typeof GlobalState.uniforms.rotation !== "undefined") {
@ -237,7 +212,6 @@
if(GlobalState.iconState.currentSubmodel < 0) {
GlobalState.iconState.currentSubmodel = GlobalState.iconState.currentIcon.numberOfShapes - 1;
}
GlobalState.interpfactor = 1.0;
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
break;
}
@ -246,7 +220,6 @@
if(GlobalState.iconState.currentSubmodel > (GlobalState.iconState.currentIcon.numberOfShapes - 1)) {
GlobalState.iconState.currentSubmodel = 0;
}
GlobalState.interpfactor = 0.0;
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
break;
}
@ -268,41 +241,12 @@
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
break;
}
case "Comma": {
GlobalState.interpfactor -= interpolationRate;
// if we're at the lowest interp, move onto previous shape
if(GlobalState.interpfactor <= 0.0) {
GlobalState.iconState.currentSubmodel--;
if(GlobalState.iconState.currentSubmodel < 0) {
GlobalState.iconState.currentSubmodel = GlobalState.iconState.currentIcon.numberOfShapes - 1;
}
GlobalState.interpfactor = 1.0;
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
break;
}
break;;
}
case "Period": {
GlobalState.interpfactor += interpolationRate;
// if we're at the highest interp, move onto next shape
if(GlobalState.interpfactor >= 1.0) {
GlobalState.iconState.currentSubmodel++;
if(GlobalState.iconState.currentSubmodel > (GlobalState.iconState.currentIcon.numberOfShapes - 1)) {
GlobalState.iconState.currentSubmodel = 0;
}
GlobalState.interpfactor = 0.0;
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
break;
}
break;
}
default: {
return;
}
};
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
glFgContext.uniform1f(GlobalState.uniforms.rotation, (GlobalState.rotations/rotationDensity));
glFgContext.uniform1f(GlobalState.uniforms.interpolation, GlobalState.interpfactor);
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
} else {return;}
}
@ -357,10 +301,8 @@
GlobalState.rotations = 2;
GlobalState.iconState.currentSubmodel = 0;
//clear buffers
if(glFgContext !== null) {
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
}
}
function renderIcon(iconData, fileMetadata = null, clearData = true) {
if(fileMetadata === null) {
fileMetadata = {
@ -368,9 +310,9 @@
"points": [{x:0,y:0,z:0},{x:0,y:0,z:0},{x:0,y:0,z:0}],
"colors": [
{r:1,g:1,b:1,a:1}, //ambient
{r:1,g:0,b:0,a:1}, // point0
{r:0,g:1,b:0,a:1}, // point1
{r:0,g:0,b:1,a:1} // point2
{r:1,g:0,b:0,a:1}, //p[0]
{r:0,g:1,b:0,a:1}, //p[1]
{r:0,g:0,b:1,a:1} //p[2]
]
}
};
@ -399,37 +341,37 @@
}
let iconProgram = createProgram(glFgContext, iconVertexShader, iconFragmentShader);
glFgContext.useProgram(iconProgram);
if(iconData.textureFormat !== "N") {
var attributes = {
color: glFgContext.getAttribLocation(iconProgram, "a_color"),
position: glFgContext.getAttribLocation(iconProgram, "a_position"),
nextPosition: glFgContext.getAttribLocation(iconProgram, "a_nextPosition")
textureCoords: glFgContext.getAttribLocation(iconProgram, "a_textureCoords"),
color: glFgContext.getAttribLocation(iconProgram, "a_color"),
};
var uniforms = {
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation"),
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight"),
scale: glFgContext.getUniformLocation(iconProgram, "u_scale"),
interpolation: glFgContext.getUniformLocation(iconProgram, "u_interp"),
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation")
scale: glFgContext.getUniformLocation(iconProgram, "u_scale")
}
} else {
var attributes = {
position: glFgContext.getAttribLocation(iconProgram, "a_position"),
color: glFgContext.getAttribLocation(iconProgram, "a_color"),
};
var uniforms = {
sampler: glFgContext.getUniformLocation(iconProgram, "u_sampler"),
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation"),
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight"),
scale: glFgContext.getUniformLocation(iconProgram, "u_scale")
}
if(iconData.textureFormat !== "N") {
attributes["textureCoords"] = glFgContext.getAttribLocation(iconProgram, "a_textureCoords");
uniforms["sampler"] = glFgContext.getUniformLocation(iconProgram, "u_sampler");
}
//.section SETUP
let verticesArray = new Array();
let nextVerticesArray = new Array();
let colourArray = new Array();
let uvArray = new Array();
let nextShape = GlobalState.iconState.currentSubmodel + 1;
if(nextShape > (iconData.numberOfShapes - 1)) {
nextShape = 0;
}
iconData.vertices.forEach(function(vertexObject){
verticesArray.push(vertexObject.shapes[GlobalState.iconState.currentSubmodel].x);
verticesArray.push(vertexObject.shapes[GlobalState.iconState.currentSubmodel].y);
verticesArray.push(vertexObject.shapes[GlobalState.iconState.currentSubmodel].z);
nextVerticesArray.push(vertexObject.shapes[nextShape].x);
nextVerticesArray.push(vertexObject.shapes[nextShape].y);
nextVerticesArray.push(vertexObject.shapes[nextShape].z);
colourArray.push(vertexObject.color.r/255);
colourArray.push(vertexObject.color.g/255);
colourArray.push(vertexObject.color.b/255);
@ -437,19 +379,12 @@
uvArray.push(vertexObject.uv.u);
uvArray.push(vertexObject.uv.v);
});
// TODO: Might need normals too for lighting...
//.section VERTICES
const positionBuffer = glFgContext.createBuffer();
glFgContext.bindBuffer(glFgContext.ARRAY_BUFFER, positionBuffer);
glFgContext.enableVertexAttribArray(attributes.position);
glFgContext.bufferData(glFgContext.ARRAY_BUFFER, new Float32Array(verticesArray), glFgContext.STATIC_DRAW);
glFgContext.vertexAttribPointer(attributes.position, 3, glFgContext.FLOAT, false, 0, 0);
//.section VERTICES_2
const nextPositionBuffer = glFgContext.createBuffer();
glFgContext.bindBuffer(glFgContext.ARRAY_BUFFER, nextPositionBuffer);
glFgContext.enableVertexAttribArray(attributes.nextPosition);
glFgContext.bufferData(glFgContext.ARRAY_BUFFER, new Float32Array(nextVerticesArray), glFgContext.STATIC_DRAW);
glFgContext.vertexAttribPointer(attributes.nextPosition, 3, glFgContext.FLOAT, false, 0, 0);
//.section COLOURS
const colorBuffer = glFgContext.createBuffer();
glFgContext.bindBuffer(glFgContext.ARRAY_BUFFER, colorBuffer);
@ -476,9 +411,8 @@
glFgContext.uniform1f(GlobalState.uniforms.rotation, GlobalState.rotations/rotationDensity);
//.section LIGHTING
let colours = fileMetadata.lighting.colors[0]; // get ambient lighting colours
let colours = fileMetadata.lighting.colors[0];
//glFgContext.uniform3f(uniforms.ambientLighting, colours.r, colours.g, colours.b);
// TODO: figure out why rendering goes all sorts of bad when we use the actual values.
glFgContext.uniform3f(uniforms.ambientLighting, 0.75, 0.75, 0.75);
//.section SCALING
@ -486,11 +420,6 @@
if(clearData){GlobalState.scale = 3.5;}
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
//.section INTERPOLATION
GlobalState.uniforms.interpolation = uniforms.interpolation;
if (clearData) {GlobalState.interpfactor = 0.0;}
glFgContext.uniform1f(GlobalState.uniforms.interpolation, GlobalState.interpfactor);
//.section WRITE
//globalize count of triangles, as well
GlobalState.dataLength = (verticesArray.length/3);
@ -565,8 +494,8 @@
let cTime = vFilesystem.timestamps.created;
let mTime = vFilesystem.timestamps.modified;
//TODO: use Time() to align JST times to user-local timezone
document.getElementById("dateCreated").textContent = timeToString(cTime);
document.getElementById("dateModified").textContent = timeToString(mTime);
document.getElementById("dateCreated").textContent = `${p0in(cTime.hours.toString())}:${p0in(cTime.minutes.toString())}:${p0in(cTime.seconds.toString())} ${p0in(cTime.day.toString())}/${p0in(cTime.month.toString())}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${p0in(mTime.hours.toString())}:${p0in(mTime.minutes.toString())}:${p0in(mTime.seconds.toString())} ${p0in(mTime.day.toString())}/${p0in(mTime.month.toString())}/${mTime.year}`;
console.info("model files (psu)", output2);
console.info("icon.sys (psu)", output);
} catch(e) {
@ -603,8 +532,8 @@
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 = timeToString(cTime);
document.getElementById("dateModified").textContent = timeToString(mTime);
document.getElementById("dateCreated").textContent = `${p0in(cTime.hours.toString())}:${p0in(cTime.minutes.toString())}:${p0in(cTime.seconds.toString())} ${p0in(cTime.day.toString())}/${p0in(cTime.month.toString())}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${p0in(mTime.hours.toString())}:${p0in(mTime.minutes.toString())}:${p0in(mTime.seconds.toString())} ${p0in(mTime.day.toString())}/${p0in(mTime.month.toString())}/${mTime.year}`;
console.info("model files (psv)", icons);
console.info("icon.sys (psv)", output);
} catch(e) {
@ -640,8 +569,8 @@
let cTime = vFilesystem.timestamps.created;
let mTime = vFilesystem.timestamps.modified;
//TODO: use Time() to align JST times to user-local timezone
document.getElementById("dateCreated").textContent = timeToString(cTime);
document.getElementById("dateModified").textContent = timeToString(mTime);
document.getElementById("dateCreated").textContent = `${p0in(cTime.hours.toString())}:${p0in(cTime.minutes.toString())}:${p0in(cTime.seconds.toString())} ${p0in(cTime.day.toString())}/${p0in(cTime.month.toString())}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${p0in(mTime.hours.toString())}:${p0in(mTime.minutes.toString())}:${p0in(mTime.seconds.toString())} ${p0in(mTime.day.toString())}/${p0in(mTime.month.toString())}/${mTime.year}`;
document.getElementById("fileCommentGame").textContent = vFilesystem.comments.game;
document.getElementById("fileCommentName").textContent = vFilesystem.comments.name;
if(vFilesystem.comments.hasOwnProperty("desc")) {
@ -690,43 +619,8 @@
cTime = vFilesystem[vFilesystem.rootDirectory]["icon.sys"].timestamps.created;
mTime = vFilesystem[vFilesystem.rootDirectory]["icon.sys"].timestamps.modified;
}
document.getElementById("dateCreated").textContent = timeToString(cTime);
document.getElementById("dateModified").textContent = timeToString(mTime);
console.info("model files (cbs)", output2);
console.info("icon.sys (cbs)", output);
} catch(e) {
if(glBgContext!==null){glBgContext.clear(glBgContext.COLOR_BUFFER_BIT);}
if(glFgContext!==null){glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);}
console.error(e);
alert(e);
}
}
}
pwsbox = document.getElementById("maxinput");
pwsbox.onchange = function(e) {
resetDisplay();
if(pwsbox.files.length === 0) {
return;
}
function decompressor(data) {
return (decodeLzari(data)).buffer;
}
GlobalState.fileReader.readAsArrayBuffer(pwsbox.files[0]);
GlobalState.fileReader.onloadend = function() {
GlobalState.fileReader.onloadend = void(0);
try {
let vFilesystem = readMaxPwsFile(GlobalState.fileReader.result, decompressor);
let output = readPS2D(vFilesystem[vFilesystem.rootDirectory]["icon.sys"].data);
updateDisplay(output);
let output2 = new Object();
Object.keys(output.filenames).forEach(function(file) {
output2[file] = readIconFile(vFilesystem[vFilesystem.rootDirectory][output.filenames[file]].data);
});
GlobalState.iconState.cachedIconSys = output;
GlobalState.iconState.currentSubmodel = 0;
GlobalState.iconState.source = output2;
GlobalState.iconState.currentIcon = output2.n;
renderIcon(output2.n, output);
document.getElementById("dateCreated").textContent = `${p0in(cTime.hours.toString())}:${p0in(cTime.minutes.toString())}:${p0in(cTime.seconds.toString())} ${p0in(cTime.day.toString())}/${p0in(cTime.month.toString())}/${cTime.year.toString()}`;
document.getElementById("dateModified").textContent = `${p0in(mTime.hours.toString())}:${p0in(mTime.minutes.toString())}:${p0in(mTime.seconds.toString())} ${p0in(mTime.day.toString())}/${p0in(mTime.month.toString())}/${mTime.year.toString()}`;
console.info("model files (cbs)", output2);
console.info("icon.sys (cbs)", output);
} catch(e) {
@ -813,15 +707,12 @@
if (typeof pako === "undefined") {
document.getElementById("cbsinput").disabled = true;
}
if (typeof decodeLzari === "undefined") {
document.getElementById("maxinput").disabled = true;
}
// TODO: More than one model shape rendering, other 2 icons (technically done? NMW though), Animation parsing, animation tweening (technically too)
//todo: More than one model shape rendering, other 2 icons (technically done? NMW though), Animation parsing, animation tweening
</script>
<span id="version">icondumper2 <a href="./documentation" id="iconjsVersion">(unknown icon.js version)</a> [C: <span id="clientVersion">Loading...</span>] &mdash; &copy; <span id="currentYear">2023</span> yellows111</span>
<script>
document.getElementById("iconjsVersion").textContent = exports.version;
document.getElementById("clientVersion").textContent = "0.8.1";
document.getElementById("clientVersion").textContent = "0.7.0";
document.getElementById("currentYear").textContent = (new Date()).getFullYear().toString();
</script>
</body>

View File

@ -62,22 +62,6 @@ switch(processObj.argv[2]) {
console.log(output);
break;
}
case "max":
case "pws": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.max");
function myUnlzari(inputBuffer) {
return (require("./lzari.js").decodeLzari(inputBuffer)).buffer;
}
const parsed = iconjs.readMaxPwsFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength), myUnlzari);
console.log(parsed);
const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data);
let output = {parsed, PS2D}
Object.keys(PS2D.filenames).forEach(function(file) {
output[file] = iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames[file]].data);
});
console.log(output);
break;
}
case "sys": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "icon.sys");
const metadata = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
@ -86,6 +70,7 @@ switch(processObj.argv[2]) {
Object.keys(metadata.filenames).forEach(function(file) {
let getFile = filesystem.readFileSync(metadata.filenames[file]);
const output = iconjs.readIconFile(getFile.buffer.slice(getFile.byteOffset, getFile.byteOffset + getFile.byteLength));
//console.log(individialIcon);
console.log(`contents of ${metadata.filenames[file]} (${file}):`, output, "\n");
});
}
@ -106,8 +91,6 @@ psv: Read a PS3 export file.
sps: Read a SharkPort export file.
xps: Read a X-Port export file.
cbs: Read a CodeBreaker Save export file.
max: Read a Max Drive export file.
pws: Read a PowerSave export file.
sys: Read a icon.sys (964 bytes) file, and attempt
to read icon files from the current directory.

261
lzari.js
View File

@ -1,261 +0,0 @@
// based on Luigi Auriemma's memory2memory LZARI modification. LZARI was created by Haruhiko Okumura.
// yellows111 modifications: forced all magic constants to be their actual values,
// static calculations have been squashed, some functions have been transcluded into others
/** @copyright MIT license:
* Based on a work by Haruhiko Okumura dated 1989-07-04.
* Copyright 2023 yellows111
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/
/* jshint bitwise: false */ // not doing this makes linters scream about BWOs
//TODO: privatize variables and document the library
var inputData = null;
var inputLocation = 0;
var low = 0;
var high = 131072;
var value = 0;
var text_buffer = new Array(4155);
var characterToSymbol = new Array(314);
var symbolToCharacter = new Array(315);
var symbolFrequency = new Array(315);
var symbolCumulative = new Array(315);
var positionCumulative = new Array(4097);
var bit_Buffer = 0;
var bit_Mask = 0;
function GetBit() {
//partial xgetc modification
if(inputLocation >= inputData.length) {return -1;}
if((bit_Mask >>= 1) === 0) {
bit_Buffer = inputData[inputLocation++];
bit_Mask = 128;
}
return +((bit_Buffer & bit_Mask) !== 0);
}
function BinarySearchSym(x) {
var i = 1;
var j = 314;
while (i < j) {
var k = ((i + j) / 2)|0;
if (symbolCumulative[k] > x) {
i = k + 1;
} else {
j = k;
}
}
return i;
}
function BinarySearchPos(x) {
var i = 1;
var j = 4096;
while (i < j) {
var k = ((i + j) / 2)|0;
if (positionCumulative[k] > x) {
i = k + 1;
} else {
j = k;
}
}
return i - 1;
}
function DecodeChar() {
var range = high - low;
var sym = BinarySearchSym((((value - low + 1) * symbolCumulative[0] - 1) / range)|0);
high = low + ((range * symbolCumulative[sym - 1]) / symbolCumulative[0])|0;
low += ((range * symbolCumulative[sym ]) / symbolCumulative[0])|0;
for ( ; ; ) {
if (low >= 65536) {
value -= 65536;
low -= 65536;
high -= 65536;
} else if (low >= 32768 && high <= 98304) {
value -= 32768;
low -= 32768;
high -= 32768;
} else if (high > 65536) { break; }
low += low;
high += high;
value = 2 * value + GetBit();
}
//transcluded UpdateModel
var character = symbolToCharacter[sym];
// do not remove above, will be overwritten otherwise!
var i;
if(symbolCumulative[0] >= 32767) {
var chr = 0;
for (i = 314; i > 0; i--) {
symbolCumulative[i] = chr;
chr += (symbolFrequency[i] = (symbolFrequency[i] + 1) >> 1);
}
symbolCumulative[0] = chr;
}
for(i = sym; symbolFrequency[i] === symbolFrequency[i - 1]; i--) {}
if (i < sym) {
var ch_i = symbolToCharacter[i];
var ch_sym = symbolToCharacter[sym];
symbolToCharacter[i] = ch_sym;
symbolToCharacter[sym] = ch_i;
characterToSymbol[ch_i] = sym;
characterToSymbol[ch_sym] = i;
//i would change these vars...
//but it looks so darn cool...
}
symbolFrequency[i]++;
while (--i >= 0) {
symbolCumulative[i]++;
}
//end transclusion
return character;
}
function DecodePosition() {
var range = high - low;
var position = BinarySearchPos((((value - low + 1) * positionCumulative[0] - 1) / range)|0);
high = low + ((range * positionCumulative[position ]) / positionCumulative[0])|0;
low += ((range * positionCumulative[position + 1]) / positionCumulative[0])|0;
for ( ; ; ) {
if (low >= 65536) {
value -= 65536;
low -= 65536;
high -= 65536;
} else if (low >= 32768 && high <= 98304) {
value -= 32768;
low -= 32768;
high -= 32768;
} else if (high > 65536) { break; }
low += low;
high += high;
value = 2 * value + GetBit();
}
return position;
}
/**
* Decompresses LZARI-formatted data.
* @param {Uint8Array} input - source data
* @returns {Uint8Array} output - uncompressed data
* @access public
*/
function decodeLzari(input) {
//transcluded reset function.
inputData = null;
inputLocation = 0;
low = 0;
high = 131072;
value = 0;
text_buffer = new Array(4155);
characterToSymbol = new Array(314);
symbolToCharacter = new Array(315);
symbolFrequency = new Array(315);
symbolCumulative = new Array(315);
positionCumulative = new Array(4097);
bit_Buffer = 0;
bit_Mask = 0;
//end transclusion
inputData = input;
inputLocation = 4;
var dataSize = new DataView(input.buffer).getInt32(0,1);
if (dataSize == 0) return(0);
if (dataSize < 0) return(-1);
var outputData = new Uint8Array(dataSize);
var outputLocation = 0;
//transcluded StartDecode
for (var i = 0; i < 17; i++) {
value = 2 * value + GetBit();
}
//transcluded StartModel
symbolCumulative[314] = 0;
for (var sym = 314; sym >= 1; sym--) {
var ch = sym - 1;
characterToSymbol[ch] = sym;
symbolToCharacter[sym] = ch;
symbolFrequency[sym] = 1;
symbolCumulative[sym - 1] = (symbolCumulative[sym] + symbolFrequency[sym]);
}
symbolFrequency[0] = 0;
positionCumulative[4096] = 0;
for (i = 4096; i >= 1; i--) { // redefine i
positionCumulative[i - 1] = (positionCumulative[i] + (10000 / (i + 200))|0);
}
//end transclusion
//normal Decode process
for (i = 0; i < 4036; i++) { // redefine i
text_buffer[i] = 32;
}
var r = 4036;
for (var count = 0; count < dataSize; ) {
if(inputLocation >= inputData.length) {break;}
var c = DecodeChar();
if (c < 256) {
outputData[outputLocation++] = c;
text_buffer[r++] = c;
r &= (4095);
count++;
} else {
i = (r - DecodePosition() - 1) & 4095; // redefine i
var j = c - 253;
for (var k = 0; k < j; k++) {
c = text_buffer[(i + k) & 4095];
outputData[outputLocation++] = c;
text_buffer[r++] = c;
r &= (4095);
count++;
}
}
}
//console.debug("LZARI I/O", {inputData, outputData});
return(outputData);
}
/**
* Define (module.)exports with all public functions.
* @exports icondumper2/lzari
*/ // start c6js
/* globals exports: true */
if(typeof exports !== "object") {
exports = {
"decodeLzari": decodeLzari
};
} else {
exports.decodeLzari = decodeLzari;
}
/* globals module: true */
if(typeof module !== "undefined") {
module.exports = exports;
}
//end c6js
//start esm
/*export {
decodeLzari
};*/
//end esm

View File

@ -1,369 +0,0 @@
// version 1.0.0 icon, 1 shape, texture type 7 (will be discarded), something 1.0f, 36 vertices (cube)
const iconHeader = new Uint8Array([
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
0x24, 0x00, 0x00, 0x00
]); // 20 bytes
// format: [xxyyzzaa][xxyyzzaa][uuvv][rgba], (8 + 8 + 4 + 4) (position.xyzw, normal.xyzw, texcoords.st, colour.rgba)
// 0x1000 = 4096 (1.0f), 0xf000 = -4096 (-1.0f)
// color is lime green (64, 255, 64, 127)
const iconData = new Uint8Array([
// 1/36, [-1, -1, -1], [0.25, 0.25] poly 1.1 z-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
// 2/36, [-1, 1, -1], [0.25, 0.75] poly 1.2 z-1
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
// 3/36, [ 1, -1, -1], [0.75, 0.75] poly 1.3 z-1
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
// 4/36, [ 1, 1, -1] poly 2.1 z-1
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
// 5/36, [ 1, -1, -1] poly 2.2 z-1
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
// 6/36, [-1, 1, -1] poly 2.3 z-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
// 7/36, [-1, -1, -1] poly 3.1 x-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
// 8/36, [-1, -1, 1] poly 3.2 x-1
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
// 9/36, [-1, 1, -1] poly 3.3 x-1
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//10/36, [-1, -1, -1] poly 4.1 x-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//11/36, [-1, -1, 1] poly 4.2 x-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//12/36, [-1, 1, 1] poly 4.3 x-1
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//13/36, [-1, 1, -1] poly 5.1 y+1
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//14/36, [-1, 1, 1] poly 5.2 y+1
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//15/36, [ 1, 1, -1] poly 5.3 y+1
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//16/36, [ 1, 1, -1] poly 6.1 y+1
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//17/36, [-1, 1, 1] poly 6.2 y+1
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//18/36, [ 1, 1, 1] poly 6.3 y+1
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//19/36, [ 1, 1, -1] poly 7.1 x+1
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//20/36, [ 1, 1, 1] poly 7.2 x+1
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//21/36, [ 1, -1, -1] poly 7.3 x+1
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//22/36, [ 1, 1, 1] poly 8.1 x+1
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//23/36, [ 1, -1, 1] poly 8.2 x+1
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//24/36, [ 1, -1, -1] poly 8.3 x+1
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//25/36, [-1, -1, -1] poly 9.1 y-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//26/36, [ 1, -1, -1] poly 9.2 y-1
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//27/36, [-1, -1, 1] poly 9.3 y-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//28/36, [-1, -1, 1] poly 10.1 y-1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//29/36, [ 1, -1, -1] poly 10.2 y-1
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//30/36, [ 1, -1, 1] poly 10.3 y-1
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//31/36, [-1, 1, 1] poly 11.1 z+1
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//32/36, [-1, -1, 1] poly 11.2 z+1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//33/36, [ 1, 1, 1] poly 11.3 z+1
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//35/36, [ 1, -1, 1] poly 12.2 z+1
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
//34/36, [ 1, 1, 1] poly 12.1 z+1
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x04,
0x00, 0x04, 0x00, 0x04, 0x40, 0xff, 0x40, 0x7f,
//36/36, [-1, -1, 1] poly 12.3 z+1
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0xf0, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x04,
0x00, 0x0c, 0x00, 0x0c, 0x40, 0xff, 0x40, 0x7f,
]); // 864 bytes
const animData = new Uint8Array([
0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
]); // 36 bytes
const texture_ = new Uint16Array(16384);
/** for compressed textures (RLE: fill with blue) **/
texture_[0] = 0x0004;
texture_[1] = 0x0000;
texture_[2] = 0x4000;
texture_[3] = 0b1_11111_00000_00000; // 16 bytes
/** generate texture data (RAW: fill with red) **/
for (let indice = 4; indice < 16384; indice++) {
texture_[indice] = 0b1_00000_00000_11111; //A1BGR5
} // 32768 bytes
const textureData = new Uint8Array(texture_.buffer);
/** ps2d data **/
const metadataSkeleton = new Uint8Array(Array.from({...[
0x50, 0x53, 0x32, 0x44, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, // PS2D files have a max limit of 16 characters per line
0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
// color1.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// color2.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// color3.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// color4.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// pos1.xyzw
0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f,
0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00,
// pos2.xyzw
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// pos3.xyzw
0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x80, 0xbf,
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00,
// light1.rgba
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f,
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00,
// light2.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// light3.rgba
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// amblight.rgba
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f,
0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00,
// title
0x82, 0x89, 0x82, 0x83, 0x82, 0x8f, 0x82, 0x8e, // icon
0x82, 0x84, 0x82, 0x95, 0x82, 0x8d, 0x82, 0x90, // dump
0x82, 0x85, 0x82, 0x92, 0x82, 0x51, 0x81, 0x40, // er2
0x82, 0x73, 0x82, 0x85, 0x82, 0x93, 0x82, 0x94, // Test
0x82, 0x73, 0x82, 0x85, 0x82, 0x98, 0x82, 0x94, // Text
0x82, 0x95, 0x82, 0x92, 0x82, 0x85, 0x81, 0x40, // ure
0x82, 0x73, 0x82, 0x99, 0x82, 0x90, 0x82, 0x85, // Type
0x81, 0x40, 0x82, 0x58, // 9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// file - normal
0x74, 0x65, 0x78, 0x74, 0x79, 0x70, 0x65, 0x31,
0x2E, 0x69, 0x63, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// file - copying
0x74, 0x65, 0x78, 0x74, 0x79, 0x70, 0x65, 0x31,
0x2E, 0x69, 0x63, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// file - deleting
0x74, 0x65, 0x78, 0x74, 0x79, 0x70, 0x65, 0x31,
0x2E, 0x69, 0x63, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//], length:964}));
], length:1024})); // psu want block size
const CombinedIconData = new Uint8Array(33792/*20+864+36+32768*/); // psu still want block size
CombinedIconData.set(iconHeader, 0);
CombinedIconData.set(iconData, 20);
CombinedIconData.set(animData, 20+864);
CombinedIconData.set(textureData, 20+864+36);
// to be honest all of this could just be a generator
/** root directory **/
const psuHeader1 = new Uint8Array(Array.from({...[
0x27, 0x84, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x16, 0x25, 0x12, 0x02, 0x05, 0xd2, 0x07,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0xd2, 0x16,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x64, 0x32, 0x5f, 0x74, 0x65, 0x78, 0x74,
0x65, 0x73, 0x74, 0x39
], length:512}));
/** . **/
const psuHeader2 = new Uint8Array(Array.from({...[
0x27, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x25, 0x12, 0x02, 0x05, 0xd2, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0xd2, 0x16,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2e
], length:512}));
/** .. **/
const psuHeader3 = new Uint8Array(Array.from({...[
0x27, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x25, 0x12, 0x02, 0x05, 0xd2, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0xd2, 0x16,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2e, 0x2e
], length:512}));
/** icon.sys **/
const psuHeader4 = new Uint8Array(Array.from({...[
0x17, 0x84, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00,
0x00, 0x16, 0x25, 0x12, 0x02, 0x05, 0xd2, 0x07,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0xd2, 0x16,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x63, 0x6f, 0x6e, 0x2e, 0x73, 0x79, 0x73
], length:512}));
/** textypeX.icn **/
const psuHeader5 = new Uint8Array(Array.from({...[
0x17, 0x84, 0x00, 0x00, 0x98, 0x83, 0x00, 0x00,
0x00, 0x16, 0x25, 0x12, 0x02, 0x05, 0xd2, 0x07,
0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0xd2, 0x16,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x74, 0x65, 0x78, 0x74, 0x79, 0x70, 0x65, 0x39,
0x2E, 0x69, 0x63, 0x6E
], length:512}));
/** write psus **/
for (let iconIndice = 0; iconIndice < 32; iconIndice++) { // realistically only needs to be 0-15, not 0-31.
let needsAlpha = 0;
if(iconIndice > 9) {
needsAlpha = 7; // if we're past 9, offset to start at A instead
}
const PsuFileOutput = new Uint8Array(37888);// 37 uncompressed blocks
CombinedIconData[8] = iconIndice; // set texture type
psuHeader1[75] = 0x30 + iconIndice+needsAlpha; // set folder name
psuHeader5[71] = 0x30 + iconIndice+needsAlpha; // set file name
metadataSkeleton[251] = 0x4f + iconIndice+needsAlpha; // set display name
metadataSkeleton[267] = 0x30 + iconIndice+needsAlpha; // set normal file name
metadataSkeleton[331] = 0x30 + iconIndice+needsAlpha; // set copy file name
metadataSkeleton[395] = 0x30 + iconIndice+needsAlpha; // set deleting file name
PsuFileOutput.set(psuHeader1,0);
PsuFileOutput.set(psuHeader2,512);
PsuFileOutput.set(psuHeader3,1024);
PsuFileOutput.set(psuHeader4,1536);
PsuFileOutput.set(metadataSkeleton,2048);
PsuFileOutput.set(psuHeader5,3072);
PsuFileOutput.set(CombinedIconData, 3584);
// and then 512 bytes of padding since we don't reach a block yet
if(typeof require !== "undefined") {
require("fs").writeFileSync(`${iconIndice.toString(36)}_file.psu`, PsuFileOutput);
}
}