Hack Any Webapp

Case Studies

Two points on one design continuum: a minimal, fail-closed, Model-B system and an elaborate, fail-open, Model-A-client/Model-B-server system.

Gizmo AI Unlimited

Shape: One monorepo: patcher/ (Node CLI, its own lockfile) and src/ (Plasmo MV3 extension) side by side. A single GitHub Action drives the patcher on a 2-hour schedule.

Evolution: 0.1.0 Plasmo scaffold → 0.2.0 SPA-nav/UI → 1.0.0 drop over-broad CSS selectors → 2.0.0 adopt declarativeNetRequest → 2.0.1 Model A2.1.0 broaden reload to all gizmo.ai subdomains → 2.2.x Model B.

Payload: Just two subscription regexes: one that gates the paywall UI (isSubscribedStore) and one that gates the game mechanics (state.snapshot.subscription.status). The functional surface is tiny; almost all the engineering is in delivery and resilience, not in the patch itself.

patcher/src/patches.ts
// Force isSubscribedStore reads to return true (paywall UI)
{ id: "is-subscribed",
find: "\\br\\(_?d\\[\\d+\\]\\)\\.isSubscribedStore\\.get\\(\\)",
flags: "g", replace: "(!0)", minMatches: 1 }

// Force the game-state subscription status check to 'subscribed'
// (unlimited hearts/hints, no cooldown, no life/hint consumption)
{ id: "subscription-status",
find: "'subscribed'===\\(0,[\\w$]+\\.default\\)\\(this,[\\w$]+\\)\\[[\\w$]+\\]\\.state\\.snapshot\\?\\.subscription\\?\\.status",
flags: "g", replace: "(!0)", minMatches: 1 }

(!0) is chosen as the universal replacement because it is a self-contained primary expression evaluating to true that stays grammatically valid in every surrounding context the minifier produces: in a ternary, a logical-OR, a negation, or a bare if. A naive ||!0 appended to a ??-laden expression would form the illegal ||…?? mix and brick the bundle.

Distribution today: Model B. The primary artifact is patches.json at 756 B. The 19 MB entry.min.js is retained only as a verification copy.

Fail policy: Fail-closed: if any rule matches fewer than minMatches times, the patcher throws, refuses to write, and the CI job opens a deduplicated GitHub issue while leaving the last-known-good artifact in place.

Source → github.com/alexey-max-fedorov/gizmo-ai-unlimited

Play Origin / P-NP

Shape: Two repos. P-NP is the server-side patcher. ProdigyOrigin is the client extension and a second build artifact: originGUI/, the in-game mod menu, bundled with esbuild and committed so the patcher suffix can fetch it at runtime. typings/ holds reverse-engineered *.d.ts files for the game.

This split mirrors the Gizmo monorepo's internal patcher/ vs src/ boundary but across repository lines, letting the patcher and the extension version, release, and even rebrand independently.

A richer patch payload

Where Gizmo flips two booleans, P-NP installs six structural hooks, with $n back-references resolved against regex capture groups in the replacement string:

P-NP/src/patches.ts
{ id: "singleton-exposure",
find: "(_instance=this\\),this\\._game=([A-Za-z_$][\\w$]*)\\}destroy\\(\\)\\{)",
flags: "", replace: "_instance=this),window.__PNP__=this,this._game=$2}destroy(){", minMatches: 1 }

{ id: "answer-question-bypass", /* auto-answer before the isOpen check */ }
{ id: "external-factory-bypass", /* …  */ }
{ id: "open-question-bypass",    /* …  */ }

{ id: "safe-bind",
/* idempotent Inversify (re)bind wrapper - matched 9× live */ }

{ id: "expose-constants",
/* alias the GameConstants map to window */ }

The runtime wrapper: a second patch layer

Beyond find/replace, P-NP wraps the patched core in a generated prefix and suffix (src/wrappers.ts) that build an entire modding runtime at load time, designed to stay resilient to the game's minifier churn.

DI service discovery by behavioral shape: Instead of hard-coding service IDs that drift every game build, __pnp_discoverService enumerates the Inversify container's binding dictionary and matches services by duck-typing:

P-NP/src/wrappers.ts
var nm = __pnp_discoverService(gc, (inst) =>
  typeof inst.getCharData === 'function'
  && ('processPlayer' in inst)
  && typeof inst.sendZoneEvent === 'function', "NetworkManager");

The same technique discovers PlayerService and MembershipService. This is the resilience philosophy of the patcher (don't bind to volatile identifiers) carried into the runtime.

A self-healing window._ facade: Lodash loads as a separate script after game.min.js and overwrites window._; the game keeps reassigning it too. The suffix installs accessor-based properties (_.instance, _.player, _.network, _.membership, _.constants, …) and a 500 ms setInterval that re-applies them whenever they go missing, plus a singleton auto-fix that swaps __PNP__ to the real fully-initialized instance once #prodigy is set.

Deep membership bypass: _.functions.setMembership(true) rewrites the discovered membership service's _data and forces hasFeatureAccess() to return true, auto-promoting to Ultra tier (derived from three feature checks).

Runtime mod-menu loader: 15 seconds after load, the suffix evals the menu bundle fetched from window.__ORIGIN_MENU_URL__, supplied by the extension via manifest.defaultMenuUrl, with a baked-in fallback URL pointing to the committed originGUI/dist/bundle.js.

Branding split without code churn

Play Origin rebranded from "Prodigy Origin" in May 2026 but deliberately froze all code-level identifiers (repo names, window.__PNP__* globals, the %cP-NP Patcher DevTools banner, data-origin-* attributes, originGameUrl/originGuiUrl storage keys) because renaming any of them would break raw.githubusercontent.com URLs, clone paths, or require a storage-migration shim. Only the most user-visible string changed: the [P-NP][Play Origin] console prefix. User-facing brand and technical identity can and should be decoupled in any system whose URLs are load-bearing.

Cross-system comparison

AxisGizmo AI UnlimitedPlay Origin (ProdigyOrigin + P-NP)
Repo topologyMonorepo (patcher + extension)Split: extension (ProdigyOrigin) ↔ patcher (P-NP)
Distribution model (today)Model B: rules + local applyModel A: file-pull, with Model B staged server-side
Patch payload2 regex rules (boolean flips)6 regex rules + a generated runtime prefix/suffix
Rule enginePlain find/replaceFind/replace with $1…$n back-references
Fail policyFail-closed: throw, keep last goodFail-open: patchDegraded flag, still publish
Primary artifactpatches.json (756 B)manifest.json (~20 KB: rules + prefix + suffix + menu URL)
Client cacheYes (~18 MB, unlimitedStorage)None: file-pull on each load
Bridge mechanismRequest/response CustomEventsdata-origin-* attributes + data-origin-ready handshake
Extra DNR useBLOCK onlyBLOCK + strip CSP/XFO + 2 cosmetic redirects
Runtime extrasConsole markersDI service discovery, membership bypass, mod-menu eval-loader
Common primitiveonreset + var URL=window.URL;onreset + var URL=window.URL; (+ semaphore line)
Self-heal cadenceEvery 2 h, issue on failureEvery 2 h, issue on failure

The systems are two points on one design continuum. Gizmo demonstrates the minimal, fail-closed, Model-B end; Play Origin demonstrates the elaborate, fail-open, Model-A-client/Model-B-server end. Both share the same spine: a scheduled re-deriving patcher, network-level blocking, MAIN/ISOLATED separation, and the onreset injection primitive.