49 Commits
Author SHA1 Message Date
AyeZeeBB 7676c1d5ea Shader registry guard: no registration of a destroyed database, no lookup index past its records
The shader expression registry registers a database from a queued job, and builds its per-key item
lists from the database's lookup without a bounds check. A database destroyed before its job ran,
or a lookup index past its items, left the registry holding pointers that crash it later
(Skate.exe+0x3f7cf33, +0x3f7ceed, +0x20960b in Sentry). The guard skips a registration for a
database already destroyed and points an out-of-range index at a valid item of the same key,
logging each.
2026-10-17 14:27:20 -05:00
AyeZeeBB d75d34614b Keyframe smoothing: key slots built once per rebuild, no playback hook, smoothness in the menus
A rebuild builds one segment per key and each needed every key's slot, so a long replay built
them once per segment: the game's rebuild is hooked too and the slots built for its first segment
are kept for the rest of it (per Behaviour and Subject). The segment player is no longer hooked
(it only counted plays for keysmooth status, every frame). The smoothness is a slider beside the
switch in the overlay (sent when let go, as each change rebuilds the open replay's camera) and
Keyframe smoothness (100, 75, 50, 25%) in the pause menu and Mod Options > Visuals. The startup
line logs on the runtime channel.
2026-10-17 12:29:53 -05:00
samitakkulaandClaude Opus 5.5 51dddf3d91 Keyframe smoothing for the replay editor's free camera
The game plays the replay camera's framing keys as cubic Hermite segments whose speed changes at
every key (the acceleration jumps there, and it brakes where a channel slows or turns back), so a
free-camera shot surges and settles around each key. keysmooth on rewrites each segment as the game
builds it, so that the game's own player plays the natural cubic spline through every key: speed
and acceleration carry on through the keys, and every key is still met exactly, at its time.

- keysmooth on|off (off by default), keysmooth amount 0..1 (1 by default; 0 is the game's curve)
  and keysmooth status; kept in %LOCALAPPDATA%\ReSkate\KeySmooth.settings. Only the free camera
  (Behaviour 1) is smoothed; the other camera modes keep the game's curve.
- A switch in Mod Options > Visuals and in the overlay's Skater > Freecam.
- The curve apart from the game, with tests (DINGOSDK_BUILD_REPLAY_KEYFRAMES_TESTS); the hooked
  functions are fingerprinted, and a build that differs gets no hook.
- contrib/keyframe-smoothing.md: what it does, how, and what was seen in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:33:32 -07:00
AyeZeeBB 1456c1fa51 Customization: a preset with a parameter type the game cannot read is left out, not applied
Scrolling shirts onto Yudi-Yuddispack's philippleinshirt_ap crashed the game (Skate.exe+0x3dc07ed):
one of the preset's set parameters has a type of 0x0c07010304040502 (null in another crash). The
game type-checks every set parameter when it next applies a preset to a material, reading the
type unchecked, and catching that crash inside addEsShaderPreset was too late: the preset was on
the material already and the next update crashed on it.

addEsShaderPreset's guard now checks the preset first, as that type check reads it: the TypeInfo,
its data, a word at +4. A preset with a parameter whose type cannot be read is left out as a
preset that is not loaded is (the item shows without it), and named in the log once with that
parameter's key. Many of the game's own types are built at run time, so the test is that the
type reads, not that it lies in the game's image.
2026-10-17 00:48:40 -05:00
AyeZeeBB 290089e16d crash fix 2026-10-16 17:34:06 -05:00
AyeZeeBB 0dcbcb3a89 Drone: other players' drones shown as a 3D model
A model is spawned at each other player's flying drone and moved every frame. It is the drone's
own model (FPV Drone by PinaPipe, CC BY 4.0, credited on the DRONE page), built by ReSkate Studio
into a pack that ReSkate.dll carries and unpacks into Mods/.reskate-builtin before the merge.
Without the pack the game's traffic cone stands in. dronemodeltest spawns one in front of you.
2026-10-15 22:39:49 -05:00
AyeZeeBB 3249f747dc Crash reports: the game's own fatal crashes come with the memory their stacks point at
Most crashes are caught by the game's own fatal handlers, which write a
dump of thread stacks only; the launcher could only copy it after the
game had exited. A crash on a freed or overwritten object then showed
the faulting registers but not the object or the list it came from.

- game_fatal_dump: the game's MiniDumpWriteDump import points at
  ReSkate. Before the game writes its dump, the crash reporter takes its
  own of the still-running game for the faulting thread; the game's dump
  follows as before. Reports say capture.source "game-fatal-handler".
- The reporter's dumps include the memory the stacks point at
  (MiniDumpWithIndirectlyReferencedMemory), a few MB more a report.
- The shared report says who asked for the dump (protocol version 4).
- Tests: a game-handler crash is captured with a heap block only its
  stack points at; ReSkate's own capture keeps it too.
2026-10-15 20:01:55 -05:00
AyeZeeBB ea3f081367 World: big maps no longer run out of spatial cull IDs while loading
A spatial transaction queue gives every object it tracks a cull ID from a
free list made once, when the queue is, at the game's
InitialCullIdListSize (40,000 in its config), and never grown. A custom
map with more objects (Cardiff) emptied it while loading and the next
insert read entry 0xffffffff of the list (crash at 0x489cfe9).

- spatial_cull_ids: as each queue makes its list, its size setting is
  raised to 262,144, one ID for every entity slot the entity pages hold;
  a larger configured value is kept. 12 bytes an ID per queue.
- The contract checks the list maker and how it reads the setting; it is
  installed at startup with the other world pools.
2026-10-15 20:01:48 -05:00
AyeZeeBB 3faa58bb42 Render-resource pool: the other renderer is allocated room for its metadata pointers
719cba1 stopped writing the metadata pointers into the other renderer
(0x2daa00 bytes, Skate.exe+0x30c64cc, kept at 0x75f2700), taking them for
the state owner's alone. They are not: the other renderer's own code reads
them through the patched view (0x030e6bff, called from 0x30a7e18) and
append (0x03e94fe6, called from 0x30d8ebb). Without the write, it read
whatever lay 18 MB past its end. With it, as in every release before, that
memory was overwritten, or the write faulted when nothing was mapped there
(the store_field crash).

- The other renderer's allocation is patched to the state owner's
  0x11ab080 bytes (0x030c64cc, mov ecx), so +0x11aae28/+0x11aae30 are
  inside it.
- Both renderers' metadata pointers are written again, before and after
  the native constructor. The resource-state and relation-mask vectors stay
  the state owner's alone: the other renderer keeps fields of its own at
  +0x2da920.
- The contract's comment says which renderer reads what, and where.
2026-10-15 18:16:22 -05:00
AyeZeeBB 719cba1166 Freezes and crashes: the game's threads no longer wait on ReSkate's slow work
Players reported the game hanging for about a second while skating. A
profile showed the memory line in the log walking every heap
(HeapSummary) on the client thread, 0.5 to 2 s every two minutes. An
audit of everything that runs on the game's update and render threads
found more of the same, and Sentry's top crashes had causes of ours.

Freezes:
- Memory log line: no heap walk or thread snapshot; the GPU part and the
  write happen on a thread of their own.
- Profile database: synchronous=NORMAL in WAL mode. A save is still kept
  when the game crashes; every play event no longer waits for the disk
  inside the game's hooks.
- ReSkate's own preferences and values (menu clicks, session sliders,
  Discord) are saved by a background writer in order; reads see a value
  until it is saved.
- Input: the foreground window and the keyboard are sampled on a thread
  of their own (GetAncestor and GetAsyncKeyState stalled the client tick
  40-50 ms when another program held Windows' lock); flight input uses
  the sample; ClipCursor at most twice a second while a menu has the
  pointer.
- Hooks installed while the game runs go in one transaction per place
  (HookBatch): throwdowns, remote players, the party, Hall of Meat.
- Off the update thread: the host's map package lookup (one table read in
  the background, not the Mods folder per offer), the trainer's map file
  and settings save, Board Break's settings save, mod music playlists and
  their covers, the Spotify station covers.
- Off the render thread: the Modding page's and the deck card's Mods
  folder reads. The overlay's three startup waits share five seconds.
- Less work per frame: Steam names reused while one a frame is asked
  again, 2 friends a tick, steam_api64.dll checked once per process,
  cosmetic names read in place rather than a system call per character,
  an outfit load no longer copies the inventory per slot, the throwdown
  type scan runs at background priority over resident memory, the Board
  Break deck label is kept a minute.

Crashes:
- Render-resource pool: the metadata fields are written to the state
  owner only. The other renderer is 0x2daa00 bytes, and +0x11aae28 is far
  past its end (store_field crash; heap corruption in every release).
- Other players' skateboard effects: a stale skateboard component is no
  longer followed, and the joint lookup is walked with a bound first (an
  endless loop in reused memory ended in GameStall).
- Launcher: Oodle decodes into the buffer it asks for with corrupt data
  possible, and its assertions no longer stop the process (heap corruption
  in the store-copy scan; a breakpoint in oo2core).

Timings (Send performance timings):
- A frame of half a second or more in a level is sent as game.freeze:
  where the time went (ReSkate's tick, the game's frame, or outside it),
  whether the renderer kept presenting, and ReSkate's busiest zones.
- Closing the game sends the level session it was in.

Music after a connection drop:
- Spotify device: an account Spotify could not be reached with is kept and
  tried again (3 s doubling to a minute); a session that drops is taken
  up again. Only an account Spotify refuses is asked for anew.
- YouTube playlist pictures that could not be fetched are tried again,
  from 30 s to 10 minutes apart.
2026-10-15 18:10:53 -05:00
AyeZeeBB 164fdffbd0 Descriptor heaps: dense custom maps no longer overflow the GPU's descriptor heap and crash it
The renderer gives each frame 131,072 shader-visible descriptors past a persistent 120,000, in
heaps of 333,333. Dense custom maps (the Los Santos City sublevels, San Andreas) draw more than
that in a frame, and the renderer wrote past the end of the heap: shaders read garbage
descriptors, which faulted or hung the GPU (page faults at nonsense addresses, in draws, texture
fetches and ray tracing alike). ReSkate now patches the device setup at boot so each heap is
D3D12's 1,000,000 and a frame may use 524,288; heaps are 644,288 descriptors. LS-B2, LS-B3 and
San Andreas (210,000 a frame at its peak) now hold.

Also in this change:
- Memory: perf memory owners/heap/gpu/report, a ledger of who commits the game's RAM; the
  render-resource pool no longer allocates its 115 MB twice; the EadpErrorsData arena is
  capped at 32 MiB (EA's error reporting is blocked anyway).
- Crash reports: tagged with the map, destination, graphics card and driver version (protocol
  v3), so Sentry can group GPU hangs by map and card.
- GPU diagnostics (--gpu-diagnostics): a fuller DRED report, the device-removal and video
  memory watches, a large-draw watch, a record of which game code made and freed each GPU heap
  and resource, checks of every GPU address bound, and a meter of each descriptor heap's use.
- World pools: peak use of entity pages, render instances and spatial blocks in the log.
- dingosdk_mod_mesh_check: checks every MeshSet a mod ships against its geometry; the MeshSet
  reader now knows static meshes' layout.
2026-10-15 13:57:11 -05:00
zeex64 dc43a50a47 Merge pull request #243 from Kalmarv/pr/lights-v2
Lights: a level with more lights of one kind than the game holds no longer crashes
2026-10-15 13:17:39 -05:00
AyeZeeBB 6df4f9a7bd Replay export: videos are encoded at 100 Mbps whichever quality is chosen
he export dialog's quality setting picks 8, 12 or 18 Mbps for the H.264
encoder (three mov eax immediates at Skate.exe+0x62c24f, +0x62c256 and
+0x62c25d in the 20260929 build). All three are now 100000 kbps.
- The code around them must match the supported build's bytes, and each
  value must be the game's own or already 100000; otherwise nothing is
  written and the log says the change was skipped.
- The log says whether the bitrate change or the frame-pacing hook was
  skipped. start() reports success if either one is in place, so
  replay_export_initialized can say active while the hook is off.
- The fix that encodes every rendered frame is unchanged.
2026-10-14 20:11:01 -05:00
KalmarvandClaude Opus 5.5 3468f556b0 Lights: a level with more of one kind than the game's table holds no longer crashes it
The game keeps each kind of light (PbrSpotLight, PbrSphereLight, ...) in a
table of 8,192 and crashes at Skate.exe+0x4ea4094 when a level makes more.
- Each kind's light factory is hooked: once the kind's table is nearly full
  it gives no entity, as the game's own factory does for a light it should
  not make. The light is not drawn and the game goes on.
- Said in the log: the kind of light and how many were left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:40:06 -06:00
AyeZeeBB 6c45e9666d World layers: a server's catalog must be the supported build's, and a player is not put out over one that is not
2.0.8's dedicated server was released with a world-layers.json scanned from
a game Steam had updated: 263 layers, where every player's game has 261. A
server that shares its layers (world_layer_sync) sends one choice for each,
the player's game turned the message down as unreadable, and nobody could
join such a server ("Join code, sender identity, or multiplayer protocol
did not match", and "Disconnected." in the server's log).
- supported_build::world_layer_count (261): how many layers the supported
  build's levels have.
- Server: a world-layers.json of another count is not used; the log says
  why and layer sync stays off, where before every player was turned away.
- --export-world-layers refuses a game folder whose count differs, so a
  release cannot be built from an updated game again.
- Player's game: a layer list of another length is left unread and every
  layer stays as the player has it; the rest of the message is taken and
  they join.
2026-10-13 18:45:37 -05:00
AyeZeeBB 9f93c3cbb2 Shader tables: mods' are merged as in 2.0.7 again, and the game's song scorer stays within its list
Shader tables
- Reverts a61f82c ("Shader crash fix"). Since 2.0.8 a mod's own shader
  programs were copied into the merged table, and the tables that cosmetic
  bundles carry were merged across mods. Each such bundle then held other
  mods' programs, and the game built them as the bundle loaded, before the
  files with their compiled variants were there. That was 2.0.8's most
  reported crash (Skate.exe+0x3f86da6, some forty machines in ten minutes).
  2.0.9's guard passed such a program over, and the same players then
  crashed a little later in cosmetic loading (+0x3f7bb65, +0x16fe1a6,
  +0x3dc07ed).
- Whatever crash a61f82c fixed comes back with this for the mods that
  needed it.
- The guard stays.

Music
- The game's read of one song for its scorer (0x14f23a0) is replaced by the
  same read with the check it lacks: a place past the end of the playlist
  asset's songs has no song, which the game already answers with 0. 2.0.9's
  check in music_select_next_hook looked at what the queue held before the
  game was asked and did not see these; the game asked for places such as
  0xd570 and crashed reading them (7 reports on 6 machines).
- Said in the log the first eight times.

Not confirmed in game: neither change has been run by a player who was
crashing.
2026-10-13 18:44:15 -05:00
AyeZeeBB f356c1eafd Music screen: large playlists no longer empty it, and two logs say why things fail
Music screen: large playlists no longer empty it, and two logs say why things fail
Music screen
- The screen's list was turned down whole, and came up empty (the game's own
  Featured row too), once it held more than 1,024 songs: a few large YouTube
  Music, Spotify or local playlists went over that. The most is now 2,000
  songs and 16,384 places in playlists (music_ui_most_songs,
  music_ui_most_places); 2,000 because the screen's own table of songs is
  read only up to 2,048.
- The rows of the player's playlists are counted as they are registered and
  held under that: with no room left, the rest of a playlist is left out of
  its list (it still plays) and the screen is never empty for it.
- A row whose "artist - title" is longer than the screen takes is left out;
  one such row turned the whole list down as well.
- Said in the log: how many songs were left unlisted, and, should the list
  still be turned down, what it held.
Logs
- Spotify device: librespot's own warnings and errors are written at the
  warning level again; only the rest of what it says stays at debug. A
  player whose Spotify connects and stays silent had nothing to send.
- Multiplayer: a message turned down with "Join code, sender identity, or
  multiplayer protocol did not match" now says which of the three it was,
  its size and first bytes, and whether it came from a dedicated server.
2026-10-13 17:38:50 -05:00
AyeZeeBB 2e40aeb6e2 Game data of another build: the launcher asks for a verify, and mods are looked at again
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.
2026-10-13 16:31:09 -05:00
AyeZeeBB 28118efc27 Objects: other players' objects are ghosts by default, and their card is a visitor's
Objects other players place are spawned locally, so the game took every one
for the local player's own: all were solid, and their card offered the
owner's buttons and named the wrong player.
- Other players' objects are ghosts the skater passes through, as in the
  game's own sessions. Each player's row in the session menu has an
  "Objects: solid / ghost" switch, remembered by Steam ID; a dedicated
  server's own objects (its parks) are solid unless switched off.
  `mp objects-solid <SteamID64|server> on|off` does the same.
- The card's own Disable and Enable on one object stand over that player's
  setting until the setting is changed.
- The card on an object someone else placed no longer offers Edit or
  Delete: the game's "is this mine" and "may this be edited" checks answer
  for who really placed it. It offers Disable instead.
- The card names the owner of the object it is for, read from the card's
  own script, instead of the owner of the object nearest the skater.
- A dedicated server has a player card of its own, "Server", shown on what
  it placed.
Server: the Mods folder is kept when the configuration is reloaded. A park
mod was reported as not being in the Mods folder after a reload.
2026-10-13 03:37:45 -05:00
AyeZeeBB 30a320ff62 Throwdowns: a grouped line score is no longer cut at its first separator
A Jam Session takes the score of the line just landed from the score HUD's
text, with the script native ParseInt, which is the C library's atoi. atoi
stops at the first character that is not a digit, so a line shown as 5,432
was read as 5 and added 5 to the player's total. Reported for Jam Sessions
and Spot Battles: lines of 5,000 and more adding one or two points.

ParseInt now reads groups of three digits whole, set apart by a comma, a
full stop, an apostrophe or a space: "5,432" is 5432. Text that is not a
grouped number is read exactly as before. The first few grouped scores are
logged, to confirm the cause in play.
2026-10-13 01:14:01 -05:00
AyeZeeBB 86aa8d38fb Music: local files, online radio, equaliser, queue, and a Music page in the menu
Sources
- Local Files: add a folder of music from the setup page (MP3, M4A, AAC, Ogg,
  FLAC, WAV; up to 200 songs a folder, 12 folders). Names and covers come from
  the files' tags. Each folder is a tile in its own row.
- Online Radio: find stations by name (radio-browser.info) or add a stream's
  address (MP3, AAC, .pls, .m3u; no HLS). Each station is a tile in its own
  row; the track name follows what the station sends. Up to 24.
- Both are decoded by the library built into the DLL (SpotifyHelper), which
  now plays files and HTTP streams as well as a program's output, reads tags
  and embedded pictures, and accepts MPEG-2 ADTS from AAC stations.

Sound
- Five-band equaliser (60 Hz, 250 Hz, 1 kHz, 4 kHz, 12 kHz) and loudness
  levelling, applied to every source on its way into the game.
- Loudness, the speakers option, the equaliser and levelling are now kept in
  music-settings.json between sessions.

Setup page
- Remade in the mod's own colours and type, with numbered steps per source.
- Queue pinned to the right: now playing, the playlist's songs with covers,
  find, and press to jump. Spotify's own queue shows once signed in.
- Cards for Local files and Online radio; equaliser and levelling under Sound.

In game
- MUSIC page in the menu: player and queue, sound settings, source status and
  a button for the setup page.
- "Loading song..." notice in the corner for songs that take a moment, with a
  switch to hide it. Presses made while a song is loading no longer restart it.
- The Spotify row is hidden when Spotify is not in use.
- Console: "spotify ..." is now "music ..." (music, music setup, music gain,
  music through, music spotify client|login|logout).

Logs
- The music sources' working lines (traces, per-song and per-bind lines,
  librespot's own) are debug only; stack captures are skipped unless debug
  logging is on. Grind exits: the per-pop line is debug only too.
- A "Pace:" line when the game takes the sound faster or slower than real
  time, to find why music sounds fast after tabbing back in.

Chat filter
- A run of digits is a number, not a word spelled with look-alikes: a race
  time such as 2:55.0 is no longer masked.
2026-10-12 16:03:35 -05:00
Belcross 837bea4c40 Grind exit triggers: pick the grind exit side with LT / RT
LT / RT pressed during a grind pick the side the skater pops out to,
independently of the trick and the body's rotation (both come from the
left stick in skate.'s own controls). Only the sideways part of the
grind pop's launch is replaced, with the game's own sideways speed; the
trick, spin, pop height and along-rail speed are untouched.

- Input Mode: Toe / Heel (character relative, needs the stance) or
  Left / Right of the direction of travel.
- Grab guard: a trigger used to pick the side is hidden from the game
  until released, so it never starts a grab.
- Lean guard: while a claimed trigger is held in a grind, the left
  stick's left/right reaches the game at the pop, so the game's lean
  exit cannot drag the board into the rail.
- Off by default; switches in Mod Options > Player, the ReSkate skater
  page, the tools menu and the console (grindexit ...). Kept between
  sessions.
- Listener slots on existing hooks only: No Bail's physics step, the
  trainer's set_trajectory detour, and the overlay's XInput capture.
  No new detours. Build-locked to 20260929 through the trainer's byte
  check.

Measured on build 25414733: 48 vanilla grind pops, 144 trigger-set
pops. See Extension/Skater/GRIND_EXITS.md.
2026-10-12 19:33:50 +01:00
AyeZeeBB 48a9cb8f5b Music: Spotify, YouTube Music and External Audio through the in-game player
Three outside sources can now be played through the game's own music
player. Each has its own row in the music screen, shown only when it is
set up, with tiles that behave like the game's playlists: track name,
cover, pause, next, and pressing a row to start that track.

How the sound gets in
- The game's song decoder is hooked. While one of these tiles is on, the
  audio of the game song behind it (its "carrier") is replaced, block by
  block, with the outside source's audio. The game's volume, fades and
  pausing still apply.
- Rows are songs of our own registered with the game's music registry
  (copies of a real song's asset with their own artist and title), so no
  game song is borrowed or renamed to list a track.

Spotify
- The game is a Spotify Connect device ("ReSkate"), built on librespot
  and linked into ReSkate.dll (SpotifyHelper/, Rust). Audio, track info
  and controls come straight from Spotify. Needs Premium.
- Optional sign-in through the Web API lists the player's playlists and
  Liked Songs as tiles with their tracks.
- librespot can no longer end the process (it does so on a free account
  and on playback errors); only its thread is stopped.

YouTube Music
- Videos and playlists added by link. yt-dlp is fetched on request into
  <Local AppData>\ReSkate\bin and checked against its published sums;
  decoding is done in the DLL, so no ffmpeg.
- Private and age-restricted songs: cookies.txt or a browser's sign-in,
  with QuickJS fetched as the JavaScript runtime yt-dlp then needs
  (Node.js is used instead when installed).

External Audio
- Any app's sound, captured per process from Windows, with the app's own
  output turned down while it plays through the game.

Setup page
- "spotify setup" opens http://127.0.0.1:8421/: device status, sign-ins,
  YouTube links and tools, the external app, loudness. Loopback only;
  changes are accepted from its own page only.

Also
- Speakers in the world play the tile's audio; grabbing one keeps the
  player on their tile.
- Playlists added while the game runs are sent to the music screen live.
- MUSIC-SETUP.md: setup guide for players.

Building the Spotify device and the YouTube decoder needs cargo; without
it ReSkate.dll builds without them.
2026-10-12 03:16:36 -05:00
AyeZeeBB 0f8af62710 Entity limit 2x
The entity and pending-update pages are twice the game's own size
(262,144 slots), down from four times. Every page set the game keeps
is that many times its normal size whether a map uses the room or not,
and the largest map load seen needs about 150,000 slots.

A page is a power of two entries, found by a shift, so the steps are
two, four and eight times; there is nothing between. The 434 patch
lines are the same instructions as before, each with the number for a
page of 0x800 entries. The render-instance pool and the spatial
buckets stay at eight times, which still covers the capacity.
2026-10-11 20:12:59 -05:00
AyeZeeBB a83cb83bb4 Raise the streamed texture limit to 32764, and warn when it is close
The game can stream 20480 textures at once. When a map and its mods
need one more, it takes a slot past the end of its table and waits for
ever on a byte that is part of a counter: a hang, then a crash in the
hang's own report (crash reports of 2026-10-11).

The limit is raised to 32764, the most a texture's 16-bit handle can
index, before the game makes its texture streaming manager:

- The manager is allocated longer (6.0 MB, was 2.3 MB) and its slot
  table and per-slot floats moved behind its end. Each of the 226
  instructions that hold one of those offsets or sizes gets another
  number; no code is added. Its four fixed lists are pointed at longer
  storage once the constructor has run.
- The renderer keeps a block of floats per slot for each of four
  layers. Its object grows to match (27 instructions), and the two
  places that index a layer's block as layer * 20480 become a multiply
  by the new count, in the same twelve bytes.
- Three functions keep 20480 entries on their own stack. Two list
  filters go through a longer list a stretch at a time, and the sort
  is given scratch room of its own past that many. Up to 20480 they
  run as the game wrote them.

Every instruction is checked byte for byte first, and nothing is
changed if any differs or the manager already exists. An empty
texture_limit_stock.txt beside the game leaves the limit as it is.

Players are told when they come close: a notice at 90% of the limit
in force and an error at 100%, each once until use falls below 80%.
The log records each new high in steps of 5%.

Also: when a function of the game finds its stack written over, the
game ends itself with exit code 0xc0000409 and no crash report. The
log now names that function and its callers first.

The site table (texture_streaming_sites.inc) is generated from a sweep
of the supported build; texture_streaming.h describes the layout.

Tested in game below 20480 textures only. Nothing has run above the
old limit, where the three stack guards and the longer lists first
matter. At 32764 the game still hangs; the notice is the protection.
2026-10-11 19:26:23 -05:00
AyeZeeBB 9b79fd7829 Guard a crash when the game describes a failed load
Guard a crash when the game describes a failed load

When a load fails, the game writes "load <address> failed with error:"
and describes each part of the load's error in turn. That list can
hold parts that were never set, and the game calls through them: six
crash reports from one player on 2.0.5 and 2.0.6 all end at
Skate.exe+0x2e3ab9c, after eight parts that read "no error".

The guard hooks that one function. Before the game describes the
list, every part that is not one of the game's own objects is emptied,
so it reads "no error" as an empty part does; the description also
runs under an exception handler. The failed load is then the game's to
deal with, as it meant to.

What the game wrote goes to the log as a warning ("The game described
the error of a load that failed"), with how many parts were emptied,
so content that does not load can be traced. The game's message names
no asset, only an address.
2026-10-11 18:00:30 -05:00
AyeZeeBB e344d585f4 Vote for the next map when a map's time is up
A dedicated server that rotates its maps can now ask players where to
go next instead of taking the pool's next map.

Server
- maps.rotation_vote: when the timer runs out, a vote between three
  maps drawn at random from the pool, "Random map" and "Stay here".
  The most votes wins, a tie is drawn, and with no votes it is the
  pool's next map. It ends early once everyone has voted.
- maps.rotation_vote_seconds (10-120, default 30) and
  maps.rotation_extend_minutes (0-240, default 15; 0 leaves "Stay
  here" out).
- maps.rotation_announce: a card when the map has 10, 5 and 1 minutes
  left.
- The vote's maps are drawn a minute ahead and sent to games
  (PacketKind::next_maps), so their pictures are ready when it opens.
- /vote extend: a built-in vote that gives the map in play
  rotation_extend_minutes more. On by default (votes.extend_map), and
  only runs while the maps are on a timer. "extend" is now a reserved
  vote name.
- Console: rotation vote on|off, rotation announce on|off,
  rotation stay <minutes>|off, rotation extend [minutes],
  votes extend ..., vote extend.

Game
- The vote opens as a popup in the middle of the screen: a tile per
  answer with its picture, name, share of the votes and count. It
  holds input as the map download card does, and works with a
  controller (D-pad, A, B), the keyboard and the mouse.
- Pictures: a mod map's icon from its folder, or from Thunderstore
  when it is not installed; the game's own maps show their loading
  screen picture, cut to its middle square. "Random map", "Stay here"
  and a map without a picture are drawn as shapes.
- Older servers and polls are unchanged: the vote is a poll, so it can
  still be answered with /1, /2... in chat.

Protocol 50: games and servers both need this build.
2026-10-11 17:21:27 -05:00
AyeZeeBB 8351654222 Votes by those who vote, map versions, maps reload, and a crash leaving a map
Votes: a vote is decided by the votes cast, not by everyone on the server. It
passes when yes is its percentage of them, at the end of its time, or sooner
once the players still to vote could not change it. A kick still needs two
yes. The card's bar is yes in green from the left and no in red from the
right, with a mark where yes has to reach. No protocol change.

Map versions: a map installed at another version than the host's is no longer
loaded as it is. The download card offers the host's version (MAP VERSION,
UPDATE), which replaces the installed one and is applied while the game runs.
For that the live merge keeps each mod's fingerprint with where its archives
were placed, and places a mod again when its files have changed since: before,
a mod replaced while the game ran was read where the old one lay. When the
host's version is gone from Thunderstore, or the old one's files are in use,
the player is told and leaves the session.

Dedicated server: "maps reload" reads the Mods folder again, so a map copied
in while the server runs can be switched to at once. The server window reads a
copy of the map list.

Map names: a custom map's level is dingolevel_reskate_<map>, and "reskate" was
left in its name ("/map reskate badlands"). The map's own name is shown and
typed now; the old spelling still finds it.

as a level unloads, the game takes each mesh variation out of its registry by the
entry's mesh and reads it unchecked, and this map's meshes unload before
their databases. A guard on that one function has the read of a mesh that is
gone go on with the next entry, and logs how many were passed over.
2026-10-11 16:12:57 -05:00
AyeZeeBB aa08050c8b Music: Favorites takes playlist 0 on the music player's channels only
The Favorites order fix gave Favorites to any channel that asked for
playlist 0. That is what the music player asks for when Favorites is
chosen, but nothing said another channel could not ask for 0 and mean
something else. The channels the music player's own queues pick songs
for are now remembered as they are seen, and only those are given
Favorites for 0; on any other channel the request goes through as it
came. The channel numbers are still the game's: none is written down.

A channel's binding was logged once per change, except for a channel
past the end of the table, which was logged on every call. Those are no
longer logged at all. The binding line now says whether the channel is
one of the music player's.

The hook on the game's candidate check is removed, with its address: it
changed nothing and only logged.
2026-10-11 10:59:08 -05:00
DeckardDetribine 63b747fd4a fix(music): play favorites sequentially in visible order 2026-10-11 11:32:30 +01:00
AyeZeeBB b9ffd72409 Replay drafts work, and the profiler lists the slowest frames
Replay drafts: the replay editor saves a draft as a pin on the recording
and a thumbnail in the game's media catalogue, which the Drafts screen
lists from. The game only makes that catalogue for a user signed in to
its backend, so under ReSkate a saved draft had nowhere to be listed.
ReSkate now registers the catalogue service and signs in a local user,
both with the game's own functions, so the game's Drafts screen works as
it was built: save, list, open and delete within a session.

A draft's footage stays in the recorder's memory, as it does in the
game, and does not outlive the session. Thumbnails of earlier sessions'
drafts are removed from the catalogue folder at startup; only folders
whose manifest is a replay draft's are touched.

Profiler: `perf sample` on the client thread now reports the slowest
client frames ahead of the function table, each with what it spent its
time in. An average over thirty seconds hides a single long frame.
2026-10-11 02:31:38 -05:00
AyeZeeBB f289666c2c Entity limit 4x, replay crash guard, -map launch option, memory tools; pause-menu fixes
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.
2026-10-10 16:15:59 -05:00
DeckardDetribine 4312a08111 fix(music): persist favorites and playlist likes 2026-10-10 17:03:08 +01:00
AyeZeeBB 978c2a2430 Guard a missing shader preset, end a stuck close, unload guard off
Costume crash: the skater scripts' item:addEsShaderPreset uses the
preset it is handed without checking that it is loaded, and the game
crashed in the material's construction shortly after arriving on a
server's map while wearing a modded costume. The call is now checked
first: an item whose preset is not there is shown without it, and the
log says so.

Stuck close: once the player closes the game, a watch starts. If the
window has gone or stopped answering and the process is still there
8 seconds later, the process is ended. A window that still answers
(the close was turned down) is left alone, and the watch stands down
after two minutes.

Level unload guard: switched off. Skipping the entry that crashed left
its reference registered, so leaving the level never finished and the
game hung with the world unloaded. The crash it covered is back until
the reference is released properly.
2026-10-09 16:58:40 -05:00
AyeZeeBB 464e189d61 Guard the game's level unload against an entry with no asset
Leaving a level could crash the game: an access violation in Skate.exe at
+0x3f5fc52, on one of its worker threads, with no ReSkate code on the stack.
Three reports from one PC show the same crash at the same address (two on
2.0.0, one on 1.1.8), each about half a second after leaving Skate3Improved on
a server of 81 to 101 players.

While a level unloads, a job walks each unloading object's table of 0x30-byte
entries and reads a number at +0xc8 of the asset each entry points to. An entry
whose asset never resolved holds null there, and the game reads it unchecked.

The loop is rewritten in place at startup so such an entry is skipped. The
2-byte nop before the loop makes room for a jz straight after the pointer is
masked, the two moves it displaced follow it, and the loop's jump back is
retargeted to the new start. Everything from the read on stays where it was;
nothing else in the function jumps into the rewritten bytes.

The patch is applied only when the game's 50 bytes are exactly what it was
written for. Otherwise the game runs as shipped and the log says why; startup
goes on either way. The log shows "Level unload crash guard: ready" when it is
in.

Not known: which asset is unresolved (the map's, or something another player
had equipped), and why it only happens some of the time. The reports hold
stacks only.
2026-10-09 14:49:33 -05:00
zeex64 b158887f06 Merge pull request #137 from Krischan-Klug/main
Add Hall of Meat: the bones a bail hurt and its Meat score
2026-10-09 10:24:36 -05:00
Krischan-KlugandClaude Opus 5.5 c7d2ca3601 Add Hall of Meat: the bones a bail hurt and its Meat score
As in skate. 3: when the local skater bails, the bones it hurt show over
the world through the body, yellow where a hit bruised one and red where
one broke it, with the bail's Meat card in the bottom left corner in the
place of skate.'s own HUD there: its time, hits (head hits double,
vehicles half again), broken bones, road rash, airtime, fall, top speed
and rotations, each scoring, and the map's best. A bone breaking pulses
the screen's edges red and, in single player, slows the game down a
moment. Off until switched on, as in skate.: in skate.'s menu (Custom
Stuff > Player) or with `hallofmeat 0|1`, saved with the profile.

Everything it draws is skate.'s own, read from the installed game: its
skeleton mesh (the Dem Bones costume) posed with the renderer's own
skinning matrices, its scoring icons, the THRASHER wordmark and its UI
brush strokes. Nothing is shipped.

How it works: No Bail already hooks the skeleton response, which runs
once a physics step right after the body contacts; it now hands that step
on, with the local skater it follows, but only while Hall of Meat is on.
Each step reads what every ragdoll body hit and how hard, and whether the
skater is a ragdoll; the bail runs on the game's own clock so slow motion
measures what the body did. The feature's own hooks, on the renderer's
skinned draw packets and main view and on the UI model's write, are
prepared at startup and switched on only with the feature, and off again
once the HUD corner and the game's speed are given back: switched off, the
game never calls them and the physics step pays nothing. The skeleton mesh
is read the first time it is on; only the images are read at startup,
since the overlay builds its atlas once. The renderer's picture of the
skater is read only while a bail shows. The menu's switch reads the
overlay model like the others. Every address is in the build table with a
fingerprint checked before use. Extension/HallOfMeat/README.md describes
the parts; its tests build with DINGOSDK_BUILD_HALL_OF_MEAT_TESTS.

The slow motion changes SimulationTime's rate, frame cap and time scale as
a session override and steps the local skater along, since skate. sets a
skater's step only when it builds its core. It runs the same at every
break, but never faster than the game ran: with the trainer's game speed
below 1 it only takes the part that is slower still. Afterwards it writes
back what it found, each setting only while it still holds what the slow
motion wrote, so the trainer's speed comes back as it was; a setting
changed from elsewhere meanwhile is left as it is and ends that slow
motion. named_setting_value reads a setting's value for it.

Tuned in play: a bruise from 7 m/s and a break from 12 m/s along the
contact normal (raised twice: a hard fall on flat ground broke bones), a
break worth ten of the lightest hits (a heavy bail's hits and breaks
outweighed the rest of the card) and a fall half of skate. 3's rate.

Fix: the overlay now draws in the back buffer's pixels, not the window's,
so it lines up on displays Windows scales (1920x1080 borderless on 4K).

The skater's render hooks, its skeleton and its body maps were first
found by gBGYo's ReSkate fork (github.com/gBGYo/ReSkate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:29:38 +02:00
AyeZeeBB b229062400 Skater effects on other players: costume and board trails, and contact sparks and dust
Other players' skaters and skateboards showed none of the game's skater
effects: not the trail or fire a costume or deck comes with, and not the
sparks, dust and puffs of a skater touching the world.

Costume and skateboard effects. The game gives another player's skater and
skateboard the right effect sets by itself, but builds them before their
outfit is on, so nothing showed. They are now built again half a second after
the outfit is applied. An always-on effect that a skater's item puts on the
skateboard's wheel joints (the Ghost Skater trail) follows the skater's own
copy of those joints, which only this player's skater keeps up to date: for
other players it is taken out of the skater's sets and built on their
skateboard, which has the same joints.

Contact effects. They come from a skater touching the world, which only
happens in that player's own game. Each game now captures its skater's
contacts (position, velocity, surface normal, surface material) and sends up
to three a tick in a new unreliable packet, PacketKind::effects. Other games
play each through the game's own function, so the effect is still chosen from
the surface and the speed. A contact is shown as late as that player's poses
are, only within 30 m of their skater, and only for a material the level's
table has: the game reads that table without a bound.

Server. A dedicated server relays effects packets to players within 150 m of
the sender, at most one packet a tick from each player. New setting
players.sync_effects (default true), console and admin command
"effects on|off". Off: the server relays none, and the roster tells games
neither to send their contacts nor to build other players' costume and
skateboard effects. Player-hosted lobbies relay through the host and always
share them.

The roster carries one more byte for the setting. Still protocol 45, which
has not been released.

New console command "mp effects": what has been captured and played, and why
the last contact was refused if one was.
2026-10-09 00:10:11 -05:00
Swagi-Redacted 94e4df12ba Sync latest bind changes, added freecam bind, tp to freecam bind, and off board up boost 2026-10-08 13:40:35 -04:00
ReSkate local d264ac778c Music: add playlist-order playback toggle 2026-10-06 03:26:37 +01:00
ReSkate local 2e6882ede1 Music: guard the playlist lookup against unresolved song entries
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.
2026-10-05 06:31:13 +01:00
WackyHckyandClaude Opus 5.5 8fa971ceae Raise physics world body pool for large custom maps
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>
2026-10-05 06:26:20 +11:00
ReGlitched 1664c0a855 layer scan fix 2026-10-03 22:37:22 -06:00
AyeZeeBB d84b32fc17 Encode every rendered frame of a replay export
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.
2026-10-03 20:57:07 -05:00
ReGlitched 2fd92ba989 crash fix + launch arg 2026-10-03 00:23:44 -06:00
ReGlitched fa07f912f4 cool hoodie :D 2026-10-02 23:08:12 -06:00
AyeZeeBB f7fc909ca7 Revert "Keep launcher alive through Skate startup; trim per-frame client work"
This reverts commit b7932535e5.
2026-10-02 20:19:51 -05:00
AyeZeeBB b7932535e5 Keep launcher alive through Skate startup; trim per-frame client work
Launcher
- "Close the launcher when Skate starts" now hides the launcher and exits
  when Skate.exe does, instead of exiting as the splash opens. Outside a
  Steam library, Skate's protection closed the game (TerminateProcess 0x32)
  when the launcher exited during startup; players confirmed turning the
  setting off avoided it.
- STATUS tile warns when Steam isn't open or signed in that PLAY starts
  offline as Unknown Player (hidden when Offline mode is chosen or while
  launching).
- Setting description updated to match.
Startup interventions (20260929)
- exit_return 0x87755f2 -> 0x87fe7fb: the last LEA RSP,[RSP]; JMP stub
  before the PUSHFQ / LOCK CMPXCHG routine, as 0x87367cf was in 20260908,
  and the return address player logs show. Only enforced once a frame site
  is armed; this build's protector has no equivalent rep stosb frame clear.
Client frame cost (profiled 2026-10-02)
- Transform interpolate is inline plain arithmetic (~1% of an MP frame).
- Script gameplay-setting and native-setting gets are served from small
  per-thread caches keyed on asset/key address, name prefix and a new
  profile::Store::changes() counter, instead of a store lookup each call.
- Hot-path reads in client tick, native settings and population controls
  use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.
2026-10-02 18:56:57 -05:00
AyeZeeBB ca1d82f636 1.0.0 Release! 2026-10-02 14:40:41 -05:00