diff --git a/fixtures/mem.bin b/fixtures/mem.bin new file mode 100644 index 0000000..cdf897a Binary files /dev/null and b/fixtures/mem.bin differ diff --git a/index.ts b/index.ts index 31fcc42..77ea792 100644 --- a/index.ts +++ b/index.ts @@ -14,10 +14,10 @@ encodeTexture( encodeModel( "PL00P010.BIN", // Feet - "obj/12_RIGHT_FOOT.obj", - "obj/15_LEFT_FOOT.obj", + "miku/12_RIGHT_FOOT.obj", + "miku/15_LEFT_FOOT.obj", // Head - "obj/01_HEAD_HELMET.obj", + "miku/01_HEAD_HAIR.obj", ); encodeRom(); diff --git a/miku/41_BUSTER.obj b/miku/41_BUSTER.obj new file mode 100644 index 0000000..cd15fcd --- /dev/null +++ b/miku/41_BUSTER.obj @@ -0,0 +1,143 @@ +# Blender v3.6.5 OBJ File: 'Megaman_Hatsune_Master05.blend' +# www.blender.org +mtllib 41_BUSTER.mtl +o 08_LEFT_ARM.001_08_LEFT_ARM.004 +v 0.083398 -0.000229 0.006083 +v 0.007347 -0.000014 0.089642 +v 0.007347 -0.000014 -0.073622 +v -0.069618 0.000204 0.006083 +v 0.165299 -0.345235 0.120119 +v 0.165299 -0.345235 -0.107369 +v -0.149938 -0.344341 0.006083 +v 0.066826 -0.256078 0.104322 +v -0.043633 -0.395247 0.006338 +v 0.048177 -0.395507 -0.048387 +v 0.067723 -0.256080 -0.083779 +v -0.079895 -0.255662 0.004969 +v 0.047624 -0.395505 0.067579 +v -0.006182 -0.344784 -0.175640 +v -0.006182 -0.344784 0.187286 +v 0.111183 -0.172699 0.050882 +v 0.008146 -0.172407 0.112265 +v 0.008146 -0.172407 -0.108120 +v 0.110160 -0.172696 -0.045896 +v -0.094089 -0.172117 0.005901 +vt 0.043490 0.738571 +vt 0.004565 0.701218 +vt 0.004399 0.662437 +vt 0.080544 0.662437 +vt 0.080689 0.701218 +vt 0.212667 0.236750 +vt 0.212667 0.206552 +vt 0.234812 0.206552 +vt 0.236509 0.236750 +vt 0.217796 0.226907 +vt 0.217796 0.257104 +vt 0.193954 0.257104 +vt 0.192256 0.226906 +vt 0.173507 0.257104 +vt 0.173507 0.226906 +vt 0.159971 0.257542 +vt 0.158964 0.257542 +vt 0.158964 0.227344 +vt 0.180130 0.236750 +vt 0.180130 0.206552 +vt 0.207307 0.206552 +vt 0.203593 0.236750 +vt 0.165988 0.257104 +vt 0.158184 0.257104 +vt 0.158184 0.226906 +vt 0.169703 0.226906 +vt 0.656250 0.554687 +vt 0.755364 0.554688 +vt 0.828125 0.554688 +vt 0.904761 0.554688 +vt 0.632722 0.262504 +vt 0.629854 0.260025 +vt 0.636074 0.260141 +vt 0.799246 0.263599 +vt 0.802599 0.261236 +vt 0.796378 0.261120 +vt 0.163168 0.278084 +vt 0.239311 0.280376 +vt 0.219613 0.209091 +vt 0.172670 0.207678 +vt 0.161356 0.254707 +vt 0.171553 0.207609 +vt 0.202963 0.206552 +vt 0.220074 0.206552 +vt 0.220074 0.236750 +vt 0.199248 0.236750 +vt 0.175087 0.226906 +vt 0.175087 0.257104 +vt 0.160379 0.257104 +vt 0.160379 0.226906 +vt 0.197849 0.206552 +vt 0.218987 0.206552 +vt 0.218987 0.236750 +vt 0.208418 0.236750 +vt 0.184845 0.206552 +vt 0.206989 0.206552 +vt 0.206989 0.236750 +vt 0.186543 0.236750 +vn -0.0028 -1.0000 -0.0000 +vn 0.8771 0.1270 -0.4632 +vn 0.1554 0.3563 -0.9214 +vn 0.0940 0.3395 -0.9359 +vn 0.2079 0.4614 -0.8625 +vn 0.8751 0.3437 -0.3406 +vn 0.8431 0.3061 -0.4422 +vn -0.6763 0.2172 -0.7039 +vn -0.7841 0.3543 -0.5096 +vn -0.6845 0.1785 -0.7068 +vn -0.6865 0.1843 -0.7034 +vn 0.9691 0.2468 0.0046 +vn 0.9713 0.2354 -0.0356 +vn 0.9858 0.1677 -0.0100 +vn -0.6525 0.2189 0.7255 +vn -0.7405 0.3606 0.5671 +vn -0.7559 0.3430 0.5576 +vn -0.7842 0.2012 0.5869 +vn 0.4001 0.2028 0.8938 +vn 0.4266 0.1664 0.8890 +vn 0.8092 0.2997 0.5054 +vn 0.4928 0.0988 0.8645 +vn 0.4681 0.8505 -0.2400 +vn -0.3031 0.9301 0.2076 +vn 0.1100 0.9938 -0.0182 +vn -0.1139 0.9935 0.0088 +vn 0.3745 -0.6573 -0.6540 +vn 0.3521 -0.6581 0.6655 +vn 0.0028 1.0000 0.0000 +vn 0.4173 0.4480 0.7907 +vn 0.4405 0.4515 -0.7760 +vn -0.9666 -0.2548 -0.0271 +vn -0.9667 -0.2548 -0.0243 +vn 0.1934 0.4551 0.8692 +vn 0.2121 0.4481 0.8685 +vn -0.6653 0.0284 0.7461 +vn -0.6571 0.0267 0.7533 +vn 0.9625 0.2713 -0.0016 +vn 0.9625 0.2712 -0.0028 +vn -0.7722 0.1599 -0.6150 +vn -0.8216 0.3546 -0.4464 +usemtl m0.037 +s off +s 1 +f 19/6/2 6/7/3 14/8/4 18/9/5 +f 1/10/6 19/11/2 18/12/5 3/13/7 +usemtl m0.038 +f 18/12/8 20/14/9 4/15/10 3/13/11 +f 16/16/12 19/17/13 1/18/14 +f 20/19/15 7/20/16 15/21/17 17/22/18 +f 17/23/19 16/24/20 1/25/21 2/26/22 +f 1/27/23 3/28/24 4/29/25 2/30/26 +f 9/31/1 10/32/27 13/33/28 +f 12/34/29 8/35/30 11/36/31 +f 8/37/30 12/38/32 9/39/33 13/40/28 +f 11/41/31 8/37/30 13/40/28 10/42/27 +f 12/38/32 11/41/31 10/42/27 9/39/33 +f 15/43/34 5/44/35 16/45/20 17/46/19 +f 4/47/36 20/48/15 17/49/18 2/50/37 +f 5/51/38 6/52/39 19/53/13 16/54/12 +f 14/55/40 7/56/41 20/57/9 18/58/8 diff --git a/src/EncodeModel.ts b/src/EncodeModel.ts index 654358f..f09e1a3 100644 --- a/src/EncodeModel.ts +++ b/src/EncodeModel.ts @@ -1,22 +1,10 @@ import { readFileSync, writeFileSync } from "fs"; -import { Vector3, Matrix4, RGBA_ASTC_12x10_Format } from "three"; - -type Block = { - start: number; - end: number; - used: number; -}; +import { Vector3, Matrix4 } from "three"; +import ByteReader from "./ByteReader"; type PackBuffer = { dataOfs: number; // Where to write the offset data: Buffer; // The data to be packed - blockIndex: number; // The block the data is packed - offset: number; // The offset of the packed data -}; - -type Combination = { - buffers: PackBuffer[]; - remainingSpace: number; }; type Primitive = { @@ -25,16 +13,6 @@ type Primitive = { vertices: Buffer; }; -// Start Packing the blocks -const blocks: Block[] = [ - { start: 0x110, end: 0xb60, used: 0 }, - { start: 0xba8, end: 0x1800, used: 0 }, - { start: 0x1830, end: 0x1dd0, used: 0 }, - { start: 0x1e18, end: 0x2220, used: 0 }, - { start: 0x2268, end: 0x26f0, used: 0 }, - { start: 0x2738, end: 0x2b40, used: 0 }, -]; - const encodeVertexBits = (num: number) => { if (num < 0) { const lowBits = 512 + num; @@ -305,79 +283,6 @@ const encodeMesh = (obj: string, materialIndex: number): Primitive => { }; }; -// Function to generate all combinations of buffers that can fit into a block -function generateCombinations( - buffers: PackBuffer[], - remainingSpace: number, -): Combination[] { - const combinations: Combination[] = []; - - function recurse( - currentCombination: PackBuffer[], - startIndex: number, - spaceLeft: number, - ) { - combinations.push({ - buffers: currentCombination, - remainingSpace: spaceLeft, - }); - - for (let i = startIndex; i < buffers.length; i++) { - const buffer = buffers[i]; - if (buffer.data.length <= spaceLeft) { - recurse( - [...currentCombination, buffer], - i + 1, - spaceLeft - buffer.data.length, - ); - } - } - } - - recurse([], 0, remainingSpace); - return combinations; -} - -// Function to pack buffers into blocks using the optimal combination strategy -function packBuffers(buffers: PackBuffer[]): PackBuffer[] { - // Sort buffers by size in descending order - const sortedBuffers = [...buffers].sort( - (a, b) => b.data.length - a.data.length, - ); - console.log(buffers); - - for (const [blockIndex, block] of blocks.entries()) { - const blockSize = block.end - block.start + 1; - const combinations = generateCombinations(sortedBuffers, blockSize); - - // Find the combination with the least remaining space - const bestCombination = combinations.reduce((best, current) => { - return current.remainingSpace < best.remainingSpace ? current : best; - }); - - // Allocate the buffers from the best combination - for (const buffer of bestCombination.buffers) { - const offset = block.start + block.used; - buffer.blockIndex = blockIndex; - buffer.offset = offset; - block.used += buffer.data.length; - - // Remove the allocated buffer from the sorted list - const bufferIndex = sortedBuffers.findIndex( - (b) => b.dataOfs === buffer.dataOfs, - ); - if (bufferIndex !== -1) { - sortedBuffers.splice(bufferIndex, 1); - } - } - } - - console.log(buffers); - return buffers.sort( - (a, b) => a.blockIndex - b.blockIndex || a.offset - b.offset, - ); -} - const encodeModel = ( // Filename to replace filename: string, @@ -387,22 +292,19 @@ const encodeModel = ( // Head hairObject: string, ) => { + // Grab the Source + const src = readFileSync(`bin/${filename}`); + // Initialize pack buffer const STRIDE = 0x18; - const pack: PackBuffer[] = []; const mesh = Buffer.alloc(0x2b40, 0); const shadowOfs: number[] = []; let maxFaces = -1; - // Body Section - const BODY_OFS = 0x80; - [ - "miku/02_BODY.obj", - "miku/03_HIPS.obj", - "miku/10_LEG_RIGHT_TOP.obj", - "miku/11_LEG_RIGHT_BOTTOM.obj", - "miku/13_LEG_LEFT_TOP.obj", - "miku/14_LEG_LEFT_BOTTOM.obj", - ].forEach((filename, index) => { + + let headerOfs = 0; + let ptrOfs = 0x2f0; + + const encodeBody = (filename: string) => { const obj = readFileSync(filename, "ascii"); const { tri, quad, vertices } = encodeMesh(obj, 0); @@ -410,9 +312,10 @@ const encodeModel = ( const quadCount = Math.floor(quad.length / 12); const vertCount = Math.floor(vertices.length / 4); // Write the number of primites - mesh.writeUInt8(triCount, BODY_OFS + index * STRIDE + 0); // tris - mesh.writeUInt8(quadCount, BODY_OFS + index * STRIDE + 1); // quads - mesh.writeUInt8(vertCount, BODY_OFS + index * STRIDE + 2); // verts + mesh.writeUInt8(triCount, headerOfs + 0); // tris + mesh.writeUInt8(quadCount, headerOfs + 1); // quads + mesh.writeUInt8(vertCount, headerOfs + 2); // verts + // Update the max number of faces to add shadows if (triCount > maxFaces) { maxFaces = triCount; @@ -422,48 +325,130 @@ const encodeModel = ( maxFaces = quadCount; } - // Push Tris - pack.push({ - dataOfs: BODY_OFS + index * STRIDE + 4, - data: tri, - blockIndex: -1, - offset: -1, - }); - // Push Quads - pack.push({ - dataOfs: BODY_OFS + index * STRIDE + 8, - data: quad, - blockIndex: -1, - offset: -1, - }); - // Push Verts - pack.push({ - dataOfs: BODY_OFS + index * STRIDE + 12, - data: vertices, - blockIndex: -1, - offset: -1, - }); + // Write Triangles + const triOfs = ptrOfs; + mesh.writeUInt32LE(ptrOfs, headerOfs + 4); + for (let i = 0; i < tri.length; i++) { + mesh[ptrOfs + i] = tri[i]; + } + ptrOfs += tri.length; + + // Write Quads + const quadOfs = ptrOfs; + mesh.writeUInt32LE(ptrOfs, headerOfs + 8); + for (let i = 0; i < quad.length; i++) { + mesh[ptrOfs + i] = quad[i]; + } + ptrOfs += quad.length; + + // Write Vertices + const vertOfs = ptrOfs; + mesh.writeUInt32LE(ptrOfs, headerOfs + 12); + for (let i = 0; i < vertices.length; i++) { + mesh[ptrOfs + i] = vertices[i]; + } + ptrOfs += vertices.length; + // Push shadows - shadowOfs.push(BODY_OFS + index * STRIDE + 0x10); - shadowOfs.push(BODY_OFS + index * STRIDE + 0x14); - }); - // Head Section - const HEAD_OFS = 0xb60; - [ - "miku/01_HEAD_HAIR.obj", - "miku/01_HEAD_FACE.obj", - "miku/01_HEAD_MOUTH.obj", - ].forEach((filename, index) => { - const obj = readFileSync(filename, "ascii"); - const { tri, quad, vertices } = encodeMesh(obj, 2); + shadowOfs.push(headerOfs + 0x10); + shadowOfs.push(headerOfs + 0x14); + headerOfs += STRIDE; + return [triCount, quadCount, vertCount, triOfs, quadOfs, vertOfs]; + }; + + const encodeFace = () => { + const dat = src.subarray(0x30, 0x30 + 0x2b40); + const local = Buffer.from(dat); + const reader = new ByteReader(local.buffer as ArrayBuffer); + + const HEAD_OFS = 0xb60; + const names = ["11_FACE", "12_MOUTH"]; + reader.seek(HEAD_OFS + STRIDE); + + names.forEach((name) => { + const triCount = reader.readUInt8(); + const quadCount = reader.readUInt8(); + const vertCount = reader.readUInt8(); + reader.seekRel(1); + + const triOfs = reader.readUInt32(); + const quadOfs = reader.readUInt32(); + const vertOfs = reader.readUInt32(); + reader.seekRel(0x08); + + // Write the number of primites + mesh.writeUInt8(triCount, headerOfs + 0); // tris + mesh.writeUInt8(quadCount, headerOfs + 1); // quads + mesh.writeUInt8(vertCount, headerOfs + 2); // verts + + const tri = local.subarray(triOfs, triOfs + triCount * 12); + const quad = local.subarray(quadOfs, quadOfs + quadCount * 12); + const vertices = local.subarray(vertOfs, vertOfs + vertCount * 4); + + // Update the max number of faces to add shadows + if (triCount > maxFaces) { + maxFaces = triCount; + } + // Update the max number of faces to add shadows + if (quadCount > maxFaces) { + maxFaces = quadCount; + } + + // Write Triangles + mesh.writeUInt32LE(ptrOfs, headerOfs + 4); + for (let i = 0; i < tri.length; i++) { + mesh[ptrOfs + i] = tri[i]; + } + ptrOfs += tri.length; + + // Write Quads + mesh.writeUInt32LE(ptrOfs, headerOfs + 8); + for (let i = 0; i < quad.length; i++) { + mesh[ptrOfs + i] = quad[i]; + } + ptrOfs += quad.length; + + // Write Vertices + mesh.writeUInt32LE(ptrOfs, headerOfs + 12); + for (let i = 0; i < vertices.length; i++) { + mesh[ptrOfs + i] = vertices[i]; + } + ptrOfs += vertices.length; + + // Push shadows + shadowOfs.push(headerOfs + 0x10); + shadowOfs.push(headerOfs + 0x14); + headerOfs += STRIDE; + }); + }; + + const encodeBullet = () => { + const dat = src.subarray(0x30, 0x30 + 0x2b40); + const local = Buffer.from(dat); + const reader = new ByteReader(local.buffer as ArrayBuffer); + + const BUSTER_OFS = 0x2220; + reader.seek(BUSTER_OFS + 2 * STRIDE); + + const triCount = reader.readUInt8(); + const quadCount = reader.readUInt8(); + const vertCount = reader.readUInt8(); + reader.seekRel(1); + + const triOfs = reader.readUInt32(); + const quadOfs = reader.readUInt32(); + const vertOfs = reader.readUInt32(); + reader.seekRel(0x08); - const triCount = Math.floor(tri.length / 12); - const quadCount = Math.floor(quad.length / 12); - const vertCount = Math.floor(vertices.length / 4); // Write the number of primites - mesh.writeUInt8(triCount, HEAD_OFS + index * STRIDE + 0); // tris - mesh.writeUInt8(quadCount, HEAD_OFS + index * STRIDE + 1); // quads - mesh.writeUInt8(vertCount, HEAD_OFS + index * STRIDE + 2); // verts + mesh.writeUInt8(triCount, headerOfs + 0); // tris + mesh.writeUInt8(quadCount, headerOfs + 1); // quads + mesh.writeUInt8(vertCount, headerOfs + 2); // verts + + const tri = local.subarray(triOfs, triOfs + triCount * 12); + const quad = local.subarray(quadOfs, quadOfs + quadCount * 12); + const vertices = local.subarray(vertOfs, vertOfs + vertCount * 4); + // Update the max number of faces to add shadows if (triCount > maxFaces) { maxFaces = triCount; @@ -473,84 +458,145 @@ const encodeModel = ( maxFaces = quadCount; } - // Push Tris - pack.push({ - dataOfs: HEAD_OFS + index * STRIDE + 4, - data: tri, - blockIndex: -1, - offset: -1, - }); - // Push Quads - pack.push({ - dataOfs: HEAD_OFS + index * STRIDE + 8, - data: quad, - blockIndex: -1, - offset: -1, - }); - // Push Verts - pack.push({ - dataOfs: HEAD_OFS + index * STRIDE + 12, - data: vertices, - blockIndex: -1, - offset: -1, - }); + // Write Triangles + mesh.writeUInt32LE(ptrOfs, headerOfs + 4); + for (let i = 0; i < tri.length; i++) { + mesh[ptrOfs + i] = tri[i]; + } + ptrOfs += tri.length; + + // Write Quads + mesh.writeUInt32LE(ptrOfs, headerOfs + 8); + for (let i = 0; i < quad.length; i++) { + mesh[ptrOfs + i] = quad[i]; + } + ptrOfs += quad.length; + + // Write Vertices + mesh.writeUInt32LE(ptrOfs, headerOfs + 12); + for (let i = 0; i < vertices.length; i++) { + mesh[ptrOfs + i] = vertices[i]; + } + ptrOfs += vertices.length; + // Push shadows - shadowOfs.push(HEAD_OFS + index * STRIDE + 0x10); - shadowOfs.push(HEAD_OFS + index * STRIDE + 0x14); - }); + shadowOfs.push(headerOfs + 0x10); + shadowOfs.push(headerOfs + 0x14); + headerOfs += STRIDE; + }; + + const encodeShoulder = (shoulder: number[]) => { + // 1 Write data from shoulder + const [triCount, quadCount, vertCount, triOfs, quadOfs, vertOfs] = shoulder; + mesh.writeUInt8(triCount, headerOfs + 0); // tris + mesh.writeUInt8(quadCount, headerOfs + 1); // quads + mesh.writeUInt8(vertCount, headerOfs + 2); // verts + + // Write Triangles, Quads, Verts + mesh.writeUInt32LE(triOfs, headerOfs + 4); + mesh.writeUInt32LE(quadOfs, headerOfs + 8); + mesh.writeUInt32LE(vertOfs, headerOfs + 12); + + // Push shadows + shadowOfs.push(headerOfs + 0x10); + shadowOfs.push(headerOfs + 0x14); + headerOfs += STRIDE; + }; + + // Body Section + let label = Buffer.from("---- BODY ----", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x80 + i] = label[i]; + } + headerOfs = 0x90; + encodeBody("miku/02_BODY.obj"); + encodeBody("miku/03_HIPS.obj"); + encodeBody("miku/10_LEG_RIGHT_TOP.obj"); + encodeBody("miku/11_LEG_RIGHT_BOTTOM.obj"); + encodeBody("miku/13_LEG_LEFT_TOP.obj"); + encodeBody("miku/14_LEG_LEFT_BOTTOM.obj"); + + // Head Section + label = Buffer.from("---- HEAD ----", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x120 + i] = label[i]; + } + headerOfs = 0x130; + encodeBody(hairObject); + encodeFace(); + + // Encode Feet + label = Buffer.from("---- FEET ----", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x180 + i] = label[i]; + } + headerOfs = 0x190; + encodeBody(rightFootObject); + encodeBody(leftFootObject); // Left Arm - const leftShoulder = "obj/07_LEFT_SHOULDER.obj"; - const leftArm = "obj/08_LEFT_ARM.obj"; - const leftHand = "obj/09_LEFT_HAND.obj"; + label = Buffer.from("-- LEFT-ARM --", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x1c0 + i] = label[i]; + } + headerOfs = 0x1d0; + const shoulder = encodeBody("miku/07_LEFT_SHOULDER.obj"); + encodeBody("miku/08_LEFT_ARM.obj"); + encodeBody("miku/09_LEFT_HAND.obj"); // Right Arm - const rightShoulder = "obj/04_RIGHT_SHOULDER.obj"; - const rightArm = "obj/05_RIGHT_ARM.obj"; - const rightHand = "obj/06_RIGHT_HAND.obj"; - - // Eyes and mouth - const eyesObject = "obj/01_HEAD_FACE.obj"; - const mouthObject = "obj/01_HEAD_MOUTH.obj"; + label = Buffer.from("-- RIGHT-ARM --", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x220 + i] = label[i]; + } + headerOfs = 0x230; + encodeBody("miku/04_RIGHT_SHOULDER.obj"); + encodeBody("miku/05_RIGHT_ARM.obj"); + encodeBody("miku/06_RIGHT_HAND.obj"); + + // Buster + label = Buffer.from("--- BUSTER ---", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x280 + i] = label[i]; + } + headerOfs = 0x290; + encodeShoulder(shoulder); + encodeBody("miku/41_BUSTER.obj"); + encodeBullet(); + + label = Buffer.from("---- PRIM ----", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[0x2e0 + i] = label[i]; + } // Create entry for face shadows - pack.push({ - dataOfs: -1, - data: Buffer.alloc((maxFaces + 4) * 4, 0x80), - blockIndex: -1, - offset: -1, - }); - const packingResult = packBuffers(pack); - packingResult.forEach((result) => { - const { dataOfs, data, offset } = result; - if (dataOfs === -1) { - shadowOfs.forEach((ofs) => mesh.writeUint32LE(offset, ofs)); - } else { - mesh.writeUint32LE(offset, dataOfs); - } - for (let i = 0; i < data.length; i++) { - mesh[offset + i] = data[i]; - } - }); + if (ptrOfs % 16) { + ptrOfs = Math.ceil(ptrOfs / 16) * 16; + } - // Replace in Game File - const src = readFileSync(`bin/${filename}`); + const shadows = Buffer.alloc((maxFaces + 4) * 4, 0x80); + label = Buffer.from("---- SHADOW ----", "ascii"); + for (let i = 0; i < label.length; i++) { + mesh[ptrOfs++] = label[i]; + } + shadowOfs.forEach((ofs) => mesh.writeUint32LE(ptrOfs, ofs)); + for (let i = 0; i < shadows.length; i++) { + mesh[ptrOfs + i] = shadows[i]; + } + ptrOfs += shadows.length; + if (ptrOfs > 0x2b40) { + throw new Error("Model length too long " + filename); + } + const remaining = 0x2b40 - ptrOfs; + console.log("Bytes remaining: 0x%s", remaining.toString(16)); + + // Copy Over the Model After the Skeleton for (let i = 0x80; i < mesh.length; i++) { src[i + 0x30] = mesh[i]; } - // const HEADER_LEN = 0x30; - // // Zero out body - // for (let i = 0x98; i < 0x110; i++) { - // src[HEADER_LEN + i] = 0; - // } - // // Zero out everything else - // for (let i = 0xb60; i < 0xba8; i++) { - // src[HEADER_LEN + i] = 0; - // } - - // writeFileSync(`out/debug_${filename}`, mesh); + writeFileSync(`out/miku-${filename}`, mesh); writeFileSync(`out/${filename}`, src); }; diff --git a/src/EncodeRom.ts b/src/EncodeRom.ts index 6821394..85868db 100644 --- a/src/EncodeRom.ts +++ b/src/EncodeRom.ts @@ -56,6 +56,70 @@ const findFileOffset = (rom: Buffer, file: Buffer) => { return -1; }; +const findPointerTable = (rom: Buffer) => { + const PLAYER_OFFSET = 0x110800; + const body = 0x80; + const head = 0xb60; + const rightArm = 0x26f0; + const eof = 0x2b40; + const leftArm = 0x1dd0; + const buster = 0x2220; + const feet = 0x1800; + + for (let i = 0; i < rom.length - 0x20; i += 4) { + const a = (rom.readUInt32LE(i + 0) & 0xffffff) - PLAYER_OFFSET; + const b = (rom.readUInt32LE(i + 4) & 0xffffff) - PLAYER_OFFSET; + const c = (rom.readUInt32LE(i + 8) & 0xffffff) - PLAYER_OFFSET; + const d = (rom.readUInt32LE(i + 12) & 0xffffff) - PLAYER_OFFSET; + const e = (rom.readUInt32LE(i + 16) & 0xffffff) - PLAYER_OFFSET; + const f = (rom.readUInt32LE(i + 20) & 0xffffff) - PLAYER_OFFSET; + const g = (rom.readUInt32LE(i + 24) & 0xffffff) - PLAYER_OFFSET; + + if (a !== body) { + continue; + } + + if (b !== head) { + continue; + } + + if (c !== rightArm) { + continue; + } + + if (d !== eof) { + continue; + } + + if (e !== leftArm) { + continue; + } + + if (f !== buster) { + continue; + } + + if (g !== feet) { + continue; + } + + return i; + } + + return -1; +}; + +const updatePointerTable = (rom: Buffer) => { + const ofs = findPointerTable(rom); + const PLAYER_OFFSET = 0x110800; + rom.writeUInt32LE(0x90 + PLAYER_OFFSET, ofs + 0); // Body + rom.writeUInt32LE(0x130 + PLAYER_OFFSET, ofs + 4); // Head + rom.writeUInt32LE(0x190 + PLAYER_OFFSET, ofs + 24); // Feet + rom.writeUInt32LE(0x1d0 + PLAYER_OFFSET, ofs + 16); // Left Arm + rom.writeUInt32LE(0x230 + PLAYER_OFFSET, ofs + 8); // Right Arm + rom.writeUInt32LE(0x290 + PLAYER_OFFSET, ofs + 20); // Buster +}; + const replaceInRom = ( sourceRom: Buffer, sourceFile: Buffer, @@ -97,6 +161,7 @@ const encodeRom = () => { const mikuTexture = readFileSync("out/PL00T.BIN"); const pl00t = readFileSync("bin/PL00T.BIN"); const pl00t2 = readFileSync("bin/PL00T2.BIN"); + console.log("--- Replaching Textures ---"); replaceInRom(rom, pl00t, mikuTexture); replaceInRom(rom, pl00t2, mikuTexture); @@ -118,13 +183,17 @@ const encodeRom = () => { ]; // Replace Models + console.log("--- Replaching Models ---"); megaman.forEach((file) => { replaceInRom(rom, file, mikuHairNorm); }); + // Update Pointer Table + updatePointerTable(rom); + // Write the result writeFileSync(romDst, rom); }; -export { encodeRom, findFileOffset }; +export { encodeRom, findFileOffset, findPointerTable }; export default encodeRom; diff --git a/src/EncodeTexture.ts b/src/EncodeTexture.ts index 6558a6c..1148fe2 100644 --- a/src/EncodeTexture.ts +++ b/src/EncodeTexture.ts @@ -255,41 +255,41 @@ const replaceTexture = ( } // Replace the face texture - const [facePal, faceImg] = encodeImage(faceBuffer); - const megamanFace = readFileSync("fixtures/face-texture.bin"); - - // Replace the second hald of the image with special weapons - for (let i = 0; i < 0x4000; i++) { - faceImg[0x4000 + i] = megamanFace[0x4080 + i]; - } - - // First we zero out the previous image - for (let i = 0x3830; i < 0x6500; i++) { - modded[i] = 0; - } - - // Compress the face texture - const [faceBitField, compressedFace] = compressTexture( - facePal, - faceImg, - 9, - 2, - ); - - // Update the bitfield length in header - modded.writeInt16LE(faceBitField.length, 0x3824); - - let faceOfs = 0x3830; - - // Write the bitfield - for (let i = 0; i < faceBitField.length; i++) { - modded[faceOfs++] = faceBitField[i]; - } - - // Write the compressed Texture - for (let i = 0; i < compressedFace.length; i++) { - modded[faceOfs++] = compressedFace[i]; - } + // const [facePal, faceImg] = encodeImage(faceBuffer); + // const megamanFace = readFileSync("fixtures/face-texture.bin"); + + // // Replace the second hald of the image with special weapons + // for (let i = 0; i < 0x4000; i++) { + // faceImg[0x4000 + i] = megamanFace[0x4080 + i]; + // } + + // // First we zero out the previous image + // for (let i = 0x3830; i < 0x6500; i++) { + // modded[i] = 0; + // } + + // // Compress the face texture + // const [faceBitField, compressedFace] = compressTexture( + // facePal, + // faceImg, + // 9, + // 2, + // ); + + // // Update the bitfield length in header + // modded.writeInt16LE(faceBitField.length, 0x3824); + + // let faceOfs = 0x3830; + + // // Write the bitfield + // for (let i = 0; i < faceBitField.length; i++) { + // modded[faceOfs++] = faceBitField[i]; + // } + + // // Write the compressed Texture + // for (let i = 0; i < compressedFace.length; i++) { + // modded[faceOfs++] = compressedFace[i]; + // } return modded; }; diff --git a/test/find-offset.test.ts b/test/find-offset.test.ts index 428481b..a587c62 100644 --- a/test/find-offset.test.ts +++ b/test/find-offset.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "bun:test"; -import { findFileOffset } from "../src/EncodeRom"; +import { findFileOffset, findPointerTable } from "../src/EncodeRom"; import { readFileSync } from "fs"; test("ISO 9660 File Offset Finder", () => { @@ -32,3 +32,15 @@ test("ISO 9660 File Offset Finder", () => { expect(fileOffset).not.toEqual(-1); }); }); + +test("Megaman Model Offset Reference", () => { + const binFilePath = process.env.SRC_ROM; + if (!binFilePath) { + console.log("SRC_ROM environment variable not set. Skipping tests."); + return; + } + + const rom = readFileSync(binFilePath); + const pointerOfs = findPointerTable(rom); + expect(pointerOfs).not.toEqual(-1); +});