- Mod merge: rebuild a merged bundle's chunk metadata the way the game
writes it (one record per chunk, in chunk GUID order), so costumes
from different mods built on the same game costume keep their
textures and meshes
- Merge audit checks that layout, and there is a new unit test for it
- Server names: only letters, numbers, spaces and - _ [ ] ( ),
1 to 64 characters; the server browser hides servers with other names
- New Centrix tag from the backend's "centrix" list: blue and white
name, badge and item animation, ranked after Dev and before Creator
A player's recent chat lines show as speech bubbles above their skater: newest nearest the head, older lines stacking upward, each popping in with a slight overshoot before it fades. Emotes render as their images, a message that is nothing but a few emotes shows them jumbo, and a message longer than a handful of lines is cut with an ellipsis so one line can never wall the screen off.
Local display options live under Settings > Interface > Multiplayer (on/off, own messages, distance, duration and how many recent lines stack) and the console gains mp chat-bubbles, chat-bubbles-own, chat-bubbles-distance, chat-bubbles-duration and chat-bubbles-history.
The chat panel and the bubbles now share one rich-text module (chat_rich), and a filtered message keeps its emote names: the bad-word mask is applied, but emote tokens are put back from the original before they are parsed.
Merge: carry added assets with a changed list, combine in-place values, list repeats once
Three merge bugs found by auditing 104 installed mods:
- A changed list (the music playlist) reached every bundle copy that holds
it, but the songs a mod added beside it only reached copies of the bundle
the mod added them to. The game keeps the playlist in eight bundles, so
the other seven named songs they did not hold. Added assets now follow
the change into whichever bundle copy takes it.
- An entry naming an asset that lost its name to another mod's asset is
now dropped from the list, both in the merged bundle and in the list
other mods' copies are given.
- Two mods changing values in place in one asset (lighting) kept one mod's
copy whole and silently dropped the other's. Plain values are now
combined, the higher-priority mod winning where both changed one. An
edit that also moved a reference or resized a list is taken whole or not
at all, and the merge notes whose changes are missing.
- A reference two mods both append to a list is listed once.
- Mods that add copies of store items under new names are no longer
loaded. The launcher, mod manager and game say why.
- A chat line passed on by the host only shows a Dev/Creator/Homie
badge when the player's own copy of it arrives too, so a host
can't put one on a line that player never wrote.
The music player's playlist walk loads a song entry, masks it and reads a flag at [rcx+0x14] before it checks the entry's identity. An unresolved or NULL entry makes that read fault and takes the game down while a playlist is built or opened.
Install a 13-byte in-place guard at the reviewed RVA 0x14f80c7 when the music contracts pass. It keeps the original flag test, shrinks the leading mov eax,0 to xor eax,eax, and adds a jrcxz that skips the dereference when the entry is NULL, landing on the loop increment. The site is matched against its contract bytes before anything is written, so an unsupported image is left untouched, and the guard is applied with VirtualProtect and FlushInstructionCache and then made read-only again.
The contract and patch bytes live with the other music contracts in Engine/Game/Build/20260929/local_music.h; the installer is Extension/Music/local_music_safety.{h,cpp}; local_music_ui.cpp invokes it from initialize_music_functions; cmake/Runtime.cmake builds it. A new dingosdk_music_safety_tests (CTest music_safety) pins the contract length, the decoded instructions and the branch target arithmetic without needing the game.
- Read Dev / Creator / Homie Steam IDs from api.reskate.dev instead of the hardcoded list
- Creator (red) and Homie (gold) tags and hoodie/board colours; Admin tag is now pink
- Global bans: lobby hosts and dedicated servers turn banned players away
("global_bans": false in ReSkateServer.json opts a server out)
- SPECIAL menu page for listed players to hide their own tag and items
- Fix hoodie and board keeping the animated colour after the animation turns off
- Multiplayer protocol 39 -> 40
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.
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.
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>
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>
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
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>
Videos exported from the replay editor played two to three times too
fast unless the frame rate was capped, or the export was slow enough to
hold it down by itself (4K, WebM). The fault is in the game's own export
path.
Replay export (Extension/Rendering/replay_export.*)
- While exporting, the game steps the replay by one frame of video
(speed / fps) for every frame it renders. The engine's movie encoder
takes a frame only while a permit byte is set; a world update sets it
once, and a frame submitted without it is dropped. Rendering two or
three frames per update, most of a clip never reached the file, and an
MP4 is exactly as long as its frame count. One clip came out as 181
frames at 30 fps and 297 at 60, 6.0 s and 5.0 s.
- The encoder's frame submit is detoured and the permit set before the
original runs, for the replay editor's encoder only (matched by its
vtable). The frame is encoded on the render thread before the call
returns, so the renderer is held to the pace the encoder keeps and no
frame is lost. The replay still runs fast on screen during an export:
that is the encoder's speed, not the file's.
- The same clip afterwards, frame rate uncapped, MP4 1080p: 505 frames
at 30 fps and 1008 at 60, 16.8 s both, audio as long as the video.
WebM and 4K take the same path and were not run again.
- Addresses are in Engine/Game/Build/20260929/replay_export.h. The hook
starts with the others in runtime_init.cpp.
A mod the merge could not use was left out silently. The game started
without it, three minutes into a loading screen, and only ReSkate.log said
which one.
Merging before launch
- PLAY runs the merge the game would have run behind its splash, on the
launcher's worker, with the step and progress on the STATUS tile
(Phase::merging). The launcher links dingosdk_mods for it, which costs
about 600 KB of exe.
- When every mod merges, the launch goes ahead and the game's own
load_catalog finds the patch already built, so the splash is shorter
rather than the work being done twice.
- When mods were left out, the launch stops at Phase::mods_broken and a
dialog names each one and its first problem, with three ways on: switch
them off and play (which writes mods.json, so the next launch does not
rediscover it the hard way), open the mod manager, or play anyway.
- Mods built for another game version do not block. They already have
their own notice and message box, and blocking on them would stop play
after every Steam update.
- The launcher resolves -dataPath out of the extra-arguments setting, so
it merges against the root the game will read.
One SDK identity for both binaries
- Two places recorded "which ReSkate did this" as the module the code was
running in, which is ReSkate.dll in the game and ReSkateLauncher.exe in
the launcher now that both merge. merge_fingerprint hashes it, so the
game matched no stamp the launcher wrote and merged everything a second
time; sdk_identity keys the exclusions file, so each would have called
the other's exclusions stale.
- Both now name ReSkate.dll beside the running executable. Skate.exe and
ReSkateLauncher.exe sit beside it and launch.cpp enforces that, so the
two agree, and unlike the data root it does not move under -dataPath.
The value is unchanged from what the runtime computed before, so nothing
re-merges on upgrade.
Naming the mod
- In game, a mod left out of the merge altogether said nothing on screen:
only a mod that merged partially did, which is the lesser failure. Both
are named now.
- The MOD MANAGER tile says which mod did not load, in the detail line's
colour rather than a pill, clipped so a long name cannot run under the
tile's artwork.
- MY MODS carries a line naming them above the list, and NOT LOADED and
OUTDATED are badges on the row instead of words at the front of its
summary.
MY MODS reaches Thunderstore parity
- Rows carry the package's icon, the same one the store shows, through a
shared mod_icon(); a mod that never came from Thunderstore keeps the
placeholder.
- An update is a button on the row, not a badge you had to open the mod to
act on, and every row has Uninstall. Rows grew to fit, and "Remove" is
"Uninstall" throughout.
- No pill for a mod that is up to date: the absence of an update button
says it, and eight green pills said nothing eight times.
Fixes
- The install panel was drawn on the Mods page's own draw list after its
children, and ImGui renders a child after its parent, so it was painted
behind the mod rows along with its Cancel button: installs ran with no
sign of it. It is its own window over the page now.
- pump_icons, which uploads decoded icons, only ran on the store page, so
MY MODS showed placeholders until a visit to GET MODS uploaded them for
it. The page does it for both lists.
- The merge dialog's closing advice overlapped its buttons.
The mod browser was a 960x620 panel inside a 1180x680 window, reached from
a tile the same size as SETTINGS. People could not find it, and once they
did there was room for about six mods.
Window
- The design size is 1440x840. Everything is laid out in design units and
multiplied by g_scale, which now scales down when that would not fit the
screen's work area, so a small laptop or a high DPI no longer gets a
window running off under the taskbar.
- The MODS tile is MOD MANAGER: taller than SETTINGS, directly above PLAY,
with the pending update count as a pill on its subtitle line.
- background.jpg is 1920x1027, so the bigger window is not upscaling a
1280x720 photo.
Mod manager, a page rather than a panel
- begin_page fills the window and draws its own minimise and close, since
it covers the main screen's. BACK or Escape leaves it.
- A nav rail holds MY MODS and GET MODS (count badges, blue for the open
one), the install actions and BACK. Refresh sits with what it refreshes,
top right of the content.
- Every mod is a full-width row: icon, name, author, description, version
and downloads, with a blue INSTALL or UPDATE and a tick box on the right.
Ticking several installs them in one run.
- Clicking a row opens that mod's overview in a popup, replacing the
side-by-side details pane that halved the list's width.
- virtual_rows draws only the rows a scrolling child actually shows.
ImGuiListClipper cannot do this, as it assumes every row is a fixed
height.
- Installing is a centred panel over a dimmed page with the activity, a
striped bar and Cancel, instead of a progress bar in the footer.
Settings
- The same page and rail: GAME, DISPLAY, KEYS and ADVANCED are nav tiles
rather than a row of tab buttons, and the content keeps to a 760 px
column so the explanations stay a readable measure.
- The Steam sign-in, Steam Guard and QR dialogs stay centred panels. They
are prompts, not pages.
Status and failures
- The STATUS tile carries its state in a coloured edge and a pill (WORKING,
READY, ACTION NEEDED, PROBLEM), its headline is 22 pt over muted detail,
and the standing facts (game build, ReSkate version) sit under a rule
instead of competing with the live status as equal rows.
- Launcher::fail keeps the raw message for the log, and problem.h turns it
into a headline and the next thing to try. The injection and PE-identity
failures ("VirtualAllocEx failed", "Injected ReSkate.dll is not present
in the child module list") all become "ReSkate could not attach to
Skate", with the anti-virus and overlay advice that actually fixes it;
the Steam download, a missing or damaged ReSkate.dll, the content cache
and a shared menu/console key each get their own. Anything unmatched
keeps its raw text.
- A failed tile grows Open logs and Copy details. Copy details puts the raw
message and the versions on the clipboard, ready to paste into Discord.
The game's trick scripts launch the no comply and the boneless through its
jump-trajectory setter; the trainer multiplies the launch speed there, told
apart by the physics states around the call. Pushing is scripted too, so the
push tuning values were never read: the top pushing speed is now applied after
each physics step, and push strength is hidden.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All new code is in Extension/Trainer. The game thread owns the trainer's state; the menu
page only queues `trainer ...` console commands and reads two snapshots.
- physics_tuning_model keeps the field and curve names the game's EBX carries, so the
editable table is built at run time from the player's own data.
- physics_tuning exports game_tuning / read_live / write_live / refresh_skater, with the
same field whitelist and curve rules as the session sync.
- no_bail publishes the physics state the local skater's selector last chose (air time,
wipeouts).
- A guest under host tuning cannot edit; teleports follow the host's noclip permission.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>