Initial Commit
This commit is contained in:
parent
a88d29e7b9
commit
a57ef32633
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
game_input/*
|
||||||
|
game_output/*
|
||||||
|
mogg_input/*
|
||||||
|
mogg_output/*
|
||||||
|
tmp/*
|
BIN
MoggcryptCpp.exe
Normal file
BIN
MoggcryptCpp.exe
Normal file
Binary file not shown.
16
README.md
16
README.md
@ -1 +1,15 @@
|
|||||||
Fuser-Stem-Replacement-Tool
|
# Fuser Stem Replacement Tool
|
||||||
|
|
||||||
|
1. Figure out which song you want to replace stems of
|
||||||
|
2. Get it's BPM
|
||||||
|
3. Prepare your own stem / song with that same BPM
|
||||||
|
4. Make sure it's **exactly** 32 bars long and optimally loops well
|
||||||
|
5. Export it as an `.ogg` file into the `mogg_input` folder
|
||||||
|
6. Copy the corresponding `.uexp` and `.uasset` file combination into the `game_input` folder
|
||||||
|
7. Run `npm start <ogg_without_extension> <game_file_without_extension>`
|
||||||
|
8. Copy the corresponding files from `game_output` into the `pakfiles` folder you have setup with the old game files and overwrite them
|
||||||
|
9. Build the new DLC `.pak` file
|
||||||
|
10. Replace it in the `Fuser/Fuser/DLC/Songs` directory
|
||||||
|
|
||||||
|
## Example (DK Rap => Adore You, 99 BPM, Vocals)
|
||||||
|
`npm start dk_rap adoreyou_lead_eb_99_fusion`
|
135
index.js
Normal file
135
index.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
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(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 replace() {
|
||||||
|
if (process.argv.length !== 4) {
|
||||||
|
console.error('Usage: node replace.js <input_mogg_file> <input_game_file>');
|
||||||
|
console.log('(No file extensions)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mogg_file = process.argv[2] + '.mogg';
|
||||||
|
const game_file = process.argv[3];
|
||||||
|
|
||||||
|
const mogg_exists = await fileExists(`mogg_output/${mogg_file}`);
|
||||||
|
const uexp_exists = await fileExists(`game_input/${game_file}.uexp`);
|
||||||
|
const uasset_exists = await fileExists(`game_input/${game_file}.uasset`);
|
||||||
|
|
||||||
|
if (!mogg_exists) {
|
||||||
|
console.error(`Couldn't find the .mogg file '${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}`);
|
||||||
|
console.log('Mogg', mogg_buffer);
|
||||||
|
|
||||||
|
// Modify .uexp
|
||||||
|
const data_buffer = await readFile(`game_input/${game_file}.uexp`);
|
||||||
|
const old_uexp_length = data_buffer.length;
|
||||||
|
const start_offset = data_buffer.indexOf('MoggSampleResource') + 19;
|
||||||
|
console.log('uexp', data_buffer);
|
||||||
|
console.log('uexp length', old_uexp_length);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
console.log("Old 1 length", old_1_length);
|
||||||
|
data_buffer.writeInt32LE(mogg_buffer.length, start_offset + 36);
|
||||||
|
console.log("New 1 length", data_buffer.readInt32LE(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);
|
||||||
|
console.log(final_buffer.length);
|
||||||
|
if (numResourceFiles === 3) {
|
||||||
|
const new_offset = song_1_insert_buffer.indexOf('MoggSampleResource', mogg_buffer.length) + 19;
|
||||||
|
console.log("New Offset", new_offset);
|
||||||
|
console.log("InsertBufferLength", song_1_insert_buffer.length);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
console.log("Old 2 length", old_2_length);
|
||||||
|
song_1_insert_buffer.writeInt32LE(mogg_buffer.length, new_offset + 36);
|
||||||
|
console.log("New 2 length", song_1_insert_buffer.readInt32LE(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`);
|
||||||
|
console.log('Uassetbuffer', uasset_buffer);
|
||||||
|
|
||||||
|
const update_size_offset = uasset_buffer.lastIndexOf(old_uexp_length - 4);
|
||||||
|
console.log("Update size offset", update_size_offset);
|
||||||
|
uasset_buffer.writeInt32LE(final_buffer.length - 4, update_size_offset);
|
||||||
|
|
||||||
|
// Finally, write
|
||||||
|
await fs.promises.writeFile(out_uasset, uasset_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
moggify().then(replace);
|
BIN
ogg2mogg.exe
Normal file
BIN
ogg2mogg.exe
Normal file
Binary file not shown.
103
package-lock.json
generated
Normal file
103
package-lock.json
generated
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"name": "fusermod",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||||
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||||
|
},
|
||||||
|
"fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"version": "7.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||||
|
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||||
|
"requires": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||||
|
"requires": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||||
|
},
|
||||||
|
"once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||||
|
},
|
||||||
|
"rimraf": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"underscore": {
|
||||||
|
"version": "1.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.0.tgz",
|
||||||
|
"integrity": "sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ=="
|
||||||
|
},
|
||||||
|
"wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "fusermod",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"moggify": "node moggify.js",
|
||||||
|
"replace": "node replace.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"underscore": "^1.12.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user