const fs = require('fs'); const execFile = require('child_process').execFile; const path = require('path'); const rimraf = require('rimraf'); rimraf.sync('tmp'); const mkdirp = require('mkdirp'); mkdirp.sync('tmp'); mkdirp.sync('mogg_input'); mkdirp.sync('mogg_output'); mkdirp.sync('game_input'); mkdirp.sync('game_output'); const fileExists = async path => !!(await fs.promises.stat(path).catch(e => false)); const readFile = async path => await fs.promises.readFile(path); async function moggify() { const files = await fs.promises.readdir('mogg_input'); for(const file of files) { console.log(`Converting ${file}...`); const from = path.join('mogg_input', file); const tmp = path.join('tmp', path.parse(file).name + '.mogg'); const to = path.join('mogg_output', path.parse(file).name + '.mogg'); await new Promise(resolve => { execFile('ogg2mogg.exe', [from, tmp], () => { execFile('MoggcryptCpp.exe', [tmp, '-e', to], () => { resolve(); }); }); }) } } async function findOGG(name) { const files = await fs.promises.readdir('mogg_output'); const found = []; for(const file of files) { if (file.toLowerCase().includes(name.toLowerCase())) { found.push(file); } } if (found.length === 0) { console.error(`Couldn't find mogg with name "${name}".`); return false; } if (found.length > 1) { console.error(`Found more than 1 mogg with name "${name}".`); return false; } return found[0]; } async function replace() { if (process.argv.length !== 4) { console.error('Usage: node replace.js '); console.log('(No file extensions)'); return; } const mogg_file = await findOGG(process.argv[2]); const game_file = process.argv[3]; const uexp_exists = await fileExists(`game_input/${game_file}.uexp`); const uasset_exists = await fileExists(`game_input/${game_file}.uasset`); if (!mogg_file) { return; } if (!uexp_exists || !uasset_exists) { console.error(`Couldn't find a .uexp and .uasset combo for '${game_file}'.`); return; } const out_uexp = path.join('game_output', game_file + '.uexp'); const out_uasset = path.join('game_output', game_file + '.uasset'); // Read .mogg const mogg_buffer = await readFile(`mogg_output/${mogg_file}`); // Modify .uexp const data_buffer = await readFile(`game_input/${game_file}.uexp`); const old_uexp_length = data_buffer.length; // Get the start offset by getting the first index of MoggSampleResource, then offset by 19 const start_offset = data_buffer.indexOf('MoggSampleResource') + 19; // Write new MOGG Buffer length + 32 to offset start_offset data_buffer.writeInt32LE(mogg_buffer.length + 32, start_offset); // Write new MOGG Buffer length to offset start_offset + 36, store old for buffering const old_1_length = data_buffer.readInt32LE(start_offset + 36); data_buffer.writeInt32LE(mogg_buffer.length, start_offset + 36); // Insert MOGG at start_offset + 36 + 4, then offset to the new position const pre_buffer_1 = data_buffer.slice(0, start_offset + 36 + 4); const post_buffer_1 = data_buffer.slice(start_offset + 36 + 4 + old_1_length); // Concat buffers const song_1_insert_buffer = Buffer.concat([pre_buffer_1, mogg_buffer, post_buffer_1]); // Only replace 2nd MoggSampleResource if numResourceFiles === 3 let final_buffer = song_1_insert_buffer; const filenameLength = final_buffer.readInt16LE(28); const numResourceFiles = final_buffer.readInt16LE(28 + filenameLength + 12); if (numResourceFiles === 3) { // Get the start offset by getting the first index of MoggSampleResource after our mogg_buffer, then offset by 19 const new_offset = song_1_insert_buffer.indexOf('MoggSampleResource', mogg_buffer.length) + 19; // Write new MOGG Buffer length + 32 to offset new_offset song_1_insert_buffer.writeInt32LE(mogg_buffer.length + 32, new_offset); // Write new MOGG Buffer length to offset new_offset + 36, store old for buffering const old_2_length = song_1_insert_buffer.readInt32LE(new_offset + 36); song_1_insert_buffer.writeInt32LE(mogg_buffer.length, new_offset + 36); // Insert MOGG at new_offset + 36 + 4, then offset to the new position const pre_buffer_2 = song_1_insert_buffer.slice(0, new_offset + 36 + 4); const post_buffer_2 = song_1_insert_buffer.slice(new_offset + 36 + 4 + old_2_length); // Concat buffers final_buffer = Buffer.concat([pre_buffer_2, mogg_buffer, post_buffer_2]); } // Finally, write await fs.promises.writeFile(out_uexp, final_buffer); // Modify .uasset const uasset_buffer = await readFile(`game_input/${game_file}.uasset`); const update_size_offset = uasset_buffer.lastIndexOf(old_uexp_length - 4); uasset_buffer.writeInt32LE(final_buffer.length - 4, update_size_offset); // Finally, write await fs.promises.writeFile(out_uasset, uasset_buffer); } moggify().then(replace);