formatting changes

* made lzari.js very es5-friendly.
* added jshint (yes, seriously) ruling to icon.js and lzari.js.
* Updated README.md
* Fixed up the werid mixed syntax on iconwriter.js
* -> basically made all the tab-based jank that only showed up correctly in...
* n++ a bit more readable outside of such. (minor tab to space consistancy fix)
* Added and removed semicolons to match some syntax standards.
* Made some things that should of been variables, variables !(global_leakage)
* expand module exporting code to clean up some 'object short notation' uses.
This commit is contained in:
yellows111 2024-02-21 01:45:19 +00:00
parent 8ab26610e6
commit a6ae91dffc
4 changed files with 133 additions and 120 deletions

View File

@ -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.
@ -39,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
@ -53,12 +55,13 @@ 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 |

78
icon.js
View File

@ -1,4 +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
var ICONJS_DEBUG = false;
var ICONJS_STRICT = true;
@ -7,7 +8,7 @@ var ICONJS_STRICT = true;
* @constant {string}
* @default
*/
const ICONJS_VERSION = "0.8.2";
const ICONJS_VERSION = "0.8.3";
/**
* The RC4 key used for ciphering CodeBreaker Saves.
@ -62,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).
@ -97,7 +98,7 @@ class yellowDataReader extends DataView {
day: super.getUint8(i+4),
month: super.getUint8(i+5),
year: super.getUint16(i+6, 1)
}};
};}
constructor(buffer) {
super(buffer);
return {
@ -106,7 +107,7 @@ class yellowDataReader extends DataView {
u32le: this.u32le.bind(this),
f32le: this.f32le.bind(this),
t64le: this.t64le.bind(this)
}
};
}
}
@ -213,7 +214,7 @@ function uncompressTexture(texData) {
uncompressed[index] = u16le(offset);
for (let indey = 0; indey < currentValue; indey++) {
uncompressed[index] = u16le(offset);
index++
index++;
}
offset += 2;
}
@ -285,13 +286,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);
@ -317,7 +318,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});
}
@ -339,7 +340,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.
@ -354,7 +355,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]
@ -389,7 +390,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.
@ -436,7 +437,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))};
}
}
@ -497,7 +498,7 @@ function readEmsPsuFile(input){
let output = new Object();
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;
@ -569,15 +570,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};
}
@ -660,11 +667,11 @@ 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;
@ -672,7 +679,7 @@ function readSharkXPortSxpsFile(input) {
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++) {
fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
const fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
switch(fdesc.type) {
case "directory": {
offset += 250;
@ -731,7 +738,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);
@ -773,13 +780,13 @@ function readCodeBreakerCbsFile(input, inflator = null) {
*/
function readMaxPwsDirectory(input, directorySize) {
const {u32le} = new yellowDataReader(input);
virtualFilesystem = new Object();
const virtualFilesystem = new Object();
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}.`; };
if(filename === "") { throw `Unexpected null filename at byte ${offset+4}.`; }
offset += 36;
const data = input.slice(offset, offset+dataSize);
offset += dataSize;
@ -802,12 +809,12 @@ function readMaxPwsDirectory(input, directorySize) {
*/
function readMaxPwsFile(input, unlzari) {
if(typeof unlzari !== "function") {
throw `No decompresser function passed. Skipping.`;
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".`;
throw "Unrecognized file identification string. Expected \"Ps2PowerSave\".";
}
//:skip 4 (u32 checksum)
const _dirName = input.slice(0x10, 0x30);
@ -834,21 +841,22 @@ function readMaxPwsFile(input, unlzari) {
/**
* Define (module.)exports with all public functions.
* @exports icondumper2/icon
*/ // start c6js
*/ // start c6js#
/* globals exports: true */
if(typeof exports !== "object") {
exports = {
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile},
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
options: {setDebug, setStrictness},
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, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile};
exports.helpers = {uncompressTexture, convertBGR5A1toRGB5A1};
exports.options = {setDebug, setStrictness};
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;
}

View File

@ -24,7 +24,7 @@
* 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;
@ -43,7 +43,7 @@ var bit_Mask = 0;
function GetBit() {
//partial xgetc modification
if(inputLocation >= inputData.length) {return -1};
if(inputLocation >= inputData.length) {return -1;}
if((bit_Mask >>= 1) === 0) {
bit_Buffer = inputData[inputLocation++];
bit_Mask = 128;
@ -52,10 +52,10 @@ function GetBit() {
}
function BinarySearchSym(x) {
let i = 1;
let j = 314;
var i = 1;
var j = 314;
while (i < j) {
let k = ((i + j) / 2)|0;
var k = ((i + j) / 2)|0;
if (symbolCumulative[k] > x) {
i = k + 1;
} else {
@ -66,10 +66,10 @@ function BinarySearchSym(x) {
}
function BinarySearchPos(x) {
let i = 1;
let j = 4096;
var i = 1;
var j = 4096;
while (i < j) {
let k = ((i + j) / 2)|0;
var k = ((i + j) / 2)|0;
if (positionCumulative[k] > x) {
i = k + 1;
} else {
@ -93,28 +93,28 @@ function DecodeChar() {
value -= 32768;
low -= 32768;
high -= 32768;
} else if (high > 65536) { break };
} else if (high > 65536) { break; }
low += low;
high += high;
value = 2 * value + GetBit();
}
//transcluded UpdateModel
let character = symbolToCharacter[sym];
var character = symbolToCharacter[sym];
// do not remove above, will be overwritten otherwise!
let i;
var i;
if(symbolCumulative[0] >= 32767) {
let chr = 0;
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--) {};
for(i = sym; symbolFrequency[i] === symbolFrequency[i - 1]; i--) {}
if (i < sym) {
let ch_i = symbolToCharacter[i];
let ch_sym = symbolToCharacter[sym];
var ch_i = symbolToCharacter[i];
var ch_sym = symbolToCharacter[sym];
symbolToCharacter[i] = ch_sym;
symbolToCharacter[sym] = ch_i;
characterToSymbol[ch_i] = sym;
@ -144,7 +144,7 @@ function DecodePosition() {
value -= 32768;
low -= 32768;
high -= 32768;
} else if (high > 65536) { break };
} else if (high > 65536) { break; }
low += low;
high += high;
value = 2 * value + GetBit();
@ -178,23 +178,22 @@ function decodeLzari(input) {
inputData = input;
inputLocation = 4;
let dataSize = new DataView(input.buffer).getInt32(0,1);
var dataSize = new DataView(input.buffer).getInt32(0,1);
if (dataSize == 0) return(0);
if (dataSize < 0) return(-1);
let outputLength = dataSize;
let outputData = new Uint8Array(dataSize);
let outputLocation = 0;
var outputData = new Uint8Array(dataSize);
var outputLocation = 0;
//transcluded StartDecode
for (let i = 0; i < 17; i++) {
for (var i = 0; i < 17; i++) {
value = 2 * value + GetBit();
}
//transcluded StartModel
symbolCumulative[314] = 0;
for (let sym = 314; sym >= 1; sym--) {
let ch = sym - 1;
for (var sym = 314; sym >= 1; sym--) {
var ch = sym - 1;
characterToSymbol[ch] = sym;
symbolToCharacter[sym] = ch;
symbolFrequency[sym] = 1;
@ -202,29 +201,29 @@ function decodeLzari(input) {
}
symbolFrequency[0] = 0;
positionCumulative[4096] = 0;
for (let i = 4096; i >= 1; i--) {
for (i = 4096; i >= 1; i--) { // redefine i
positionCumulative[i - 1] = (positionCumulative[i] + (10000 / (i + 200))|0);
}
//end transclusion
//normal Decode process
for (let i = 0; i < 4036; i++) {
for (i = 0; i < 4036; i++) { // redefine i
text_buffer[i] = 32;
}
var r = 4036;
for (let count = 0; count < dataSize; ) {
if(inputLocation >= inputData.length) {break};
let c = DecodeChar();
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 {
let i = (r - DecodePosition() - 1) & 4095;
let j = c - 253;
for (let k = 0; k < j; k++) {
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;
@ -241,14 +240,15 @@ function decodeLzari(input) {
* Define (module.)exports with all public functions.
* @exports icondumper2/lzari
*/ // start c6js
/* globals exports: true */
if(typeof exports !== "object") {
exports = {
decodeLzari
"decodeLzari": decodeLzari
};
} else {
exports.decodeLzari = decodeLzari;
}
/* globals module: true */
if(typeof module !== "undefined") {
module.exports = exports;
}

View File

@ -174,16 +174,17 @@ const animData = new Uint8Array([
0x00, 0x00, 0x00, 0x00
]); // 36 bytes
/** generate texture data (RAW: fill with red) **/
const texture_ = new Uint16Array(16384);
for (let indice = 0; indice < 16384; indice++) {
texture_[indice] = 0b1_00000_00000_11111; //A1BGR5
} // 32768 bytes
/** for compressed textures (RLE: fill with blue) **/
texture_[0] = 0x0004;
texture_[1] = 0x0000;
texture_[2] = 0x4000;
texture_[3] = 0b1_11111_00000_00000;
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);
@ -271,6 +272,7 @@ 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,
@ -344,7 +346,7 @@ for (let iconIndice = 0; iconIndice < 32; iconIndice++) { // realistically only
if(iconIndice > 9) {
needsAlpha = 7; // if we're past 9, offset to start at A instead
}
const PsuFileOutput = new Uint8Array(37888); // 37 uncompressed blocks
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