Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created January 8, 2025 14:49
Show Gist options
  • Save Kattoor/a11484ea352b9b24fccf90a99849b1ed to your computer and use it in GitHub Desktop.
Save Kattoor/a11484ea352b9b24fccf90a99849b1ed to your computer and use it in GitHub Desktop.
pff-to-3di-to-obj.mjs
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