#!/usr/bin/env python3 """GDPS patcher: redirects Geometry Dash 2.2 binaries to a private server. Replaces http(s)://www.boomlings.com/database (plain and base64 forms) with the GDPS URL of the SAME length, so the binary layout is untouched. Usage: python3 gdps_patch.py GeometryDash.exe [libcocos2d.dll ...] python3 gdps_patch.py libcocos2dcpp.so Options: --url URL base URL of the server for http:// (default below) --no-backup do not create *.bak copies """ import base64, os, shutil, sys DEFAULT_URL = "http://gdps.themaster2009sf.ru/db" ORIG_HTTP = "http://www.boomlings.com/database" ORIG_HTTPS = "https://www.boomlings.com/database" def fit(url: str, length: int) -> str: """Make url exactly `length` chars by doubling the slash before the path.""" if len(url) > length: raise SystemExit(f"URL '{url}' longer than {length} chars - choose a shorter one") scheme, rest = url.split("://", 1) host, _, path = rest.partition("/") pad = length - len(url) return f"{scheme}://{host}{'/' * (pad + 1)}{path}" def build_pairs(url: str): host_path = url.split("://", 1)[1] new_http = fit("http://" + host_path, len(ORIG_HTTP)) new_https = fit("https://" + host_path, len(ORIG_HTTPS)) pairs = [] for old, new in ((ORIG_HTTPS, new_https), (ORIG_HTTP, new_http)): pairs.append((old.encode(), new.encode())) b_old, b_new = base64.b64encode(old.encode()), base64.b64encode(new.encode()) pairs.append((b_old, b_new)) # url-safe base64 variant (GD uses it for some strings) pairs.append((base64.urlsafe_b64encode(old.encode()), base64.urlsafe_b64encode(new.encode()))) # base64 without padding pairs.append((b_old.rstrip(b"="), b_new.rstrip(b"="))) out, seen = [], set() for o, n in pairs: assert len(o) == len(n), (o, n) if o not in seen: seen.add(o) out.append((o, n)) return out def patch_bytes(data: bytes, pairs): total, report = 0, [] for old, new in pairs: c = data.count(old) if c: data = data.replace(old, new) total += c report.append(f" {old.decode()} -> {new.decode()} x{c}") return data, total, report def patch_file(path: str, pairs, backup=True) -> int: with open(path, "rb") as f: data = f.read() new, total, report = patch_bytes(data, pairs) print(f"[{os.path.basename(path)}] replacements: {total}") print("\n".join(report)) if total: if backup and not os.path.exists(path + ".bak"): shutil.copy2(path, path + ".bak") assert len(new) == len(data) with open(path, "wb") as f: f.write(new) return total def main(argv): url, backup, files = DEFAULT_URL, True, [] it = iter(argv) for a in it: if a == "--url": url = next(it) elif a == "--no-backup": backup = False elif a in ("-h", "--help"): print(__doc__); return 0 else: files.append(a) if not files: print(__doc__); return 1 pairs = build_pairs(url) grand = 0 for p in files: if not os.path.isfile(p): print(f"skip: {p} not found"); continue grand += patch_file(p, pairs, backup) if grand == 0: print("Nothing replaced: file already patched or not a GD 2.2 binary.") return 2 print("Done.") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))