From 3b77535e5f647a356f22b7c3225969cd8b1a3c10 Mon Sep 17 00:00:00 2001 From: rcelyte Date: Wed, 19 Apr 2023 01:03:58 +0000 Subject: [PATCH] Support item reordering in save data conversion --- makefile | 2 +- qpm.json | 2 +- src/conversions.hpp | 207 ++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 95 +++++--------------- 4 files changed, 230 insertions(+), 76 deletions(-) create mode 100644 src/conversions.hpp diff --git a/makefile b/makefile index 40c3e8c..016f7f4 100644 --- a/makefile +++ b/makefile @@ -36,7 +36,7 @@ $(OBJDIR)/%.cpp.o: %.cpp extern makefile | ndk \"name\": \"Mapping Extensions\",\n\ \"id\": \"MappingExtensions\",\n\ \"author\": \"StackDoubleFlow, rxzz0, rcelyte\",\n\ - \"version\": \"0.22.2\",\n\ + \"version\": \"0.22.3\",\n\ \"packageId\": \"com.beatgames.beatsaber\",\n\ \"packageVersion\": \"1.28.0_4124311467\",\n\ \"description\": \"This adds a host of new things you can do with your maps as a mapper, and allows you to play said maps as a player. An update of the port of the PC original mod by Kyle 1413. Previously maintained by zoller27osu.\",\n\ diff --git a/qpm.json b/qpm.json index daafed3..140fdea 100644 --- a/qpm.json +++ b/qpm.json @@ -4,7 +4,7 @@ "info": { "name": "MappingExtensions", "id": "MappingExtensions", - "version": "0.22.2", + "version": "0.22.3", "url": null, "additionalData": {} }, diff --git a/src/conversions.hpp b/src/conversions.hpp new file mode 100644 index 0000000..58213c8 --- /dev/null +++ b/src/conversions.hpp @@ -0,0 +1,207 @@ +#pragma once +#include + +template struct Linematch; + +struct BpmState { + struct BpmChangeData { + float time, beat, bpm; + }; + std::vector changes; + uint32_t current = 0; + BpmState(float startBpm, System::Collections::Generic::List_1 *events) { + const uint32_t count = events->get_Count(); + bool skipFirst = (count > 0 && events->get_Item(0)->get_beat() == 0); + changes.reserve(count + !skipFirst); + BpmChangeData lastEntry = {0, 0, skipFirst ? events->get_Item(0)->get_bpm() : startBpm}; + changes.push_back(lastEntry); + for(uint32_t i = skipFirst; i < count; i++) { + BeatmapSaveDataVersion3::BeatmapSaveData::BpmChangeEventData *event = events->get_Item(i); + const float beat = event->get_beat(); + lastEntry = {lastEntry.time + (beat - lastEntry.beat) / lastEntry.bpm * 60, beat, event->get_bpm()}; + changes.push_back(lastEntry); + } + } + float GetTime(float beat) { + while(current > 0 && changes[current].beat >= beat) + --current; + while(current < changes.size() - 1 && changes[current + 1].beat < beat) + ++current; + return changes[current].time + (beat - changes[current].beat) / changes[current].bpm * 60; + } +}; + +static inline GlobalNamespace::ColorType ConvertColorType(BeatmapSaveDataVersion3::BeatmapSaveData::NoteColorType noteType) { + switch(noteType) { + case BeatmapSaveDataVersion3::BeatmapSaveData::NoteColorType::ColorA: return GlobalNamespace::ColorType::ColorA; + case BeatmapSaveDataVersion3::BeatmapSaveData::NoteColorType::ColorB: return GlobalNamespace::ColorType::ColorB; + default: return GlobalNamespace::ColorType::None; + } +} + +template<> struct Linematch { + float time; + int32_t lineIndex; + GlobalNamespace::ColorType colorType; + GlobalNamespace::NoteCutDirection cutDirection; + float cutDirectionAngleOffset; + Linematch(GlobalNamespace::NoteData *from) : + time(from->get_time()), + lineIndex(from->get_lineIndex()), + colorType(from->get_colorType()), + cutDirection(from->get_cutDirection()), + cutDirectionAngleOffset(from->get_cutDirectionAngleOffset()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::ColorNoteData *from, BpmState *bpmState) : + time(bpmState->GetTime(from->get_beat())), + lineIndex(from->get_line()), + colorType(ConvertColorType(from->get_color())), + cutDirection(from->get_cutDirection()), + cutDirectionAngleOffset(from->get_angleOffset()) {} +}; + +template<> struct Linematch { + float time; + int32_t lineIndex; + Linematch(GlobalNamespace::NoteData *from) : + time(from->get_time()), + lineIndex(from->get_lineIndex()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::BombNoteData *from, BpmState *bpmState) : + time(bpmState->GetTime(from->get_beat())), + lineIndex(from->get_line()) {} +}; + + +template<> struct Linematch { + float time; + int32_t lineIndex; + float duration; + int32_t width; + int32_t height; + Linematch(GlobalNamespace::ObstacleData *from) : + time(from->get_time()), + lineIndex(from->get_lineIndex()), + duration(from->get_duration()), + width(from->get_width()), + height(from->get_height()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::ObstacleData *from, BpmState *bpmState) : + time(bpmState->GetTime(from->get_beat())), + lineIndex(from->get_line()), + duration(bpmState->GetTime(from->get_beat() + from->get_duration()) - time), + width(from->get_width()), + height(from->get_height()) {} +}; + +struct SliderLinematch { + GlobalNamespace::ColorType colorType; + float headTime; + int32_t headLineIndex; + GlobalNamespace::NoteCutDirection headCutDirection; + float tailTime; + int32_t tailLineIndex; + SliderLinematch(GlobalNamespace::SliderData *from) : + colorType(from->get_colorType()), + headTime(from->get_time()), + headLineIndex(from->get_headLineIndex()), + headCutDirection(from->get_headCutDirection()), + tailTime(from->get_tailTime()), + tailLineIndex(from->get_tailLineIndex()) {} + SliderLinematch(BeatmapSaveDataVersion3::BeatmapSaveData::BaseSliderData *from, BpmState *bpmState) : + colorType(ConvertColorType(from->get_colorType())), + headTime(bpmState->GetTime(from->get_beat())), + headLineIndex(from->get_headLine()), + headCutDirection(from->get_headCutDirection()), + tailTime(bpmState->GetTime(from->get_tailBeat())), + tailLineIndex(from->get_tailLine()) {} +}; + +template<> struct Linematch { + SliderLinematch base; + GlobalNamespace::NoteCutDirection tailCutDirection; + GlobalNamespace::SliderMidAnchorMode midAnchorMode; + float headControlPointLengthMultiplier; + float tailControlPointLengthMultiplier; + Linematch(GlobalNamespace::SliderData *from) : + base(from), + tailCutDirection(from->get_tailCutDirection()), + midAnchorMode(from->get_midAnchorMode()), + headControlPointLengthMultiplier(from->get_headControlPointLengthMultiplier()), + tailControlPointLengthMultiplier(from->get_tailControlPointLengthMultiplier()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::SliderData *from, BpmState *bpmState) : + base(from, bpmState), + tailCutDirection(from->get_tailCutDirection()), + midAnchorMode(from->get_sliderMidAnchorMode()), + headControlPointLengthMultiplier(from->get_headControlPointLengthMultiplier()), + tailControlPointLengthMultiplier(from->get_tailControlPointLengthMultiplier()) {} +}; + +template<> struct Linematch { + SliderLinematch base; + int32_t sliceCount; + float squishAmount; + Linematch(GlobalNamespace::SliderData *from) : + base(from), + sliceCount(from->get_sliceCount()), + squishAmount(from->get_squishAmount()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::BurstSliderData *from, BpmState *bpmState) : + base(from, bpmState), + sliceCount(from->get_sliceCount()), + squishAmount(from->get_squishAmount()) {} +}; + +template<> struct Linematch { + float time; + int32_t lineIndex; + GlobalNamespace::OffsetDirection offsetDirection; + Linematch(GlobalNamespace::WaypointData *from) : + time(from->get_time()), + lineIndex(from->get_lineIndex()), + offsetDirection(from->get_offsetDirection()) {} + Linematch(BeatmapSaveDataVersion3::BeatmapSaveData::WaypointData *from, BpmState *bpmState) : + time(bpmState->GetTime(from->get_beat())), + lineIndex(from->get_line()), + offsetDirection(from->get_offsetDirection()) {} +}; + +std::array GetLineLayers(BeatmapSaveDataVersion3::BeatmapSaveData::BombNoteData *saveData) {return {saveData->get_layer()};} +std::array GetLineLayers(BeatmapSaveDataVersion3::BeatmapSaveData::ColorNoteData *saveData) {return {saveData->get_layer()};} +std::array GetLineLayers(BeatmapSaveDataVersion3::BeatmapSaveData::ObstacleData *saveData) {return {saveData->get_layer()};} +std::array GetLineLayers(BeatmapSaveDataVersion3::BeatmapSaveData::WaypointData *saveData) {return {saveData->get_layer()};} +std::array GetLineLayers(BeatmapSaveDataVersion3::BeatmapSaveData::BaseSliderData *saveData) {return {saveData->get_headLayer(), saveData->get_tailLayer()};} + +template +struct LayerCache { + struct Entry { + Linematch match; + std::array layers; + }; + std::vector cache; + std::vector matched; + size_t head = 0, failCount = 0; + LayerCache(System::Collections::Generic::List_1 *list, float startBpm, System::Collections::Generic::List_1 *bpmEvents) : matched(list->get_Count()) { + BpmState bpmState(startBpm, bpmEvents); + cache.reserve(matched.size()); + for(size_t i = 0; i < matched.size(); ++i) { + SaveData *entry = list->get_Item(i); + cache.push_back({Linematch(entry, &bpmState), GetLineLayers(entry)}); + } + } + template std::array restore(BeatmapData *data) { + const Linematch match(data); + std::array res = {}; + size_t i = head; + for(; i < cache.size(); ++i) { + if(matched[i]) + continue; + if(memcmp(&match, &cache[i].match, sizeof(match))) + continue; + res = cache[i].layers; + matched[i] = true; + break; + } + failCount += (i == cache.size()); + for(; head < cache.size(); ++head) + if(!matched[head]) + break; + return res; + } +}; diff --git a/src/main.cpp b/src/main.cpp index 95aff40..5fa981d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,91 +142,38 @@ MAKE_HOOK_MATCH(GameplayCoreSceneSetupData_GetTransformedBeatmapDataAsync, &Glob /* PC version hooks */ +#include "conversions.hpp" MAKE_HOOK_MATCH(BeatmapDataLoader_GetBeatmapDataFromBeatmapSaveData, &GlobalNamespace::BeatmapDataLoader::GetBeatmapDataFromBeatmapSaveData, GlobalNamespace::BeatmapData*, ::BeatmapSaveDataVersion3::BeatmapSaveData* beatmapSaveData, ::GlobalNamespace::BeatmapDifficulty beatmapDifficulty, float startBpm, bool loadingForDesignatedEnvironment, ::GlobalNamespace::EnvironmentKeywords* environmentKeywords, ::GlobalNamespace::EnvironmentLightGroups* environmentLightGroups, ::GlobalNamespace::DefaultEnvironmentEvents* defaultEnvironmentEvents, ::GlobalNamespace::PlayerSpecificSettings* playerSpecificSettings) { - System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BeatmapSaveData::ColorNoteData*> *notes = beatmapSaveData->colorNotes; - System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BeatmapSaveData::BombNoteData*> *bombs = beatmapSaveData->bombNotes; - System::Collections::Generic::List_1 *obstacles = beatmapSaveData->obstacles; - System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BeatmapSaveData::SliderData*> *sliders = beatmapSaveData->sliders; - System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BeatmapSaveData::BurstSliderData*> *bursts = beatmapSaveData->burstSliders; - System::Collections::Generic::List_1 *waypoints = beatmapSaveData->waypoints; - // notes->Sort(); bombs->Sort(); obstacles->Sort(); sliders->Sort(); bursts->Sort(); waypoints->Sort(); - GlobalNamespace::BeatmapData *result = BeatmapDataLoader_GetBeatmapDataFromBeatmapSaveData(beatmapSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, environmentKeywords, environmentLightGroups, defaultEnvironmentEvents, playerSpecificSettings); if(!active) - return result; - - uint32_t noteIndex = 0, bombIndex = 0, obstacleIndex = 0, sliderIndex = 0, burstIndex = 0, waypointIndex = 0; - logger->info("Restoring %u notes, %u bombs, %u obstacles, %u sliders, %u burst sliders, and %u waypoints", notes->get_Count(), bombs->get_Count(), obstacles->get_Count(), sliders->get_Count(), bursts->get_Count(), waypoints->get_Count()); + return BeatmapDataLoader_GetBeatmapDataFromBeatmapSaveData(beatmapSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, environmentKeywords, environmentLightGroups, defaultEnvironmentEvents, playerSpecificSettings); + LayerCache bombCache(beatmapSaveData->bombNotes, startBpm, beatmapSaveData->bpmEvents); + LayerCache noteCache(beatmapSaveData->colorNotes, startBpm, beatmapSaveData->bpmEvents); + LayerCache obstacleCache(beatmapSaveData->obstacles, startBpm, beatmapSaveData->bpmEvents); + LayerCache burstCache(beatmapSaveData->burstSliders, startBpm, beatmapSaveData->bpmEvents); + LayerCache sliderCache(beatmapSaveData->sliders, startBpm, beatmapSaveData->bpmEvents); + LayerCache waypointCache(beatmapSaveData->waypoints, startBpm, beatmapSaveData->bpmEvents); + logger->info("Restoring %zu notes, %zu bombs, %zu obstacles, %zu sliders, %zu burst sliders, and %zu waypoints", + noteCache.cache.size(), bombCache.cache.size(), obstacleCache.cache.size(), sliderCache.cache.size(), burstCache.cache.size(), waypointCache.cache.size()); + GlobalNamespace::BeatmapData *result = BeatmapDataLoader_GetBeatmapDataFromBeatmapSaveData(beatmapSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, environmentKeywords, environmentLightGroups, defaultEnvironmentEvents, playerSpecificSettings); for(System::Collections::Generic::LinkedListNode_1 *iter = result->get_allBeatmapDataItems()->head, *end = iter ? iter->prev : NULL; iter; iter = iter->next) { GlobalNamespace::BeatmapDataItem *item = iter->item; if(GlobalNamespace::NoteData *data = il2cpp_utils::try_cast(item).value_or(nullptr); data) { - System::Collections::Generic::IReadOnlyList_1 *source = (System::Collections::Generic::IReadOnlyList_1*)notes; - uint32_t *sourceIndex = ¬eIndex; - if(data->gameplayType == GlobalNamespace::NoteData::GameplayType::Bomb) - source = (System::Collections::Generic::IReadOnlyList_1*)bombs, sourceIndex = &bombIndex; - if(*sourceIndex >= ((System::Collections::Generic::IReadOnlyCollection_1*)source)->get_Count()) { - logger->warning("Failed to restore line layer for NoteData (%s)", ((void*)source == (void*)notes) ? "Color" : "Bomb"); - goto next; - } - BeatmapSaveDataVersion3::BeatmapSaveData::BeatmapSaveDataItem *saveNote = source->get_Item((*sourceIndex)++); - // GlobalNamespace::NoteLineLayer oldLayer = data->noteLineLayer; - if(BeatmapSaveDataVersion3::BeatmapSaveData::ColorNoteData *saveData = il2cpp_utils::try_cast(saveNote).value_or(nullptr); saveData) - data->noteLineLayer = saveData->get_layer(); - else if(BeatmapSaveDataVersion3::BeatmapSaveData::BombNoteData *saveData = il2cpp_utils::try_cast(saveNote).value_or(nullptr); saveData) - data->noteLineLayer = saveData->get_layer(); - else - logger->error("Failed to cast note data"); - /*if(data->noteLineLayer != oldLayer) - logger->info(" NoteData restore %d -> %d", (int)oldLayer, (int)data->noteLineLayer);*/ + data->noteLineLayer = (data->gameplayType == GlobalNamespace::NoteData::GameplayType::Bomb) ? bombCache.restore(data)[0] : noteCache.restore(data)[0]; } else if(GlobalNamespace::ObstacleData *data = il2cpp_utils::try_cast(item).value_or(nullptr); data) { - if(obstacleIndex >= obstacles->get_Count()) { - logger->warning("Failed to restore line layer for ObstacleData"); - goto next; - } - BeatmapSaveDataVersion3::BeatmapSaveData::ObstacleData *saveData = obstacles->get_Item(obstacleIndex++); - if(!saveData) { - logger->error("ObstacleData should not be null!"); - goto next; - } - // GlobalNamespace::NoteLineLayer oldLayer = data->lineLayer; - data->lineLayer = saveData->get_layer(); - /*if(data->lineLayer != oldLayer) - logger->info(" ObstacleData restore %d -> %d", (int)oldLayer, (int)data->lineLayer);*/ + data->lineLayer = obstacleCache.restore(data)[0]; } else if(GlobalNamespace::SliderData *data = il2cpp_utils::try_cast(item).value_or(nullptr); data) { - System::Collections::Generic::IReadOnlyList_1 *source = (System::Collections::Generic::IReadOnlyList_1*)sliders; - uint32_t *sourceIndex = &sliderIndex; - if(data->sliderType == GlobalNamespace::SliderData::Type::Burst) - source = (System::Collections::Generic::IReadOnlyList_1*)bursts, sourceIndex = &burstIndex; - if(*sourceIndex >= ((System::Collections::Generic::IReadOnlyCollection_1*)source)->get_Count()) { - logger->warning("Failed to restore line layers for SliderData (%s)", ((void*)source == (void*)sliders) ? "Normal" : "Burst"); - goto next; - } - // GlobalNamespace::NoteLineLayer oldLayers[2] = {data->headLineLayer, data->tailLineLayer}; - BeatmapSaveDataVersion3::BeatmapSaveData::BaseSliderData *saveData = source->get_Item((*sourceIndex)++); - data->headBeforeJumpLineLayer = data->headLineLayer = saveData->get_headLayer(); - data->tailBeforeJumpLineLayer = data->tailLineLayer = saveData->get_tailLayer(); - /*if(data->headLineLayer != oldLayers[0] || data->tailLineLayer != oldLayers[1]) - logger->info(" SliderData restore (%d, %d) -> (%d, %d)", (int)oldLayers[0], (int)oldLayers[1], (int)data->headLineLayer, (int)data->tailLineLayer);*/ + std::array layers = (data->sliderType == GlobalNamespace::SliderData::Type::Burst) ? burstCache.restore(data) : sliderCache.restore(data); + data->headBeforeJumpLineLayer = data->headLineLayer = layers[0]; + data->tailBeforeJumpLineLayer = data->tailLineLayer = layers[1]; } else if(GlobalNamespace::WaypointData *data = il2cpp_utils::try_cast(item).value_or(nullptr); data) { - if(waypointIndex >= waypoints->get_Count()) { - logger->warning("Failed to restore line layer for WaypointData"); - goto next; - } - BeatmapSaveDataVersion3::BeatmapSaveData::WaypointData *saveData = waypoints->get_Item(waypointIndex++); - if(!saveData) { - logger->error("WaypointData should not be null!"); - goto next; - } - // GlobalNamespace::NoteLineLayer oldLayer = data->lineLayer; - data->lineLayer = saveData->get_layer(); - /*if(data->lineLayer != oldLayer) - logger->info(" WaypointData restore %d -> %d", (int)oldLayer, (int)data->lineLayer);*/ + data->lineLayer = waypointCache.restore(data)[0]; } - next: // TODO: goto bad if(iter == end) break; } - if(notes->get_Count() != noteIndex || bombs->get_Count() != bombIndex || obstacles->get_Count() != obstacleIndex || sliders->get_Count() != sliderIndex || bursts->get_Count() != burstIndex || waypoints->get_Count() != waypointIndex) - logger->warning("Failed to restore %u notes, %u bombs, %u obstacles, %u sliders, %u burst sliders, and %u waypoints", notes->get_Count() - noteIndex, bombs->get_Count() - bombIndex, obstacles->get_Count() - obstacleIndex, sliders->get_Count() - sliderIndex, bursts->get_Count() - burstIndex, waypoints->get_Count() - waypointIndex); + if(noteCache.failCount || bombCache.failCount || obstacleCache.failCount || sliderCache.failCount || burstCache.failCount || waypointCache.failCount) + logger->warning("Failed to restore %zu notes, %zu bombs, %zu obstacles, %zu sliders, %zu burst sliders, and %zu waypoints", + noteCache.failCount, bombCache.failCount, obstacleCache.failCount, sliderCache.failCount, burstCache.failCount, waypointCache.failCount); return result; } @@ -566,7 +513,7 @@ MAKE_HOOK_MATCH(StaticBeatmapObjectSpawnMovementData_LineYPosForLineLayer, &Glob extern "C" DL_EXPORT void setup(ModInfo& info) { info.id = "MappingExtensions"; - info.version = "0.22.2"; + info.version = "0.22.3"; modInfo = info; logger = new Logger(modInfo, LoggerOptions(false, true)); logger->info("Leaving setup!");