{"id":51029111,"url":"https://github.com/sawyershoemaker/omoggle-fluox","last_synced_at":"2026-06-21T22:30:39.591Z","repository":{"id":363011174,"uuid":"1261637956","full_name":"sawyershoemaker/omoggle-fluox","owner":"sawyershoemaker","description":"source-level facial-landmark spoof for omoggle's live scoring, with a full reverse-engineering writeup","archived":false,"fork":false,"pushed_at":"2026-06-07T00:48:29.000Z","size":21,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T03:34:04.078Z","etag":null,"topics":["omoggle","reverse-engineering","script","tampermonkey"],"latest_commit_sha":null,"homepage":"https://omoggle.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sawyershoemaker.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-07T00:43:38.000Z","updated_at":"2026-06-07T00:48:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sawyershoemaker/omoggle-fluox","commit_stats":null,"previous_names":["sawyershoemaker/omoggle-fluox"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sawyershoemaker/omoggle-fluox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sawyershoemaker%2Fomoggle-fluox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sawyershoemaker%2Fomoggle-fluox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sawyershoemaker%2Fomoggle-fluox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sawyershoemaker%2Fomoggle-fluox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sawyershoemaker","download_url":"https://codeload.github.com/sawyershoemaker/omoggle-fluox/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sawyershoemaker%2Fomoggle-fluox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34628453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["omoggle","reverse-engineering","script","tampermonkey"],"created_at":"2026-06-21T22:30:38.950Z","updated_at":"2026-06-21T22:30:39.583Z","avatar_url":"https://github.com/sawyershoemaker.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# omoggle-fluox\n\ntampermonkey script that artificially boosts score on [Omoggle](https://omoggle.com) by editing the facial landmark geometry at its source rather than tampering with score values after the fact. most scripts being circulated interact with ever-changing stored values, breaking scripts and adding detection vectors with each update. editing landmark geometry locally allows for minimal tampering and the inability to break with updates.\n\n\u003e for the full reverse-engineering process (capturing traffic, getting past cloudflare, deobfuscating the bundle, decoding the binary frame, and reversing the scoring pipeline), see the [writeup](writeup.md).\n\n## 1. typical scripts\n\na typical paid script for this patches the reported scores by intercepting the websocket message.\n`{\"type\":\"score_update\",\"payload\":{\"score\":5.7}}` and changing the 5.7 to 8.5\n\nhowever, with the newest update:\n\n- the readable `score_update` / `score_submit` messages are SECONDARY\n- the client also streams raw facial landmarks to the server every 500 ms in a binary frame\n- ^ server can recompute score from those landmarks, checking for inconsistencies and handing out bans\n\nso if you boost the readable number but leave landmarks untouched, there is now a contradiction\n\nwe just need to attack the single source of truth, \u003cins\u003ethe landmarks themselves\u003c/ins\u003e. :)\n\n## 2. architecture\n\nthis is what a deobfuscated version of omoggle's (poorly coded) per-frame loop looks like:\n\n```js\nm = faceLandmarker.detectForVideo(video, performance.now());\nlet j = m.faceLandmarks?.[0] ?? null;  // the landmark array with about 478 points {x,y,z}\nif (j \u0026\u0026 j.length \u003e= 468) {\n  let e = computeMetrics(j, {videoWidth, videoHeight, ...});  // geometry to metrics to overall score\n  // ^ this drives the on screen score, SCAN_STATE (data channel to opponent), and the binary ws frame\n}\n```\n\nthis means that every consumer of the score: the ui, p2p `SCAN_STATE`, and the binary telemetry all read from the same `j`. if we can mutate `j` once, immediately after `detectForVideo` returns, then everything downstream will be derived from our modified geometry, allowing everything to remain mutually consistent.\n\ntherefore we only wrap one function:\n\n```js\nFaceLandmarker.prototype.detectForVideo\n```\n\n## 3. scoring pipeline\n\nhow does this fancy system score your face anyways?\n\nwell, it computes a 0-10 `overall` score from the given landmarks.\n\nreproduced roughly from bundle:\n\n```text\n#normalization\ni  = videoWidth / videoHeight                 # for aspect correction\no(p)  = { x: p.x * i, y: p.y }                # stretch x into a square frame\nroll  = angle(o[10], o[152]) - 90             # determine head roll from forehead(10) to chin(152)\np(idx) = rotate(o[idx], around nose o[1], by -roll)   # get non rotated and upright face\n\n# metrics\ncanthalTilt (eyes) = -avg( angle(33,133), angle(362,263))\njawWidth           = maxJawPairDist / faceHeight(10,152)\nsymmetry           = 100 * (1 - asymmetry / 0.09)\nmidfaceRatio       = |lip(0).y - eyeLineY| / faceHeight\ncheekboneWidth     = cheekWidth(234,454) / jawWidth\neyeAspectRatio     = avg(eyeHeight/eyeWidth, left \u0026 right)\neyeSpacing         = dist(133,362) / faceWidth\n\n# subscores from band-mapping F() \u0026 a weighted sum\nharmony J = .18*jaw + .24*midface + .18*cheek + .16*eyeAsp + .24*F(spacing)\nZ = .12*eyes + .14*jaw + .024*symmetry + .14*midface + .1*cheek + .08*eyeAsp + .18*J\noverall = round( 10 * clamp(Z * qualityMultiplier, 1.1, 10) ) / 10\n```\n\ntwo *very* popular helper functions:\n\n```js\nA = (a, b) =\u003e Math.hypot(a.x - b.x, a.y - b.y);          // distance\nD = (a, b) =\u003e Math.atan2(b.y - a.y, b.x - a.x) * 180/pi;  // angle in deg\n```\n\n`overall` rises when the metrics land inside their ideal bands, so we just move the landmarks such that each metric sits at the center of its band!\n\n(`qualityMultiplier` is a separate gate that comes from pose/centering/stillness, capping the final score.)\n\n---\n\n## 4. morph algorithm\n\n`morphLandmarks(lm, videoW, videoH, strength)` mutates the existing landmark array. fear not, it operates in\nthe same aspect corrected and de‑rotated space that the app uses, so each adjustment maps directly to the metric.\n\n### 3 steps for a man, 1 step for mankind\n\n1. compute aspect `i`, head roll `c`, nose center `d` and project the *touched* indices into p‑space with `p(idx)`.\n  don't forget to save the original raw coordinates for the final blend!\n  additionally, our references: (forehead `10`, chin `152`, nose `1`) are read but we don't move them since they fix scale and orientation for us.\n2. per-metric adjustments with each pushing one metric toward its band center (as previously explained)\n    - for eye aspect, set the vertical eyelid gap = `0.26 * eyeWidth` for each eye (indices 159/145, 386/374).\n    - for jaw width, scale the jaw points (172/397, 150/379, 171/396) horizontally about the face axis so `jawWidth/faceHeight` goes towards `0.68`.\n    - for cheekbone width, scale cheek points (234, 454) so `cheekWidth/jawWidth` goes towards `1.14`.\n    - for midface ratio, move the upper‑lip point (0) vertically so `|lip.y - eyeLine| / faceHeight` goes towards `0.305`.\n    - for symmetry, each of the 19 symmetric landmark pairs must be equalized such that each point's distance from the vertical/horizontal center to asymmetry to 0 to symmetry to 100.\n    - for canthal tilt, rotate each eye‑corner line (33 to 133, 362 to 263) so its angle reaches the value that yields tilt roughly 4.25.\n3. invert and blend by projecting each adjusted point back to raw normalized coordinates\n  equationally, this looks like adding c, dividing x by i, and then linearly interpolating between the real and ideal positions by `strength`\n\n---\n\n## 5. controls\n\n\n| key                                    | result                 |\n| -------------------------------------- | ---------------------- |\n| `=` / `+`                              | strength +5%           |\n| `-` / `_`                              | strength −5%           |\n| `0`                                    | reset to default (50%) |\n| left/right screen edge tap (on mobile) | −/+ strength           |\n\n\nconsole usage (`window.__fluox`):\n\n```js\n__fluox.strength            // strength value\n__fluox.setStrength(0.7)    // unnecessary mutator (shoutout ap csa)\n__fluox.bump(+0.05)         // increase\n__fluox.reset()             // reset\n__fluox.ops                 // toggles: { tilt, eyeAspect, jaw, cheekbone, midface, symmetry }\n__fluox.targets             // ideal band centers\n__fluox.diagnose()          // { hooked, classLabel, framesMorphed, lastVideo, lastError, webpackReq, ... } (mostly for debugging)\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsawyershoemaker%2Fomoggle-fluox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsawyershoemaker%2Fomoggle-fluox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsawyershoemaker%2Fomoggle-fluox/lists"}