724 lines
32 KiB
HTML
724 lines
32 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<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>
|
|
<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.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 {
|
|
color: yellow;
|
|
text-align: right;
|
|
line-height: 0.50em;
|
|
}
|
|
#version {position: fixed;bottom:4px;right:4px}
|
|
#advanced {display: none}
|
|
#iconcanvas {position: absolute; left: 8px}
|
|
#titlebox {width: 99%;position: absolute;}
|
|
input[type="file"] {line-height: 2em;}
|
|
#version {text-shadow: 1px 1px 2px black;}
|
|
a {color: #ccc;}
|
|
.inputbox {
|
|
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;
|
|
}
|
|
</style>
|
|
<meta data-comment="WebGL Shader: Icon">
|
|
<script type="text/plain" id="shader-icon-v">
|
|
attribute vec3 a_position;
|
|
attribute vec3 a_normal;
|
|
attribute vec2 a_textureCoords;
|
|
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;
|
|
//uniform highp vec3 u_lightColorC;
|
|
|
|
varying lowp vec4 v_color;
|
|
varying lowp vec2 v_textureCoords;
|
|
|
|
void main() {
|
|
float angle = radians(360.0) * u_rotation;
|
|
vec2 pos = vec2(cos(angle), sin(angle));
|
|
// 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
|
|
u_scale
|
|
);
|
|
// flip it, scale it
|
|
v_textureCoords = a_textureCoords;
|
|
v_color = a_color;
|
|
}
|
|
</script>
|
|
<script type="text/plain" id="shader-icon-f">
|
|
varying lowp vec4 v_color;
|
|
|
|
uniform highp vec3 u_ambientLight;
|
|
|
|
void main() {
|
|
highp vec3 ambientColor = (u_ambientLight * vec3(v_color));
|
|
gl_FragColor = vec4(ambientColor, v_color.a);
|
|
}
|
|
</script>
|
|
<script type="text/plain" id="shader-icon-f2">
|
|
varying lowp vec2 v_textureCoords;
|
|
varying lowp vec4 v_color;
|
|
|
|
uniform sampler2D u_sampler;
|
|
uniform highp vec3 u_ambientLight;
|
|
|
|
void main() {
|
|
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 (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);
|
|
}
|
|
}
|
|
</script>
|
|
<meta data-comment="WebGL Shader: Background">
|
|
<script type="text/plain" id="shader-bg-v">
|
|
attribute vec2 a_position;
|
|
attribute vec4 a_color;
|
|
|
|
varying lowp vec4 vColor;
|
|
|
|
void main() {
|
|
// x, y, z, scale (w)
|
|
gl_Position = vec4(a_position, 0.99, 1.0);
|
|
vColor = a_color;
|
|
}
|
|
</script>
|
|
<script type="text/plain" id="shader-bg-f">
|
|
precision lowp float;
|
|
varying lowp vec4 vColor;
|
|
|
|
void main() {
|
|
gl_FragColor = vColor;
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<span><b>Options:</b></span>
|
|
<label for="showExtractedInputOption">Show icon.sys and raw icon file readers.</label>
|
|
<input id="showExtractedInputOption" type="checkbox"></input>
|
|
<hr>
|
|
<div id="titlebox">
|
|
<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>
|
|
<canvas id="bgcanvas" width="480" height="480"></canvas>
|
|
<canvas id="iconcanvas" width="480" height="480"></canvas>
|
|
<hr>
|
|
<p>Normal: <kbd id="iconn">(no file)<wbr></kbd> Copying: <kbd id="iconc">(no file)<wbr></kbd> Deleting: <kbd id="icond">(no file)</kbd></p>
|
|
<div id="advanced">
|
|
<hr>
|
|
<div class="inputbox">
|
|
<label for="input">icon.sys goes here:</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>
|
|
<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>
|
|
<input type="file" id="psuinput" name="psuinput" accept=".psu" />
|
|
</div>
|
|
<div class="inputbox">
|
|
<label for="psvinput">PS3 export file (.psv) goes here:</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>
|
|
<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>
|
|
<input type="file" id="cbsinput" name="cbsinput" accept=".cbs" />
|
|
</div>
|
|
<p>
|
|
<span>Date created: </span><span id="dateCreated">--:--:-- --/--/----</span><span> UTC+09:00</span>
|
|
<wbr><span>–</span>
|
|
<span>Date modified: </span><span id="dateModified">--:--:-- --/--/----</span><span> UTC+09:00</span>
|
|
</p>
|
|
<p>
|
|
<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 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(
|
|
function(nodeObject) {
|
|
nodeObject.onclick = function() {
|
|
Array.from(allInputs).forEach(function(elementObject) {elementObject.value = null;});
|
|
}
|
|
}
|
|
);
|
|
function p0in(input) { // "prefix 0 if needed"
|
|
return ((input.length>=2) ? input : `0${input}`);
|
|
};
|
|
// rotation stuff
|
|
const rotationDensity = 60;
|
|
document.body.onkeydown = function(ev) {
|
|
if(glBgContext === null || GlobalState.iconState.currentIcon === null) {return;}
|
|
if(typeof GlobalState.uniforms.rotation !== "undefined") {
|
|
switch(ev.code) {
|
|
case "ArrowLeft": {
|
|
GlobalState.rotations--;
|
|
if(GlobalState.rotations < -rotationDensity) {GlobalState.rotations = -1;}
|
|
break;
|
|
}
|
|
case "ArrowRight": {
|
|
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;
|
|
}
|
|
case "Minus": {
|
|
GlobalState.iconState.currentSubmodel--;
|
|
if(GlobalState.iconState.currentSubmodel < 0) {
|
|
GlobalState.iconState.currentSubmodel = GlobalState.iconState.currentIcon.numberOfShapes - 1;
|
|
}
|
|
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
|
|
break;
|
|
}
|
|
case "Equal": {
|
|
GlobalState.iconState.currentSubmodel++;
|
|
if(GlobalState.iconState.currentSubmodel > (GlobalState.iconState.currentIcon.numberOfShapes - 1)) {
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
}
|
|
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
|
|
break;
|
|
}
|
|
case "Digit1": {
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
GlobalState.iconState.currentIcon = GlobalState.iconState.source.n;
|
|
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
|
|
break;
|
|
}
|
|
case "Digit2": {
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
GlobalState.iconState.currentIcon = GlobalState.iconState.source.c;
|
|
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
|
|
break;
|
|
}
|
|
case "Digit3": {
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
GlobalState.iconState.currentIcon = GlobalState.iconState.source.d;
|
|
renderIcon(GlobalState.iconState.currentIcon, GlobalState.iconState.cachedIconSys, false);
|
|
break;
|
|
}
|
|
default: {
|
|
return;
|
|
}
|
|
};
|
|
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
|
|
glFgContext.uniform1f(GlobalState.uniforms.rotation, (GlobalState.rotations/rotationDensity));
|
|
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
|
|
} else {return;}
|
|
}
|
|
function updateDisplay(input) {
|
|
document.getElementById("title1").textContent = input.title[0];
|
|
document.getElementById("title2").textContent = input.title[1];
|
|
document.getElementById("iconn").textContent = input.filenames.n;
|
|
document.getElementById("iconc").textContent = input.filenames.c;
|
|
document.getElementById("icond").textContent = input.filenames.d;
|
|
let colours = input.background;
|
|
if(glBgContext !== null) {
|
|
colors = [
|
|
colours.colors[0].r/255,
|
|
colours.colors[0].g/255,
|
|
colours.colors[0].b/255,
|
|
(colours.alpha*2)/255,
|
|
colours.colors[1].r/255,
|
|
colours.colors[1].g/255,
|
|
colours.colors[1].b/255,
|
|
(colours.alpha*2)/255,
|
|
colours.colors[2].r/255,
|
|
colours.colors[2].g/255,
|
|
colours.colors[2].b/255,
|
|
(colours.alpha*2)/255,
|
|
colours.colors[3].r/255,
|
|
colours.colors[3].g/255,
|
|
colours.colors[3].b/255,
|
|
(colours.alpha*2)/255
|
|
];
|
|
drawBackground(colors);
|
|
}
|
|
}
|
|
function resetDisplay() {
|
|
//reset displayed elements
|
|
document.getElementById("title1").textContent = "\uff0d";
|
|
document.getElementById("title2").textContent = "\uff0d";
|
|
document.getElementById("iconn").textContent = "?";
|
|
document.getElementById("iconc").textContent = "?";
|
|
document.getElementById("icond").textContent = "?";
|
|
document.getElementById("dateCreated").textContent = "--:--:-- --/--/----";
|
|
document.getElementById("dateModified").textContent = "--:--:-- --/--/----";
|
|
document.getElementById("fileCommentGame").textContent = "(no title)";
|
|
document.getElementById("fileCommentName").textContent = "(no description)";
|
|
document.getElementById("fileCommentDesc").textContent = "(no other text)";
|
|
//reset globalstate parameters
|
|
GlobalState.iconState.cachedIconSys = null;
|
|
GlobalState.iconState.currentIcon = null;
|
|
GlobalState.iconState.source = null;
|
|
GlobalState.uniforms.rotation = null;
|
|
GlobalState.uniforms.scale = null;
|
|
GlobalState.dataLength = 0;
|
|
GlobalState.rotations = 2;
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
//clear buffers
|
|
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
|
|
}
|
|
function renderIcon(iconData, fileMetadata = null, clearData = true) {
|
|
if(fileMetadata === null) {
|
|
fileMetadata = {
|
|
"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]
|
|
]
|
|
}
|
|
};
|
|
};
|
|
if(glFgContext === null) {return -1;} else {
|
|
const texture = glFgContext.createTexture();
|
|
glFgContext.bindTexture(glFgContext.TEXTURE_2D, texture);
|
|
if (iconData.textureFormat !== "N") {
|
|
let rgb5a1_converted;
|
|
if (iconData.textureFormat === "C") {
|
|
let uncompressed = uncompressTexture(iconData.texture.data);
|
|
rgb5a1_converted = convertBGR5A1toRGB5A1(uncompressed);
|
|
} else {
|
|
rgb5a1_converted = convertBGR5A1toRGB5A1(iconData.texture);
|
|
}
|
|
glFgContext.texImage2D(glFgContext.TEXTURE_2D, 0, glFgContext.RGBA, 128, 128, 0, glFgContext.RGBA, glFgContext.UNSIGNED_SHORT_5_5_5_1, rgb5a1_converted);
|
|
glFgContext.generateMipmap(glFgContext.TEXTURE_2D);
|
|
}
|
|
//.section PROGRAM.icon
|
|
if(iconData.textureFormat !== "N") {
|
|
var iconVertexShader = createShader(glFgContext, glFgContext.VERTEX_SHADER, document.getElementById("shader-icon-v").text);
|
|
var iconFragmentShader = createShader(glFgContext, glFgContext.FRAGMENT_SHADER, document.getElementById("shader-icon-f2").text);
|
|
} else {
|
|
var iconVertexShader = createShader(glFgContext, glFgContext.VERTEX_SHADER, document.getElementById("shader-icon-v").text);
|
|
var iconFragmentShader = createShader(glFgContext, glFgContext.FRAGMENT_SHADER, document.getElementById("shader-icon-f").text);
|
|
}
|
|
let iconProgram = createProgram(glFgContext, iconVertexShader, iconFragmentShader);
|
|
glFgContext.useProgram(iconProgram);
|
|
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")
|
|
}
|
|
}
|
|
//.section SETUP
|
|
let verticesArray = new Array();
|
|
let colourArray = new Array();
|
|
let uvArray = new Array();
|
|
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);
|
|
colourArray.push(vertexObject.color.r/255);
|
|
colourArray.push(vertexObject.color.g/255);
|
|
colourArray.push(vertexObject.color.b/255);
|
|
colourArray.push((vertexObject.color.a > 1) ? (vertexObject.color.a/255): 1);
|
|
uvArray.push(vertexObject.uv.u);
|
|
uvArray.push(vertexObject.uv.v);
|
|
});
|
|
//.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 COLOURS
|
|
const colorBuffer = glFgContext.createBuffer();
|
|
glFgContext.bindBuffer(glFgContext.ARRAY_BUFFER, colorBuffer);
|
|
glFgContext.enableVertexAttribArray(attributes.color);
|
|
glFgContext.bufferData(glFgContext.ARRAY_BUFFER, new Float32Array(colourArray), glFgContext.STATIC_DRAW);
|
|
glFgContext.vertexAttribPointer(attributes.color, 4, glFgContext.FLOAT, false, 0, 0);
|
|
if(iconData.textureFormat !== "N") {
|
|
//.section UV
|
|
const uvBuffer = glFgContext.createBuffer();
|
|
glFgContext.bindBuffer(glFgContext.ARRAY_BUFFER, uvBuffer);
|
|
glFgContext.enableVertexAttribArray(attributes.textureCoords);
|
|
glFgContext.bufferData(glFgContext.ARRAY_BUFFER, new Float32Array(uvArray), glFgContext.STATIC_DRAW);
|
|
glFgContext.vertexAttribPointer(attributes.textureCoords, 2, glFgContext.FLOAT, false, 0, 0);
|
|
//.section TEXTURE
|
|
glFgContext.activeTexture(glFgContext.TEXTURE0);
|
|
glFgContext.bindTexture(glFgContext.TEXTURE_2D, texture);
|
|
glFgContext.uniform1i(uniforms.sampler, 0);
|
|
}
|
|
//.section ROTATE
|
|
// 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;}
|
|
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, 0.75, 0.75, 0.75);
|
|
|
|
//.section SCALING
|
|
GlobalState.uniforms.scale = uniforms.scale;
|
|
if(clearData){GlobalState.scale = 3.5;}
|
|
glFgContext.uniform1f(GlobalState.uniforms.scale, GlobalState.scale);
|
|
|
|
//.section WRITE
|
|
//globalize count of triangles, as well
|
|
GlobalState.dataLength = (verticesArray.length/3);
|
|
glFgContext.drawArrays(glFgContext.TRIANGLES, 0, GlobalState.dataLength);
|
|
}
|
|
}
|
|
filebox = document.getElementById("input");
|
|
filebox.onchange = function(e) {
|
|
resetDisplay();
|
|
if(filebox.files.length === 0) {
|
|
return;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(filebox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let output = readPS2D(GlobalState.fileReader.result);
|
|
console.info("icon.sys", output);
|
|
updateDisplay(output);
|
|
} catch(e) {
|
|
if(glBgContext!==null){glBgContext.clear(glBgContext.COLOR_BUFFER_BIT);}
|
|
console.error(e);
|
|
alert(e);
|
|
}
|
|
}
|
|
}
|
|
iconbox = document.getElementById("icon");
|
|
iconbox.onchange = function(e) {
|
|
if(iconbox.files.length === 0) {
|
|
return;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(iconbox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let output = readIconFile(GlobalState.fileReader.result);
|
|
GlobalState.iconState.cachedIconSys = null;
|
|
GlobalState.iconState.source = null;
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
GlobalState.iconState.currentIcon = output;
|
|
renderIcon(output);
|
|
console.info("model data (ic*)",output);
|
|
} catch(e) {
|
|
if(glFgContext!==null){glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);}
|
|
console.error(e);
|
|
alert(e);
|
|
}
|
|
}
|
|
}
|
|
psubox = document.getElementById("psuinput");
|
|
psubox.onchange = function(e) {
|
|
resetDisplay();
|
|
if(psubox.files.length === 0) {
|
|
return;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(psubox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let vFilesystem = readEmsPsuFile(GlobalState.fileReader.result);
|
|
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);
|
|
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}`;
|
|
console.info("model files (psu)", output2);
|
|
console.info("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);}
|
|
console.error(e);
|
|
alert(e);
|
|
}
|
|
}
|
|
}
|
|
psvbox = document.getElementById("psvinput");
|
|
psvbox.onchange = function(e) {
|
|
resetDisplay();
|
|
if(psvbox.files.length === 0) {
|
|
return;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(psvbox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let inputData = readPsvFile(GlobalState.fileReader.result);
|
|
let output = readPS2D(inputData["icon.sys"]);
|
|
updateDisplay(output);
|
|
const icons = {
|
|
n: readIconFile(inputData.icons.n),
|
|
c: readIconFile(inputData.icons.c),
|
|
d: readIconFile(inputData.icons.d),
|
|
}
|
|
GlobalState.iconState.cachedIconSys = output;
|
|
GlobalState.iconState.currentSubmodel = 0;
|
|
GlobalState.iconState.source = icons;
|
|
GlobalState.iconState.currentIcon = icons.n;
|
|
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}`;
|
|
console.info("model files (psv)", icons);
|
|
console.info("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);}
|
|
console.error(e);
|
|
alert(e);
|
|
}
|
|
}
|
|
}
|
|
spsbox = document.getElementById("spsinput");
|
|
spsbox.onchange = function(e) {
|
|
resetDisplay();
|
|
if(spsbox.files.length === 0) {
|
|
return;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(spsbox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let vFilesystem = readSharkXPortSxpsFile(GlobalState.fileReader.result);
|
|
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);
|
|
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}`;
|
|
document.getElementById("fileCommentGame").textContent = vFilesystem.comments.game;
|
|
document.getElementById("fileCommentName").textContent = vFilesystem.comments.name;
|
|
if(vFilesystem.comments.hasOwnProperty("desc")) {
|
|
document.getElementById("fileCommentDesc").textContent = vFilesystem.comments.desc;
|
|
}
|
|
console.info("model files (*ps)", output2);
|
|
console.info("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);}
|
|
console.error(e);
|
|
alert(e);
|
|
}
|
|
};
|
|
}
|
|
cbsbox = document.getElementById("cbsinput");
|
|
cbsbox.onchange = function(e) {
|
|
resetDisplay();
|
|
if(cbsbox.files.length === 0) {
|
|
return;
|
|
}
|
|
function inflator(data) {
|
|
return (pako.inflate(data)).buffer;
|
|
}
|
|
GlobalState.fileReader.readAsArrayBuffer(cbsbox.files[0]);
|
|
GlobalState.fileReader.onloadend = function() {
|
|
GlobalState.fileReader.onloadend = void(0);
|
|
try {
|
|
let vFilesystem = readCodeBreakerCbsFile(GlobalState.fileReader.result, inflator);
|
|
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);
|
|
let cTime = vFilesystem.timestamps.created;
|
|
let mTime = vFilesystem.timestamps.modified;
|
|
//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()}`;
|
|
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);
|
|
gl.compileShader(shader);
|
|
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
if (success) {
|
|
return shader;
|
|
}
|
|
|
|
console.debug(gl.getShaderInfoLog(shader), (new String().padStart(60, "-")), source);
|
|
gl.deleteShader(shader);
|
|
}
|
|
function createProgram(gl, vertexShader, fragmentShader) {
|
|
let program = gl.createProgram();
|
|
gl.attachShader(program, vertexShader);
|
|
gl.attachShader(program, fragmentShader);
|
|
gl.linkProgram(program);
|
|
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
if (success) {
|
|
return program;
|
|
}
|
|
|
|
console.debug(gl.getProgramInfoLog(program));
|
|
gl.deleteProgram(program);
|
|
}
|
|
const bgCanvas = document.getElementById("bgcanvas");
|
|
const glBgContext = bgCanvas.getContext("webgl");
|
|
const fgCanvas = document.getElementById("iconcanvas");
|
|
const glFgContext = fgCanvas.getContext("webgl");
|
|
function drawBackground(colorInput) {
|
|
//.section PROGRAM.background
|
|
let backgroundVertexShader = createShader(glBgContext, glBgContext.VERTEX_SHADER, document.getElementById("shader-bg-v").text);
|
|
let backgroundFragmentShader = createShader(glBgContext, glBgContext.FRAGMENT_SHADER, document.getElementById("shader-bg-f").text);
|
|
let backgroundProgram = createProgram(glBgContext, backgroundVertexShader, backgroundFragmentShader);
|
|
glBgContext.useProgram(backgroundProgram);
|
|
let attributes = {
|
|
position: glBgContext.getAttribLocation(backgroundProgram, "a_position"),
|
|
color: glBgContext.getAttribLocation(backgroundProgram, "a_color")
|
|
};
|
|
//.section POSITION
|
|
const positions = [-1,1, 1,1, -1,-1, 1,-1];
|
|
const positionBuffer = glBgContext.createBuffer();
|
|
glBgContext.bindBuffer(glBgContext.ARRAY_BUFFER, positionBuffer);
|
|
glBgContext.enableVertexAttribArray(attributes.position);
|
|
glBgContext.bufferData(glBgContext.ARRAY_BUFFER, new Float32Array(positions), glBgContext.STATIC_DRAW);
|
|
glBgContext.vertexAttribPointer(attributes.position, 2, glBgContext.FLOAT, false, 0, 0);
|
|
//.section COLOR
|
|
const colors = (Array.isArray(colorInput)) ? colorInput : [1,0,0,1, 0,1,0,1, 0,0,1,1, 1,1,1,1];
|
|
const colorBuffer = glBgContext.createBuffer();
|
|
glBgContext.bindBuffer(glBgContext.ARRAY_BUFFER, colorBuffer);
|
|
glBgContext.enableVertexAttribArray(attributes.color);
|
|
glBgContext.bufferData(glBgContext.ARRAY_BUFFER, new Float32Array(colors), glBgContext.STATIC_DRAW);
|
|
glBgContext.vertexAttribPointer(attributes.color, 4, glBgContext.FLOAT, false, 0, 0);
|
|
//.section WRITE
|
|
glBgContext.drawArrays(glBgContext.TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
if(glBgContext !== null) {
|
|
//.section CONFIGURATION
|
|
glFgContext.enable(glFgContext.DEPTH_TEST);
|
|
glFgContext.enable(glFgContext.CULL_FACE);
|
|
glFgContext.cullFace(glFgContext.BACK);
|
|
//.section CLEAR
|
|
glBgContext.clearColor(0.1,0.1,0.4,1);
|
|
glBgContext.clear(glBgContext.COLOR_BUFFER_BIT | glBgContext.DEPTH_BUFFER_BIT);
|
|
glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);
|
|
drawBackground();
|
|
} else {
|
|
bgCanvas.style.display = "none";
|
|
fgCanvas.style.display = "none";
|
|
}
|
|
document.getElementById("showExtractedInputOption").onchange = function(e) {
|
|
document.getElementById("advanced").style.display = ((e.target.checked) ? "block" : "none");
|
|
}
|
|
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
|
|
</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+u1";
|
|
document.getElementById("currentYear").textContent = (new Date()).getFullYear().toString();
|
|
</script>
|
|
</body>
|
|
</html>
|