audit finding #13, the last one. summon() gave every unit of a SummonNumber>1 card the identical command point, so goblins/archers stacked on one spot and their positions - which are hashed - diverged from the client, which places them on a ring. ported LogicMath::sin/cos: SIN_TABLE (91 entries, sin(deg)*1024) extracted verbatim from the binary, with the same quadrant folding, plus sin/cos scaled helpers. then LogicBattle::getSpawnOffset: the per-count ring (radius = 1000*collisionRadius / sin(180/n,1000), angle = base + 360*index/divisor + 90), with the bottom player's y mirrored. summon() adds the offset per unit. tests: sin(0/90/180/270)=0/1024/0/-1024, cos(0)=full scale, a single unit gets (0,0) and a pair splits across x; goblins no longer share a point. residual: the per-unit deploy stagger (charData[+200]*index/count % DeployTime) is not applied - all units still deploy_timer=DeployTime; that field's source column is unidentified, left for later. this closes the 17-item audit's actionable list. remaining known gaps are the deploy stagger above and the heartbeat follow-up (bot commands in-band + drop periodic snapshots). |
||
|---|---|---|
| assets | ||
| config | ||
| crates | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
scroll-server
A server for the scroll client (Clash Royale 2.0.1306, iOS). Takes a real
client from TCP connect to the lobby: login, own home data, chests, card
upgrades, the shop.
Requirements
- Rust 1.75+
- PostgreSQL 16
Running
createdb scroll
cargo run --release -p scroll-server
Listens on 0.0.0.0:9339, the port the client hardcodes. The schema is created
on first boot. DATABASE_URL defaults to postgres:///scroll.
On macOS a Homebrew cluster may need LC_ALL=C to start at all.
That runs everything in one process. For real isolation, SCROLL_MODE=supervised
makes it spawn the three services as child processes and restart any that dies,
with backoff:
SCROLL_MODE=supervised ./target/release/scroll-server
They can also be started by hand, in any order:
cargo run --release -p auth-service
cargo run --release -p game-service
cargo run --release -p gateway
Checking it without a phone
scroll-probe speaks the same protocol and prints the decoded lobby:
cargo run --release -p gateway --bin scroll-probe -- 127.0.0.1:9339
Flags: --claim-free-chest, --buy-chest <name>, --desync. Pass an account
and pass token to reconnect as an existing player:
scroll-probe 127.0.0.1:9339 0 1 <passtoken>.
Pointing a client at it
The client hardcodes game.clashroyaleapp.com. Patch that string in the binary
inside the ipa to your machine's address, keeping the 23 byte slot NUL padded so
nothing around it moves.
Configuration
| variable | default |
|---|---|
DATABASE_URL |
postgres:///scroll |
SCROLL_MODE |
single, or supervised |
SCROLL_BIN_DIR |
next to the running binary |
SCROLL_AUTH_LISTEN |
127.0.0.1:9401 |
SCROLL_GAME_LISTEN |
127.0.0.1:9402 |
SCROLL_GATEWAY_LISTEN |
0.0.0.0:9339 |
SCROLL_CSV_ROOT |
assets |
SCROLL_SHOP |
config/shop.json |
SCROLL_STARTER_PROFILE |
built in |
RUST_LOG |
info |
Shop
config/shop.json lists what the server sells, as Name#count:
{
"offers": [
{ "id": 1, "give": "Gold#1000" },
{ "id": 5, "give": "chest:Gold#1" },
{ "id": 7, "give": "Diamonds#500", "cost": "Gold#20000" }
]
}
Names resolve against the csv tables. cost is optional; without it the server
charges the same price the client computes from the game data.