diff --git a/gltf-exporter.js b/gltf-exporter.js index 6a1899c..ec97293 100644 --- a/gltf-exporter.js +++ b/gltf-exporter.js @@ -11,19 +11,36 @@ const gltfConstants = { "REPEAT": 10497 }; +function getCrc32(data) { + let output = -1; + for (let byteIndex of data) { + for (let index = 0; index < 8; index++, byteIndex >>>= 1) { + output = (output >>> 1) ^ (-((output ^ byteIndex) & 1) & 0xEDB88320); + } // 0xEDB88320 is a reverse polynomial that's common in crc32 + } //i know it's some bitwise madness, it works, either way. + return ((~output) >>> 0); +} + +function getAdler32(data) { + let s1 = 1; + let s2 = 0; + for (let index of data) { + s1 = (s1 + index) % 65521; + s2 = (s2 + s1) % 65521; + } + return (s2 << 16) | s1; +} + function rgb5a1_rgb8(colour) { - let b = ( colour & 0b11111); + let b = ( colour & 0b11111); let g = ((colour >> 5) & 0b11111); let r = ((colour >> 10) & 0b11111); - //let a = ( color >> 15 ); let output = new Number(); output |= ((r * 8) << 16); - output |= ((g * 8) << 8); - output |= ((b * 8) << 0); - output *= 256; - output += (255); // couldn't do a |= as that converts it to signed, (a+1)*255 + output |= ((g * 8) << 8); + output |= ((b * 8) << 0); return output; -} // TODO: support textures :P +} function imf2gltf(icon = null, filename = "untitled") { if (icon === null) { @@ -83,15 +100,10 @@ function imf2gltf(icon = null, filename = "untitled") { gltfOutput.materials = [{ "name": `Material (${filename}#${index})`, "pbrMetallicRoughness": { - "baseColorFactor": [1.0, 1.0, 1.0, 1.0], - "metallicFactor": 0.0, - "roughnessFactor": 1.0 + "baseColorTexture": {"index":0, "texCoord": 0} }, - "extensions": { // or else artifacts. - "KHR_materials_specular": { - "specularFactor": 0, // this value is less respected then sCF, blender/glTF is main example of this. - "specularColorFactor": [0, 0, 0] - } + "extensions": { // or we get annoying PBR and specular stuff we don't need + "KHR_materials_unlit": {} } }]; gltfOutput.buffers = [{"uri": `${filename}.bin`, "byteLength": outputFloatArray.byteLength}]; @@ -146,7 +158,7 @@ function imf2gltf(icon = null, filename = "untitled") { "count": icon.vertices.length, "type": "VEC2", "max": [ 1.0, 1.0], - "min": [ 0.0, 0.0], + "min": [-1.0, -1.0], "name": "Texture Coordinate Accessor" }, { @@ -160,10 +172,77 @@ function imf2gltf(icon = null, filename = "untitled") { } ]; gltfOutput.asset = {"version": "2.0", "generator": `icondumper2/${icondumper2.version}`} - gltfOutput.extensionsUsed = ["KHR_materials_specular"]; + gltfOutput.extensionsUsed = ["KHR_materials_unlit"]; + gltfOutput.textures = [{"source": 0}]; + gltfOutput.images = [{"name": `Texture (${filename}#${index})`, "uri": `${filename}.png`}] gltfOutputArray[index] = (gltfOutput); } - return {objects: gltfOutputArray, buffer: outputFloatArray}; + let texture16 = null; // Uint16Array(16384) + switch(icon.textureFormat) { + case "N": { + texture16 = (new Uint16Array(16384)).fill(0xffff); + break; + } + case "C": { + texture16 = icondumper2.helpers.uncompressTexture(icon.texture.data); + break; + } + case "U": { + texture16 = icon.texture; + break; + } + } + let texture24 = new Uint8Array(49983); + texture24.set([ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x02, 0x00, 0x00, 0x00, // you may know + 0x4c, 0x5c, 0xf6, 0x9c, // what this is from 0x89. + 0x00, 0x00, 0xc3, 0x06, 0x49, 0x44, 0x41, 0x54, + 0x78, 0x01 // if you didn't get it, here's a clue + ],0); + let textureOffset = 43; + let texture24Data = new Array(); + let texture24CheckedData = new Array(); + for (let x = 0; x < 128; x++) { + let line = [(x === 127 ? 1 : 0), 0x81, 0x01, 0x7e, 0xfe, 0x00]; + texture24Data = texture24Data.concat(line); + texture24CheckedData.push(0); + let scanline = new Array(128*3); + for (let y = 0; y < 128; y++) { + color = rgb5a1_rgb8(texture16[(x*128)+y]); + scanline[(y*3) ] = ((color >> 0 ) & 255); + scanline[(y*3)+1] = ((color >> 8 ) & 255); + scanline[(y*3)+2] = ((color >> 16) & 255); + } + texture24Data = texture24Data.concat(scanline); + texture24CheckedData = texture24CheckedData.concat(scanline); + } + texture24.set(texture24Data, textureOffset); + textureOffset += texture24Data.length; + let a32conv = new DataView(new ArrayBuffer(4)); + a32conv.setInt32(0, getAdler32(new Uint8Array(texture24CheckedData))) + texture24.set([a32conv.getUint8(0), a32conv.getUint8(1), a32conv.getUint8(2), a32conv.getUint8(3)], textureOffset); + textureOffset += 4; + let crc32 = getCrc32(new Uint8Array([ + 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, ...texture24Data, + a32conv.getUint8(0), a32conv.getUint8(1), + a32conv.getUint8(2), a32conv.getUint8(3) + ])); + texture24.set([ + (crc32 >> 24) & 0xff, + (crc32 >> 16) & 0xff, + (crc32 >> 8) & 0xff, + crc32 & 0xff + ], textureOffset); + textureOffset += 4; + texture24.set([ + 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, + 0xae, 0x42, 0x60, 0x82 + ], textureOffset); + return {objects: gltfOutputArray, buffer: outputFloatArray, texture: texture24}; } function loadAndConvertIcon(inputData, attemptedFilename = "-") { @@ -174,14 +253,17 @@ function loadAndConvertIcon(inputData, attemptedFilename = "-") { const glTF_output = imf2gltf(inputData, filename); for (let index = 0; index < (inputData.numberOfShapes); index++) { (require("fs")).writeFileSync(`${filename}_${index}.gltf`, new TextEncoder().encode(JSON.stringify(glTF_output.objects[index]))); - console.log(`Saved shape ${filename}#${index} as "${filename}_${index}.gltf".`); + console.info(`Saved shape ${filename}#${index} as "${filename}_${index}.gltf".`); } (require("fs")).writeFileSync(`${filename}.bin`, glTF_output.buffer); - console.log(`Saved glTF buffer as "${filename}.bin".\n`); + console.info(`Saved glTF buffer as "${filename}.bin".`); + + (require("fs")).writeFileSync(`${filename}.png`, glTF_output.texture); + console.info(`Saved texture as "${filename}.png".\n`); } // can anything de-dupe this code somehow? (index.js) -console.log(`icon.js version ${icondumper2.version}, 2023 (c) yellows111`); +console.info(`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"); @@ -241,7 +323,7 @@ switch(processObj.argv[2]) { } default: { //Template literal goes here. - console.log( + console.info( `${(processObj.argv.length > 2) ? "Unknown argument: "+processObj.argv[2]+"\n\n": ""}icondumper2 node.js client (glTF exporter version) subcommands: psu: Read a EMS Memory Adapter export file. psv: Read a PS3 export file. diff --git a/icon.js b/icon.js index 67ccfac..597f567 100644 --- a/icon.js +++ b/icon.js @@ -2,7 +2,7 @@ //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.5.0"; +ICONJS_VERSION = "0.5.1"; function setDebug(value) { ICONJS_DEBUG = !!value; @@ -148,7 +148,7 @@ function readPS2D(input) { d: stringScrubber((new TextDecoder("utf-8")).decode(int_filename_d)) } if(ICONJS_DEBUG){ - console.log({header, titleOffset, bgAlpha, bgColors, lightIndices, lightColors, title, filenames}); + console.debug({header, titleOffset, bgAlpha, bgColors, lightIndices, lightColors, title, filenames}); } return {filenames, title, background: {colors: bgColors, alpha: bgAlpha}, lighting: {points: lightIndices, colors: lightColors}}; } @@ -267,7 +267,7 @@ function readIconFile(input) { } } if(ICONJS_DEBUG){ - console.log({magic, numberOfShapes, textureType, textureFormat, numberOfVertexes, chunkLength, vertices, animationHeader, animData, texture}); + console.debug({magic, numberOfShapes, textureType, textureFormat, numberOfVertexes, chunkLength, vertices, animationHeader, animData, texture}); } return {numberOfShapes, vertices, textureFormat, texture, animData}; } @@ -307,7 +307,7 @@ function readEntryBlock(input) { const int_filename = input.slice(0x40, 512); const filename = stringScrubber((new TextDecoder("utf-8")).decode(int_filename)); if(ICONJS_DEBUG){ - console.log({permissions, type, size, createdTime, sectorOffset, dirEntry, modifiedTime, specialSection, filename}); + console.debug({permissions, type, size, createdTime, sectorOffset, dirEntry, modifiedTime, specialSection, filename}); } return {type, size, filename, createdTime, modifiedTime}; } @@ -330,7 +330,7 @@ function readEmsPsuFile(input){ } case "file": { if(ICONJS_DEBUG){ - console.log(`PARSING | F: "${fdesc.filename}" O: ${offset} S: ${fdesc.size}`); + console.debug(`PARSING | F: "${fdesc.filename}" O: ${offset} S: ${fdesc.size}`); } offset += 512; const originalOffset = offset; @@ -404,7 +404,7 @@ function readPsvFile(input){ d: input.slice(dModelOffset, dModelOffset+dModelSize), } if (ICONJS_DEBUG) { - console.log({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}; } @@ -449,7 +449,7 @@ function readSxpsDescriptor(input) { const int_shiftjisName = input.slice(178, 242); // Because why parse a PS2D when you can hard-code it? //:skip 8 if(ICONJS_DEBUG) { - console.log({int_filename, size, startSector, endSector, permissions, type, timestamps, int_asciiName, int_shiftjisName}); + console.debug({int_filename, size, startSector, endSector, permissions, type, timestamps, int_asciiName, int_shiftjisName}); } return {type, size, filename, timestamps}; } @@ -495,7 +495,7 @@ function readSharkXPortSxpsFile(input) { } case "file": { if(ICONJS_DEBUG){ - console.log(`PARSING | F: "${fdesc.filename}" O: ${offset} S: ${fdesc.size}`); + console.debug(`PARSING | F: "${fdesc.filename}" O: ${offset} S: ${fdesc.size}`); } offset += 250; output[fdesc.filename] = { diff --git a/input.htm b/input.htm index 1a4c1bc..a8507ba 100644 --- a/input.htm +++ b/input.htm @@ -376,7 +376,7 @@ filebox.files[0].arrayBuffer().then(function(d){ try { let output = readPS2D(d); - console.log("icon.sys", output); + console.info("icon.sys", output); updateDisplay(output); } catch(e) { if(glBgContext!==null){glBgContext.clear(glBgContext.COLOR_BUFFER_BIT);} @@ -393,7 +393,7 @@ try { let output = readIconFile(d); renderIcon(output); - console.log("model data (ic*)",output); + console.info("model data (ic*)",output); } catch(e) { if(glFgContext!==null){glFgContext.clear(glFgContext.COLOR_BUFFER_BIT | glFgContext.DEPTH_BUFFER_BIT);} alert(e); @@ -421,8 +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 (psu)", output2); - console.log("icon.sys (psu)", output); + 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);} @@ -452,8 +452,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 (psv)", icons); - console.log("icon.sys (psv)", output); + 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);} @@ -484,8 +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 (*ps)", output2); - console.log("icon.sys (*ps)", output); + 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);} @@ -502,7 +502,7 @@ return shader; } - console.log(gl.getShaderInfoLog(shader), (new String().padStart(60, "-")), source); + console.debug(gl.getShaderInfoLog(shader), (new String().padStart(60, "-")), source); gl.deleteShader(shader); } function createProgram(gl, vertexShader, fragmentShader) { @@ -515,7 +515,7 @@ return program; } - console.log(gl.getProgramInfoLog(program)); + console.debug(gl.getProgramInfoLog(program)); gl.deleteProgram(program); } const bgCanvas = document.getElementById("bgcanvas"); @@ -553,6 +553,8 @@ 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); @@ -569,6 +571,6 @@ icondumper2 (unknown icon.js version) [C: Loading...] — © 2023 yellows111 - + \ No newline at end of file