Mod archives (Engine/Vfs/mod_merge*)
- A free archive index no longer has to sit at or below the highest index
the layout already declares. The engine keeps archives in a table keyed
by layer, install chunk and a 16-bit index, with no range to stay
inside; what it cannot survive is a reference to an archive the layout
never declared. Indices are taken lowest first, per package directory.
- So a mod that collides takes an index of its own and its file is hard
linked rather than concatenated, and no two mods share one archive's
4 GB of addressable bytes. Sharing is left for a mod added while the
game runs, which has no declared index to take, and for a directory
with no index left.
- A placement addresses its payload with a 32-bit offset, so both append
paths now refuse to cross 4 GB instead of wrapping round and pointing
the game at some other payload: CasStore::write fails with the archive
that is full, and the live-apply path checks archive 1's size plus the
mod's before appending and asks for a restart, which gives the mod an
archive of its own anyway.
Injection through a hooked LoadLibraryW (Engine/Core/Platform)
- A player could not launch at all: "Child LoadLibraryW entry bytes match
neither Windows' kernel32 file nor this process". Anti-virus and
overlays hook the entry with a jump to a per-process trampoline, so the
bytes differ in the launcher and in the child and neither comparison
could match. The guard already allowed the same hook when both
processes carried it identically, so it refused on an accident of how
the hook was written rather than on intent.
- inline_jump_length recognises the forms a hooking engine writes (rel32
jmp or call, push/ret, jmp [rip], mov rax/jmp rax, short jmp). When one
is present and every byte after it still matches the clean kernel32
file, the launch goes ahead: the body is Windows' own, and the provider
image already matched on timestamp, checksum and size. A wholesale
replacement is still refused, and now says so.
- The failure names the modules loaded into Skate that came from neither
Windows nor the game folder, instead of guessing at "security
software". An accepted hook is logged as a warning with the same list.
Launching (Launcher/launch.cpp)
- Exporting loose scripts is a convenience, and a folder that would not
take it was failing the whole launch: fs::create_directories threw and
nothing caught it. The export is skipped, RESKATE_LOOSE_FILES goes to
0 so the runtime does not look for files that are not there, and Skate
starts. Someone whose install is under Program Files can play; only
editing loose scripts is lost.
Failures worth reading (Launcher/problem.h)
- A hooked LoadLibraryW, a refused write and ERROR_ELEVATION_REQUIRED
each explain themselves now. Windows reports the last two in the user's
own language -- "Zugriff verweigert", "La operación solicitada requiere
elevación" -- so the rules key on the operation names and the error
number, which are never translated.
Saved skaters (Extension/Customization, Extension/Profile)
- Outfits stopped saving and every slot showed the standard skater. The
load hook marks a preset blocked on entry and clears it only on a full
success, so a load refused for a catalog that was not ready yet left
the preset blocked for the session and every later save was refused as
previous_load_rejected. The game asks once per level load and does not
ask again, so one lost race cost a skater until a restart. Logs show
the catalog arriving three seconds after the refused load, at startup
and again on a map switch 17 minutes later.
- The record is remembered in CosmeticRuntime::pending_loads and applied
by retry_pending_cosmetic_loads from the customization pump, which
already waits for refresh_cosmetic_catalog. The preset stays blocked
until a load really succeeds, so nothing is written over the outfit on
disk.
- Those records belong to the loadout manager's array, which can be
replaced in between, so each pointer is checked against the live array
exactly as a save checks one -- manager current, begin/end sane, 0x48
stride, at most 10 records, in range and aligned. A record that no
longer belongs is dropped as retry_record_moved rather than written
through.
Under Steam + Proton on Linux the launcher always reported Steam as
offline, so the game ran on the offline stand-in and servers could not be
joined. steam_signed_in() needs ActiveProcess\ActiveUser, and Proton's
stand-in steam.exe only writes ActiveProcess\pid.
- When ActiveUser is missing and the launcher is running under Proton,
the account comes from the native Steam client instead: the most recent
entry in config/loginusers.vdf under STEAM_COMPAT_CLIENT_INSTALL_PATH,
which Steam sets for compatibility tools and Wine's
wine_get_dos_file_name maps to a DOS path.
- The Steam name on the launcher's plate and the SteamID that --offline
reports are read from that directory too. Proton's
C:\Program Files (x86)\Steam holds no account files, so offline mode
fell back to 76561197960265729.
- loginusers.vdf's MostRecent key is matched in either case. Steam has
also written it as "mostrecent", which picked the first account in the
file instead of the most recent.
Windows is unchanged: the variable and the Wine export are both absent
there, so ActiveUser is still required.
Diagnosed, with a confirmed workaround and the suggested fix, by
BenjaminJAnderson.
FixesDingo-Shenanigans/ReSkate#5
Co-authored-by: BenjaminJAnderson <45207455+BenjaminJAnderson@users.noreply.github.com>