Add "build-client" and "patch" scripts to package.json

main
peshomir 2025-07-03 18:22:06 +03:00
parent 14c94507f3
commit 998ecc3bd4
2 changed files with 112 additions and 102 deletions

112
build.js
View File

@ -15,6 +15,7 @@ fs.writeFileSync("./build/index.html", fs.readFileSync("./build/index.html").toS
fs.writeFileSync("./build/sw.js", fs.readFileSync("./build/sw.js").toString().replace("buildTimestamp", buildTimestamp)); fs.writeFileSync("./build/sw.js", fs.readFileSync("./build/sw.js").toString().replace("buildTimestamp", buildTimestamp));
const buildClientCode = () => /** @type {Promise<void>} */(new Promise((resolve, reject) => { const buildClientCode = () => /** @type {Promise<void>} */(new Promise((resolve, reject) => {
console.log("Building client code...");
webpack({ webpack({
mode: 'production', mode: 'production',
entry: { fxClient: "./src/main.js" }, entry: { fxClient: "./src/main.js" },
@ -27,95 +28,97 @@ const buildClientCode = () => /** @type {Promise<void>} */(new Promise((resolve,
if (err.details) console.error(err.details); if (err.details) console.error(err.details);
return reject(err); return reject(err);
} }
const info = stats.toJson(); const info = stats?.toJson();
if (stats.hasWarnings()) console.warn(info.warnings); if (stats?.hasWarnings()) console.warn(info?.warnings);
if (stats.hasErrors()) { if (stats?.hasErrors()) {
console.error(info.errors); console.error(info?.errors);
reject("Webpack compilation error"); return reject("Webpack compilation error");
} }
else resolve(); fs.writeFileSync(
"./build/fx.bundle.js",
Buffer.concat([fs.readFileSync("./game/build_artefacts.js"), fs.readFileSync("./build/fx.bundle.js")])
);
resolve();
}); });
})); }));
let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).trim(); async function patchGameCode() {
const exposeVarsToGlobalScope = true; let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).trim();
// need to first remove the iife wrapper so the top-level functions aren't inlined
if (exposeVarsToGlobalScope && script.startsWith("\"use strict\"; (function () {") && script.endsWith("})();")) const exposeVarsToGlobalScope = true;
// need to first remove the iife wrapper so the top-level functions aren't inlined
if (exposeVarsToGlobalScope && script.startsWith("\"use strict\"; (function () {") && script.endsWith("})();"))
script = script.slice("\"use strict\"; (function () {".length, -"})();".length); script = script.slice("\"use strict\"; (function () {".length, -"})();".length);
if (exposeVarsToGlobalScope && script.startsWith("(function () {") && script.endsWith("})();")) if (exposeVarsToGlobalScope && script.startsWith("(function () {") && script.endsWith("})();"))
script = script.slice("(function () {".length, -"})();".length); script = script.slice("(function () {".length, -"})();".length);
// uncompress strings // uncompress strings
// this will break if the sequence `"];` appears in one of the strings // this will break if the sequence `"];` appears in one of the strings
const stringArrayRaw = script.match(/var S=(\[.+?"\]);/)?.[1]; const stringArrayRaw = script.match(/var S=(\[.+?"\]);/)?.[1];
if (stringArrayRaw === undefined) throw new Error("cannot find the string array"); if (stringArrayRaw === undefined) throw new Error("cannot find the string array");
const stringArray = JSON.parse(stringArrayRaw); const stringArray = JSON.parse(stringArrayRaw);
script = script.replace(/\bS\[(\d+)\]/g, (_match, index) => `"${stringArray[index]}"`); script = script.replace(/\bS\[(\d+)\]/g, (_match, index) => `"${stringArray[index]}"`);
const modUtils = new ModUtils(minifyCode(script)); const modUtils = new ModUtils(minifyCode(script));
import applyPatches from './patches/main.js'; const { default: applyPatches } = await import('./patches/main.js');
console.log("Applying patches..."); console.log("Applying patches...");
applyPatches(modUtils); applyPatches(modUtils);
// for versions ^1.99.5.2 // for versions ^1.99.5.2
const minificationResult = UglifyJS.minify(modUtils.script, { const minificationResult = UglifyJS.minify(modUtils.script, {
"compress": { "arrows": false }, "compress": { "arrows": false },
"mangle": false "mangle": false
}); });
if (minificationResult.error) { if (minificationResult.error) {
console.log("error while passing through UglifyJS, replaceCode replacements might have caused errors"); console.log("error while passing through UglifyJS, replaceCode replacements might have caused errors");
throw minificationResult.error; throw minificationResult.error;
} }
if (minificationResult.warnings) console.log(minificationResult.warnings); if (minificationResult.warnings) console.log(minificationResult.warnings);
modUtils.script = minificationResult.code; modUtils.script = minificationResult.code;
const { const {
matchDictionaryExpression, matchDictionaryExpression,
generateRegularExpression generateRegularExpression
} = modUtils; } = modUtils;
const dictionary = modUtils.dictionary; const dictionary = modUtils.dictionary;
[ [
/,this\.(?<gIsTeamGame>\w+)=this\.\w+<7\|\|9===this\.\w+,/g, /,this\.(?<gIsTeamGame>\w+)=this\.\w+<7\|\|9===this\.\w+,/g,
/=function\((\w+),(\w+),\w+\){\1===(?<game>\w+)\.(?<playerId>\w+)\?\w+\(175," "\+\w+\(\d+,\[(?<playerData>\w+)\.(?<playerNames>\w+)\[\2\]\]\)\+": ",1001,\2,\w+\(/g, /=function\((\w+),(\w+),\w+\){\1===(?<game>\w+)\.(?<playerId>\w+)\?\w+\(175," "\+\w+\(\d+,\[(?<playerData>\w+)\.(?<playerNames>\w+)\[\2\]\]\)\+": ",1001,\2,\w+\(/g,
/function \w+\(\)\{if\(2===(?<game>\w+)\.(?<gameState>\w+)\)return 1;\w+\.\w+\(\),\1\.\2=2,\1\.\w+=\1.\w+\}/g, /function \w+\(\)\{if\(2===(?<game>\w+)\.(?<gameState>\w+)\)return 1;\w+\.\w+\(\),\1\.\2=2,\1\.\w+=\1.\w+\}/g,
/(function \w+\((\w+),(?<fontSize>\w+),(?<x>\w+),(?<y>\w+),(?<canvas>\w+)\){)(\6\.fillText\((?<playerData>\w+)\.(?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<game>\w+)\.(?<gHumans>\w+)&&2!==\8\.(?<playerStates>\w+)\[[^}]+)}/g, /(function \w+\((\w+),(?<fontSize>\w+),(?<x>\w+),(?<y>\w+),(?<canvas>\w+)\){)(\6\.fillText\((?<playerData>\w+)\.(?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<game>\w+)\.(?<gHumans>\w+)&&2!==\8\.(?<playerStates>\w+)\[[^}]+)}/g,
/\w+\.font=(?<fontGeneratorFunction>\w+\.\w+\.\w+)\(1,\.39\*this\.\w+\),/g /\w+\.font=(?<fontGeneratorFunction>\w+\.\w+\.\w+)\(1,\.39\*this\.\w+\),/g
].forEach(matchDictionaryExpression); ].forEach(matchDictionaryExpression);
const rawCodeSegments = [ const rawCodeSegments = [
`aQ.eI(e0)?aQ.eE(e0)?a38=__L([a38]):(player=aQ.eF(e0),oq=__L([b0.uS.zG(@playerData.@rawPlayerNames[player],b0.p9.qQ(0,10),150)])+" ",oq=(oq+=__L([b0.wx.a07(playerData.@playerBalances[player])])+" ")+__L([b0.wx.a07(playerData.@playerTerritories[player])])+" ",`, `aQ.eI(e0)?aQ.eE(e0)?a38=__L([a38]):(player=aQ.eF(e0),oq=__L([b0.uS.zG(@playerData.@rawPlayerNames[player],b0.p9.qQ(0,10),150)])+" ",oq=(oq+=__L([b0.wx.a07(playerData.@playerBalances[player])])+" ")+__L([b0.wx.a07(playerData.@playerTerritories[player])])+" ",`,
"1===a.b?this.@gLobbyMaxJoin=this.@gHumans:this.gLobbyMaxJoin=this.@data.@playerCount,this.tZ=this.gLobbyMaxJoin,this.@gBots=this.gLobbyMaxJoin-this.gHumans,this.sg=0,", "1===a.b?this.@gLobbyMaxJoin=this.@gHumans:this.gLobbyMaxJoin=this.@data.@playerCount,this.tZ=this.gLobbyMaxJoin,this.@gBots=this.gLobbyMaxJoin-this.gHumans,this.sg=0,",
"[0]=__L(),@strs[1]=@game.@gIsSingleplayer?__L():__L(),", "[0]=__L(),@strs[1]=@game.@gIsSingleplayer?__L():__L(),",
"?(this.gB=Math.floor(.066*aK.fw),g5=aK.g5-4*@uiSizes.@gap-this.gB):", "?(this.gB=Math.floor(.066*aK.fw),g5=aK.g5-4*@uiSizes.@gap-this.gB):",
`for(a0L=new Array(@game.@gMaxPlayers),a0A.font=a07,@i=game.gMaxPlayers-1;0<=i;i--)a0L[i]=i+1+".",@playerData.@playerNames[i]=aY.qW.tm(playerData.@rawPlayerNames[i],a07,a0W),a0K[i]=Math.floor(a0A.measureText(playerData.playerNames[i]).width);`, `for(a0L=new Array(@game.@gMaxPlayers),a0A.font=a07,@i=game.gMaxPlayers-1;0<=i;i--)a0L[i]=i+1+".",@playerData.@playerNames[i]=aY.qW.tm(playerData.@rawPlayerNames[i],a07,a0W),a0K[i]=Math.floor(a0A.measureText(playerData.playerNames[i]).width);`,
`var dt=@MenuManager.@getState();if(6===dt){if(4211===d)` `var dt=@MenuManager.@getState();if(6===dt){if(4211===d)`
] ]
rawCodeSegments.forEach(code => { rawCodeSegments.forEach(code => {
const { expression } = generateRegularExpression(code, true); const { expression } = generateRegularExpression(code, true);
//console.log(expression); //console.log(expression);
matchDictionaryExpression(expression); matchDictionaryExpression(expression);
}); });
modUtils.executePostMinifyHandlers(); modUtils.executePostMinifyHandlers();
script = modUtils.script; script = modUtils.script;
console.log("Building client code...") // the dictionary should maybe get embedded into one of the files in the bundle
fs.writeFileSync(
await buildClientCode(); "./game/build_artefacts.js",
// the dictionary should maybe get embedded into one of the files in the bundle
fs.writeFileSync(
"./build/fx.bundle.js",
`const buildTimestamp = "${buildTimestamp}"; const dictionary = ${JSON.stringify(dictionary)};\n` `const buildTimestamp = "${buildTimestamp}"; const dictionary = ${JSON.stringify(dictionary)};\n`
+ fs.readFileSync("./build/fx.bundle.js").toString() );
);
console.log("Formatting code..."); console.log("Formatting code...");
script = beautify(script, { script = beautify(script, {
"indent_size": 1, "indent_size": 1,
"indent_char": "\t", "indent_char": "\t",
"max_preserve_newlines": 5, "max_preserve_newlines": 5,
@ -134,8 +137,13 @@ script = beautify(script, {
"comma_first": false, "comma_first": false,
"e4x": false, "e4x": false,
"indent_empty_lines": false "indent_empty_lines": false
}); });
fs.writeFileSync("./build/game.js", script);
console.log("Wrote ./build/game.js");
}
if (!process.argv.includes("--skip-patching")) await patchGameCode();
if (!process.argv.includes("--patch-only")) await buildClientCode();
fs.writeFileSync("./build/game.js", script);
console.log("Wrote ./build/game.js");
console.log("Build done"); console.log("Build done");

View File

@ -6,7 +6,9 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "node index.js", "build": "node index.js",
"build-only": "node build.js" "build-only": "node build.js",
"build-client": "node build.js --skip-patching",
"patch": "node build.js --patch-only"
}, },
"repository": { "repository": {
"type": "git", "type": "git",