I’ve been playing Granblue Fantasy for years now, and like most people, I was using Viramate. It’s a browser extension that adds keybindings and quality of life stuff that should’ve been in the game to begin with.
When they started detecting and banning people for using it, I got curious. My friends got hit, I didn’t, but I wanted to understand how they were actually catching people. So I started digging through their obfuscated JavaScript.
The fundamental problem
Browser games are basically impossible to secure. You’re running everything client-side, which means:
- Anyone can modify the code
- Anyone can intercept network requests
- Anyone can manipulate the DOM
- Anyone can mess with execution timing
- Anyone can inject scripts
You can’t prevent this. The only thing you can do is detect when someone’s doing it and report back to the server.
Three separate modules
They don’t just have one anti-cheat system. They have three modules (util/od, util/ob, and util/oc) that do essentially the same thing but with different detection codes and timing patterns.
I’m guessing this is for redundancy and to make reverse engineering more tedious. If someone figures out how to bypass one module, the other two are still running.
String obfuscation
Every string in their code is built character by character:
var joinStrings = function() {
return $.makeArray(arguments).join("");
};
// Instead of "length"
var length = joinStrings("l", "e", "n", "g", "t", "h");
// Instead of "XMLHttpRequest"
var xhr = joinStrings("X", "M", "L", "H", "t", "t", "p", "R", "e", "q", "u", "e", "s", "t");
This avoids pattern matching from automated scanners. If you’re searching for code that checks “XMLHttpRequest” modifications, you won’t find that exact string in the source. Any debugger shows you the real strings at runtime anyway.
Batch reporting
The core system collects detections and sends them in batches to different endpoints:
// Module 'od' reports to "od/query"
sendData("query", payload);
// Module 'ob' reports to "ob/r"
sendData("r", payload);
// Module 'oc' reports to "oc/s"
sendData("s", payload);
Different timing per module:
- od: 3011ms delay
- ob: 5011ms delay
- oc: 3011ms delay (but doesn’t actually send - more on this later)
Each suspicious action gets assigned a detection code. Instead of immediately reporting everything, they batch it up. This avoids flooding their backend and makes it harder to correlate your actions with the reports.
Click pattern analysis
// od & oc: 5011ms window
tapCount = ((event.x || event.y) && dateNow() - lastTime < 5011) ? 0 : tapCount + 1;
// ob: 7011ms window
tapCount = ((event.x || event.y) && dateNow() - lastTime < 7011) ? 0 : tapCount + 1;
They’re not just looking for rapid clicking. They’re systematically capturing coordinates for every game action:
// Pattern used across multiple actions
tB = [M.FIXED_VALUE_X + t.x, M.FIXED_VALUE_Y + t.y, actionType];
// Found in:
// - onTapStartDirection (starting auto)
// - AddNormalAttackQueue (attacks)
// - popShowAbility (abilities)
// - renderPopSummonDetail (summons)
// - changeAutoMode (changing auto)
Every attack, ability use, and auto-battle toggle needs to come with believable interaction coordinates. If you’re a bot just sending API calls without proper tap data, you’ll trigger detection codes 1001 or 1100.
This is also how they build behavioral profiles.
Performance monitoring
They check for abnormal frame rates:
// od & oc: code 7001, ob: code 1700
// Triggers if FPS > 35
return window.createjs && window.createjs.Ticker &&
window.createjs.Ticker.getFPS() > 35;
They detect timing manipulation by actually testing it:
var currentFPS = window.createjs.Ticker.getFPS();
var currentInterval = window.createjs.Ticker.getInterval();
// Temporarily change the interval
window.createjs.Ticker.setInterval(currentInterval + 100);
var isManipulated = (window.createjs.Ticker.getFPS() == currentFPS);
window.createjs.Ticker.setInterval(currentInterval);
They change the frame interval and check if FPS actually responds. If someone’s overridden the timing functions to speed-hack, this catches it. Detection codes 7002 (od/oc) or 2700 (ob).
Known tool detection
They check for specific tools with DOM queries:
// Viramate - od/oc: 8001, ob: 1800
'script[src^="chrome-extension://fgpokpknehglcioijejfeebigdnbnokj"]'
// Other tools
'[id^=gbfTool]' // od/oc: 9001, ob: 1900
'[id^=guraburu]' // od/oc: 9003, ob: 3900
'input[id*=boss_mode_1]' // od/oc: 9005, ob: 5900
Viramate’s Chrome extension ID is hardcoded across all three modules. The other selectors look for DOM elements that only exist when specific cheating tools are running.
Network interception detection
// od/oc: 10102, ob: 21010
return window.hookAjax || window.unHookAjax || window._ahrealxhr;
Catches AJAX hooking libraries that intercept and modify network requests.
Code integrity checks
They check if you’ve modified core JavaScript objects:
var originalXHRLength = (window.XMLHttpRequest + "").length;
// Later...
return originalXHRLength !== (window.XMLHttpRequest + "").length;
Converting objects to strings and comparing lengths detects if you’ve replaced or modified them. They do this for game-specific code too, especially in raids.
The monitoring loop
All checks run continuously with exponential backoff:
var validateWithRetry = function(code, delay, increment, checkFunction) {
var check = function() {
if (checkFunction()) {
queueQuery(code);
} else {
// od & oc: 4001 exception, ob: 1400 exception
if (4001 !== code) { // or 1400 for ob
delay += increment;
}
setTimeout(check, delay);
}
};
setTimeout(check, delay);
};
Most checks start every 5-7 seconds and back off if nothing’s detected. The special codes (4001 for od/oc, 1400 for ob) maintain constant intervals - these are baseline “nothing detected” states.
Debug mode detection
var hashPrefix = window.location.hash.split("/")[0];
return hashPrefix !== "debug";
Returns true when you’re not in debug mode:
- Normal players: Continuously report baseline codes (4001 for od/oc, 1400 for ob)
- Debug mode users: Stop sending baseline codes
If you’re in debug mode, you go silent on baseline reporting. Since the system expects normal players to constantly ping with these codes, stopping is itself suspicious.
One module doesn’t work
Module oc is broken. It does all the detection work and queues reports, but never calls $.ajax() to actually send them.
Could be:
- Disabled backup system
- Honeypot to confuse reverse engineers
- Bug that nobody noticed
- Meant to be enabled later
Detection codes
Module ‘od’ & ‘oc’:
1001 - Abnormal clicking patterns
2002 - XMLHttpRequest tampering (od only)
2003 - Raid code modifications (od only)
4001 - Normal player baseline
7001 - FPS > 35
7002 - Timing manipulation
8001 - Viramate
8002 - Marketing/tracking elements
9001 - GBF Tool
9002 - GFE tool
9003 - Guraburu tool
9004 - TKE tool
9005 - Boss mode in DOM
10102 - AJAX hooking
Module ‘ob’:
1100 - Abnormal clicking patterns
1400 - Normal player baseline
1700 - FPS > 35
1800 - Viramate
1900 - GBF Tool
2700 - Timing manipulation
2800 - Marketing/tracking elements
2900 - GFE tool
3900 - Guraburu tool
4900 - TKE tool
5900 - Boss mode in DOM
21010 - AJAX hooking
Is it worth it?
Three near-identical modules with different codes for the same detections. One of them doesn’t even send data. The obfuscation is trivial to bypass with a debugger.
But it works for what it is: client-side telemetry. It filters out casual tool users, collects data on what’s being used, and makes automation slightly more annoying to write. The actual security is server-side - rate limiting, behavioral analysis, sanity checks on game state.
This is just logging.