This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
// Local patch: make the multi-account limits configurable.
|
||||
//
|
||||
// Upstream hard-codes:
|
||||
// - one merchant per IP and per owner,
|
||||
// - three non-merchant characters per IP and per owner,
|
||||
// and only checks some of those values at login.
|
||||
//
|
||||
// This patch reads the limits from options.js in both places:
|
||||
// - is_player_allowed() (login, node/server_functions.js)
|
||||
// - enforce_limitations() (backend cron, adventure_functions.js)
|
||||
//
|
||||
// New options (defaults match upstream):
|
||||
// merchant_ip_limit: 1
|
||||
// merchant_owner_limit: 1
|
||||
// ip_limit: 3
|
||||
// character_limit: 3
|
||||
//
|
||||
// The patch is idempotent and fails loudly if an anchor is missing.
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const app = path.resolve(process.argv[2] || "/app");
|
||||
const serverFunctionsFile = path.join(app, "node", "server_functions.js");
|
||||
const adventureFunctionsFile = path.join(app, "adventure_functions.js");
|
||||
|
||||
function fail(message) {
|
||||
console.error("patch-multi-account-limits: " + message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function replaceOnce(source, from, to, label) {
|
||||
const count = source.split(from).length - 1;
|
||||
if (count !== 1) {
|
||||
fail(label + ": expected 1 anchor, found " + count);
|
||||
}
|
||||
return source.replace(from, to);
|
||||
}
|
||||
|
||||
function patchServerFunctions(source) {
|
||||
const marker = "function get_multi_account_identity(player) {";
|
||||
const oldMerchant =
|
||||
'\tif (player.type == "merchant") {\n' +
|
||||
"\t\tfor (var id in players) {\n" +
|
||||
"\t\t\tif ((players[id].stones && 0) || id == player.socket.id || is_free(player)) {\n" +
|
||||
"\t\t\t\tcontinue;\n" +
|
||||
"\t\t\t}\n" +
|
||||
'\t\t\tif (players[id].owner == player.owner && players[id].type == "merchant") {\n' +
|
||||
"\t\t\t\treturn false;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\treturn true;\n" +
|
||||
"\t}";
|
||||
|
||||
if (source.includes(marker)) {
|
||||
if (source.includes(oldMerchant)) {
|
||||
fail("server_functions.js has a partial merchant patch");
|
||||
}
|
||||
console.log("patch-multi-account-limits: server_functions.js already patched");
|
||||
return source;
|
||||
}
|
||||
|
||||
source = replaceOnce(
|
||||
source,
|
||||
'function get_limit_identity(player) {\n\treturn player && player.mainframe === true ? "mainframe:" + player.owner : get_ip_server(player);\n}',
|
||||
'function get_limit_identity(player) {\n\treturn player && player.mainframe === true ? "mainframe:" + player.owner : get_ip_server(player);\n}\n\n' +
|
||||
"// Multi-account identity; mirrors the auth/temp_auth handling in enforce_limitations().\n" +
|
||||
"function get_multi_account_identity(player) {\n" +
|
||||
'\tif (player.temp_auth) return "owner:" + player.owner;\n' +
|
||||
'\tif (player.auth_id) return "auth:" + player.auth_id;\n' +
|
||||
"\treturn get_limit_identity(player);\n" +
|
||||
"}",
|
||||
"get_limit_identity",
|
||||
);
|
||||
|
||||
const newMerchant =
|
||||
"\t// Multi-account limits: configured in options.js; the backend cron uses the same values.\n" +
|
||||
'\tif (player.type == "merchant") {\n' +
|
||||
"\t\tvar merchant_limit = options.merchant_owner_limit === undefined ? 1 : options.merchant_owner_limit;\n" +
|
||||
"\t\tvar merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;\n" +
|
||||
"\t\tvar merchants = 0;\n" +
|
||||
"\t\tvar merchant_ips = 0;\n" +
|
||||
"\t\tvar merchant_identity = get_multi_account_identity(player);\n" +
|
||||
"\t\tfor (var id in players) {\n" +
|
||||
'\t\t\tif (id == player.socket.id || is_free(players[id]) || players[id].type != "merchant") {\n' +
|
||||
"\t\t\t\tcontinue;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t\tif (players[id].owner == player.owner) {\n" +
|
||||
"\t\t\t\tmerchants++;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t\tif (get_multi_account_identity(players[id]) == merchant_identity) {\n" +
|
||||
"\t\t\t\tmerchant_ips++;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\tif (merchants >= merchant_limit || merchant_ips >= merchant_ip_limit) {\n" +
|
||||
"\t\t\treturn false;\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\treturn true;\n" +
|
||||
"\t}";
|
||||
|
||||
source = replaceOnce(source, oldMerchant, newMerchant, "merchant branch");
|
||||
return source;
|
||||
}
|
||||
|
||||
function patchAdventureFunctions(source) {
|
||||
const marker = "var merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;";
|
||||
const oldMerchant =
|
||||
'\t\t\t\tif (player.type === "merchant" && (mips[player.ip] > 1 || mowners[player.owner] > 1) && to_disconnect.indexOf(player.name) === -1) {';
|
||||
const oldOther =
|
||||
'\t\t\t\tif (player.type !== "merchant" && (ips[player.ip] > 3 * (ipx[player.ip] || 1) || owners[player.owner] > 3) && to_disconnect.indexOf(player.name) === -1) {';
|
||||
|
||||
if (source.includes(marker)) {
|
||||
if (source.includes(oldMerchant) || source.includes(oldOther)) {
|
||||
fail("adventure_functions.js has a partial limits patch");
|
||||
}
|
||||
console.log("patch-multi-account-limits: adventure_functions.js already patched");
|
||||
return source;
|
||||
}
|
||||
|
||||
source = replaceOnce(
|
||||
source,
|
||||
"\t\t\tvar to_disconnect = [];",
|
||||
"\t\t\tvar to_disconnect = [];\n" +
|
||||
"\t\t\tvar merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;\n" +
|
||||
"\t\t\tvar merchant_owner_limit = options.merchant_owner_limit === undefined ? 1 : options.merchant_owner_limit;\n" +
|
||||
"\t\t\tvar ip_limit = options.ip_limit === undefined ? 3 : options.ip_limit;\n" +
|
||||
"\t\t\tvar character_limit = options.character_limit === undefined ? 3 : options.character_limit;",
|
||||
"limit variables",
|
||||
);
|
||||
|
||||
source = replaceOnce(
|
||||
source,
|
||||
oldMerchant,
|
||||
"\t\t\t\tif (\n" +
|
||||
'\t\t\t\t\tplayer.type === "merchant" &&\n' +
|
||||
"\t\t\t\t\t(mips[player.ip] > merchant_ip_limit || mowners[player.owner] > merchant_owner_limit) &&\n" +
|
||||
"\t\t\t\t\tto_disconnect.indexOf(player.name) === -1\n" +
|
||||
"\t\t\t\t) {",
|
||||
"merchant condition",
|
||||
);
|
||||
|
||||
source = replaceOnce(
|
||||
source,
|
||||
oldOther,
|
||||
"\t\t\t\tif (\n" +
|
||||
'\t\t\t\t\tplayer.type !== "merchant" &&\n' +
|
||||
"\t\t\t\t\t(ips[player.ip] > ip_limit * (ipx[player.ip] || 1) || owners[player.owner] > character_limit) &&\n" +
|
||||
"\t\t\t\t\tto_disconnect.indexOf(player.name) === -1\n" +
|
||||
"\t\t\t\t) {",
|
||||
"non-merchant condition",
|
||||
);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
let serverFunctions = fs.readFileSync(serverFunctionsFile, "utf8");
|
||||
serverFunctions = patchServerFunctions(serverFunctions);
|
||||
fs.writeFileSync(serverFunctionsFile, serverFunctions);
|
||||
|
||||
let adventureFunctions = fs.readFileSync(adventureFunctionsFile, "utf8");
|
||||
adventureFunctions = patchAdventureFunctions(adventureFunctions);
|
||||
fs.writeFileSync(adventureFunctionsFile, adventureFunctions);
|
||||
|
||||
console.log("patch-multi-account-limits: wrote " + serverFunctionsFile + " and " + adventureFunctionsFile);
|
||||
Reference in New Issue
Block a user