Created
January 8, 2025 14:49
-
-
Save Kattoor/a11484ea352b9b24fccf90a99849b1ed to your computer and use it in GitHub Desktop.
pff-to-3di-to-obj.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'fs'; | |
const bhdDirectory = 'C:\\Users\\jaspe\\Desktop\\NovaLogic\\Delta Force Black Hawk Down'; | |
const packedFilePath = `${bhdDirectory}\\resource.pff`; | |
const outputDirectory = `${bhdDirectory}\\extracted\\obj`; | |
if (!fs.existsSync(outputDirectory)) { | |
fs.mkdirSync(outputDirectory, {recursive: true}); | |
} | |
const buffer = fs.readFileSync(packedFilePath); | |
const centralDirectory = readCentralDirectory(); | |
for (const {fileSize, fileOffset, name} of centralDirectory) { | |
try { | |
fs.writeFileSync(`${outputDirectory}\\${name}.obj`, convert3diToWavefront(buffer.subarray(fileOffset, fileOffset + fileSize))); | |
} catch (e) { | |
console.log(`Error converting ${name} to obj`); | |
} | |
} | |
function readCentralDirectory() { | |
let offset = 8; | |
const amountOfRecords = buffer.readUint16LE(offset); | |
offset = buffer.length; | |
const entries = []; | |
for (let i = 0; i < amountOfRecords; i++) { | |
let entryMetadataBytes = buffer.subarray(offset - 36, offset); | |
offset -= 36; | |
const deleted = entryMetadataBytes.readUint32LE(0); | |
const fileOffset = entryMetadataBytes.readUint32LE(4); | |
const fileSize = entryMetadataBytes.readUint32LE(8); | |
const datetime = new Date(entryMetadataBytes.readUint32LE(12) * 1000); | |
const name = entryMetadataBytes.subarray(16, 32).toString().replaceAll('\x00', ''); | |
const idk = entryMetadataBytes.readUint32LE(32); | |
if (name.endsWith('.3di')) { | |
entries.push({deleted, fileOffset, fileSize, datetime, name: name.split('.').slice(0, -1).join(''), idk}); | |
} | |
} | |
return entries; | |
} | |
function convert3diToWavefront(bytes) { | |
let offset = 176; | |
const amountOfIdk = bytes.readInt8(offset); | |
offset += 1; | |
offset += 59; | |
offset += amountOfIdk * 48; | |
const amountOfTgas = bytes.readInt8(offset); | |
offset += 1; | |
offset += 60 * amountOfTgas | |
offset += 51; | |
const amountOfVertices = bytes.readInt32LE(offset); | |
offset += 8; | |
const amountOfNormals = bytes.readInt32LE(offset); | |
offset += 8; | |
const amountOfFaces = bytes.readInt32LE(offset); | |
offset += 72; | |
const vertices = []; | |
for (let i = 0; i < amountOfVertices; i++) { | |
const x = bytes.readInt16LE(offset); | |
offset += 2; | |
const y = bytes.readInt16LE(offset); | |
offset += 2; | |
const z = bytes.readInt16LE(offset); | |
offset += 2; | |
const w = bytes.readInt16LE(offset); | |
offset += 2; | |
vertices.push({x, y, z}); | |
} | |
const normals = []; | |
for (let i = 0; i < amountOfNormals; i++) { | |
const x = bytes.readInt16LE(offset); | |
offset += 2; | |
const y = bytes.readInt16LE(offset); | |
offset += 2; | |
const z = bytes.readInt16LE(offset); | |
offset += 2; | |
const w = bytes.readInt16LE(offset); | |
offset += 2; | |
normals.push({x, y, z}); | |
} | |
const faces = []; | |
for (let i = 0; i < amountOfFaces; i++) { | |
const x = bytes.readInt16LE(offset); | |
offset += 2; | |
const y = bytes.readInt16LE(offset); | |
offset += 2; | |
const z = bytes.readInt16LE(offset); | |
offset += 2; | |
const w = bytes.readInt16LE(offset); | |
offset += 2; | |
offset += (44 - 8); | |
faces.push({x, y, z, w}); | |
} | |
let wavefrontContent = ''; | |
for (let i = 0; i < vertices.length; i++) { | |
wavefrontContent += `v ${vertices[i].x / 2500} ${vertices[i].y / 2500} ${vertices[i].z / 2500} 1.0\n`; | |
} | |
for (let i = 0; i < faces.length; i++) { | |
wavefrontContent += `f ${faces[i].x + 1} ${faces[i].y + 1} ${faces[i].z + 1}\n`; | |
} | |
return wavefrontContent; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment