mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 18:37:07 +02:00
Read a mod's copy from the archive block it is in; link the lobby and mesh tests
Two fixes taken from worthlessnorms' pull request 16. The bug it was
opened for, rewritten copies read back from the mod's folder, was fixed
separately in 6fd3845.
Mod merge (Engine/Vfs/mod_merge_cas_store.cpp, mod_merge_combine.cpp)
- To combine two mods' edits the merge reads each mod's copy from the
mod's own folder, which means turning its placement in the merged
patch back into the mod's archive and offset. It took the first of the
mod's blocks in that patch archive. A mod added while the game runs
has all its archives appended to the patch's archive 1, so a copy in
its second archive was looked for in its first, the read failed, and
the asset kept the highest-priority copy.
- unshift() picks the block the offset falls in. Of two blocks that
start at the same byte the later one is used: the earlier is an empty
archive.
- Reproduced with a cosmetic mod rebuilt as two archives and added to a
running merge: cas_main_bundlereftable "could not be combined (Cannot
read ...\cas_01.cas)" before, "combined 2 edits (101 preset(s))"
after. A launch merge gives every archive an index of its own and was
not affected; 40 installed mods merge exactly as before.
- mod_merge_unshift_tests covers an archive with its own index, three
appended blocks, bytes before the first block, and an empty archive.
Tests (CMakeLists.txt, Extension/Multiplayer/Test/mesh_tests.cpp)
- multiplayer_lobbies did not link: steam_lobbies.cpp calls
contains_bad_words and the target did not link dingosdk_word_filter.
- multiplayer_mesh did not link: four functions the session code calls
had no stub. Two are the ones the pull request adds
(update_developer_hoodie, remote_skater_generation); the developer
board has since added update_developer_board and remote_board_entity.
- Both targets build and their tests pass.
Co-authored-by: worthlessnorms <110003169+worthlessnorms@users.noreply.github.com>
This commit is contained in:
1 parent
09c3ff0af8
commit
04e50f7dee
9 files changed
+96
-197
No files matched your search
@@ -1,76 +0,0 @@
|
||||
name: Build ReSkate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version stamped into the binaries (e.g. 0.2.0)
|
||||
required: true
|
||||
default: 0.0.0-dev
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Windows x64 Release
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure
|
||||
env:
|
||||
RESKATE_BACKTRACE_URL: ${{ secrets.RESKATE_BACKTRACE_URL }}
|
||||
run: cmake --preset vs2022-x64 -DDINGOSDK_LAUNCHER_AUTO_UPDATE=ON '-DDINGOSDK_VERSION=${{ inputs.version }}'
|
||||
|
||||
- name: Build launcher and runtime
|
||||
run: cmake --build --preset release --parallel 4 --target dingosdk_launcher dingosdk_runtime
|
||||
|
||||
- name: Stage package
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$package = 'build/package'
|
||||
New-Item -ItemType Directory -Path "$package/licenses" -Force | Out-Null
|
||||
Copy-Item -LiteralPath 'build/vs2022-x64/Release/ReSkateLauncher.exe' -Destination $package
|
||||
Copy-Item -LiteralPath 'build/vs2022-x64/Release/ReSkate.dll' -Destination $package
|
||||
$symbols = 'build/symbols'
|
||||
New-Item -ItemType Directory -Path $symbols -Force | Out-Null
|
||||
foreach ($name in @('ReSkate', 'ReSkateLauncher')) {
|
||||
Copy-Item -LiteralPath "build/vs2022-x64/Release/$name.pdb" -Destination $symbols
|
||||
}
|
||||
Copy-Item -LiteralPath 'build/vs2022-x64/Release/ReSkate.dll' -Destination $symbols
|
||||
Copy-Item -LiteralPath 'build/vs2022-x64/Release/ReSkateLauncher.exe' -Destination $symbols
|
||||
Copy-Item -LiteralPath 'LICENSE' -Destination "$package/LICENSE.txt"
|
||||
foreach ($library in @('imgui', 'detours', 'rapidjson', 'sqlite', 'miniz', 'bcdec')) {
|
||||
Copy-Item -LiteralPath "External/$library/LICENSE.txt" -Destination "$package/licenses/$library-LICENSE.txt"
|
||||
}
|
||||
foreach ($library in @('spdlog', 'lz4', 'zstd', 'steam_networking')) {
|
||||
Copy-Item -LiteralPath "External/$library/LICENSE" -Destination "$package/licenses/$library-LICENSE.txt"
|
||||
}
|
||||
Copy-Item -Path 'External/fonts/*-LICENSE.txt' -Destination "$package/licenses"
|
||||
|
||||
- name: Upload ReSkate ZIP
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ReSkate-windows-x64-${{ github.sha }}
|
||||
path: build/package/
|
||||
archive: true
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload matching debug symbols
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ReSkate-symbols-${{ github.sha }}
|
||||
path: build/symbols/
|
||||
archive: true
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
@@ -1,112 +0,0 @@
|
||||
name: Linux Server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [linux-server-native, main]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version stamped into the binaries and the asset name (e.g. 1.0.3)
|
||||
required: true
|
||||
default: 0.0.0-dev
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Linux x64 Server
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake libssl-dev
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
if [ -n "${{ inputs.version }}" ]; then echo "VERSION=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_ENV"
|
||||
else echo "VERSION=0.0.0-dev" >> "$GITHUB_ENV"; fi
|
||||
|
||||
- name: Configure
|
||||
run: cmake --preset linux-x64 -DDINGOSDK_BUILD_MULTIPLAYER_TESTS=ON "-DDINGOSDK_VERSION=${{ env.VERSION }}"
|
||||
|
||||
- name: Build server and tests
|
||||
run: cmake --build --preset linux-release --parallel 4
|
||||
|
||||
- name: Run portable tests
|
||||
run: ctest --test-dir build/linux --output-on-failure
|
||||
|
||||
- name: Fetch Steam libs
|
||||
# Same split as the Windows zip's DLLs: libsteam_api.so from the
|
||||
# Steamworks SDK redist, steamclient.so (+tier0/vstdlib) from the
|
||||
# SteamCMD SDK Redist depot (anonymous). Valve binaries, bundled
|
||||
# like the Windows release bundles its DLLs.
|
||||
run: |
|
||||
set -eux
|
||||
mkdir -p build/steamlibs
|
||||
# Pinned and hashed: this library is loaded into the server, and a
|
||||
# branch name is whatever that branch holds on the day CI runs. Both
|
||||
# values also live in contrib/setup-linux-server-libs.sh.
|
||||
SDK_COMMIT=df2baabf574a738ef1ea90a7e89339107fc0a279
|
||||
SDK_SHA256=eb2dd015b84177cf4f4326fe578aab375fd8931bbbd719c7492420d9777007fe
|
||||
curl -fSL --retry 3 -o build/steamlibs/libsteam_api.so \
|
||||
"https://raw.githubusercontent.com/rlabrecque/SteamworksSDK/$SDK_COMMIT/redistributable_bin/linux64/libsteam_api.so"
|
||||
echo "$SDK_SHA256 build/steamlibs/libsteam_api.so" | sha256sum -c -
|
||||
test "$(stat -c%s build/steamlibs/libsteam_api.so)" -gt 100000
|
||||
nm -D --defined-only build/steamlibs/libsteam_api.so \
|
||||
| grep "SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v01" > /dev/null
|
||||
mkdir -p /tmp/steamcmd && cd /tmp/steamcmd
|
||||
curl -fSL --retry 3 -o steamcmd.tar.gz \
|
||||
"https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz"
|
||||
tar xzf steamcmd.tar.gz
|
||||
./steamcmd.sh +@sSteamCmdForcePlatformType linux +login anonymous +app_update 1007 +quit
|
||||
cp linux64/steamclient.so linux64/libtier0_s.so linux64/libvstdlib_s.so \
|
||||
"$GITHUB_WORKSPACE/build/steamlibs/"
|
||||
ls -lh "$GITHUB_WORKSPACE/build/steamlibs/"
|
||||
|
||||
- name: Stage package
|
||||
run: |
|
||||
set -eux
|
||||
package='build/package-linux'
|
||||
mkdir -p "$package/licenses"
|
||||
cp build/linux/ReSkateServer "$package/"
|
||||
cp build/steamlibs/*.so "$package/"
|
||||
cp contrib/setup-linux-server-libs.sh "$package/"
|
||||
cp contrib/reskate-server.service "$package/"
|
||||
cp Server/README-linux.md "$package/"
|
||||
cp Server/README.txt "$package/README-server.txt"
|
||||
cp LICENSE "$package/LICENSE.txt"
|
||||
cp contrib/setup-linux-server-libs.sh "$package/"
|
||||
cp contrib/reskate-server.service "$package/"
|
||||
cp Server/README-linux.md "$package/"
|
||||
cp Server/README.txt "$package/README-server.txt"
|
||||
cp LICENSE "$package/LICENSE.txt"
|
||||
for library in rapidjson sqlite miniz bcdec; do
|
||||
cp "External/$library/LICENSE.txt" "$package/licenses/$library-LICENSE.txt"
|
||||
done
|
||||
for library in spdlog lz4 zstd steam_networking; do
|
||||
cp "External/$library/LICENSE" "$package/licenses/$library-LICENSE.txt"
|
||||
done
|
||||
cp External/fonts/*-LICENSE.txt "$package/licenses/"
|
||||
file "$package/ReSkateServer"
|
||||
|
||||
- name: Upload Linux server
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ReSkateServer-Linux-${{ env.VERSION }}
|
||||
path: build/package-linux/
|
||||
archive: true
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
@@ -143,6 +143,7 @@ if(DINGOSDK_BUILD_MULTIPLAYER_TESTS AND WIN32)
|
||||
add_executable(dingosdk_multiplayer_lobby_tests
|
||||
Extension/Multiplayer/Test/lobby_tests.cpp Extension/Multiplayer/Steam/steam_lobbies.cpp
|
||||
Extension/Multiplayer/Net/protocol.cpp)
|
||||
target_link_libraries(dingosdk_multiplayer_lobby_tests PRIVATE dingosdk_word_filter)
|
||||
add_test(NAME multiplayer_lobbies COMMAND dingosdk_multiplayer_lobby_tests)
|
||||
add_executable(dingosdk_multiplayer_mesh_tests
|
||||
Extension/Multiplayer/Test/mesh_tests.cpp Extension/Multiplayer/Net/protocol.cpp Extension/Multiplayer/Remote/playback_buffers.cpp
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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; }
|
||||
}
|
||||
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[{initial, 1}] = {4, 0};
|
||||
own.at[{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[{initial, 1}] = {1, 5000};
|
||||
appended.at[{initial, 2}] = {1, 9096};
|
||||
appended.at[{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[{initial, 1}] = {1, 700};
|
||||
empty_first.at[{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;
|
||||
}
|
||||
@@ -7,6 +7,22 @@
|
||||
|
||||
namespace dingosdk::mods::detail {
|
||||
|
||||
bool unshift(const ArchivePlacement& placement, std::string_view directory, std::uint16_t& archive,
|
||||
std::uint32_t& offset) {
|
||||
const ArchivePlacement::Spot* block{};
|
||||
std::uint16_t origin{};
|
||||
for (const auto& [from, spot] : placement.at) {
|
||||
if (from.first != directory || spot.archive != archive || spot.offset > offset) continue;
|
||||
// Blocks are appended in archive order, so of two that start at the same
|
||||
// byte the earlier one is empty and the offset is in the later.
|
||||
if (!block || spot.offset >= block->offset) { block = &spot; origin = from.second; }
|
||||
}
|
||||
if (!block) return false;
|
||||
archive = origin;
|
||||
offset = static_cast<std::uint32_t>(offset - block->offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
CasStore::CasStore(fs::path baseRoot, fs::path output, const native_db::Node& layout)
|
||||
: GameArchives(std::move(baseRoot), layout), output_(std::move(output)) {}
|
||||
|
||||
|
||||
@@ -400,15 +400,9 @@ fb::TocDocument combine(const fs::path& baseToc, const fs::path& baseRoot,
|
||||
// The contributor's own copy still sits at its original offset in
|
||||
// its own folder, which is where a later merge reads it from.
|
||||
auto unshifted = entry.file;
|
||||
if (placement && unshifted.location.patch) {
|
||||
for (const auto& [origin, spot] : placement->at) {
|
||||
if (spot.archive != unshifted.location.archive ||
|
||||
origin.first != store.directory(unshifted.location.installChunk)) continue;
|
||||
unshifted.location.archive = origin.second;
|
||||
unshifted.offset = static_cast<std::uint32_t>(unshifted.offset - spot.offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (placement && unshifted.location.patch)
|
||||
if (const auto* directory = store.find_directory(unshifted.location.installChunk))
|
||||
unshift(*placement, *directory, unshifted.location.archive, unshifted.offset);
|
||||
state.history[id].push_back(
|
||||
{root, unshifted, entry.asset.sha1, entry.asset.resourceMeta, isBase});
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ struct ArchivePlacement {
|
||||
std::map<std::pair<std::string, std::uint16_t>, Spot> at;
|
||||
};
|
||||
|
||||
// Reverses CasStore::shift: turns a placement in the merged patch back into the
|
||||
// mod's own archive number and offset there. Several of a mod's archives can
|
||||
// share one patch archive (a mod added while the game runs has them all
|
||||
// appended to archive 1), so the block is the one the offset falls in, not the
|
||||
// first in that archive. False when the mod has no block there.
|
||||
bool unshift(const ArchivePlacement& placement, std::string_view directory, std::uint16_t& archive,
|
||||
std::uint32_t& offset);
|
||||
|
||||
fb::Sha1 sha1_of(std::span<const std::byte> bytes);
|
||||
|
||||
// Where the launch's merge put every installed mod's archives, and which
|
||||
|
||||
@@ -35,6 +35,8 @@ bool clear_lobby_guest_objects() { ++simulated_guest_wipes; return true; }
|
||||
namespace dingosdk {
|
||||
bool teleport_local_skater(const std::array<float, 3>&) { return true; }
|
||||
void update_board_lock(std::uintptr_t, std::uintptr_t, bool) noexcept {}
|
||||
void update_developer_hoodie(std::uintptr_t, std::uintptr_t, std::uint64_t, std::uint64_t, DeveloperHoodieState &) noexcept {}
|
||||
void update_developer_board(std::uintptr_t, std::uintptr_t, std::uint64_t, std::uint64_t, DeveloperBoardState &) noexcept {}
|
||||
}
|
||||
namespace dingosdk::multiplayer {
|
||||
bool local_allows_player_collision(std::uintptr_t, std::uintptr_t) noexcept { return false; }
|
||||
@@ -248,6 +250,8 @@ std::uint64_t remote_pose_updates() noexcept { return 0; }
|
||||
std::uint64_t remote_board_pose_updates() noexcept { return 0; }
|
||||
NativeAnimationStats remote_animation_stats() noexcept { return {}; }
|
||||
std::uintptr_t remote_skater_entity() noexcept { return 0; }
|
||||
std::uintptr_t remote_board_entity() noexcept { return 0; }
|
||||
std::uint64_t remote_skater_generation() noexcept { return 0; }
|
||||
void note_remote_distance(float) noexcept {}
|
||||
void update_native_party(std::uintptr_t, const PartyRoster &, unsigned, bool) noexcept {}
|
||||
void set_native_party_changes(bool) noexcept {}
|
||||
|
||||
@@ -44,6 +44,10 @@ if(DINGOSDK_BUILD_LAUNCHER_TESTS AND WIN32)
|
||||
add_executable(dingosdk_mod_scoring_tests Engine/Vfs/Test/mod_scoring_tests.cpp)
|
||||
target_link_libraries(dingosdk_mod_scoring_tests PRIVATE dingosdk_mods)
|
||||
add_test(NAME mod_scoring COMMAND dingosdk_mod_scoring_tests "${DINGOSDK_TEST_GAME_ROOT}")
|
||||
add_executable(dingosdk_mod_merge_unshift_tests Engine/Vfs/Test/mod_merge_unshift_tests.cpp)
|
||||
target_link_libraries(dingosdk_mod_merge_unshift_tests PRIVATE dingosdk_mods)
|
||||
target_include_directories(dingosdk_mod_merge_unshift_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME mod_merge_unshift COMMAND dingosdk_mod_merge_unshift_tests)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
Reference in new issue
Block a user