Files
ReSkate/Engine/Vfs/Test/mod_merge_unshift_tests.cpp
AyeZeeBB 76330e40bb Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties
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.
2026-10-04 13:27:21 -05:00

65 lines
3.1 KiB
C++

// A placement in the merged patch has to be turned back into the mod's own
// archive and offset before the mod's copy can be read for a combine. When
// several of a mod's archives share one patch archive, that means picking the
// block the offset falls in.
#include "Engine/Vfs/mod_merge_internal.h"
#include <iostream>
using namespace dingosdk::mods::detail;
namespace {
int failures = 0;
void check(bool condition, const char* what) {
if (!condition) { std::cerr << "FAILED: " << what << "\n"; ++failures; }
}
// A mod's archive: the package folder it is in and its number there.
std::pair<std::string, std::uint16_t> of(const std::string& directory, std::uint16_t archive) {
return {directory, archive};
}
bool lands(const ArchivePlacement& placement, std::string_view directory, std::uint16_t archive, std::uint32_t offset,
std::uint16_t expected_archive, std::uint32_t expected_offset) {
return unshift(placement, directory, archive, offset) && archive == expected_archive && offset == expected_offset;
}
} // namespace
int main() {
const std::string initial = "configurations/layout/initialinstallpackage";
const std::string standard = "configurations/layout/defaultinstallpackage";
// The usual launch placement: each archive at the start of an index of its own.
ArchivePlacement own;
own.at[of(initial, 1)] = {4, 0};
own.at[of(standard, 1)] = {9, 0};
check(lands(own, initial, 4, 1234, 1, 1234), "an archive with an index of its own keeps its offsets");
check(lands(own, standard, 9, 0, 1, 0), "the other package folder has its own blocks");
std::uint16_t archive = 9;
std::uint32_t offset = 10;
check(!unshift(own, initial, archive, offset) && archive == 9 && offset == 10,
"an archive the mod has no block in is left alone");
// A mod added while the game runs: archives 1, 2 and 3 appended to the patch's archive 1.
ArchivePlacement appended;
appended.at[of(initial, 1)] = {1, 5000};
appended.at[of(initial, 2)] = {1, 9096};
appended.at[of(initial, 3)] = {1, 20000};
check(lands(appended, initial, 1, 5000, 1, 0), "the first block starts at its own byte 0");
check(lands(appended, initial, 1, 9095, 1, 4095), "the last byte of the first block is still the first archive");
check(lands(appended, initial, 1, 9096, 2, 0), "an offset in the second block is the second archive");
check(lands(appended, initial, 1, 15000, 2, 5904), "offsets are counted from the block they fall in");
check(lands(appended, initial, 1, 20001, 3, 1), "and so on for a third");
archive = 1;
offset = 4999;
check(!unshift(appended, initial, archive, offset), "bytes before the mod's first block are not the mod's");
// An empty archive's block starts where the next one does; the data is in the next.
ArchivePlacement empty_first;
empty_first.at[of(initial, 1)] = {1, 700};
empty_first.at[of(initial, 2)] = {1, 700};
check(lands(empty_first, initial, 1, 700, 2, 0) && lands(empty_first, initial, 1, 900, 2, 200),
"an empty archive's block yields to the one that follows it");
if (failures) return 1;
std::cout << "mod merge unshift: ok\n";
return 0;
}