0.8.0 (actually) forgot to -a

This commit is contained in:
yellows111 2023-12-18 14:03:51 +00:00
parent eb8bc8e068
commit 23ef7c7ca1
7 changed files with 183 additions and 22 deletions

View File

@ -14,7 +14,7 @@ jobs:
- name: Install node.js - name: Install node.js
uses: actions/setup-node@main uses: actions/setup-node@main
- name: Compile documentation with JSDoc - 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 - name: Upload Artifacts
uses: actions/upload-artifact@main uses: actions/upload-artifact@main
with: with:

2
.gitignore vendored
View File

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

View File

@ -20,6 +20,7 @@ As of writing, there was no exporter that exists for the format that exhibited o
* SharkPort export files (.sps) * SharkPort export files (.sps)
* X-Port export files (.xps) * X-Port export files (.xps)
* CodeBreaker Save export files (.cbs) * CodeBreaker Save export files (.cbs)
* Max Drive "PowerSave"/export files (.max)
* PS2 icons (.ico, .icn) * PS2 icons (.ico, .icn)
* PS2D format (icon.sys) * PS2D format (icon.sys)
@ -30,6 +31,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. * 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 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. * 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: ## What it doesn't do:
* Create, manipulate or otherwise taint save files. * Create, manipulate or otherwise taint save files.
@ -56,6 +58,7 @@ Because it replaced what *was* left of icondumper (1).
| index.js | Node.js example client. | | index.js | Node.js example client. |
| gltf-exporter.js | Node.js client to export icons to glTF 2. | | gltf-exporter.js | Node.js client to export icons to glTF 2. |
| index.htm | HTML reference client. | | index.htm | HTML reference client. |
| lzari.js | A LZARI decompression-only library. |
## Included example files: ## Included example files:
| Directory | Description | Formats | | Directory | Description | Formats |

View File

@ -322,6 +322,23 @@ switch(processObj.argv[2]) {
} }
break; 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": { case "sys": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "icon.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)); const PS2D = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
@ -352,6 +369,8 @@ psv: Read a PS3 export file.
sps: Read a SharkPort export file. sps: Read a SharkPort export file.
xps: Read a X-Port export file. xps: Read a X-Port export file.
cbs: Read a CodeBreaker Save 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 sys: Read a icon.sys (964 bytes) file, and attempt
to read icon files from the current directory. to read icon files from the current directory.

87
icon.js
View File

@ -1,5 +1,4 @@
//To swap between mjs/esm and c6js, go to the end of this file, and (un)comment your wanted module mode. //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.)
var ICONJS_DEBUG = false; var ICONJS_DEBUG = false;
var ICONJS_STRICT = true; var ICONJS_STRICT = true;
@ -8,7 +7,7 @@ var ICONJS_STRICT = true;
* @constant {string} * @constant {string}
* @default * @default
*/ */
const ICONJS_VERSION = "0.7.0"; const ICONJS_VERSION = "0.8.0";
/** /**
* The RC4 key used for ciphering CodeBreaker Saves. * The RC4 key used for ciphering CodeBreaker Saves.
@ -488,7 +487,7 @@ function readEntryBlock(input) {
function readEmsPsuFile(input){ function readEmsPsuFile(input){
const header = readEntryBlock(input.slice(0,0x1ff)); const header = readEntryBlock(input.slice(0,0x1ff));
if(header.size > 0x7f) { 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 fsOut = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps};
let output = new Object(); let output = new Object();
@ -637,7 +636,7 @@ function readSharkXPortSxpsFile(input) {
let offset = 4; let offset = 4;
const ident = input.slice(offset, offset+identLength); const ident = input.slice(offset, offset+identLength);
if((new TextDecoder("utf-8")).decode(ident) !== "SharkPortSave") { 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); offset += (identLength + 4);
const titleLength = u32le(offset); const titleLength = u32le(offset);
@ -756,19 +755,95 @@ function readCodeBreakerCbsFile(input, inflator = null) {
const inflatedData = inflator(decipheredData); const inflatedData = inflator(decipheredData);
const fsOut = {rootDirectory: dirName, timestamps}; const fsOut = {rootDirectory: dirName, timestamps};
fsOut[dirName] = readCodeBreakerCbsDirectory(inflatedData); fsOut[dirName] = readCodeBreakerCbsDirectory(inflatedData);
if(ICONJS_DEBUG) {
console.debug({magic, dataOffset, compressedSize, dirName, permissions, displayName});
}
return fsOut; 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);
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}.`; };
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 = {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. * Define (module.)exports with all public functions.
* @exports icondumper2/icon * @exports icondumper2/icon
*/ // start c6js */ // start c6js
if(typeof exports !== "object") {
exports = { exports = {
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile}, readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile},
helpers: {uncompressTexture, convertBGR5A1toRGB5A1}, helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
options: {setDebug, setStrictness}, options: {setDebug, setStrictness},
version: ICONJS_VERSION version: ICONJS_VERSION
}; };
} else {
exports.readers = {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile};
exports.helpers = {uncompressTexture, convertBGR5A1toRGB5A1};
exports.options = {setDebug, setStrictness};
exports.version = ICONJS_VERSION;
}
if(typeof module !== "undefined") { if(typeof module !== "undefined") {
module.exports = exports; module.exports = exports;
@ -776,6 +851,6 @@ if(typeof module !== "undefined") {
//end c6js //end c6js
//start esm //start esm
/*export { /*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 //end esm

View File

@ -6,6 +6,7 @@
<meta name="description" content="A HTML client for icondumper2"></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> <script src="icon.js"></script>
<script src="lzari.js"></script>
<!-- If you need pako to be optional, remove/comment the bottom line. This will disable support for CBS reading, however --> <!-- 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.es5.min.js" integrity="sha512-tHdgbM+jAAm3zeGYP67IjUouqHYEMuT/Wg/xvTrfEE7zsSX2GJj0G26pyobvn8Hb2LVWGp+UwsLM2HvXwCY+og==" crossorigin="anonymous"></script> <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> <style>
@ -33,6 +34,9 @@
.last-input { .last-input {
border-right: 1px gray solid; border-right: 1px gray solid;
} }
.inputbox > input {
width: 100%;
}
</style> </style>
<meta data-comment="WebGL Shader: Icon"> <meta data-comment="WebGL Shader: Icon">
<script type="text/plain" id="shader-icon-v"> <script type="text/plain" id="shader-icon-v">
@ -137,31 +141,35 @@
<div id="advanced"> <div id="advanced">
<hr> <hr>
<div class="inputbox"> <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" /> <input type="file" id="input" name="input" accept=".sys" />
</div> </div>
<div class="inputbox last-input"> <div class="inputbox last-input">
<label for="icon">raw icon file goes here:</label> <label for="icon">raw icon file:</label>
<input type="file" id="icon" name="icon" accept=".icn, .ico" /> <input type="file" id="icon" name="icon" accept=".icn, .ico" />
</div> </div>
</div> </div>
<hr> <hr>
<div class="inputbox"> <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" /> <input type="file" id="psuinput" name="psuinput" accept=".psu" />
</div> </div>
<div class="inputbox"> <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" /> <input type="file" id="psvinput" name="psvinput" accept=".psv" />
</div> </div>
<div class="inputbox"> <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" /> <input type="file" id="spsinput" name="spsinput" accept=".sps, .xps" />
</div> </div>
<div class="inputbox last-input"> <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" /> <input type="file" id="cbsinput" name="cbsinput" accept=".cbs" />
</div> </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> <p>
<span>Date&nbsp;created: </span><span id="dateCreated">--:--:--&nbsp;--/--/----</span><span> UTC+09:00</span> <span>Date&nbsp;created: </span><span id="dateCreated">--:--:--&nbsp;--/--/----</span><span> UTC+09:00</span>
<wbr><span>&ndash;</span> <wbr><span>&ndash;</span>
@ -305,8 +313,10 @@
GlobalState.rotations = 2; GlobalState.rotations = 2;
GlobalState.iconState.currentSubmodel = 0; GlobalState.iconState.currentSubmodel = 0;
//clear buffers //clear buffers
if(glFgContext !== null) {
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT); glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
} }
}
function renderIcon(iconData, fileMetadata = null, clearData = true) { function renderIcon(iconData, fileMetadata = null, clearData = true) {
if(fileMetadata === null) { if(fileMetadata === null) {
fileMetadata = { fileMetadata = {
@ -635,6 +645,41 @@
} }
} }
} }
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) {
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);
}
}
}
function createShader(gl, type, source) { function createShader(gl, type, source) {
let shader = gl.createShader(type); let shader = gl.createShader(type);
gl.shaderSource(shader, source); gl.shaderSource(shader, source);
@ -716,7 +761,7 @@
<span id="version">icondumper2 <a href="./documentation" id="iconjsVersion">(unknown icon.js version)</a> [C: <span id="clientVersion">Loading...</span>] &mdash; &copy; <span id="currentYear">2023</span> yellows111</span> <span id="version">icondumper2 <a href="./documentation" id="iconjsVersion">(unknown icon.js version)</a> [C: <span id="clientVersion">Loading...</span>] &mdash; &copy; <span id="currentYear">2023</span> yellows111</span>
<script> <script>
document.getElementById("iconjsVersion").textContent = exports.version; document.getElementById("iconjsVersion").textContent = exports.version;
document.getElementById("clientVersion").textContent = "0.7.0+u1"; document.getElementById("clientVersion").textContent = "0.8.0";
document.getElementById("currentYear").textContent = (new Date()).getFullYear().toString(); document.getElementById("currentYear").textContent = (new Date()).getFullYear().toString();
</script> </script>
</body> </body>

View File

@ -62,6 +62,22 @@ switch(processObj.argv[2]) {
console.log(output); console.log(output);
break; 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": { case "sys": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "icon.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)); 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) { Object.keys(metadata.filenames).forEach(function(file) {
let getFile = filesystem.readFileSync(metadata.filenames[file]); let getFile = filesystem.readFileSync(metadata.filenames[file]);
const output = iconjs.readIconFile(getFile.buffer.slice(getFile.byteOffset, getFile.byteOffset + getFile.byteLength)); 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"); 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. sps: Read a SharkPort export file.
xps: Read a X-Port export file. xps: Read a X-Port export file.
cbs: Read a CodeBreaker Save 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 sys: Read a icon.sys (964 bytes) file, and attempt
to read icon files from the current directory. to read icon files from the current directory.