No description
Find a file
WiseDev 447cc091bc spread multi-unit cards on the client's cos/sin ring
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).
2026-08-24 14:33:06 +03:00
assets pull the arena tilemaps out of the ipa 2026-08-23 10:13:37 +03:00
config generate the daily card shop server side 2026-08-23 08:47:55 +03:00
crates spread multi-unit cards on the client's cos/sin ring 2026-08-24 14:33:06 +03:00
.gitignore nuke the tests and the dead code 2026-08-23 07:51:04 +03:00
Cargo.lock move players and accounts into postgres 2026-08-23 08:40:13 +03:00
Cargo.toml supervised mode: run the services as child processes 2026-08-23 09:46:59 +03:00
LICENSE add MIT license 2026-08-23 09:13:19 +03:00
README.md supervised mode: run the services as child processes 2026-08-23 09:46:59 +03:00

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.