The Mods page judged a mod empty when it had no layout.toc, reskate-levels.json or parks, so a
plugin (reskate-plugin.json with an "entry"), music, objects or variants mod got the
"nothing to load" warning although the game loads it. mods::has_content now decides that from
what mod_contents lists (or variants), for the launcher and the catalogue's log note alike.
Icons came only from the Thunderstore listing, so a mod installed by hand or not yet published
showed an empty square. Without a package, the icon.png in the mod's folder is read on the icon
worker instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A third ReSkate tab in the game's pause menu (native_menu.cpp slot 2, the Store's tab art), drawn by
the overlay as the other two are (mod_manager_page.inl, included into hub_page.cpp):
- BROWSE: search, categories in a panel of their own (shown only, hidden or either, kept in the
profile), sort, a featured carousel and rows (most popular, recently updated, new, the biggest
categories), a grid for results, and a mod's own page with its README, what it brings with it,
and INSTALL, switch on or off and uninstall. Works from a controller: the carousel's card is
one stop that left and right turn, and B goes back from a mod's page (the page's Back is held
meanwhile).
- MY MODS: the Mods folder with the states the ReSkate menu shows; UPDATES: Update all.
- The foot of the page shows the install's progress and the apply's, and Apply (View on a pad).
Leaving the menu with changes not applied asks first (a two-answer card in message_card.cpp,
answered by the runtime as the message is); LATER closes the menu.
Behind it, Extension/Assets/mod_store: Thunderstore's listing (cached on disk), the installed mods,
and a queue that downloads and installs packages with their dependencies, updates and uninstalls
them. An update or uninstall of a mod the game holds open waits in Mods/.reskate-pending and the
launcher makes it before the next start (launcher_mods::apply_pending). Package icons load into
their own SRV slots (store_icons.cpp), sharing the launcher's icon cache.
Tests: mod_store_view (search, categories, the front page's rows, install plans), the pending
updates and uninstalls in mod_manager_tests, and the third tab in native_menu_view_tests.
- Forgetting uninstalled packs' records cannot close the launcher: a
brought-in.json that cannot be rewritten (a locked or read-only Mods
folder) is logged as a warning; the mods are gone already.
- A dependency the player had before is matched ignoring case, as Windows
matches folders: one installed under another spelling counts as installed
(it is not downloaded again or renamed) and is never recorded as brought in.
- UNINSTALL waits while an install runs, so the two never rewrite
brought-in.json at once.
- A mod pack installs from GET MODS: a package with nothing to load of its
own is taken when its manifest.json lists dependencies, as the install
options already said; it was refused, so no pack ever got a record.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Keep my other mods on when loading a profile", in_use compared every enabled mod in order, so
a player's own mod placed above the profile's mods made the profile read NO PROFILE IN USE although
all its mods were on, and loading it again moved that mod back down. Only the profile's own mods
count now: which are on, and their order among themselves.
DingoSDKDebugInitialize answers the launcher with a bare TRUE or FALSE. On
FALSE the launcher threw "DingoSDKDebugInitialize returned FALSE", which the
STATUS tile turned into "ReSkate could not attach to Skate" with anti-virus
advice, although the attach had worked and the runtime had logged its reason.
ReSkate.dll now exports DingoSDKStartupFailure, a 1 KB buffer that every
FALSE path fills with its reason (the first one stays). The launcher reads it
out of Skate before closing it. With an older ReSkate.dll it reads the ERROR
lines that Skate process wrote to ReSkate.log during the call instead, and
with neither it says it was not told why. problem.h explains these messages
before the attach rules: a save in use or a folder another game holds says
Skate is already running, hook and overlay failures keep the attach advice,
and anything else leads with the runtime's own reason. Bootstrap and the
local profile hand their reason back, and the profile checks that used to
return false without a word now log one.
Fixes#90, fixes#346.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mod list now notices a reskate-plugin.json that names an entry, reskate-objects.json and
reskate-music.json, and mods::mod_contents lists what a mod holds, most telling first. The
launcher's Type column shows the first of them (with "+" when there are more, all of them on
hover), and the in-game Mods page joins them. A reskate-plugin.json with only variants or
resident assets is not a plugin.
The UPDATE button moved from its own spot before the Type column to right after the blue
old -> new version, cutting a long name short rather than pushing into Type.
A Thunderstore install was refused unless the package held layout.toc, reskate-levels.json or a
parks folder, so a package with only a plugin, or a pack whose manifest only lists dependencies,
could not be installed from GET MODS. reskate-plugin.json now counts as content (and finds the mod
inside a package like the other markers), and a manifest with dependencies is a mod pack, as
InstallOptions::require_content always said.
- The category menu on GET MODS, and a new one on MY MODS, take each category as shown only, hidden or
either (a click goes round them), with how many mods are in each; each page's choice is kept in the
launcher's settings. On MY MODS a mod Thunderstore does not list, or lists in no category, is under
"No category".
- Before a launch whose mods give the game other data than the last launch's (the mods with a layout,
their files and load order, and the variants in play), the game's own shader cache
(%LOCALAPPDATA%\ReSkate\Game\Skate\cache) is emptied: shaders kept from earlier mods were found to
break the picture after many changes. The shader_cache folder the game ships with is left alone.
It is placed by its size every frame: placed only when it appeared, it kept
the last confirmation's height, so a short one after the long dependency
list sat near the top of the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each dependency the confirmation offers has its own checkbox, all ticked, in
a list that scrolls past eight, so a player can keep any of them. The first
checkbox ticks or unticks them all, shows a mixed mark while only some are
ticked, and says how many go ("Uninstall 12 of 19 modpack dependencies?").
What it frees counts only the ticked ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Thunderstore install now notes, per package asked for, the dependencies it
downloaded that were not installed before (Mods/brought-in.json), and the
uninstall offers only those, so a mod the player had already is never offered
(nor one the install only updated). A pack installed before this has no
record, so its uninstall offers nothing. Dependency names longer than a mod
folder's are matched to the cut folder thunderstore::folder_for installs.
The confirmation's checkbox reads "Uninstall N modpack dependencies?", with
its label spaced like the settings page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Installing a mod pack installs its dependencies, but uninstalling it left
them all behind. The uninstall confirmation now lists the mods the chosen
ones list as dependencies (and theirs in turn) that no mod staying
installed still lists, with "Also uninstall the N mods it brought in,
which nothing else needs" ticked. A dependency another mod needs is never
offered; unticked, only the chosen mods go, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loading a profile switches every installed mod it does not list off. With
"Keep my other mods on when loading a profile" (saved in profiles.json),
a profile switches its own mods on and leaves the rest as they are, so a
shared profile (a pack of mods) can be added to a player's own set. A
profile then counts as in use when its own mods match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first level the game loads from the splash took San Vansterdam, and the startup commands'
"load" then loaded the start map over it. queue_menu_bam now takes the map from the front of
RESKATE_STARTUP_COMMANDS when it is installed and can load (its start point as the console's
"load" picks one), and the startup commands drop that "load" rather than run it again. A map that
cannot load then still leaves the game in San Vansterdam.
The launcher lists San Vansterdam, the game's other maps and the custom maps of the mods that
load (start_maps.h), keeps the choice with its settings and starts the game in it, as its -map
does: a "load" at the front of the startup commands. A map no longer listed when the world can
load it (a map mod switched off or left out of the merge) is dropped after 20 s, and the game stays
in San Vansterdam. A launcher kept open no longer adds a launch's map to the last one's commands.
Tests: Launcher/Test/start_maps_tests.cpp.
Now that features are Luau plugins, nothing needs mods of ReSkate's own:
- The OFFICIAL tab and its rail tile are gone, with Launcher/official_mods (the backend's list, the
install, update and sync, and Play's wait for them).
- GET MODS lists and updates Board Break, Road Rash and the rest like any other package, and MY
MODS no longer marks them Official or keeps them out of UPDATE ALL.
- The merge may leave one out over a conflict like any other mod; the version stamp, the on/off
helpers and the move out of the old .reskate-official folder are gone from builtin_packs.
- Installed copies stay in Mods as ordinary mods. The retired first Board Break is still left out.
From pull request 301 (tsullivan13), fitted to the newer MY MODS page:
- UPDATE ALL leaves the left rail for a bar over the list, made like the conflicts bar and
under it: how many updates wait and what they download, each mod's old -> new version (the
first six, then how many more), Show only these (the Updates filter and back) and
UPDATE ALL (N) with the download size. Hidden with nothing waiting, off while an install runs.
- A row with an update shows its old -> new version in blue after the author.
- Ctrl+F goes to the search box on MY MODS and GET MODS, as their placeholders say.
Co-authored-by: tsullivan13 <25965236+tsullivan13@users.noreply.github.com>
Resolved where main's trainer meets the plugin branch:
- Board Break, the drone, Road Rash, Hall of Meat and Grind Exits are plugins here, so their
built-in code stays deleted: the trainer no longer hands Board Break's wear values back (the
plugin does not tune physics), its QUICK SETTINGS list no longer names Grind Exits or Board
Break, and the drone's debug action and menu are not brought back.
- The trainer's pump power, gravity, revert boost and transition help run beside the plugins'
pushes and velocities on each physics step; both sides' new declarations are kept.
- The game's own pad reads go through game_pad_read (the gamepad filter, Dark Pop's D-pad hiding,
the grind sticks) and then the plugin branch's menu and hidden-button handling.
- The trainer's forced grind settings are noted as the stance like any other value.
From 2.1.1's Sentry reports (60,000 in three days, 2026-10-21):
- Textures: a stale render texture handle takes the game's default texture instead of reading
through null (Skate.exe+0x3e897fb, a third of all reports, every one with TheVlixOne-Nike_Pack 2.3.x).
- Physics: the wake pass's bitset is made as long as the world's owner list once indices pass
20000, after long multiplayer sessions (+0x3d99e2f, +0x3d9ee43, +0x3d9ee47).
- Music: more than 1024 candidates are picked by ReSkate; the game's shuffle wrote past its stack table.
- Streaming: a staged read's piece that reaches past its 64 KB buffer is left unfilled (VCRUNTIME memcpy).
- Multiplayer: a remote skater or board whose parent bus link is damaged is left alive instead of
destroyed (+0x2b94cd0).
- Mesh pool: on an integrated GPU its budget is capped by the RAM and page file free; the Memory
line says what the PC has left.
Launcher:
- A folder with non-ASCII letters is a hard stop (the boot crash +0x18df4cf came after "Start anyway").
- The game's data files are checked against the supported build's sizes (from Steam's manifest);
missing, cut-short or online-only files send the player to verifying.
- One game per folder: the launcher will not merge or start while one runs there, and ReSkate.dll
holds the folder's mutex.
- Advice on the ready screen for PCs short of RAM and page file; reports carry pc.ram_gb and
pc.commit_free_gb.
Stuck loads:
- A load in one stage for two minutes sends a stuck-load report (every thread's stack and the log,
error.type stall, load.stage, load.seconds) while the game goes on (crash protocol 6).
- A load the player closes sends its timing with the stage it was in.
Logs: the PanelTrace diagnostics and the neighborhood unlock override's observation lines are gone.
The launcher fell back on a list of its own (Board Break's pack and Road Rash) before it had ever
read one from ReSkate. Official mods now come only from that list.
MY MODS: a table with an animated drag for the load order, a conflicts bar with Check conflicts, and a Resolve popup whose choices are kept. The check runs at startup and after changes; conflicts no longer show as problems, block Play, or hold mod files during an update.
Conflicts: only game EBX that cannot be combined, or Lua scripts with different versions; colourways and same-name additions are not conflicts.
Official mods: installed in Mods/<Namespace-Name> and turned on or off through mods.json, so they show in MY MODS; the old .reskate-official folder is moved over.
Merge: a scan cache (Mods/.reskate-scan.txt) keeps each unchanged mod's repair, mesh and texture scans, so big maps are not re-read every merge.
Launcher: an announcement from the backend shows under STATUS, with HIDE.
The device-removal watcher always runs, and the fatal handler notes a DXGI stop in gpu-breadcrumbs.txt. Crash reports carry a gpu.breadcrumbs tag (protocol 5).
Where two mods change one of the game's assets in ways that cannot both be
in the game (a resource or chunk changed differently, an EBX whose changes
cannot be combined, different assets added under one name), the one lower
in the load order is left out whole, so neither loads half its changes.
The merge used to keep the higher copy of each asset and lose the rest of
the lower mod's change silently, which broke Skaterize beside deckorate.
A check before the merge (find_conflicts) reads only the bundles two or
more mods ship and applies the merge's rule: about 2 s for 296 mods, on
several threads, each mod's copy of a bundle by itself. When it finds
conflicts nothing is built, the catalog leaves the losing mods out and
merges once. The merge keeps its own check behind it, tracking per asset
the last change a mod made itself, so a change carried through other mods'
copies is still caught. A conflict is remembered in .reskate-excluded.json
with the mod it lost to, and stays out while both are unchanged and the
winner still loads above it.
The launcher says "Checking the mods" and "Checking the mods for
conflicts" during those steps. Its mod problems screen says MOD CONFLICT
when that is all there is, and the Mod Manager shows a CONFLICT badge, an
N CONFLICTS banner, a Conflicts filter and, on the mod, which mod it lost
to and how to load it instead.
The trainer keeps upstream's page and naming (SANDBOX; noclip, No Bail and
the boosts on Skater > Cheats; the full screen controller menu) with its own
TUNE sections, presets card and search inside. The MOVEMENT tab is gone:
`trainer open movement` opens Skater > Cheats, where the Revert Boost rows
now sit. Upstream's Hardcore preset sits beside Realistic; saving
trainer.json and reading the map file stay off the client thread.
Board Break's wear and the trainer no longer undo each other: wear writes
four tuning values itself, and every trainer write used to put them back to
the trainer's copy (and a level loaded on a worn board took them as stock).
board_break::handling_value() tells the trainer what wear wants; values the
player has not changed are handed back to it, their stock is the game's own.
Tests: Hardcore's check follows the sections (topic, not the old lists);
the session test pokes a flip speed past the trainer's own limit (100).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UNINSTALL, under OPEN FOLDER, takes the mod's stamp away first (a folder
without it is no mod to the game), then its folder, and clears whether it
was turned off so an install brings it back on. The card says UNINSTALLING
meanwhile, and UNINSTALL FAILED when Windows keeps the files (Skate still
running). Cards are as tall as their buttons.
The launcher still reads the list at start and looks up each mod's latest
version, moves in a copy installed from Thunderstore and takes away retired
mods, but it downloads nothing by itself. Each card on the OFFICIAL tab says
NOT INSTALLED, INSTALLED, UPDATE AVAILABLE, DOWNLOADING or UPDATING, with an
INSTALL or UPDATE TO vX button (official_mods::install, on a thread of its
own), OPEN FOLDER once installed, and the On / Off switch beside its pills.
The banner counts how many are installed. Play waits for an install under
way.
Board Break and Road Rash start only when their official mod is installed
and on (mods::official_in_use), and say in the log which it is not; the
menus leave them out otherwise, as when turned off.
The live test (RESKATE_OFFICIAL_LIVE) checks that nothing downloads until
asked, then installs each.
The OFFICIAL tab has an on/off switch on each mod's card, with an OFF pill
while it is off. The list of those turned off is
Mods/.reskate-official/official-off.txt, one folder name a line, which the
launcher's sync never removes. A mod turned off stays downloaded and kept
up to date, but official_packs() leaves it out, so nothing merges it, no
deck comes from it and the in-game Mods page does not list it.
Board Break and Road Rash do not start while their official mod is off,
and say so in the log. The menu model carries that as `off`, and the side
rail, the addon tabs and tiles, the home tiles, the pause menu's settings
and the native tools menu leave them out without a gap. category_tabs
takes a list built at run time for that.
Tests: an official mod turned off stays in place and is left out, comes
back when turned on, and only a plain folder name is written.
Both documents come from the ReSkate backend (GET /api/v1/legal/privacy and /terms, edited on its
Legal page), so a change reaches players without a release. Settings has a button for each, opening
a panel like the changelogs': title, version and date, the Markdown drawn as the README viewer draws
it, and a switch to the other one.
Each launch looks up the current versions (GET /api/v1/legal) in the background. One newer than
the player agreed to, or none agreed to yet, puts up an agreement over the home screen: a short
summary, a way into each document, QUIT and I AGREE, which saves the versions agreed to
(accepted_privacy_version, accepted_terms_version in the launcher's settings). A backend that
cannot be reached holds nobody up. Launches that skip the window (--no-gui, Discord joins) are
not asked.
The game finds its own folder with the ANSI GetModuleFileNameA and decodes it as UTF-8, so a path
with Cyrillic, accented or Japanese characters (often a OneDrive folder) crashes it on start
(Skate.exe+0x18df4cf in Sentry). The launcher now says so and asks before starting; No is the default.
Steam for Mac with NotProton or CrossOver passes the compatibility tool a path inside its app
bundle, below the folder with config/loginusers.vdf, and the tool's stand-in keeps no record of the
running client, so the launcher said Steam was not running and started offline. It now looks up
from that path for config/loginusers.vdf, and when started by the native Steam client under Wine
takes its most recent account where the running-client checks fail. Windows and Proton, where those
checks pass, are unchanged. The offline panel's hint about the launch option shows only under Wine.
MACOS-SETUP.md gives the launch option (/bin/bash, ROSETTA_ADVERTISE_AVX=1 for the game's CPU
check) for the launcher in a folder of its own.
Based on https://github.com/Dingo-Shenanigans/ReSkate/pull/219.
Co-authored-by: codejunky <pmp.codejunky@gmail.com>
The update check reads GitHub's release list either way (one call, as before) and notes whether a
pre-release newer than the latest release is there; the setting is offered only then, or while it
is on so it can be turned off. Unreachable GitHub keeps what the last check found.
Flipping "Install ReSkate updates" or "Test pre-releases" saves and checks again straight away, so
PLAY or the update button changes to match: updates off while one is offered goes back to PLAY,
pre-releases on offers the pre-release, off the latest release. A flip while a check is running is
checked as soon as it ends (each frame asks; it only compares two settings).
Reported in #265 by Kayyo321; first fixed in #267 by Jean-LucTch, whose
approach this keeps to the folder being installed.
"Install folder" named each file with fs::relative, which follows links:
a file link inside the folder to a file outside it came out as
../../<file>, and the copy wrote over that file (ReSkate.dll among what
it could reach).
- Files are named by where they sit in the chosen folder
(lexically_relative), never by where a link leads, and go through
safe_entry like a zip's entries.
- A symbolic link, junction or mount point inside the folder refuses the
install, saying which. Links on the way to the folder or to the Mods
folder (a library moved to another drive) stay fine, as do cloud
placeholders such as OneDrive's, which name nothing else: #267 refused
any reparse point on those paths, so a game reached through a junction
could install nothing.
- Tests: a junction inside a mod folder is refused and the file it leads
to is untouched, with no staging left behind (this fails without the
fix); a Mods folder and a picked folder reached through junctions still
install; a file symbolic link is refused (skipped without Developer
Mode, which creating one needs).
Co-authored-by: Jean-Luc T. <88255067+Jean-LucTch@users.noreply.github.com>
- page_banner: each page opens with its heading, what it is for, and how
things stand at the right (a status pill and a caption). card: the
panel the pages draw on, lighter edged on hover, blue edged when marked.
- MY MODS and GET MODS rows are cards with a gap between them (list_row),
in lists without a border. MY MODS' banner counts problems and waiting
updates and holds the load-order explanation; GET MODS' says whether
the listing loaded and what is installed or waiting. The ticked bar on
GET MODS is a card.
- OFFICIAL: a card per mod with its status and version pills, its
Thunderstore facts, and THUNDERSTORE and OPEN FOLDER.
- PROFILES: new-profile and import cards side by side; a card per profile
with a picture of its first mods' icons, pills for what it holds, LOAD
and UPDATE, and the rest in a menu.
- The borderless window has a frame style for resizing, snapping and its
shadow, with WM_NCCALCSIZE keeping the whole window client. Its edges
and corners resize it; maximised, it is inset by the frame Windows puts
past the screen.
- WM_GETMINMAXINFO holds it to the size it opens at.
- The swap chain's buffers follow the client size (Renderer::resize).
- Frames are drawn from WM_SIZE too, so dragging an edge (Windows' own
message loop) does not stretch the last frame.
Every update, and every install over an installed mod, sent the old
folder to the Recycle Bin, which filled up with old versions.
- commit() moves the installed copy aside into the staging folder, moves
the new one in, then deletes the old for good. A new copy that cannot
go in puts the old one back; a copy with a file in use is not replaced
and stays whole.
- Uninstalling still goes to the Recycle Bin. The Replace dialog says the
installed copy is deleted.
The launcher reads the official list from the ReSkate backend (or the last one it read, or its own)
and keeps each mod in Mods/.reskate-official at its latest version; a copy the player installed is
moved in, the deprecated Board Break retired. The game loads them always on and leaves out Mods
folder copies; the browser no longer lists them. The built-in zips are gone (FPV drone too), and
built-in folders no longer carried are removed.
Fourth Studio's Board Break pack (ten decks and every sound) and Road Rash wounds are built into
ReSkate.dll beside the FPV drone, unpacked under Mods/.reskate-builtin and always on.
- The Thunderstore copies (Fourth_Studio-Board_Break_Pack, Fourth_Studio-Road_Rash and the
deprecated Fourth_Studio-Board_Break, or the same put in by hand) are moved to the Recycle Bin
as the launcher starts and dropped from mods.json; the merge leaves them out should one still
be there, and the Thunderstore browser no longer lists, updates or installs them, a dependency
on one included.
- Board Break's deck list, sounds and deck settings look in the built-in packs too.
- Names with spaces inside them (Board Break's "Body Impact 1.wav") unpack.
- The build makes each zip in assets/builtin into one zstd stream (ReSkateBuiltinPacker,
Engine/Vfs/builtin_pack_format.h), which the DLL carries: repeated files across a pack cost next
to nothing. Board Break 15.0 -> 5.4 MB, the drone 6.0 -> 5.4 MB, Road Rash 36.1 -> 34.2 MB;
ReSkate.dll 86 -> 70 MB. Plain zips still unpack.
- Tests: the mods taken for built-in ones and their retirement, and a pack unpacking to exactly
its files, refusing one that leaves its folder or is cut short (RESKATE_BUILTIN_PACKS also
unpacks the packs a build made).
path::string() uses the ANSI code page and throws on a character it lacks (a Cyrillic folder on
an English Windows). The remaining places it was used now go through path_utf8:
- Mods: a deck folder or a mod folder named outside the code page no longer leaves the Board Break
decks empty, or the Mods folder ignored altogether.
- Game and mod archives: a file under Win32 with such a name is passed over instead of stopping
the read.
- pose-dump no longer ends the multiplayer session when the game folder is not ASCII.
- The item browser's status line and a Board Break warning no longer fail on such a path.
- Launcher: system fonts are named to ImGui in UTF-8, which it reads.
Sentry's minidump endpoint streams every attachment but the dump to an object store, which this
self-hosted Sentry does not run: ReSkate.log and report.json were dropped from every crash
(outcome upload_failed). Each crash now goes to the envelope endpoint as sentry-native sends one:
the event, the minidump, report.json and the log, all kept.
- The event carries the log's last 100 lines as breadcrumbs (debug lines only among the last 25),
the mods the game ran with (enabled, outdated, excluded), how long it had run, and an anonymous
install ID as its user. Its time is the crash's, however late it is sent.
- crash.site tags where it crashed ("Skate.exe+0x3d9ed9d"), as Sentry names game code by the
nearest export; mods.count how many mods were on.
- An accepted report is kept in sent/ (the newest 10) instead of deleted, so the player still has
their crashes.
- multipart.* is report_envelope.*; the receipt check also reads the envelope endpoint's
{"id": ...}.