"Cannot open file for SHA-256" was all a player saw when the launcher
could not read ReSkate.dll, at startup and again right after each
download. That is an antivirus holding or blocking the file.
- A sharing violation is waited out for up to five seconds: a file that
was just written is often held by the scanner for a moment.
- Otherwise the error names the file and its folder, says to add the
folder to the antivirus exclusions where that is the likely cause, and
carries the Windows error code.
A double-clicked server closed its window with the error still in it. It only
waits when it is alone on a visible console, so terminals, hosting panels and
hidden scheduled tasks still see it exit at once.
A port of 70000 was narrowed to 4464 without a word, and the next saved
setting wrote 4464 over the owner's typo. The check sits in load_config
because config_error only ever sees the narrowed value.
tag_text dropped a trailing lead byte even when its character was whole, so
"Skate Café" was listed as "Skate Caf". The browser shows the tag name until
the server answers a query, so hosts without a forwarded query port kept it.
Stock worlds allocate 8,200 bodies (+1 internal) from FBPhysicsWorldSize.
Studio maps give every collision part its own static body, so large maps
(e.g. a 41k-part GTA IV export) hit the FatalAssert
"bodyIndex < m_freeBodyList.capacity()" in FBPhysicsWorld::asyncAllocateBody
and crash on load.
Hook fb::FBPhysicsWorld creation (RVA 0x02606ff0) and raise the size
descriptor before it is copied into the job arguments, so every free list
and per-body array is sized consistently. BodyCount becomes 65,000 (kept
below 16 bits); the pools a static mesh body also draws from (geometry,
owner, hull/mesh, shape, aggregate/fixed shape, fixed partition,
persistent spatial index) scale by the same factor, capped at 2^20.
Contacts, joints, queries and culling tables keep their native sizes.
Both native callers build the descriptor on their own stack, so raising
it in place only affects that world. Contract bytes for the entry, the
descriptor copy, the internal-body increment and both call sites are
validated before the hook is enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-ups to the trainer (PR 37), and one multiplayer change.
Off until asked for (Extension/Trainer)
- The jump read-out and the LB+RB controller shortcuts start off. Jumps
are logged only while the read-out or telemetry recording is on, and
trick launches only when the trainer scaled them. A saved trainer.json
keeps what the player chose.
Memory search on demand (trainer.cpp, trainer_classes.cpp)
- The search for the game's tuning classes reads all writable memory.
It ran for every player after a level loaded, and up to seven times
when it found nothing. It now runs only when a class value or the flip
speed is not the game's own (the player's, or a host's they skate
with) or the EVERYTHING list is open, and at most three times a level.
- For those players it runs again on each level, which brings fresh
copies; a search from an earlier level used to count as done.
Guests under enforced physics
- A guest is locked by the session's enforce setting, from the moment
of joining (Engine/Game/Multiplayer/session_physics.h), not by whether
the enforcement status text was set yet.
- A guest's class values no longer come back after a map change.
The host's whole setup reaches its guests (protocol 39)
- Only Gameplay/SkatePhysicsTuning travelled, so a host could push,
sprint or jump further than the guests it held to the game's own.
- New packet physics_extras (30): the host's class values, trick
multipliers and auto push (trainer_session.h), sent when they change
and to players who join later, taken from the host only. A dedicated
server's are the game's own and it ignores the packet.
- Under enforcement a guest's own trick multipliers stand down for the
host's, whatever the boosts switch says. With enforcement off nothing
changes.
- A guest refuses bytes that are not an encoding, holds values to the
trainer's own ranges, and leaves out class values written for another
game build's table.
- protocol_version 38 -> 39: released versions disconnect from a peer
that sends a packet they cannot decode.
Out-of-view players are posed every frame again (Session/session.cpp)
- Players outside the camera's view and past 8 m were sampled at 20 Hz
(b763631). The game's replays record each skater as it stood every
frame and their camera looks anywhere, so those players moved at
20 Hz in replays. Removed; the distance rule (20 Hz past 200 m, 10 Hz
past 350 m) stays. This gives back the client-tick time it saved on
busy servers.
Cap outfit packets and stop dropping recipients when a relay cannot be
built. Add per-player relay limits for outfits, skater sound and voice,
and a back-off for Steam IDs that keep failing to join.
Throwdowns: only participants can write rows, turns and scores; a copy
that fails to spawn no longer blocks the others.
Throttle remote skater and skateboard re-creation, cap remote objects
per session, and show developer and friend marks only for identities
Steam vouches for. Route all connections through Steam's relays.
Dedicated server: validate map changes, clean player names, keep vote
cooldowns across reconnects, and cut ban labels on a character boundary.
Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties
Saved skaters (Extension/Customization, Extension/Skater)
- The selected slot, and slots before it, came up as the standard
skater after a start or map change. The game asks for a slot's saved
outfit once per level, and that happened before the cosmetics catalog
could be read. The first selection now waits for the catalog (60 s at
most), and later map changes check against the catalog already read.
- The retry from abdda77 is removed: the record it wrote to is a
temporary on the game's stack, so it could never work.
Pause menu (Extension/UI/NativeMenu)
- ReSkate's entries stopped working after enough map changes: each
level change took new slots in a fixed table of 1024 for the same
buttons. A button now keeps its slot, and a full table hands out the
slot that has gone longest unused.
Throwdowns (Extension/Throwdowns, Runtime/runtime_init.cpp)
- Multiplayer crashed when someone placed a throwdown after a map
change. The event types are freed with the level but were cached for
the process. They are now cleared when a level is left, checked
before use, and looked for again in each level.
MY MODS (Launcher/gui_mods.cpp, gui.cpp)
- Search, a filter and an order; a switch per mod; a menu per row.
- Multi-select with tick boxes, Shift-click and Ctrl+A, and a bar to
enable, disable, move, update or uninstall everything ticked.
Lobby parties (Extension/Multiplayer/Session)
- A lobby is no longer one party. Its host keeps parties the way a
dedicated server does: invite, accept, join, leave, kick, promote,
open and close, with "/p" party chat. The "Lobby party" switch is
gone. Protocol version unchanged (38).
Mod merge (Engine/Vfs), follow-ups to e715b9b
- Files rewritten into the merged patch are tracked by position, so
another mod's added assets no longer shift the lookup.
- Two mods adding different assets under one name is now noted, with
whose copy the maps get.
- mod_merge_unshift_tests compiles with the launcher tests on.
Presets, trick sliders and the values share one screen and one set of values, so a preset
cannot say "on" while a slider says otherwise. Every built-in preset is a dial (1 = the game's
own). Reset everything clears locks and trick sliders too. A player's grinds, pumping, climbing,
vaults and bails confirmed 56 more class values as read by the game (243 of 314).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018szL7EmApPZxh9GK4Phw6x
A mod that adds songs can ship reskate-music.json
({"schema":1,"playlists":[{"name","songs":["artist - title", ...]}]}). Each
playlist is merged into the game's music screen the way a content-cache
station is, under the id "mod:<mod folder>:<name>", with its songs matched
against the songs the game has registered; entries that match nothing are
ignored and a playlist left empty is dropped. A file of the wrong shape is
logged and skipped.
The file is read by parse_music_playlists in Engine/Vfs/mod_music, which a new
test covers (ids, which entries survive, the limits, and that a malformed
document is refused). The format is described in the README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Song records in the content cache carry a field-10 message (10 artist, 11 title,
12 cdn:/ cover art). Read the art per song and send it as the song's
presentation field 12, resolved to the CDN rendition like a station's art, so
the game's music screen and now-playing toast show covers instead of a black
square. The catalogue test pins one song's art.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The music menu listed only the playlists the game's own music assets declare.
The licensed stations (San Van Soundtrack, 80's Mixtape, ...) live in the
content cache's music chunk: a record with the station id (field 1), its tracks
(repeated field 2, "Artist - Title") and a message with its display name and
cdn:/ artwork (field 10, parts 10 and 11). Read them into Catalogs, merge their
membership into the music catalogue (only songs the game registered, so no
unknown id is referenced), and send the name and artwork with each playlist.
The catalogue test pins two stations from the cache.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A fixture of a made-up game, a level mod with its own level superbundle holding
a copy of a bundle, and an asset mod that changes one asset to name a new song
and adds the song, its wave, the wave's resource and one asset nothing names.
The merged copy in the map's TOC must gain exactly the three the changed asset
leads to, whichever mod has priority. Against upstream's merge it fails with the
merge's own warning that the mod's additions to the bundle are missing.
The documents are built in the test, so it needs no game files.
A second scenario puts two mods' copies in the adder's own TOC and checks nothing is
carried into it and the merged bundle is just the union.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Maps ship their own copy of game bundles such as
bam_levelroot/bam_coregameassets, and that copy loads instead of the game's
on their levels. Changes an asset mod makes to the game's EBX already
propagate into those copies; assets it adds did not. A mod that appends a new
song to the master music playlist therefore left the playlist naming a song
the map's copy never contained, so the song was missing on custom maps.
collect_asset_overrides now also records what an asset mod adds to a game
bundle, and combine inserts it into other mods' copies of the same bundle,
after their own assets, unless the copy already has that name:
- an added EBX is carried only when one of the mod's recorded changes imports
it, or another carried addition does (the playlist names the song, the song
imports its wave), so an addition never travels without the change that
needs it;
- a resource the mod adds under the same name as a carried EBX goes with it
(a wave's sound-bank resource registers the wave; without it the game
crashes instantiating the wave);
- TOC chunks the mod adds that its carried EBX name are listed in each
superbundle those assets go into, pointing at where the mod's archive
landed (a map's superbundle resolves chunks from its own TOC).
A copy in the TOC the adding mod ships its own bundle in (cosmetic mods share one)
merges with that mod's bundle and already has the asset, so only copies in other
superbundles, i.e. maps' level TOCs, receive additions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The game keeps much of its tuning in data-defined classes (push speeds,
everything on foot, dive and glide, bail speeds...). The trainer finds 24 of
them in memory by the defaults and field order the game's data ships and lists
their 314 values by name. Push speed now drives the push class's own speeds;
auto push, off-board jump height and wider trick height ranges come with it.
The Tune tab opens on a Realistic or a Fun short list. Built on ReSkate 1.0.7.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018szL7EmApPZxh9GK4Phw6x
Show each mod's size in the mod manager
A mod's size was only visible in the package overview and the install
prompt, so there was no telling from either list what a mod costs to
download or how much room the installed ones take.
- MY MODS: each row carries the size of the mod's folder on disk after
the version ("v1.0.1 / 410 MB / by anton / 1 map"), and so does the
line under the title in a mod's overview. The folders are measured
when the Mods folder is scanned, so the figure follows an install, an
update or Refresh. The game's merged patch hard links a mod's archives
rather than copying them, so this is also what removing the mod gives
back.
- GET MODS: each row carries the download size Thunderstore reports for
the latest version, in the same place ("v1.0.0 / 101 MB / 0 downloads
/ 2026-10-04"), as the package overview already did.
- size_text moves out of gui_mods_browse.cpp's file-local namespace so
both pages format a size the same way.
The two figures are not the same thing: one is the unpacked folder, the
other the zip, so a mod reads a little larger once installed.
Two fixes taken from worthlessnorms' pull request 16. The bug it was
opened for, rewritten copies read back from the mod's folder, was fixed
separately in 6fd3845.
Mod merge (Engine/Vfs/mod_merge_cas_store.cpp, mod_merge_combine.cpp)
- To combine two mods' edits the merge reads each mod's copy from the
mod's own folder, which means turning its placement in the merged
patch back into the mod's archive and offset. It took the first of the
mod's blocks in that patch archive. A mod added while the game runs
has all its archives appended to the patch's archive 1, so a copy in
its second archive was looked for in its first, the read failed, and
the asset kept the highest-priority copy.
- unshift() picks the block the offset falls in. Of two blocks that
start at the same byte the later one is used: the earlier is an empty
archive.
- Reproduced with a cosmetic mod rebuilt as two archives and added to a
running merge: cas_main_bundlereftable "could not be combined (Cannot
read ...\cas_01.cas)" before, "combined 2 edits (101 preset(s))"
after. A launch merge gives every archive an index of its own and was
not affected; 40 installed mods merge exactly as before.
- mod_merge_unshift_tests covers an archive with its own index, three
appended blocks, bytes before the first block, and an empty archive.
Tests (CMakeLists.txt, Extension/Multiplayer/Test/mesh_tests.cpp)
- multiplayer_lobbies did not link: steam_lobbies.cpp calls
contains_bad_words and the target did not link dingosdk_word_filter.
- multiplayer_mesh did not link: four functions the session code calls
had no stub. Two are the ones the pull request adds
(update_developer_hoodie, remote_skater_generation); the developer
board has since added update_developer_board and remote_board_entity.
- Both targets build and their tests pass.
Co-authored-by: worthlessnorms <110003169+worthlessnorms@users.noreply.github.com>
With several cosmetic mods installed, some mods' items were missing from
the inventory, and an outfit using one logged it as "no longer
installed".
Bundle merge (Engine/Vfs/mod_merge_combine.cpp)
- Every cosmetic mod adds its items to shared collections
(items/itemmastercollection and one per season). Two mods editing one
collection are combined, but the combine was failing with "Cannot read
<mod>\...\cas_01.cas" and keeping the highest-priority copy, so the
other mods' entries were lost.
- The failed read was not of an edit at all. A mod's untouched copy of
an asset another mod changed is rewritten to carry that change, and
the rewritten copy lives in the merged patch. It was still recorded as
that mod's own contribution, so the combine went to the mod's folder
for it, at the patch's offset, and one bad read made the whole asset
fall back. Renumbered collision resources were recorded the same way.
- A rewritten copy is no longer a contribution. Only the mods that
really edited an asset are combined, and an asset one mod edited is
not "combined" at all.
A mod update was only visible on the mod manager's own page, so the
usual way to find one was a mod behaving oddly in game.
- PLAY carries a pill in its corner while Thunderstore has newer
versions: "1 MOD UPDATE", or the count. Dark on the blue tile, blue on
a grey one.
- Pressing it asks first, listing each mod and the version it would move
to, with three ways on: update them and play, open the mod manager, or
play anyway. Playing on the versions you have is a real choice -- a map
you are part-way through, an update you do not trust yet -- so it stays
offered, and taking it is remembered for the session rather than asked
again on every launch.
- Only from READY. INSTALL, DOWNLOAD, UPDATE and RETRY are untouched: a
mod update has no business standing between someone and a 14 GB game
download.
- The dialog re-checks as it draws. If the pending list emptied while it
was open, it launches instead of showing an empty box.
"Update them and play" launches when the install finishes, which is what
it says and what it did not do:
- The promise is only made if the install really started, so a refused
start cannot launch instantly with nothing updated.
- An update that failed stays on the page with the reason on screen and
logs why it did not launch. Starting the game on a half-updated mod set
is how the cosmetic crash in a30a838 happened.
- Leaving the page with BACK cancels it: that is a change of mind, not a
request to be launched into the game a minute later.
collect_install takes the result under the panel's mutex at the top of
the frame, so once installing is false the outcome is settled and the
error check is reading the real answer.