The launcher reads the official list from the ReSkate backend (or the last one it read, or its own)
and keeps each mod in Mods/.reskate-official at its latest version; a copy the player installed is
moved in, the deprecated Board Break retired. The game loads them always on and leaves out Mods
folder copies; the browser no longer lists them. The built-in zips are gone (FPV drone too), and
built-in folders no longer carried are removed.
Fourth Studio's Board Break pack (ten decks and every sound) and Road Rash wounds are built into
ReSkate.dll beside the FPV drone, unpacked under Mods/.reskate-builtin and always on.
- The Thunderstore copies (Fourth_Studio-Board_Break_Pack, Fourth_Studio-Road_Rash and the
deprecated Fourth_Studio-Board_Break, or the same put in by hand) are moved to the Recycle Bin
as the launcher starts and dropped from mods.json; the merge leaves them out should one still
be there, and the Thunderstore browser no longer lists, updates or installs them, a dependency
on one included.
- Board Break's deck list, sounds and deck settings look in the built-in packs too.
- Names with spaces inside them (Board Break's "Body Impact 1.wav") unpack.
- The build makes each zip in assets/builtin into one zstd stream (ReSkateBuiltinPacker,
Engine/Vfs/builtin_pack_format.h), which the DLL carries: repeated files across a pack cost next
to nothing. Board Break 15.0 -> 5.4 MB, the drone 6.0 -> 5.4 MB, Road Rash 36.1 -> 34.2 MB;
ReSkate.dll 86 -> 70 MB. Plain zips still unpack.
- Tests: the mods taken for built-in ones and their retirement, and a pack unpacking to exactly
its files, refusing one that leaves its folder or is cut short (RESKATE_BUILTIN_PACKS also
unpacks the packs a build made).
After Steam updated skate., some installs had the supported Skate.exe over
the new build's data (a copied game, or a download that stopped part-way).
The launcher called that ready, every mod was left out as "made for game
build 29544144, not the installed 29663206; rebuild it with ReSkate Studio",
and the mods stayed out after the game files were put right.
Launcher
- Ready now also needs the game's data to be the supported build, read from
Data\layout.toc against supported_build::game_data_changelist (29544144).
Data of another build, or none, reads "Game files need verifying".
- DOWNLOAD in that state checks every file (-validate) instead of fetching
only what is missing. A download that fails part-way no longer ends on
ready because Skate.exe arrived.
- Data whose build cannot be read does not block.
Mods
- A mod left out because the game was of another build is no longer
remembered in .reskate-excluded.json: it is checked on every launch. One
already remembered for it is looked at again.
- When it is the game that is not the supported build, the mod's message
says to verify the game files in the launcher, not to rebuild the mod.
Crash dumps now go to the project's own Sentry server. The launcher's
out-of-process helper is unchanged; only where it posts and what it expects
back are:
- The default endpoint is the Sentry project's minidump URL. Report
attributes travel as Sentry tags, with the release as reskate@<version>.
- An upload counts as accepted when the server answers with an event ID.
- "backtrace" is "crash_report" throughout: the namespace, files, CMake
targets, DINGOSDK_CRASH_REPORT_URL, DINGOSDK_BUILD_CRASH_REPORT_TESTS and
the RESKATE_CRASH_REPORT_URL override.
Performance timings are new, and off unless "Send performance timings" is
ticked in the launcher's settings (RESKATE_TRACING=1). A small tracer
(Engine/Core/Debug/trace.h) posts each finished step to Sentry as a
transaction from a background thread; the launcher and the game it starts
share one trace. Timed: the update check, the Steam download, Thunderstore
installs, the mod merge, starting the game, the launch up to the first
playable level, each level load, joining a session, fetching the identity
lists, and the time spent in each level with its average frame rate and
long frames. Turning crash reports off turns timings off too.
Memory
- The entity and pending-update pages are four times the game's own size
(524,288 slots), down from eight times. At eight, the game kept a
3.5 GB block for them that was fully in RAM on custom maps; the largest
map load seen needs about 150,000 slots. The render-instance pool and
spatial buckets stay at eight times.
- The log has a "Memory:" line when the game's memory, the players in the
session or the skaters built change: committed, in RAM, players, skaters
built, player distance and map.
- Console: `perf memory` lists the game's memory by block size.
`perf memory mark` remembers it, and the next `perf memory` says what
was added since. `perf memory trace` also records who asked for each
block until `perf memory stop`. `perf memory big` lists the largest
blocks with how much of each is in RAM and a sample of what is in them.
RESKATE_MEMORY_TRACE=<megabytes|all> starts the trace with the game.
Replay crash guard
- The game's replay recorder crashes when its stream has no room for the
next chunk: it writes to the chunk it was not given (Skate.exe+0x48efa34,
crash reports of 2026-10-10). The function now runs under a handler
that, for that one fault alone, returns as if no chunk was ended, and
says so in the log. Any other fault still goes to the crash reporter.
The skip path has not been triggered in testing.
Launcher
- `-map <name>` (also --map, -map=<name>) loads that map as soon as the
game is up, named as the console's `load` names one. With --no-gui and
no other options the launch still uses the saved launcher settings.
Pause menu
- The Multiplayer and Mod Options pages step aside while the game's side
menu, or a screen opened from it such as Settings, is over them. They
drew over both before.
- A button still held when a page comes back is not a press on it: the
Back that closed the side menu no longer dismissed the page as well.
Multiplayer cosmetics
- A player whose mods add or remove cosmetic slots (stickers, say) no
longer has the whole outfit refused. Their items go to this game's slots
by slot: a slot they lack stays empty, an item in a slot this game
lacks is left out, and the log says how many of each.
The game shows on the player's Discord profile where they are skating.
It talks to the Discord app on the same PC over its local pipe, with no
SDK; without Discord running nothing happens.
- Solo: "On <map>", "Solo skating", and how long they have been in.
- In a session: "On <map>", "Server: <name>" or "Lobby: <name>", the
player count, and how long since they joined. A session with a
password shows "Private server" or "Private lobby" and no name.
Joining: a status carries the session's join code when anyone may walk
in (no password, not full, a dedicated server or a publicly listed
lobby). Discord shows that as "Ask to Join"; the game answers yes for
the player at once, and the other player's game is handed the code and
joins. If their game is not running, Discord starts the ReSkate
launcher, which the game registers for under the player's own
discord-<application id> URL scheme.
Switches: "Discord status" in the launcher's settings (off starts the
game with --no-discord, and the game never opens Discord's pipe), and
the same in the in-game Multiplayer settings, kept in the profile.
The model gains join_code: the code of the session a player is in as
host or guest (invite was a host's only).
ReSkate.log only said "Steam is not running or not signed in", even with
Steam open and signed in. steam_offline_reason() names the check that
failed (ActiveProcess pid or ActiveUser is 0, the process cannot be
opened or identified, has exited, or is not steam.exe) and start_game
appends it to that line. steam_signed_in() behaves as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VNYF3aYa2q3MkFXc5xYnDd
Server browser
- Official servers (the backend's "official_servers") sort first and are
drawn in ReSkate blue
- Servers Steam friends are skating in sort next, drawn in green with the
friends' names; searching a friend's name finds their server
- A player in a session they joined can join another server straight from
the list; hosts still end their session first, and a Steam friend's join
offer never replaces a session
- Joining a dedicated server from the list records it as the player's
public session, so friends see it and the row shows HERE
- Blocked servers (the backend's "blocked_servers") are hidden and cannot
be joined, by code either
- When the backend sets "server_tokens_required", dedicated servers that
signed in without a login token are hidden, except on the player's own
network
- A search that gets no internet list from Steam keeps the last list for
two minutes, and waits six seconds before treating it as empty
Roles
- New Staff tag from the backend's "staff" list: green name, badge and
item animation, ranked after Dev
- Staff can pick a rainbow for their items on the Special page; it is sent
as a solid colour older builds can show, so the protocol is unchanged
Dedicated server
- steam_token: sign in with a Steam game server login token and keep the
same Steam ID every start; empty signs in anonymously as before
- A listed server without a token says so at startup, and when the token
rule starts or stops hiding it
- Linux servers update themselves from the release's "server_linux"
tarball, with the machine's curl and tar, pinned by SHA-256, and carry
on in the same process
Launcher
- Only create the MiniDumpWriteDump API-set shim under Wine/Proton
Toolhelp returns module paths through the ANSI code page, so a folder like "Skate Тест" came back with '?' in it, the injected DLL was never matched, and the launcher killed the game. The loader's own list keeps the real path.
Mod archives (Engine/Vfs/mod_merge*)
- A free archive index no longer has to sit at or below the highest index
the layout already declares. The engine keeps archives in a table keyed
by layer, install chunk and a 16-bit index, with no range to stay
inside; what it cannot survive is a reference to an archive the layout
never declared. Indices are taken lowest first, per package directory.
- So a mod that collides takes an index of its own and its file is hard
linked rather than concatenated, and no two mods share one archive's
4 GB of addressable bytes. Sharing is left for a mod added while the
game runs, which has no declared index to take, and for a directory
with no index left.
- A placement addresses its payload with a 32-bit offset, so both append
paths now refuse to cross 4 GB instead of wrapping round and pointing
the game at some other payload: CasStore::write fails with the archive
that is full, and the live-apply path checks archive 1's size plus the
mod's before appending and asks for a restart, which gives the mod an
archive of its own anyway.
Injection through a hooked LoadLibraryW (Engine/Core/Platform)
- A player could not launch at all: "Child LoadLibraryW entry bytes match
neither Windows' kernel32 file nor this process". Anti-virus and
overlays hook the entry with a jump to a per-process trampoline, so the
bytes differ in the launcher and in the child and neither comparison
could match. The guard already allowed the same hook when both
processes carried it identically, so it refused on an accident of how
the hook was written rather than on intent.
- inline_jump_length recognises the forms a hooking engine writes (rel32
jmp or call, push/ret, jmp [rip], mov rax/jmp rax, short jmp). When one
is present and every byte after it still matches the clean kernel32
file, the launch goes ahead: the body is Windows' own, and the provider
image already matched on timestamp, checksum and size. A wholesale
replacement is still refused, and now says so.
- The failure names the modules loaded into Skate that came from neither
Windows nor the game folder, instead of guessing at "security
software". An accepted hook is logged as a warning with the same list.
Launching (Launcher/launch.cpp)
- Exporting loose scripts is a convenience, and a folder that would not
take it was failing the whole launch: fs::create_directories threw and
nothing caught it. The export is skipped, RESKATE_LOOSE_FILES goes to
0 so the runtime does not look for files that are not there, and Skate
starts. Someone whose install is under Program Files can play; only
editing loose scripts is lost.
Failures worth reading (Launcher/problem.h)
- A hooked LoadLibraryW, a refused write and ERROR_ELEVATION_REQUIRED
each explain themselves now. Windows reports the last two in the user's
own language -- "Zugriff verweigert", "La operación solicitada requiere
elevación" -- so the rules key on the operation names and the error
number, which are never translated.
Saved skaters (Extension/Customization, Extension/Profile)
- Outfits stopped saving and every slot showed the standard skater. The
load hook marks a preset blocked on entry and clears it only on a full
success, so a load refused for a catalog that was not ready yet left
the preset blocked for the session and every later save was refused as
previous_load_rejected. The game asks once per level load and does not
ask again, so one lost race cost a skater until a restart. Logs show
the catalog arriving three seconds after the refused load, at startup
and again on a map switch 17 minutes later.
- The record is remembered in CosmeticRuntime::pending_loads and applied
by retry_pending_cosmetic_loads from the customization pump, which
already waits for refresh_cosmetic_catalog. The preset stays blocked
until a load really succeeds, so nothing is written over the outfit on
disk.
- Those records belong to the loadout manager's array, which can be
replaced in between, so each pointer is checked against the live array
exactly as a save checks one -- manager current, begin/end sane, 0x48
stride, at most 10 records, in range and aligned. A record that no
longer belongs is dropped as retry_record_moved rather than written
through.
Under Steam + Proton on Linux the launcher always reported Steam as
offline, so the game ran on the offline stand-in and servers could not be
joined. steam_signed_in() needs ActiveProcess\ActiveUser, and Proton's
stand-in steam.exe only writes ActiveProcess\pid.
- When ActiveUser is missing and the launcher is running under Proton,
the account comes from the native Steam client instead: the most recent
entry in config/loginusers.vdf under STEAM_COMPAT_CLIENT_INSTALL_PATH,
which Steam sets for compatibility tools and Wine's
wine_get_dos_file_name maps to a DOS path.
- The Steam name on the launcher's plate and the SteamID that --offline
reports are read from that directory too. Proton's
C:\Program Files (x86)\Steam holds no account files, so offline mode
fell back to 76561197960265729.
- loginusers.vdf's MostRecent key is matched in either case. Steam has
also written it as "mostrecent", which picked the first account in the
file instead of the most recent.
Windows is unchanged: the variable and the Wine export are both absent
there, so ActiveUser is still required.
Diagnosed, with a confirmed workaround and the suggested fix, by
BenjaminJAnderson.
FixesDingo-Shenanigans/ReSkate#5
Co-authored-by: BenjaminJAnderson <45207455+BenjaminJAnderson@users.noreply.github.com>