Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created June 12, 2025 10:49
Show Gist options
  • Save Kattoor/d0dcc13b2521959772915f7fbf5a71b3 to your computer and use it in GitHub Desktop.
Save Kattoor/d0dcc13b2521959772915f7fbf5a71b3 to your computer and use it in GitHub Desktop.
import {spawn} from 'child_process';
import {EOL} from 'os';
const assetRipperPath = 'C:\\Users\\kattoor\\Downloads\\AssetRipper\\AssetRipper.GUI.Free.exe';
const assetFolderPath = 'C:\\Program Files (x86)\\Steam\\steamapps\\common\\BitCraft Online Demo';
const outPath = 'C:\\Users\\kattoor\\projects\\bitcraft-assets';
let apiBase;
function startAssetRipperAndListenToStdOut(executablePath) {
let buffer = '';
const child = spawn(executablePath, [], {stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true});
child.stdout.setEncoding('utf8');
child.stdout.on('data', async (chunk) => {
buffer += chunk;
const lines = buffer.split(EOL);
buffer = lines.pop() || '';
for (const line of lines) {
await processStdOutLine(line);
}
});
}
const stdoutActions = [
{
match: /Now listening on: http:\/\/127\.0\.0\.1:(\d+)/,
action: (match) => {
const port = match[1];
apiBase = `http://127.0.0.1:${port}`;
console.log(`Port found: ${port}`);
console.log(`Loading folder ${assetFolderPath}`);
loadFolder(apiBase, assetFolderPath);
}
},
{
match: /Processing : Finished processing assets/,
action: () => {
console.log('Folder loaded.');
console.log(`Exporting files to ${outPath}`);
exportPrimaryContent(apiBase, outPath);
}
},
{
match: / : \((\d+)\/(\d+)\) Exporting '(.*)'/,
action: (match) => {
const [, index, total, file] = match;
console.log(`Extracting file ${index} of ${total}: ${file}`);
}
}
];
async function processStdOutLine(line) {
for (const {match, action} of stdoutActions) {
const result = line.match(match);
if (result) {
action(result);
break;
}
}
}
async function loadFolder(apiBase, folderPath) {
const body = new URLSearchParams({path: folderPath});
await fetch(`${apiBase}/LoadFolder`, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body,
redirect: 'manual'
});
}
async function exportPrimaryContent(apiBase, outputFolderPath) {
const body = new URLSearchParams({path: outputFolderPath});
await fetch(`${apiBase}/Export/PrimaryContent`, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body,
redirect: 'manual'
});
}
await startAssetRipperAndListenToStdOut(assetRipperPath);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment