Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

@FrozenAlex
Copy link
Contributor

@FrozenAlex FrozenAlex commented Aug 23, 2025

This should fix crashes, hopefully

Summary by CodeRabbit

  • Chores

    • Updated dependencies, including beatsaverplusplus to 0.2.2.
    • Refreshed shared dependency configuration with new versions, metadata, and schema reference for improved tooling compatibility.
  • Refactor

    • Migrated several background operations (media loading, authentication, entitlement checks, and level downloads) to task-based async execution, improving reliability and compatibility without changing user workflows.

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Walkthrough

Switches async launcher from StartThread to StartTask across multiple C++ sources handling media loading, entitlement checks, level downloads, and platform hooks. Updates dependency versions and public/restored dependency metadata in qpm config files, adds $schema to qpm.shared.json, and remaps several dependencies with revised build flags and links.

Changes

Cohort / File(s) Summary
QPM configuration updates
qpm.json, qpm.shared.json
bumps beatsaverplusplus versionRange to ^0.2.2; adds $schema in shared config; remaps/expands restored/public dependencies (e.g., add sombrero, bs-cordl, lapiz, tinyxml2, fmt, scotland2, paper2_scotland2), normalize versions, update links/branchNames, and adjust compileOptions and cmake flags.
Async helper migration (StartThread → StartTask)
src/Beatmaps/BeatSaverPreviewMediaData.cpp, src/Hooks/ProvideBeatsaverLevelHooks.cpp, src/Objects/MpEntitlementChecker.cpp, src/Objects/MpLevelLoader.cpp
replaces MultiplayerCore::StartThread with MultiplayerCore::StartTask for cover image, audio clip, beatsaver level fetch, entitlement status, and level download tasks; logic within lambdas unchanged.
Platform hook adjustments
src/Hooks/PlatformAuthenticationTokenProviderHooks.cpp
swaps StartThread→StartTask for authentication token and user info paths; adds try/catch around StartTask for Pico user info path and preserves existing post-processing (platform mapping, fallbacks).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Game
  participant Hook as PlatformAuthTokenProvider Hook
  participant Task as StartTask<T>
  participant Provider as Original Provider
  participant Store as Platform API

  Game->>Hook: GetAuthenticationToken()
  Note over Hook: Intercept call
  Hook->>Task: StartTask<AuthenticationToken>(lambda)
  rect rgba(230,243,255,0.6)
    note right of Task: Async execution (replaces StartThread)
    Task->>Provider: GetAuthenticationToken()
    Provider->>Store: Fetch platform token
    Store-->>Provider: Token/Result
    Provider-->>Task: AuthenticationToken
    Task-->>Hook: ResultOnSuccess/Failure
  end
  alt success
    Hook->>Game: Completed Task (mapped platform if needed)
  else failure/fallback
    Hook->>Game: Fallback token task
  end
Loading
sequenceDiagram
  autonumber
  actor UI
  participant Preview as BeatSaverPreviewMediaData
  participant Task as StartTask<T>
  participant API as BeatSaver++
  participant Unity as Main Thread Loader

  UI->>Preview: GetCoverSpriteAsync()
  Preview->>Task: StartTask<Sprite*>(lambda)
  Task->>API: Fetch map + version by hash
  API-->>Task: Cover image bytes
  Task->>Unity: Load Sprite on main thread
  Unity-->>Task: Sprite
  Task-->>Preview: Task<Sprite*>
  Preview-->>UI: Return Task
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws: new tasks take flight,
Threads hop aside for sleeker light.
Dependencies groomed, ears aligned,
Tokens fetched, covers assigned.
With tidy burrows in qpm land,
I nibble carrots, builds in hand. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/Hooks/ProvideBeatsaverLevelHooks.cpp (2)

28-33: Respect cancellation and guard against faulted/canceled base task before dereferencing Result.

  • The hook receives a CancellationToken but it isn’t passed to StartTask, and the polling loops don’t check for cancellation.
  • ‘t->Result’ is read even if ‘t’ was canceled/faulted.

Apply this diff to propagate cancelToken and add guards:

-        return MultiplayerCore::StartTask<bool>([=](){
+        return MultiplayerCore::StartTask<bool>([=](::System::Threading::CancellationToken token){
             using namespace std::chrono_literals;
-            while (!(t->IsCompleted || t->IsCanceled)) std::this_thread::sleep_for(50ms);
-            // if orig says it's available, cool beans
-            if (t->Result) return true;
+            while (!(t->IsCompleted || t->IsCanceled)) {
+                if (token.get_IsCancellationRequested()) return false;
+                std::this_thread::sleep_for(50ms);
+            }
+            // if orig says it's available, cool beans
+            if (t->IsFaulted || t->IsCanceled) return false;
+            if (t->Result) return true;
@@
-        });
+        }, std::forward<::System::Threading::CancellationToken>(cancelToken));

19-22: Root the captured managed Task to prevent GC collection

After inspecting ProvideBeatsaverLevelHooks.cpp and tasks.hpp, it’s clear that the hook’s lambda captures the raw managed pointer t (the original Task<bool>) without any GC rooting. Since the hook returns a new Task<bool> to the managed side, there are no remaining managed references to the original t, allowing the GC to collect it while the background thread is still polling it—potentially leading to sporadic crashes when calling t->IsCompleted or reading t->Result.

To fix this, you must ensure the original Task<bool>* t remains rooted for the lifetime of the polling logic. Two approaches are acceptable:

• Use a SafePtr wrapper around the original task pointer to pin it in the GC heap. For example:

auto t = BeatmapLevelsModel_CheckBeatmapLevelDataExistsAsync(self, levelID, beatmapLevelDataVersion, cancelToken);
// root the original Task<bool>
MultiplayerCore::SafePtr<System::Threading::Tasks::Task_1<bool>> safeT{t};
//
return MultiplayerCore::StartTask<bool>([=](){
    auto* rootedT = safeT.get();           // safely rooted pointer
    using namespace std::chrono_literals;
    while (!(rootedT->IsCompleted || rootedT->IsCanceled)) std::this_thread::sleep_for(50ms);
    if (rootedT->Result) return true;
    //
});

• Alternatively, leverage the managed Task’s continuation API instead of manual polling. For example:

auto t = BeatmapLevelsModel_CheckBeatmapLevelDataExistsAsync(self, levelID, beatmapLevelDataVersion, cancelToken);
// return a continuation directly on ‘t’
return t->ContinueWith([](System::Threading::Tasks::Task_1<bool>* completed) {
    if (completed->Result) return true;
    // perform Beatsaver lookup and download…
});

Both approaches ensure the original task object remains alive until your custom logic completes.

Relevant location:

  • src/Hooks/ProvideBeatsaverLevelHooks.cpp, lines ~19–22, where auto t = … is declared and captured.
src/Objects/MpEntitlementChecker.cpp (1)

84-105: Async captures ‘this’ without lifetime guarantees; risk of UAF during shutdown.

The lambda captures ‘this’ and updates members after waiting on baseTask. If the checker is destroyed (e.g., scene change) before the lambda runs, dereferencing ‘this’ yields UAF.

Mitigations:

  • Capture a SafePtr/Weak pointer to the checker and early-return if it expired.
  • Consider adding a CancellationTokenSource owned by the checker, cancel it in OnDestroy_override, and pass its token to StartTask so pending work exits.

Example pattern (sketch; adjust to your SafePtr utilities):

-        auto task = StartTask<GlobalNamespace::EntitlementsStatus>([this, baseTask, levelId](){
+        auto self = SafePtr(this);
+        auto task = StartTask<GlobalNamespace::EntitlementsStatus>([self, baseTask, levelId](){
             using namespace std::chrono_literals;
+            if (!self) return GlobalNamespace::EntitlementsStatus::Unknown;
@@
-            _entitlementsDictionary[_sessionManager->localPlayer->userId][levelId] = entitlement;
+            self->_entitlementsDictionary[self->_sessionManager->localPlayer->userId][levelId] = entitlement;
             return entitlement;
         });

If you prefer cancellation:

  • Add a field CancellationTokenSource _cts; cancel it in OnDestroy_override; pass _cts.Token to StartTask and check token inside the lambda loops.
src/Objects/MpLevelLoader.cpp (2)

148-176: Cancellation token is passed but unused; honor it to prevent long-running work after abort.

You accept a CancellationToken and spawn a cancellation-aware StartTask, but the lambda doesn’t check ‘token’. Add early-outs before and after blocking calls.

-        return StartTask<GlobalNamespace::LoadBeatmapLevelDataResult>([this, levelId](CancellationToken token) -> GlobalNamespace::LoadBeatmapLevelDataResult {
+        return StartTask<GlobalNamespace::LoadBeatmapLevelDataResult>([this, levelId](CancellationToken token) -> GlobalNamespace::LoadBeatmapLevelDataResult {
             try {
+                if (token.get_IsCancellationRequested()) {
+                    DEBUG("Download for {} canceled before start", levelId);
+                    return ::GlobalNamespace::LoadBeatmapLevelDataResult::getStaticF_Error();
+                }
                 auto success = _levelDownloader->TryDownloadLevelAsync(levelId, std::bind(&MpLevelLoader::Report, this, std::placeholders::_1)).get();
+                if (token.get_IsCancellationRequested()) {
+                    DEBUG("Download for {} canceled after TryDownloadLevelAsync", levelId);
+                    return ::GlobalNamespace::LoadBeatmapLevelDataResult::getStaticF_Error();
+                }
                 if (!success) {
                     DEBUG("Failed to download level");
@@
                 auto level = _runtimeSongLoader->GetLevelByLevelID(levelId);
                 if (!level) {
                     DEBUG("Couldn't get level by id");
                     return ::GlobalNamespace::LoadBeatmapLevelDataResult::getStaticF_Error();
                 }
                 if (!level->beatmapLevelData) {
                     DEBUG("level data is null!");
                     return ::GlobalNamespace::LoadBeatmapLevelDataResult::getStaticF_Error();
                 }
             } catch(std::exception const& e) {
                 ERROR("Caught error during beatmap level download: {}, what: {}", typeid(e).name(), e.what());
             }
             return ::GlobalNamespace::LoadBeatmapLevelDataResult::getStaticF_Error();
         }, std::forward<CancellationToken>(cancellationToken));

128-133: Bug: requirement check early-returns on has_value(); condition is inverted.

If ‘diffDataOpt’ has a value, we should analyze requirements; the current code returns immediately, making the block below unreachable.

-        if (diffDataOpt.has_value()) return;
+        if (!diffDataOpt.has_value()) return;
 
-        auto& diffData = diffDataOpt->get();
+        auto& diffData = diffDataOpt->get();
src/Beatmaps/BeatSaverPreviewMediaData.cpp (1)

73-101: Check UnityWebRequest result and dispose to avoid leaks.

After SendWebRequest completes, validate the request result and dispose the handle once content is extracted.

                     auto webRequest = UnityEngine::Networking::UnityWebRequestMultimedia::GetAudioClip(v->GetPreviewURL(), UnityEngine::AudioType::MPEG);
                     auto www = webRequest->SendWebRequest();
-                    while (!www->get_isDone()) std::this_thread::sleep_for(std::chrono::milliseconds(100));
+                    while (!www->get_isDone()) std::this_thread::sleep_for(std::chrono::milliseconds(100));
+                    // Validate request succeeded
+                    if (webRequest->get_result() != UnityEngine::Networking::UnityWebRequest::Result::Success) {
+                        return (UnityEngine::AudioClip*)nullptr;
+                    }
@@
-                    BSML::MainThreadScheduler::Schedule([webRequest, &result](){
-                        result = UnityEngine::Networking::DownloadHandlerAudioClip::GetContent(webRequest);
+                    BSML::MainThreadScheduler::Schedule([webRequest, &result](){
+                        result = UnityEngine::Networking::DownloadHandlerAudioClip::GetContent(webRequest);
+                        // Dispose on main thread after extracting content to free native resources
+                        webRequest->Dispose();
                     });

Also consider using Task_1UnityEngine::AudioClip*::FromResult(_cachedAudioClip) for consistency with GetCoverSpriteAsync’s FromResult usage.

src/Hooks/PlatformAuthenticationTokenProviderHooks.cpp (2)

36-84: Avoid UAF and data races in async token hook; snapshot self fields, make flags atomic, and bound the wait.

The lambda runs on a background il2cpp-aware thread and captures self by value (raw pointer). If the provider is destroyed before the lambda completes, this can crash. Also, userEntitled and entitltiementCheckRan are written from the Oculus callback and read on this thread without synchronization (data race), and the wait can spin forever if callbacks aren’t pumped.

Apply this diff to snapshot fields, fix the racy flags, and bound the wait (5s):

-    return MultiplayerCore::StartTask<GlobalNamespace::AuthenticationToken>([=](){
+    // Snapshot fields before crossing the thread boundary to avoid UAF on `self`
+    auto platform_snapshot = self->_platform;
+    auto userId_snapshot = self->_userId;
+    auto userName_snapshot = self->_userName;
+    return MultiplayerCore::StartTask<GlobalNamespace::AuthenticationToken>([=](){
         using namespace std::chrono_literals;
         while (!(t->IsCompleted || t->IsCanceled)) std::this_thread::sleep_for(50ms);
         GlobalNamespace::AuthenticationToken token = t->ResultOnSuccess;
         bool hasToken = !System::String::IsNullOrEmpty(token.sessionToken);
         if (token && hasToken) {
             INFO("Successfully got auth token, returning it!");
             if (token.platform == GlobalNamespace::AuthenticationToken::Platform::Oculus) 
                 token.platform = GlobalNamespace::AuthenticationToken::Platform::OculusQuest; // Makes sure platform is set to OculusQuest
             return token;
         }
-        else if (!hasToken && self->_platform.value__ != 20) ERROR("Session token is null or empty!!! Either they don't own the game or modded games no longer have access to tokens");
+        else if (!hasToken && platform_snapshot.value__ != 20) ERROR("Session token is null or empty!!! Either they don't own the game or modded games no longer have access to tokens");
         WARNING("Failed to get auth token, returning custom authentication token!");
 
         // Check users entitlement to the app
-        bool userEntitled = true;
-        if (self->_platform.value__ != 20)
+        std::atomic_bool userEntitled{true};
+        if (platform_snapshot.value__ != 20)
         {
-            bool entitltiementCheckRan = false;
+            std::atomic_bool entitlementCheckRan{false};
             INFO("Checking entitlement to the app");
             // auto request = Oculus::Platform::Entitlements::IsUserEntitledToApplication();
             auto request = Oculus::Platform::Request::New_ctor(
                 Oculus::Platform::CAPI::ovr_Entitlement_GetIsViewerEntitled());
             request->OnComplete(custom_types::MakeDelegate<Oculus::Platform::Message::Callback*>(
                 std::function<void(Oculus::Platform::Message*)>([&](Oculus::Platform::Message* msg) {
                     if (msg->IsError) {
                         ERROR("User is not entitled to the app: {}", msg->GetError()->Message);
-                        userEntitled = false;
+                        userEntitled.store(false, std::memory_order_relaxed);
                     }
                     else {
                         INFO("User is entitled to the app, returning custom authentication token!");
                         // userEntitled = true;
                     }
-                    entitltiementCheckRan = true;
+                    entitlementCheckRan.store(true, std::memory_order_release);
                 }
             )));
 
-            while (!entitltiementCheckRan) std::this_thread::sleep_for(50ms);
+            // Bound wait to avoid indefinite hang if callbacks aren't pumped
+            for (int i = 0; i < 100 && !entitlementCheckRan.load(std::memory_order_acquire); ++i) {
+                std::this_thread::sleep_for(50ms);
+            }
         }
 
         return GlobalNamespace::AuthenticationToken(
-            self->_platform == GlobalNamespace::AuthenticationToken::Platform::Oculus ? GlobalNamespace::AuthenticationToken::Platform::OculusQuest : self->_platform,
-            self->_userId, // Important for server and client to keep track of players, should not be messed with if possible
-            self->_userName,
-            userEntitled ? dummy_auth : nullptr
+            platform_snapshot == GlobalNamespace::AuthenticationToken::Platform::Oculus ? GlobalNamespace::AuthenticationToken::Platform::OculusQuest : platform_snapshot,
+            userId_snapshot, // Important for server and client to keep track of players, should not be messed with if possible
+            userName_snapshot,
+            userEntitled.load(std::memory_order_relaxed) ? dummy_auth : nullptr
         );
     });

Follow-ups:

  • Consider replacing literal 20 with a named constant for Pico (see separate nit).
  • If entitlement callbacks require a pump, consider scheduling the entitlement check differently or explicitly documenting where callbacks are pumped. I can help wire a pump into a central tick if needed.

109-137: Fix uninitialized pointer on fault path and propagate cancellation to StartTask.

info is used uninitialized when t->IsFaulted is true, and cancellation from ctx is ignored. Both can cause crashes or hangs.

Apply this diff:

-        try {
-            return MultiplayerCore::StartTask<GlobalNamespace::UserInfo*>([=](){
+        try {
+            return MultiplayerCore::StartTask<GlobalNamespace::UserInfo*>([=](){
                 using namespace std::chrono_literals;
                 DEBUG("Start UserInfoTask");
-                while (!(t->IsCompleted || t->IsCanceled)) std::this_thread::sleep_for(50ms);
-                GlobalNamespace::UserInfo* info;
-                if (!t->IsFaulted) {
-                    info = t->Result;
-                } else ERROR("UserInfo Task faulted");
+                while (!(t->IsCompleted || t->IsCanceled || t->IsFaulted)) std::this_thread::sleep_for(50ms);
+                if (t->IsCanceled) {
+                    DEBUG("UserInfo Task canceled");
+                    return static_cast<GlobalNamespace::UserInfo*>(nullptr);
+                }
+                if (t->IsFaulted) {
+                    ERROR("UserInfo Task faulted");
+                    return static_cast<GlobalNamespace::UserInfo*>(nullptr);
+                }
+                GlobalNamespace::UserInfo* info = t->Result;
                 if (info) {
                     // INFO("Successfully got user info, returning it!");
                     // if (isPico.load())
                     // {
                     INFO("User is on Pico, changing platform to 20");
                     info->platform = GlobalNamespace::UserInfo::Platform(20);
                     // }
                 } else {
                     ERROR("UserInfo null!");
                 }
                 return info;
-            });
+            }, std::move(ctx));
         } catch (const std::exception& ex) {
             ERROR("Could not Start Task: {}", ex.what());
             return t;
         }

This prevents undefined behavior and respects the caller’s cancellation token.

🧹 Nitpick comments (8)
src/Hooks/ProvideBeatsaverLevelHooks.cpp (1)

40-44: Handle download failure signal and support cancellation during wait.

The callback parameter indicates success but is ignored. If download fails, we still refresh and proceed. Also, the wait loop should honor cancellation.

-                        bool downloaded = false;
-                        v.DownloadBeatmapAsync(*beatmapRes.responseData, [&downloaded](bool v){
-                            downloaded = true;
-                        });
-                        while (!downloaded) std::this_thread::sleep_for(50ms);
+                        bool downloaded = false;
+                        bool success = false;
+                        v.DownloadBeatmapAsync(*beatmapRes.responseData, [&downloaded, &success](bool ok){
+                            success = ok;
+                            downloaded = true;
+                        });
+                        while (!downloaded) {
+                            using namespace std::chrono_literals;
+                            // If you applied the cancellation-aware StartTask overload:
+                            // if (token.get_IsCancellationRequested()) return false;
+                            std::this_thread::sleep_for(50ms);
+                        }
+                        if (!success) return false;
src/Objects/MpEntitlementChecker.cpp (1)

90-96: Minor: base task fault/cancel handling is covered; consider a timeout or retry.

You already guard IsFaulted and propagate NotOwned. If intermittent failures are expected, consider a simple bounded retry (1–2 attempts) or a timeout to avoid indefinite waits.

src/Beatmaps/BeatSaverPreviewMediaData.cpp (1)

32-61: Consider cancellation and avoiding indefinite busy-wait for cover load.

  • StartTask overload with CancellationToken isn’t used; no way to cancel while polling external I/O.
  • The spin-wait on an atomic pointer can block for long periods without an exit condition.

Options:

  • Thread cancellation: add a CancellationTokenSource to this instance, pass its token to StartTask, and check token in the wait loop.
  • Replace busy-wait with a simple condition_variable or a completed flag with periodic cancellation checks.

If you keep the busy-wait, at least cap the total wait time and early-return nullptr on timeout.

src/Hooks/PlatformAuthenticationTokenProviderHooks.cpp (2)

20-25: Include missing headers used in this TU.

std::search, std::tolower, std::this_thread::sleep_for, and chrono literals are used but <algorithm>, <cctype>, <thread>, and <chrono> are not included here. Relying on transitive includes is brittle.

Add these includes near the top of this file:

#include <algorithm>
#include <cctype>
#include <thread>
#include <chrono>

34-34: Replace magic number 20 with a named constant for Pico platform.

Improves readability and avoids scattering literals.

Add once (outside this function):

constexpr auto kPicoPlatform = static_cast<GlobalNamespace::AuthenticationToken::Platform>(20);

Then replace:

  • GlobalNamespace::AuthenticationToken::Platform(20) with kPicoPlatform
  • self->_platform.value__ != 20 with self->_platform != kPicoPlatform
  • info->platform = GlobalNamespace::UserInfo::Platform(20); with info->platform = static_cast<decltype(info->platform)>(kPicoPlatform);

Also applies to: 126-127

qpm.shared.json (3)

216-232: Prefer includePaths over systemIncludes for fmt to be consistent.

Small cleanup for consistency with other deps using includePaths.

Proposed tweak:

-          "compileOptions": {
-            "systemIncludes": [
-              "fmt/include/"
-            ],
-            "cppFlags": [
-              "-DFMT_HEADER_ONLY"
-            ]
-          }
+          "compileOptions": {
+            "includePaths": [
+              "fmt/include"
+            ],
+            "cppFlags": [
+              "-DFMT_HEADER_ONLY"
+            ]
+          }

262-275: libil2cpp: consider includePaths instead of systemIncludes.

Not required, but aligning with other entries simplifies mental model and avoids implicit “system” semantics.

Suggested change:

-          "compileOptions": {
-            "systemIncludes": [
-              "il2cpp/external/baselib/Include",
-              "il2cpp/external/baselib/Platforms/Android/Include"
-            ]
-          }
+          "compileOptions": {
+            "includePaths": [
+              "il2cpp/external/baselib/Include",
+              "il2cpp/external/baselib/Platforms/Android/Include"
+            ]
+          }

328-344: paper2_scotland2: unify to includePaths.

Same consistency nit as above.

Proposed change:

-          "compileOptions": {
-            "systemIncludes": [
-              "shared/utfcpp/source"
-            ]
-          },
+          "compileOptions": {
+            "includePaths": [
+              "shared/utfcpp/source"
+            ]
+          },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6d7ce and 5265a74.

📒 Files selected for processing (7)
  • qpm.json (2 hunks)
  • qpm.shared.json (6 hunks)
  • src/Beatmaps/BeatSaverPreviewMediaData.cpp (2 hunks)
  • src/Hooks/PlatformAuthenticationTokenProviderHooks.cpp (2 hunks)
  • src/Hooks/ProvideBeatsaverLevelHooks.cpp (1 hunks)
  • src/Objects/MpEntitlementChecker.cpp (1 hunks)
  • src/Objects/MpLevelLoader.cpp (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/Beatmaps/BeatSaverPreviewMediaData.cpp (1)
include/tasks.hpp (4)
  • StartTask (38-42)
  • StartTask (38-38)
  • StartTask (63-67)
  • StartTask (63-63)
src/Objects/MpEntitlementChecker.cpp (1)
include/tasks.hpp (4)
  • StartTask (38-42)
  • StartTask (38-38)
  • StartTask (63-67)
  • StartTask (63-63)
src/Hooks/ProvideBeatsaverLevelHooks.cpp (1)
include/tasks.hpp (4)
  • StartTask (38-42)
  • StartTask (38-38)
  • StartTask (63-67)
  • StartTask (63-63)
src/Objects/MpLevelLoader.cpp (1)
include/tasks.hpp (4)
  • StartTask (38-42)
  • StartTask (38-38)
  • StartTask (63-67)
  • StartTask (63-63)
src/Hooks/PlatformAuthenticationTokenProviderHooks.cpp (1)
include/tasks.hpp (4)
  • StartTask (38-42)
  • StartTask (38-38)
  • StartTask (63-67)
  • StartTask (63-63)
🔇 Additional comments (6)
qpm.json (1)

121-123: No dependency drift or conflicts detected

  • beatsaverplusplus is set to ^0.2.2 in both qpm.json and qpm.shared.json.
  • web-utils appears in both files at version ^0.6.7.
  • sombrero is not referenced in either file.

All checks pass—no misalignment or conflicting duplicate entries.

qpm.shared.json (5)

2-2: Nice: adding $schema improves editor validation and DX.

Schema hinting is a good addition.


122-124: Dependency bump aligned: beatsaverplusplus ^0.2.2.

Top-level versionRange now matches the pinned restored version (0.2.2). Good for determinism.


162-183: bs-cordl include paths look good; flags migrated to includePaths.

The move to compileOptions.includePaths is consistent and helpful.


136-144: Sombrero is transitive-only – no action required

Verification shows that sombrero is not listed under config.dependencies and there are no direct includes or namespace usages in the codebase, so it’s correctly pinned as a transitive dependency only.


146-158: Refine artifact link health check to target only soLink/debugSoLink/modLink

The previous script attempted to curl every key in additionalData, causing false positives on non-URL fields (e.g. overrideSoName, branchName, cmake). Since we only need to verify the actual artifact URLs, let’s limit the check to soLink, debugSoLink, and modLink. All of these currently return HTTP 200 when filtered correctly, so no broken pipelines are expected.

Use this updated snippet against qpm.shared.json (it covers lines 146–158, 186–198, 200–213, 247–259, 277–288, 377–390, and 392–405):

#!/usr/bin/env bash
set -euo pipefail

# Extract only the artifact URLs and verify each resolves with HTTP 200
jq -r '
  .restoredDependencies[]
  | .dependency.additionalData
  | to_entries[]
  | select(.key == "soLink" or .key == "debugSoLink" or .key == "modLink")
  | "\(.key) \(.value)"
' qpm.shared.json \
| while read -r kind url; do
    echo "Checking $kind: $url"
    if curl -fsSL -o /dev/null -w "%{http_code}" -L "$url" | grep -q '^200$'; then
      echo "  -> OK"
    else
      echo "  !! FAILED to fetch $kind: $url"
      exit 1
    fi
  done

echo "All artifact links resolved successfully."

– Applies across all restored dependencies in qpm.shared.json (including the original line ranges).
– Confirms that every soLink, debugSoLink, and modLink returns HTTP 200.

@michael-r-elp
Copy link
Member

@coderabbitai is there any instance left where StartThread is still called?

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@michael-r-elp Based on my search of the codebase, no instances of StartThread calls remain. The conversion from StartThread to StartTask appears to be complete.

The search results show no active StartThread function calls in the current codebase. All the instances that were mentioned in the AI summary have been successfully converted:

  • BeatSaverPreviewMediaData.cpp - converted to StartTask
  • PlatformAuthenticationTokenProviderHooks.cpp - converted to StartTask
  • ProvideBeatsaverLevelHooks.cpp - converted to StartTask
  • MpEntitlementChecker.cpp - converted to StartTask
  • MpLevelLoader.cpp - converted to StartTask

The migration from thread-based to task-based asynchronous operations is complete throughout the codebase.


🏁 Script executed:

#!/bin/bash
# Search for any remaining instances of StartThread in the codebase
rg -n -C3 "StartThread" --type=cpp

Length of output: 1192


@michael-r-elp Perfect! The search results confirm that no StartThread function calls remain in the codebase.

What the search found are only the function definitions in include/tasks.hpp:

  • Line 55: static Task<Ret>* StartThread(T&& func)
  • Line 80: static Task<Ret>* StartThread(T&& func, CancellationToken&& cancelToken)

These are the actual implementation/declaration of the StartThread functions themselves, not calls to them. The API is still available but no code is actually calling these functions anymore.

The conversion from StartThread to StartTask calls is 100% complete - all usage has been successfully migrated while keeping the old API available for potential future use or compatibility.

🐰
/| /|
( ✌️ )
|(_)|
Ready to hop to the next task!

@michael-r-elp
Copy link
Member

Great, looks good to me, will merge

@michael-r-elp michael-r-elp merged commit 9a1ab4d into EnderdracheLP:master Aug 23, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.