Compare commits
10 Commits
1b5f483df8
...
f05ad6fac4
Author | SHA1 | Date |
---|---|---|
yellows111 | f05ad6fac4 | |
yellows111 | a6ae91dffc | |
yellows111 | 8ab26610e6 | |
yellows111 | b0212bc607 | |
yellows111 | 4031ec9e18 | |
yellows111 | aa53573a14 | |
yellows111 | d57d9129de | |
yellows111 | 23ef7c7ca1 | |
yellows111 | eb8bc8e068 | |
yellows111 | 73e618369b |
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Install node.js
|
||||
uses: actions/setup-node@main
|
||||
- name: Compile documentation with JSDoc
|
||||
run: npx jsdoc ./icon.js -d ./documentation -R ./README.md
|
||||
run: npx jsdoc ./icon.js ./lzari.js -d ./documentation -R ./README.md
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
|
|
|
@ -13,6 +13,8 @@ icon.sys
|
|||
*.p2m
|
||||
*.md
|
||||
*.cbs
|
||||
*.max
|
||||
*.pws
|
||||
|
||||
# Unused data
|
||||
|
||||
|
|
26
README.md
26
README.md
|
@ -6,9 +6,11 @@ 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 type 3.
|
||||
* Failing to decompress some specific RLE-compressed icons. (types above 8)
|
||||
* Not rendering any color for texture types 0-3.
|
||||
* Failing to decompress some specific RLE-compressed icons. (types with bit 4 enabled)
|
||||
* 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.
|
||||
|
||||
|
@ -20,6 +22,7 @@ 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)
|
||||
|
||||
|
@ -30,6 +33,7 @@ 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.
|
||||
|
@ -37,9 +41,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 requires use of `const`, `let` and `class` declarations.
|
||||
The library currently requires use of `const`, `let` and `class` declarations, template literals, and destructuring assignment for variables.
|
||||
|
||||
Any JavaScript implementation should work if they support all three of these declarations.
|
||||
Any JavaScript implementation should work if they support all of the required features.
|
||||
|
||||
### Tested clients:
|
||||
* Chrome (or Blink-based browser) 49 (or higher) - HTML reference client
|
||||
|
@ -50,12 +54,14 @@ Any JavaScript implementation should work if they support all three of these dec
|
|||
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. |
|
||||
| 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 |
|
||||
|
|
153
gltf-exporter.js
153
gltf-exporter.js
|
@ -99,13 +99,22 @@ function imf2gltf(icon = null, filename = "untitled") {
|
|||
}]; // no indices because who needs indexing when you're transcoding?
|
||||
gltfOutput.materials = [{
|
||||
"name": `Material (${filename}#${index})`,
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorTexture": {"index":0, "texCoord": 0}
|
||||
},
|
||||
"pbrMetallicRoughness": null,
|
||||
"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 = [
|
||||
{
|
||||
|
@ -173,16 +182,14 @@ function imf2gltf(icon = null, filename = "untitled") {
|
|||
];
|
||||
gltfOutput.asset = {"version": "2.0", "generator": `icondumper2/${icondumper2.version}`}
|
||||
gltfOutput.extensionsUsed = ["KHR_materials_unlit"];
|
||||
gltfOutput.textures = [{"source": 0}];
|
||||
gltfOutput.images = [{"name": `Texture (${filename}#${index})`, "uri": `${filename}.png`}]
|
||||
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;
|
||||
|
@ -192,57 +199,61 @@ function imf2gltf(icon = null, filename = "untitled") {
|
|||
break;
|
||||
}
|
||||
}
|
||||
let texture24 = new Uint8Array(49983);
|
||||
texture24.set([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
|
||||
0x08, 0x02, 0x00, 0x00, 0x00, // you may know
|
||||
0x4c, 0x5c, 0xf6, 0x9c, // what this is from 0x89.
|
||||
0x00, 0x00, 0xc3, 0x06, 0x49, 0x44, 0x41, 0x54,
|
||||
0x78, 0x01 // if you didn't get it, here's a clue
|
||||
],0);
|
||||
let textureOffset = 43;
|
||||
let texture24Data = new Array();
|
||||
let texture24CheckedData = new Array();
|
||||
for (let x = 0; x < 128; x++) {
|
||||
let line = [(x === 127 ? 1 : 0), 0x81, 0x01, 0x7e, 0xfe, 0x00];
|
||||
texture24Data = texture24Data.concat(line);
|
||||
texture24CheckedData.push(0);
|
||||
let scanline = new Array(128*3);
|
||||
for (let y = 0; y < 128; y++) {
|
||||
color = rgb5a1_rgb8(texture16[(x*128)+y]);
|
||||
scanline[(y*3) ] = ((color >> 0 ) & 255);
|
||||
scanline[(y*3)+1] = ((color >> 8 ) & 255);
|
||||
scanline[(y*3)+2] = ((color >> 16) & 255);
|
||||
if(texture16 !== null) {
|
||||
let texture24 = new Uint8Array(49983);
|
||||
texture24.set([
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
|
||||
0x08, 0x02, 0x00, 0x00, 0x00, // you may know
|
||||
0x4c, 0x5c, 0xf6, 0x9c, // what this is from 0x89.
|
||||
0x00, 0x00, 0xc3, 0x06, 0x49, 0x44, 0x41, 0x54,
|
||||
0x78, 0x01 // if you didn't get it, here's a clue
|
||||
],0);
|
||||
let textureOffset = 43;
|
||||
let texture24Data = new Array();
|
||||
let texture24CheckedData = new Array();
|
||||
for (let x = 0; x < 128; x++) {
|
||||
let line = [(x === 127 ? 1 : 0), 0x81, 0x01, 0x7e, 0xfe, 0x00];
|
||||
texture24Data = texture24Data.concat(line);
|
||||
texture24CheckedData.push(0);
|
||||
let scanline = new Array(128*3);
|
||||
for (let y = 0; y < 128; y++) {
|
||||
color = rgb5a1_rgb8(texture16[(x*128)+y]);
|
||||
scanline[(y*3) ] = ((color >> 0 ) & 255);
|
||||
scanline[(y*3)+1] = ((color >> 8 ) & 255);
|
||||
scanline[(y*3)+2] = ((color >> 16) & 255);
|
||||
}
|
||||
texture24Data = texture24Data.concat(scanline);
|
||||
texture24CheckedData = texture24CheckedData.concat(scanline);
|
||||
}
|
||||
texture24Data = texture24Data.concat(scanline);
|
||||
texture24CheckedData = texture24CheckedData.concat(scanline);
|
||||
texture24.set(texture24Data, textureOffset);
|
||||
textureOffset += texture24Data.length;
|
||||
let a32conv = new DataView(new ArrayBuffer(4));
|
||||
a32conv.setInt32(0, getAdler32(new Uint8Array(texture24CheckedData)))
|
||||
texture24.set([a32conv.getUint8(0), a32conv.getUint8(1), a32conv.getUint8(2), a32conv.getUint8(3)], textureOffset);
|
||||
textureOffset += 4;
|
||||
let crc32 = getCrc32(new Uint8Array([
|
||||
0x49, 0x44, 0x41, 0x54, 0x78, 0x01, ...texture24Data,
|
||||
a32conv.getUint8(0), a32conv.getUint8(1),
|
||||
a32conv.getUint8(2), a32conv.getUint8(3)
|
||||
]));
|
||||
texture24.set([
|
||||
(crc32 >> 24) & 0xff,
|
||||
(crc32 >> 16) & 0xff,
|
||||
(crc32 >> 8) & 0xff,
|
||||
crc32 & 0xff
|
||||
], textureOffset);
|
||||
textureOffset += 4;
|
||||
texture24.set([
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x49, 0x45, 0x4E, 0x44,
|
||||
0xae, 0x42, 0x60, 0x82
|
||||
], textureOffset);
|
||||
return {objects: gltfOutputArray, buffer: outputFloatArray, texture: texture24};
|
||||
} else {
|
||||
return {objects: gltfOutputArray, buffer: outputFloatArray, texture: null};
|
||||
}
|
||||
texture24.set(texture24Data, textureOffset);
|
||||
textureOffset += texture24Data.length;
|
||||
let a32conv = new DataView(new ArrayBuffer(4));
|
||||
a32conv.setInt32(0, getAdler32(new Uint8Array(texture24CheckedData)))
|
||||
texture24.set([a32conv.getUint8(0), a32conv.getUint8(1), a32conv.getUint8(2), a32conv.getUint8(3)], textureOffset);
|
||||
textureOffset += 4;
|
||||
let crc32 = getCrc32(new Uint8Array([
|
||||
0x49, 0x44, 0x41, 0x54, 0x78, 0x01, ...texture24Data,
|
||||
a32conv.getUint8(0), a32conv.getUint8(1),
|
||||
a32conv.getUint8(2), a32conv.getUint8(3)
|
||||
]));
|
||||
texture24.set([
|
||||
(crc32 >> 24) & 0xff,
|
||||
(crc32 >> 16) & 0xff,
|
||||
(crc32 >> 8) & 0xff,
|
||||
crc32 & 0xff
|
||||
], textureOffset);
|
||||
textureOffset += 4;
|
||||
texture24.set([
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x49, 0x45, 0x4E, 0x44,
|
||||
0xae, 0x42, 0x60, 0x82
|
||||
], textureOffset);
|
||||
return {objects: gltfOutputArray, buffer: outputFloatArray, texture: texture24};
|
||||
}
|
||||
|
||||
function loadAndConvertIcon(inputData, attemptedFilename = "-") {
|
||||
|
@ -257,9 +268,10 @@ function loadAndConvertIcon(inputData, attemptedFilename = "-") {
|
|||
}
|
||||
(require("fs")).writeFileSync(`${filename}.bin`, glTF_output.buffer);
|
||||
console.info(`Saved glTF buffer as "${filename}.bin".`);
|
||||
|
||||
(require("fs")).writeFileSync(`${filename}.png`, glTF_output.texture);
|
||||
console.info(`Saved texture as "${filename}.png".\n`);
|
||||
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)
|
||||
|
@ -322,6 +334,23 @@ 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));
|
||||
|
@ -352,6 +381,8 @@ 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.
|
||||
|
|
190
icon.js
190
icon.js
|
@ -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.
|
||||
//LOOKING FOR: LZARI implementation (for MAX and PWS files. THIS WILL COMPLETE ICONDUMPER2.)
|
||||
/* 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
|
||||
var ICONJS_DEBUG = false;
|
||||
var ICONJS_STRICT = true;
|
||||
|
||||
|
@ -8,7 +8,7 @@ var ICONJS_STRICT = true;
|
|||
* @constant {string}
|
||||
* @default
|
||||
*/
|
||||
const ICONJS_VERSION = "0.7.0";
|
||||
const ICONJS_VERSION = "0.8.4";
|
||||
|
||||
/**
|
||||
* 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,13 +92,14 @@ 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 {
|
||||
|
@ -107,7 +108,7 @@ class yellowDataReader extends DataView {
|
|||
u32le: this.u32le.bind(this),
|
||||
f32le: this.f32le.bind(this),
|
||||
t64le: this.t64le.bind(this)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +153,7 @@ function setDebug(value) {
|
|||
* @public
|
||||
*/
|
||||
function setStrictness(value) {
|
||||
console.info("setStrictness is deprecated!");
|
||||
ICONJS_STRICT = !!value;
|
||||
}
|
||||
|
||||
|
@ -162,15 +164,18 @@ function setStrictness(value) {
|
|||
* @access protected
|
||||
*/
|
||||
function getTextureFormat(i) {
|
||||
if (i<8) {
|
||||
if(i==3) {
|
||||
return 'N';
|
||||
if(ICONJS_DEBUG) {
|
||||
console.debug("Texture format: %i", i);
|
||||
}
|
||||
// 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';
|
||||
} else if (i>=8) {
|
||||
return 'C';
|
||||
} else {
|
||||
return void(0);
|
||||
return "U"; // Uncompressed.
|
||||
} else { // if bit 3 isn't set...
|
||||
return "N"; // No texture.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +194,7 @@ function uncompressTexture(texData) {
|
|||
let uncompressed = new Uint16Array(16384);
|
||||
let offset = 0;
|
||||
for (let index = 0; index < 16384;) {
|
||||
currentValue = u16le(offset);
|
||||
let 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
|
||||
|
@ -197,7 +202,7 @@ function uncompressTexture(texData) {
|
|||
currentValue = u16le(offset);
|
||||
}
|
||||
offset += 2;
|
||||
if (currentValue >= 0xff00) {
|
||||
if (currentValue >= 0xfe00) { // everyone says this is 0xff00 but gauntlet:DL tells me its lower
|
||||
//do a raw copy of the next currentValue bytes
|
||||
let length = ((0x10000 - currentValue));
|
||||
for (let enumerator = 0; enumerator < length; enumerator++) {
|
||||
|
@ -210,7 +215,7 @@ function uncompressTexture(texData) {
|
|||
uncompressed[index] = u16le(offset);
|
||||
for (let indey = 0; indey < currentValue; indey++) {
|
||||
uncompressed[index] = u16le(offset);
|
||||
index++
|
||||
index++;
|
||||
}
|
||||
offset += 2;
|
||||
}
|
||||
|
@ -282,13 +287,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);
|
||||
|
@ -314,7 +319,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});
|
||||
}
|
||||
|
@ -336,7 +341,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.
|
||||
|
@ -351,7 +356,7 @@ function readIconFile(input) {
|
|||
const textureFormat = getTextureFormat(textureType);
|
||||
//:skip 4
|
||||
const numberOfVertexes = u32le(16);
|
||||
if(!!(numberOfVertexes % 3)){
|
||||
if((numberOfVertexes % 3) > 0){
|
||||
throw `Not enough vertices to define a triangle (${numberOfVertexes % 3} vertices remained).`;
|
||||
}
|
||||
// format: [xxyyzzaa * numberOfShapes][xxyyzzaa][uuvvrgba], ((8 * numberOfShapes) + 16) [per chunk]
|
||||
|
@ -386,7 +391,7 @@ function readIconFile(input) {
|
|||
vertices.push({shapes, normal, uv, color});
|
||||
}
|
||||
offset = (20+(numberOfVertexes * chunkLength));
|
||||
animationHeader = {id: u32le(offset), length: u32le(offset+4), speed: f32le(offset+8), "offset": u32le(offset+12), keyframes: u32le(offset+16)};
|
||||
const 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.
|
||||
|
@ -433,7 +438,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.
|
||||
size = u32le(offset);
|
||||
const size = u32le(offset);
|
||||
texture = {size, data: input.slice(offset+4, offset+(4+size))};
|
||||
}
|
||||
}
|
||||
|
@ -488,13 +493,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 = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps};
|
||||
let output = new Object();
|
||||
let fsOut = {"__proto__": null, length: header.size, rootDirectory: header.filename, timestamps: header.timestamps};
|
||||
let output = {"__proto__": null};
|
||||
let offset = 512;
|
||||
for (let index = 0; index < header.size; index++) {
|
||||
fdesc = readEntryBlock(input.slice(offset, offset + 512));
|
||||
const fdesc = readEntryBlock(input.slice(offset, offset + 512));
|
||||
switch(fdesc.type) {
|
||||
case "directory": {
|
||||
offset += 512;
|
||||
|
@ -566,15 +571,21 @@ 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};
|
||||
}
|
||||
|
@ -637,7 +648,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);
|
||||
|
@ -657,19 +668,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);
|
||||
//const totalSize = u32le(offset); has data, unused in script
|
||||
offset += 4;
|
||||
const header = readSxpsDescriptor(input.slice(offset, offset + 250));
|
||||
offset += 250;
|
||||
// alright now lets parse some actual data
|
||||
let fsOut = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps, comments};
|
||||
let output = new Object();
|
||||
let fsOut = {"__proto__": null, length: header.size, rootDirectory: header.filename, timestamps: header.timestamps, comments};
|
||||
let output = {"__proto__": null};
|
||||
for (let index = 0; index < (header.size - 2); index++) {
|
||||
fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
|
||||
const fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
|
||||
switch(fdesc.type) {
|
||||
case "directory": {
|
||||
offset += 250;
|
||||
|
@ -703,7 +714,7 @@ function readSharkXPortSxpsFile(input) {
|
|||
*/
|
||||
function readCodeBreakerCbsDirectory(input) {
|
||||
const {u32le, t64le} = new yellowDataReader(input);
|
||||
const virtualFilesystem = new Object();
|
||||
const virtualFilesystem = {"__proto__": null};
|
||||
for (let offset = 0; offset < input.byteLength;) {
|
||||
const timestamps = {created: t64le(offset), modified: t64le(offset+8)};
|
||||
const dataSize = u32le(offset+16);
|
||||
|
@ -728,7 +739,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);
|
||||
|
@ -754,28 +765,105 @@ 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 = {rootDirectory: dirName, timestamps};
|
||||
const fsOut = {"__proto__": null, 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
|
||||
exports = {
|
||||
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile},
|
||||
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
|
||||
options: {setDebug, setStrictness},
|
||||
version: ICONJS_VERSION
|
||||
};
|
||||
|
||||
*/ // start c6js#
|
||||
/* globals exports: true */
|
||||
if(typeof exports !== "object") {
|
||||
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},
|
||||
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, uncompressTexture, convertBGR5A1toRGB5A1, setDebug, ICONJS_VERSION
|
||||
readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile, uncompressTexture, convertBGR5A1toRGB5A1, setDebug, ICONJS_VERSION
|
||||
};*/
|
||||
//end esm
|
241
index.htm
241
index.htm
|
@ -4,10 +4,12 @@
|
|||
<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>
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
<style>
|
||||
html {color: #ccc; background: black; font-family: sans-serif}
|
||||
#title1, #title2 {
|
||||
|
@ -23,22 +25,31 @@
|
|||
#version {text-shadow: 1px 1px 2px black;}
|
||||
a {color: #ccc;}
|
||||
.inputbox {
|
||||
display: inline-grid;
|
||||
display: table-cell;
|
||||
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;
|
||||
|
@ -50,11 +61,12 @@
|
|||
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(
|
||||
(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
|
||||
(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
|
||||
u_scale
|
||||
);
|
||||
// flip it, scale it
|
||||
|
@ -125,7 +137,7 @@
|
|||
<h1 id="title1">No File</h1>
|
||||
<h1 id="title2">Loaded</h1>
|
||||
</div>
|
||||
<span>Background/icon preview (Keyboard controls: rotate: ←/→, scale: ↑/↓, frame-step: −/=, change icon: 1:N/2:C/3:D):</span><br>
|
||||
<span>Background/icon preview (Keyboard controls: rotate: ←/→, scale: ↑/↓, step: −/=, play: </>, 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>
|
||||
|
@ -133,31 +145,35 @@
|
|||
<div id="advanced">
|
||||
<hr>
|
||||
<div class="inputbox">
|
||||
<label for="input">icon.sys goes here:</label>
|
||||
<label for="input">icon.sys:</label>
|
||||
<input type="file" id="input" name="input" accept=".sys" />
|
||||
</div>
|
||||
<div class="inputbox">
|
||||
<label for="icon">raw icon file goes here:</label>
|
||||
<div class="inputbox last-input">
|
||||
<label for="icon">raw icon file:</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) goes here:</label>
|
||||
<label for="psuinput">EMS Memory Adapter export file (.psu):</label>
|
||||
<input type="file" id="psuinput" name="psuinput" accept=".psu" />
|
||||
</div>
|
||||
<div class="inputbox">
|
||||
<label for="psvinput">PS3 export file (.psv) goes here:</label>
|
||||
<label for="psvinput">PS3 export file (.psv):</label>
|
||||
<input type="file" id="psvinput" name="psvinput" accept=".psv" />
|
||||
</div>
|
||||
<div class="inputbox">
|
||||
<label for="spsinput">SharkPort/X-Port export file (.sps, .xps) goes here:</label>
|
||||
<label for="spsinput">SharkPort/X-Port export file (.sps, .xps):</label>
|
||||
<input type="file" id="spsinput" name="spsinput" accept=".sps, .xps" />
|
||||
</div>
|
||||
<div class="inputbox">
|
||||
<label for="cbsinput">CodeBreaker Save export file (.cbs) goes here:</label>
|
||||
<label for="cbsinput">CodeBreaker Save export file (.cbs):</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 created: </span><span id="dateCreated">--:--:-- --/--/----</span><span> UTC+09:00</span>
|
||||
<wbr><span>–</span>
|
||||
|
@ -167,8 +183,12 @@
|
|||
<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 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 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 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(
|
||||
|
@ -179,10 +199,15 @@
|
|||
}
|
||||
);
|
||||
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
|
||||
const rotationDensity = 60;
|
||||
var rotationDensity = 60;
|
||||
var interpolationRate = 0.34;
|
||||
document.body.onkeydown = function(ev) {
|
||||
if(glBgContext === null || GlobalState.iconState.currentIcon === null) {return;}
|
||||
if(typeof GlobalState.uniforms.rotation !== "undefined") {
|
||||
|
@ -212,6 +237,7 @@
|
|||
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;
|
||||
}
|
||||
|
@ -220,6 +246,7 @@
|
|||
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;
|
||||
}
|
||||
|
@ -241,12 +268,41 @@
|
|||
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;}
|
||||
}
|
||||
|
@ -280,7 +336,7 @@
|
|||
}
|
||||
}
|
||||
function resetDisplay() {
|
||||
//reset displayed elements
|
||||
// reset displayed elements
|
||||
document.getElementById("title1").textContent = "\uff0d";
|
||||
document.getElementById("title2").textContent = "\uff0d";
|
||||
document.getElementById("iconn").textContent = "?";
|
||||
|
@ -291,7 +347,7 @@
|
|||
document.getElementById("fileCommentGame").textContent = "(no title)";
|
||||
document.getElementById("fileCommentName").textContent = "(no description)";
|
||||
document.getElementById("fileCommentDesc").textContent = "(no other text)";
|
||||
//reset globalstate parameters
|
||||
// reset globalstate parameters
|
||||
GlobalState.iconState.cachedIconSys = null;
|
||||
GlobalState.iconState.currentIcon = null;
|
||||
GlobalState.iconState.source = null;
|
||||
|
@ -300,8 +356,10 @@
|
|||
GlobalState.dataLength = 0;
|
||||
GlobalState.rotations = 2;
|
||||
GlobalState.iconState.currentSubmodel = 0;
|
||||
//clear buffers
|
||||
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
|
||||
// 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) {
|
||||
|
@ -309,10 +367,10 @@
|
|||
"lighting": {
|
||||
"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}, //p[0]
|
||||
{r:0,g:1,b:0,a:1}, //p[1]
|
||||
{r:0,g:0,b:1,a:1} //p[2]
|
||||
{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
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -341,37 +399,37 @@
|
|||
}
|
||||
let iconProgram = createProgram(glFgContext, iconVertexShader, iconFragmentShader);
|
||||
glFgContext.useProgram(iconProgram);
|
||||
var attributes = {
|
||||
color: glFgContext.getAttribLocation(iconProgram, "a_color"),
|
||||
position: glFgContext.getAttribLocation(iconProgram, "a_position"),
|
||||
nextPosition: glFgContext.getAttribLocation(iconProgram, "a_nextPosition")
|
||||
};
|
||||
var uniforms = {
|
||||
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight"),
|
||||
scale: glFgContext.getUniformLocation(iconProgram, "u_scale"),
|
||||
interpolation: glFgContext.getUniformLocation(iconProgram, "u_interp"),
|
||||
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation")
|
||||
}
|
||||
if(iconData.textureFormat !== "N") {
|
||||
var attributes = {
|
||||
position: glFgContext.getAttribLocation(iconProgram, "a_position"),
|
||||
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")
|
||||
}
|
||||
} 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")
|
||||
}
|
||||
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);
|
||||
|
@ -379,12 +437,19 @@
|
|||
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);
|
||||
|
@ -407,21 +472,27 @@
|
|||
// sets the angle uniform to 2/rotationDensity, this puts the icon at an angle.
|
||||
// globalize uniform rotation
|
||||
GlobalState.uniforms.rotation = uniforms.rotation;
|
||||
if(clearData){GlobalState.rotations = 2;}
|
||||
if (clearData) {GlobalState.rotations = 2;}
|
||||
glFgContext.uniform1f(GlobalState.uniforms.rotation, GlobalState.rotations/rotationDensity);
|
||||
|
||||
//.section LIGHTING
|
||||
let colours = fileMetadata.lighting.colors[0];
|
||||
//glFgContext.uniform3f(uniforms.ambientLighting, colours.r, colours.g, colours.b);
|
||||
let colours = fileMetadata.lighting.colors[0]; // get ambient lighting colours
|
||||
// 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
|
||||
GlobalState.uniforms.scale = uniforms.scale;
|
||||
if(clearData){GlobalState.scale = 3.5;}
|
||||
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
|
||||
// globalize count of triangles, as well
|
||||
GlobalState.dataLength = (verticesArray.length/3);
|
||||
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
|
||||
}
|
||||
|
@ -493,9 +564,9 @@
|
|||
renderIcon(output2.n, output);
|
||||
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 = `${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}`;
|
||||
// TODO: use Time() to align JST times to user-local timezone
|
||||
document.getElementById("dateCreated").textContent = timeToString(cTime);
|
||||
document.getElementById("dateModified").textContent = timeToString(mTime);
|
||||
console.info("model files (psu)", output2);
|
||||
console.info("icon.sys (psu)", output);
|
||||
} catch(e) {
|
||||
|
@ -531,9 +602,9 @@
|
|||
renderIcon(icons.n, 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 = `${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}`;
|
||||
// TODO: use Time() to align JST times to user-local timezone
|
||||
document.getElementById("dateCreated").textContent = timeToString(cTime);
|
||||
document.getElementById("dateModified").textContent = timeToString(mTime);
|
||||
console.info("model files (psv)", icons);
|
||||
console.info("icon.sys (psv)", output);
|
||||
} catch(e) {
|
||||
|
@ -568,9 +639,9 @@
|
|||
renderIcon(output2.n, output);
|
||||
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 = `${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}`;
|
||||
// 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("fileCommentGame").textContent = vFilesystem.comments.game;
|
||||
document.getElementById("fileCommentName").textContent = vFilesystem.comments.name;
|
||||
if(vFilesystem.comments.hasOwnProperty("desc")) {
|
||||
|
@ -613,14 +684,49 @@
|
|||
renderIcon(output2.n, output);
|
||||
let cTime = vFilesystem.timestamps.created;
|
||||
let mTime = vFilesystem.timestamps.modified;
|
||||
//TODO: use Time() to align JST times to user-local timezone
|
||||
// TODO: use Time() to align JST times to user-local timezone
|
||||
if(cTime.year === 0) {
|
||||
// if root directory time is null, read icon.sys instead
|
||||
cTime = vFilesystem[vFilesystem.rootDirectory]["icon.sys"].timestamps.created;
|
||||
mTime = vFilesystem[vFilesystem.rootDirectory]["icon.sys"].timestamps.modified;
|
||||
}
|
||||
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()}`;
|
||||
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);
|
||||
console.info("model files (cbs)", output2);
|
||||
console.info("icon.sys (cbs)", output);
|
||||
} catch(e) {
|
||||
|
@ -707,12 +813,15 @@
|
|||
if (typeof pako === "undefined") {
|
||||
document.getElementById("cbsinput").disabled = true;
|
||||
}
|
||||
//todo: More than one model shape rendering, other 2 icons (technically done? NMW though), Animation parsing, animation tweening
|
||||
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)
|
||||
</script>
|
||||
<span id="version">icondumper2 <a href="./documentation" id="iconjsVersion">(unknown icon.js version)</a> [C: <span id="clientVersion">Loading...</span>] — © <span id="currentYear">2023</span> yellows111</span>
|
||||
<script>
|
||||
document.getElementById("iconjsVersion").textContent = exports.version;
|
||||
document.getElementById("clientVersion").textContent = "0.7.0";
|
||||
document.getElementById("clientVersion").textContent = "0.8.1";
|
||||
document.getElementById("currentYear").textContent = (new Date()).getFullYear().toString();
|
||||
</script>
|
||||
</body>
|
||||
|
|
19
index.js
19
index.js
|
@ -62,6 +62,22 @@ 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));
|
||||
|
@ -70,7 +86,6 @@ 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");
|
||||
});
|
||||
}
|
||||
|
@ -91,6 +106,8 @@ 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.
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
// 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
|
|
@ -0,0 +1,369 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue