Seperate module.exports into readers, helpers and options

Also add helpers to exports (uncompress and bgr2rgb functions)
Still stuck on the whole oversaturation thing.

Considering writing something to convert icon objects to a more usable model format.
This commit is contained in:
yellows111 2023-10-24 14:45:55 +01:00
parent d5ec733b07
commit 42286ba7f8
3 changed files with 153 additions and 74 deletions

20
icon.js
View File

@ -1,7 +1,8 @@
//todo: Make this a module/mjs file. C6 compatibility can stay, if needed.
//LOOKING FOR: LZARI implementation (for MAX), description of CBS compression (node zlib doesn't tackle it, even with RC4'ing the data)
ICONJS_DEBUG = false;
ICONJS_STRICT = true;
ICONJS_VERSION = "0.4.2";
ICONJS_VERSION = "0.5.0";
function setDebug(value) {
ICONJS_DEBUG = !!value;
@ -77,6 +78,7 @@ function convertBGR5A1toRGB5A1(bgrData) {
let r = ((bgrData[index] >> 10) & 0b11111);
// rrrrrgggggbbbbba (a = 1 because 0 is 127 which is 1.0f opacity for the GS)
let newValue = 0b0000000000000001;
// mind you, the alpha bit ^ seems to be set randomly on textures, anyway. Maybe i'm reading these wrong?
newValue |= (r << 1);
newValue |= (g << 6);
newValue |= (b << 11);
@ -166,7 +168,8 @@ function readIconFile(input) {
}};
const magic = u32le(0);
if (magic !== 0x010000) {
// USER WARNING: APPARENTLY NOT ALL ICONS ARE 0x010000. THIS THROW WILL BE DROPPED LATER.
// USER NOTICE: So far, I have yet to parse an icon that hasn't had 0x00010000 as it's magic.
// Can someone provide me pointers to such an icon if one exists?
throw `Not a PS2 icon file (was ${magic}, expected ${0x010000})`;
}
const numberOfShapes = u32le(4);
@ -237,6 +240,7 @@ function readIconFile(input) {
case 'U': {
//where every 16-bit entry is a BGR5A1 color 0b[bbbbbgggggrrrrra]
texture = new Uint16Array(input.slice(offset, (offset+0x8000)));
//see convertBGR5A1toRGB5A1() for more info.
break;
}
case 'C': {
@ -257,6 +261,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);
texture = {size, data: input.slice(offset+4, offset+(4+size))};
}
@ -333,7 +338,7 @@ function readEmsPsuFile(input){
offset += ((fdesc.size & 0b11111111110000000000) + 1024);
} else {
offset += fdesc.size;
// if we're already filling sectors fully, no to change anything about it
// if we're already filling 1k blocks fully, why change the value?
}
output[fdesc.filename] = {
size: fdesc.size,
@ -370,7 +375,7 @@ function readPsvFile(input){
const type1 = u32le(56);
const type2 = u32le(60);
if(type1 !== 0x2c && type2 !== 2) {
throw `Not parsing, this is not a PS2 save export (was ${type1}:${type2}, expected 44:2)`;
throw `Not parsing, this is not in the PS2 save export format (was ${type1}:${type2}, expected 44:2)`;
}
const displayedSize = u32le(64);
const ps2dOffset = u32le(68);
@ -509,5 +514,10 @@ function readSharkXPortSxpsFile(input) {
if(typeof module !== "undefined") {
// for C6JS
module.exports = {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, setDebug, setStrictness};
module.exports = {
readers: {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile},
helpers: {uncompressTexture, convertBGR5A1toRGB5A1},
options: {setDebug, setStrictness},
version: ICONJS_VERSION
};
}

105
index.js
View File

@ -1,4 +1,5 @@
const iconjs = require("./icon.js");
const icondumper2 = require("./icon.js");
const iconjs = icondumper2.readers;
const filesystem = require("fs");
const processObj = require("process");
@ -7,45 +8,71 @@ require("util").inspect.defaultOptions.maxArrayLength = 10;
require("util").inspect.defaultOptions.compact = true;
require("util").inspect.defaultOptions.depth = 2;
// debugger
iconjs.setDebug(false);
// output debugging information
icondumper2.options.setDebug(false);
// node.js client
if(processObj.argv[2] === "psu") {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu");
const parsed = iconjs.readEmsPsuFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
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);
} else if(processObj.argv[2] === "psv") {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psv");
const parsed = iconjs.readPsvFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
console.log(parsed);
const PS2D = iconjs.readPS2D(parsed["icon.sys"]);
let output = {parsed, PS2D};
console.log(output);
} else if(processObj.argv[2] === "sps") {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.sps");
const parsed = iconjs.readSharkXPortSxpsFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
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);
} else {
let inputFile = filesystem.readFileSync(processObj.argv[2] ? processObj.argv[2] : "icon.sys");
const metadata = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
console.log("\noutput:", metadata, "\n")
console.log(`icon.js version ${icondumper2.version}, 2023 (c) yellows111`);
switch(processObj.argv[2]) {
case "psu": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu");
const parsed = iconjs.readEmsPsuFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
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 "psv": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psv");
const parsed = iconjs.readPsvFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
console.log(parsed);
const PS2D = iconjs.readPS2D(parsed["icon.sys"]);
let output = {parsed, PS2D};
console.log(output);
break;
}
case "sps":
case "xps": {
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.sps");
const parsed = iconjs.readSharkXPortSxpsFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
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));
console.log("\noutput:", metadata, "\n")
if(processObj.argv.length > 4 && processObj.argv[4].toLowerCase() === "--no-read-models") {break;} else {
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");
});
}
break;
}
default: {
//Template literal goes here.
console.log(
`${(processObj.argv.length > 2) ? "Unknown argument: "+processObj.argv[2]+"\n\n": ""}icondumper2 node.js client subcommands:
psu: Read a EMS Memory Adapter export file.
psv: Read a PS3 export file.
sps: Read a SharkPort export file.
xps: Read a X-Port export file.
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");
});
sys: Read a icon.sys (964 bytes) file, and attempt
to read icon files from the current directory.
(suppress behaviour with --no-read-models)
` ); // end of template
}
}

102
input.htm
View File

@ -28,6 +28,7 @@
attribute vec4 a_color;
uniform float u_rotation;
uniform float u_scale;
uniform highp vec3 u_ambientLight;
//uniform highp vec3 u_lightColorA;
//uniform highp vec3 u_lightColorB;
@ -44,7 +45,7 @@
(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
3.5
u_scale
);
// flip it, scale it
v_textureCoords = a_textureCoords;
@ -72,10 +73,13 @@
mediump vec4 texture_c = texture2D(u_sampler, v_textureCoords);
highp vec3 ambientColorT = (u_ambientLight * vec3(texture_c));
highp vec3 ambientColorV = (u_ambientLight * vec3(v_color));
//This has issues with oversaturation, but it means blended icons work.
//This has issues with oversaturation (JXCR), but it means blended icons work.
//This also makes scaling a bit strange (JXCR, WLK). Why does it change depending on the scale?
if(v_color == vec4(1.0,1.0,1.0,1.0)) {
gl_FragColor = vec4((ambientColorT * ambientColorV), texture_c.a);
} else {
//WLK *SHOULD* follow this path, but doesn't. What can I do to fix this?
//Removing this path makes the models very dark, doing this fixes it, with oversaturation on false positives.
gl_FragColor = vec4((ambientColorT * ambientColorV) * 2.0, texture_c.a);
}
}
@ -113,9 +117,9 @@
<h1 id="title1">&#xFF2E;&#xFF4F;&#x3000;&#xFF26;&#xFF49;&#xFF4C;&#xFF45;</h1>
<h1 id="title2">&#xFF2C;&#xFF4F;&#xFF41;&#xFF44;&#xFF45;&#xFF44;</h1>
</div>
<span>Background/icon preview (rotate: &larr;/&rarr; keys):</span><br>
<canvas id="bgcanvas" width="360" height="360"></canvas>
<canvas id="iconcanvas" width="360" height="360"></canvas>
<span>Background/icon preview (rotate: &larr;/&rarr; keys, scale: &uarr;/&darr; keys):</span><br>
<canvas id="bgcanvas" width="480" height="480"></canvas>
<canvas id="iconcanvas" width="480" height="480"></canvas>
<hr>
<p>Normal: <kbd id="iconn">(no&nbsp;file)<wbr></kbd> Copying: <kbd id="iconc">(no&nbsp;file)<wbr></kbd> Deleting: <kbd id="icond">(no&nbsp;file)</kbd></p>
<div id="advanced">
@ -146,28 +150,52 @@
<span>File comments: </span><span id="fileCommentGame">(no title)</span></span><span> - </span><span id="fileCommentName">(no description)</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}};
// 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"]');
allInputs.forEach(
function(nodeObject) {
nodeObject.onclick = function() {
allInputs.forEach(function(elementObject) {elementObject.value = null;});
}
}
);
// rotation stuff
const rotationDensity = 60;
rotations = 2;
document.body.onkeydown = function(ev) {
if(glBgContext === null) {return;}
if(typeof URotation !== "undefined") {
if(typeof GlobalState.uniforms.rotation !== "undefined") {
switch(ev.code) {
case "ArrowLeft": {
rotations--;
if(rotations < -rotationDensity) {rotations = -1;}
GlobalState.rotations--;
if(GlobalState.rotations < -rotationDensity) {GlobalState.rotations = -1;}
break;
}
case "ArrowRight": {
rotations++;
if(rotations > rotationDensity) {rotations = 1;}
GlobalState.rotations++;
if(GlobalState.rotations > rotationDensity) {GlobalState.rotations = 1;}
break;
}
case "ArrowUp": {
GlobalState.scale -= 0.1;
if(GlobalState.scale <= 2) {GlobalState.scale = 2.0;}
break;
}
case "ArrowDown": {
GlobalState.scale += 0.1;
if(GlobalState.scale >= 6.0) {GlobalState.scale = 6.0;}
break;
}
default: {
return;
}
};
glFgContext.uniform1f(URotation, (rotations/rotationDensity));
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, ULength);
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
glFgContext.uniform1f(GlobalState.uniforms.rotation, (GlobalState.rotations/rotationDensity));
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
} else {return;}
}
// I usually don't do in-body <script>'s, but I didn't want to do an await onload again
function updateDisplay(input) {
document.getElementById("title1").textContent = input.title[0];
document.getElementById("title2").textContent = input.title[1];
@ -254,7 +282,8 @@
};
var uniforms = {
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation"),
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight")
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight"),
scale: glFgContext.getUniformLocation(iconProgram, "u_scale")
}
} else {
var attributes = {
@ -264,7 +293,8 @@
var uniforms = {
sampler: glFgContext.getUniformLocation(iconProgram, "u_sampler"),
rotation: glFgContext.getUniformLocation(iconProgram, "u_rotation"),
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight")
ambientLighting: glFgContext.getUniformLocation(iconProgram, "u_ambientLight"),
scale: glFgContext.getUniformLocation(iconProgram, "u_scale")
}
}
//.section SETUP
@ -309,19 +339,24 @@
//.section ROTATE
// sets the angle uniform to 2/rotationDensity, this puts the icon at an angle.
// globalize uniform rotation
URotation = uniforms.rotation;
rotations = 2;
glFgContext.uniform1f(URotation, rotations/rotationDensity);
GlobalState.uniforms.rotation = uniforms.rotation;
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);
glFgContext.uniform3f(uniforms.ambientLighting, 1, 1, 1);
//.section SCALING
GlobalState.uniforms.scale = uniforms.scale;
GlobalState.scale = 3.5;
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
//.section WRITE
//globalize count of triangles, as well
ULength = (verticesArray.length/3);
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, ULength);
GlobalState.dataLength = (verticesArray.length/3);
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
}
}
document.getElementById("strictnessOption").onchange = function(e) {
@ -358,7 +393,7 @@
try {
let output = readIconFile(d);
renderIcon(output);
console.log("model data",output);
console.log("model data (ic*)",output);
} catch(e) {
if(glFgContext!==null){glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);}
alert(e);
@ -386,7 +421,8 @@
//TODO: use Time() to align JST times to user-local timezone
document.getElementById("dateCreated").textContent = `${cTime.hours.toString().padStart("2","0")}:${cTime.minutes.toString().padStart("2","0")}:${cTime.seconds.toString().padStart("2","0")} ${cTime.day.toString().padStart("2","0")}/${cTime.month.toString().padStart("2","0")}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${mTime.hours.toString().padStart("2","0")}:${mTime.minutes.toString().padStart("2","0")}:${mTime.seconds.toString().padStart("2","0")} ${mTime.day.toString().padStart("2","0")}/${mTime.month.toString().padStart("2","0")}/${mTime.year}`;
console.log("model files", output2);
console.log("model files (psu)", output2);
console.log("icon.sys (psu)", 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);}
@ -405,13 +441,19 @@
let inputData = readPsvFile(d);
let output = readPS2D(inputData["icon.sys"]);
updateDisplay(output);
renderIcon(readIconFile(inputData.icons.n), output);
console.log(readIconFile(inputData.icons.n));
const icons = {
n: readIconFile(inputData.icons.n),
c: readIconFile(inputData.icons.c),
d: readIconFile(inputData.icons.d),
}
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 = `${cTime.hours.toString().padStart("2","0")}:${cTime.minutes.toString().padStart("2","0")}:${cTime.seconds.toString().padStart("2","0")} ${cTime.day.toString().padStart("2","0")}/${cTime.month.toString().padStart("2","0")}/${cTime.year}`;
document.getElementById("dateModified").textContent = `${mTime.hours.toString().padStart("2","0")}:${mTime.minutes.toString().padStart("2","0")}:${mTime.seconds.toString().padStart("2","0")} ${mTime.day.toString().padStart("2","0")}/${mTime.month.toString().padStart("2","0")}/${mTime.year}`;
console.log("model files (psv)", icons);
console.log("icon.sys (psv)", 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);}
@ -442,7 +484,8 @@
document.getElementById("dateModified").textContent = `${mTime.hours.toString().padStart("2","0")}:${mTime.minutes.toString().padStart("2","0")}:${mTime.seconds.toString().padStart("2","0")} ${mTime.day.toString().padStart("2","0")}/${mTime.month.toString().padStart("2","0")}/${mTime.year}`;
document.getElementById("fileCommentGame").textContent = vFilesystem.comments.game;
document.getElementById("fileCommentName").textContent = vFilesystem.comments.name;
console.log("model files", output2);
console.log("model files (*ps)", output2);
console.log("icon.sys (*ps)", 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);}
@ -459,7 +502,7 @@
return shader;
}
console.log(gl.getShaderInfoLog(shader), source);
console.log(gl.getShaderInfoLog(shader), (new String().padStart(60, "-")), source);
gl.deleteShader(shader);
}
function createProgram(gl, vertexShader, fragmentShader) {
@ -509,7 +552,6 @@
}
if(glBgContext !== null) {
//.section CONFIGURATION
glBgContext.enable(glBgContext.DEPTH_TEST);
glFgContext.enable(glFgContext.DEPTH_TEST);
//.section CLEAR
glBgContext.clearColor(0.1,0.1,0.4,1);
@ -523,10 +565,10 @@
document.getElementById("showExtractedInputOption").onchange = function(e) {
document.getElementById("advanced").style.display = ((e.target.checked) ? "block" : "none");
}
//todo: Animation parsing, animation tweening
//todo: More than one model shape rendering, other 2 icons, Animation parsing, animation tweening
</script>
<span id="version">icondumper2 <span id="iconjsVersion">(unknown icon.js version)</span> [C: <span id="clientVersion">Loading...</span>] &mdash; &copy; 2023 yellows111</span>
<script>document.getElementById("iconjsVersion").textContent = ICONJS_VERSION;</script>
<script>document.getElementById("clientVersion").textContent = "0.6.2";</script>
<script>document.getElementById("clientVersion").textContent = "0.6.3";</script>
</body>
</html>