0.8.0 (actually) forgot to -a
This commit is contained in:
parent
eb8bc8e068
commit
23ef7c7ca1
|
@ -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
|
||||
|
||||
|
|
|
@ -20,6 +20,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 +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.
|
||||
* 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.
|
||||
|
@ -56,6 +58,7 @@ Because it replaced what *was* left of icondumper (1).
|
|||
| 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. |
|
||||
|
||||
## Included example files:
|
||||
| Directory | Description | Formats |
|
||||
|
|
|
@ -322,6 +322,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 +369,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.
|
||||
|
|
97
icon.js
97
icon.js
|
@ -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.
|
||||
//LOOKING FOR: LZARI implementation (for MAX and PWS files. THIS WILL COMPLETE ICONDUMPER2.)
|
||||
var ICONJS_DEBUG = false;
|
||||
var ICONJS_STRICT = true;
|
||||
|
||||
|
@ -8,7 +7,7 @@ var ICONJS_STRICT = true;
|
|||
* @constant {string}
|
||||
* @default
|
||||
*/
|
||||
const ICONJS_VERSION = "0.7.0";
|
||||
const ICONJS_VERSION = "0.8.0";
|
||||
|
||||
/**
|
||||
* The RC4 key used for ciphering CodeBreaker Saves.
|
||||
|
@ -488,7 +487,7 @@ 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();
|
||||
|
@ -637,7 +636,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);
|
||||
|
@ -756,19 +755,95 @@ function readCodeBreakerCbsFile(input, inflator = null) {
|
|||
const inflatedData = inflator(decipheredData);
|
||||
const fsOut = {rootDirectory: dirName, timestamps};
|
||||
fsOut[dirName] = readCodeBreakerCbsDirectory(inflatedData);
|
||||
if(ICONJS_DEBUG) {
|
||||
console.debug({magic, dataOffset, compressedSize, dirName, permissions, displayName});
|
||||
}
|
||||
return fsOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Max Drive (MAX) or PowerSave (PWS) file's directory structure.
|
||||
* @param {ArrayBuffer} input - Uncompressed input
|
||||
* @returns {Object} (user didn't write a description)
|
||||
* @protected
|
||||
*/
|
||||
function readMaxPwsDirectory(input, directorySize) {
|
||||
const {u32le} = new yellowDataReader(input);
|
||||
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.
|
||||
* @exports icondumper2/icon
|
||||
*/ // start c6js
|
||||
exports = {
|
||||
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile},
|
||||
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
|
||||
options: {setDebug, setStrictness},
|
||||
version: ICONJS_VERSION
|
||||
};
|
||||
if(typeof exports !== "object") {
|
||||
exports = {
|
||||
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, readCodeBreakerCbsFile, readMaxPwsFile},
|
||||
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
|
||||
options: {setDebug, setStrictness},
|
||||
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") {
|
||||
module.exports = exports;
|
||||
|
@ -776,6 +851,6 @@ if(typeof module !== "undefined") {
|
|||
//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
|
63
index.htm
63
index.htm
|
@ -6,6 +6,7 @@
|
|||
<meta name="description" content="A HTML client for icondumper2"></meta>
|
||||
<title>icondumper2 - HTML reference client</title>
|
||||
<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 -->
|
||||
<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>
|
||||
|
@ -33,6 +34,9 @@
|
|||
.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">
|
||||
|
@ -137,31 +141,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 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" />
|
||||
</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 last-input">
|
||||
<label for="cbsinput">CodeBreaker Save export file (.cbs) goes here:</label>
|
||||
<div class="inputbox">
|
||||
<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>
|
||||
|
@ -305,7 +313,9 @@
|
|||
GlobalState.rotations = 2;
|
||||
GlobalState.iconState.currentSubmodel = 0;
|
||||
//clear buffers
|
||||
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
|
||||
if(glFgContext !== null) {
|
||||
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
function renderIcon(iconData, fileMetadata = null, clearData = true) {
|
||||
if(fileMetadata === null) {
|
||||
|
@ -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) {
|
||||
let shader = gl.createShader(type);
|
||||
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>] — © <span id="currentYear">2023</span> yellows111</span>
|
||||
<script>
|
||||
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();
|
||||
</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.
|
||||
|
|
Loading…
Reference in New Issue