diff --git a/NewGamePlus.rc b/NewGamePlus.rc index 6159908..3839b90 100644 Binary files a/NewGamePlus.rc and b/NewGamePlus.rc differ diff --git a/src/filesystem/fs_util.cpp b/src/filesystem/fs_util.cpp index 93a2b67..169ce33 100644 --- a/src/filesystem/fs_util.cpp +++ b/src/filesystem/fs_util.cpp @@ -62,7 +62,7 @@ bool HasValidPointOfNoReturnSave() }); } -bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHash) +bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHash) noexcept { static const auto basePath = GetCpSaveFolder(); @@ -74,49 +74,82 @@ bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHas const auto metadataPath = basePath / aSaveName / "metadata.9.json"; - if (!std::filesystem::is_regular_file(metadataPath)) + std::error_code ec{}; + + if (!std::filesystem::is_regular_file(metadataPath, ec)) { return false; } auto padded = simdjson::padded_string::load(metadataPath.string()); - if (padded.error() != simdjson::error_code::SUCCESS) + if (padded.error() != simdjson::SUCCESS) { return false; } - auto json2 = simdjson::dom::parser{}; + simdjson::dom::parser parser{}; + simdjson::dom::element document{}; - const auto document = json2.parse(padded.value()); + if (parser.parse(padded.value()).get(document) != simdjson::SUCCESS) + { + return false; + } if (!document.is_object()) { return false; } - if (!document["RootType"].is_string()) + if (!document.at_key("RootType").is_string()) + { + return false; + } + + simdjson::dom::element saveMetadata{}; + + if (document.at_key("Data").at_key("metadata").get(saveMetadata) != simdjson::SUCCESS) { return false; } - const auto saveMetadata = document["Data"]["metadata"]; + // ISSUE: old saves (might not be from PC?) don't have save metadata correct + // Switch to noexcept versions + + int64_t gameVersion{}; + + if (saveMetadata.at_key("gameVersion").get_int64().get(gameVersion) != simdjson::SUCCESS) + { + return false; + } - if (minSupportedGameVersion > int64_t(saveMetadata["gameVersion"])) + if (minSupportedGameVersion > gameVersion) { return false; } const auto isPointOfNoReturn = aSaveName.starts_with("PointOfNoReturn"); - aPlaythroughHash = Red::FNV1a64(saveMetadata["playthroughID"].get_c_str().value()); + std::string_view playthroughId{}; + + if (saveMetadata.at_key("playthroughID").get_string().get(playthroughId) != simdjson::SUCCESS) + { + return false; + } + + aPlaythroughHash = Red::FNV1a64(playthroughId.data()); if (isPointOfNoReturn) { return true; } - const auto questsDone = saveMetadata["finishedQuests"].get_string().value(); + std::string_view questsDone{}; + + if (saveMetadata.at_key("finishedQuests").get_string().get(questsDone) != simdjson::SUCCESS) + { + return false; + } using std::operator""sv; auto questsSplitRange = std::views::split(questsDone, " "sv); @@ -128,23 +161,34 @@ bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHas return true; } - constexpr auto q307_active_fact = "q307_blueprint_acquired=1"; + constexpr auto q307ActiveFact = "q307_blueprint_acquired=1"; + + simdjson::dom::array importantFacts{}; - for (auto fact : saveMetadata["facts"]) + if (!saveMetadata.at_key("facts").get_array().get(importantFacts) != simdjson::SUCCESS) { - if (fact.is_string()) + return false; + } + + for (auto fact : importantFacts) + { + std::string_view factValueString{}; + + if (fact.get_string().get(factValueString) != simdjson::SUCCESS) + { + continue; + } + + if (factValueString == q307ActiveFact) { - if (fact.get_string().value() == q307_active_fact) - { - return true; - } + return true; } } return false; } -bool IsValidForNewGamePlus(std::string_view aSaveName) +bool IsValidForNewGamePlus(std::string_view aSaveName) noexcept { uint64_t dummy{}; return IsValidForNewGamePlus(aSaveName, dummy); diff --git a/src/filesystem/fs_util.hpp b/src/filesystem/fs_util.hpp index 46c3362..1e08cfd 100644 --- a/src/filesystem/fs_util.hpp +++ b/src/filesystem/fs_util.hpp @@ -3,6 +3,6 @@ namespace files { std::filesystem::path GetCpSaveFolder(); bool HasValidPointOfNoReturnSave(); - bool IsValidForNewGamePlus(std::string_view aSaveName); - bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHash); -} \ No newline at end of file + bool IsValidForNewGamePlus(std::string_view aSaveName) noexcept; + bool IsValidForNewGamePlus(std::string_view aSaveName, uint64_t& aPlaythroughHash) noexcept; + } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b62ceeb..6af4ae6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,7 @@ RED4EXT_C_EXPORT void RED4EXT_CALL Query(RED4ext::PluginInfo* aInfo) { aInfo->name = L"New Game+"; aInfo->author = L"not_alphanine"; - aInfo->version = RED4EXT_SEMVER_EX(1, 0, 0, 0, 0); // Set your version here. + aInfo->version = RED4EXT_SEMVER_EX(1, 0, 0, 1, 0); // Set your version here. aInfo->runtime = RED4EXT_RUNTIME_INDEPENDENT; aInfo->sdk = RED4EXT_SDK_LATEST; } diff --git a/src/redscript_api/redscriptBindings.hpp b/src/redscript_api/redscriptBindings.hpp index cf1c35a..258f137 100644 --- a/src/redscript_api/redscriptBindings.hpp +++ b/src/redscript_api/redscriptBindings.hpp @@ -207,14 +207,8 @@ inline constexpr auto Q003Chip = Red::TweakDBID("Items.q003_chip"); inline constexpr auto Q003ChipCracked = Red::TweakDBID("Items.q003_chip_cracked"); inline constexpr auto Q003ChipCrackedFunds = Red::TweakDBID("Items.q003_chip_cracked_funds"); -inline constexpr auto Q001Lexington = Red::TweakDBID("Items.Preset_Q001_Lexington"); inline constexpr auto CyberdeckSplinter = Red::TweakDBID("Items.CyberdeckSplinter"); -inline constexpr auto PresetLexingtonWilson = Red::TweakDBID("Items.Preset_Lexington_Wilson"); -inline constexpr auto PresetLexingtonWilsonRare = Red::TweakDBID("Items.Preset_Lexington_Wilson_Rare"); -inline constexpr auto PresetLexingtonWilsonEpic = Red::TweakDBID("Items.Preset_Lexington_Wilson_Epic"); -inline constexpr auto PresetLexingtonWilsonLegendary = Red::TweakDBID("Items.Preset_Lexington_Wilson_Legendary"); -inline constexpr auto MQ011WilsonGun = Red::TweakDBID("Items.mq011_wilson_gun"); inline bool IsForbidden(Red::TweakDBID aId) { @@ -223,9 +217,8 @@ inline bool IsForbidden(Red::TweakDBID aId) aId == PersonalLink2 || aId == MaTppHead || aId == WaTppHead || aId == FppHead || aId == HolsteredFists || aId == MQ024DataCarrier || aId == Skippy || aId == SkippyPostQuest || aId == PresetSkippy || aId == PresetSkippyPostQuest || aId == SaburoDataCarrier || aId == SaburoDataCarrierCracked || - aId == Q003Chip || aId == Q003ChipCracked || aId == Q003ChipCrackedFunds || aId == Q001Lexington || - aId == CyberdeckSplinter || aId == PresetLexingtonWilson || aId == PresetLexingtonWilsonRare || - aId == PresetLexingtonWilsonEpic || aId == PresetLexingtonWilsonLegendary || aId == MQ011WilsonGun; + aId == Q003Chip || aId == Q003ChipCracked || aId == Q003ChipCrackedFunds || + aId == CyberdeckSplinter; } }; @@ -750,6 +743,11 @@ class NewGamePlusSystem : public Red::IGameSystem return HasTag("WeaponMod") || m_itemType == Red::gamedataItemType::Prt_Program; } + bool IsDyingNight() const + { + return HasTag("Lexington_Wilson"); + } + bool IsAllowedType() const { using Red::gamedataItemType; @@ -908,6 +906,12 @@ class NewGamePlusSystem : public Red::IGameSystem return; } + if (aExtendedData.IsDyingNight()) + { + // Just fuck off... + return; + } + if (aExtendedData.m_tdbId == "Items.money") { m_saveData.m_playerMoney += aItem.itemQuantity; diff --git a/wolvenkit/fileTreeState.json b/wolvenkit/fileTreeState.json index 12327bd..11e2c66 100644 --- a/wolvenkit/fileTreeState.json +++ b/wolvenkit/fileTreeState.json @@ -1 +1 @@ -{"archive":true,"archive\\base":true,"archive\\mod":true,"archive\\base\\quest":true,"archive\\base\\quest\\main_quests":true,"archive\\base\\quest\\main_quests\\part1":true} \ No newline at end of file +{"archive":true,"archive\\base":true,"archive\\mod":true,"archive\\base\\quest":true,"archive\\base\\quest\\main_quests":true,"archive\\base\\quest\\main_quests\\part1":true,"archive\\base\\gameplay":true,"archive\\base\\gameplay\\gui":true,"archive\\base\\gameplay\\gui\\fullscreen":true,"archive\\base\\gameplay\\gui\\fullscreen\\main_menu":true,"archive\\base\\gameplay\\gui\\fullscreen\\main_menu\\new_game_plus_selection.inkatlas":true,"archive\\base\\gameplay\\gui\\fullscreen\\main_menu\\new_game_plus_selection.xbm":true,"archive\\base\\gameplay\\gui\\fullscreen\\main_menu\\new_game_plus_selection_1080p.xbm":true,"archive\\base\\gameplay\\gui\\fullscreen\\new_game_plus":true,"archive\\base\\gameplay\\gui\\fullscreen\\new_game_plus\\newgameplus_select_savegame.inkwidget":true,"archive\\base\\gameplay\\gui\\fullscreen\\new_game_plus\\newgameplus_select_starting_point.inkwidget":true,"archive\\base\\gameplay\\gui\\fullscreen\\new_game_plus\\newgameplus_stats_adjustment.inkwidget":true,"archive\\base\\localization":true,"archive\\base\\localization\\en-us":true,"archive\\base\\localization\\en-us\\new_game_plus":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q001":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q001\\q001_restored_subtitles.json":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q101":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q101\\q101_restored_subtitles.json":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q116":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\q116\\q116_restored_subtitles.json":true,"archive\\base\\localization\\en-us\\new_game_plus\\subtitles\\subtitles.json":true,"archive\\base\\localization\\en-us\\new_game_plus\\new_game_plus_onscreens.json":true,"archive\\base\\quest\\main_quests\\part1\\q101":true,"archive\\base\\quest\\main_quests\\part1\\q101\\scenes":true,"archive\\base\\quest\\main_quests\\part1\\q101\\scenes\\q101_08_takemura_v_room.scene":true,"archive\\base\\quest\\main_quests\\part1\\q116":true,"archive\\base\\quest\\main_quests\\part1\\q116\\scenes":true,"archive\\base\\quest\\main_quests\\part1\\q116\\scenes\\q116_05_mikoshi.scene":true,"archive\\base\\quest\\main_quests\\prologue":true,"archive\\base\\quest\\main_quests\\prologue\\q001":true,"archive\\base\\quest\\main_quests\\prologue\\q001\\scenes":true,"archive\\base\\quest\\main_quests\\prologue\\q001\\scenes\\q001_00a_before_mission.scene":true,"archive\\base\\quest\\main_quests\\prologue\\q001\\scenes\\q001_00d_leaving.scene":true,"archive\\ep1":true,"archive\\ep1\\quest":true,"archive\\ep1\\quest\\main_quests":true,"archive\\ep1\\quest\\main_quests\\q306":true,"archive\\ep1\\quest\\main_quests\\q306\\scenes":true,"archive\\ep1\\quest\\main_quests\\q306\\scenes\\q306_10_finale.scene":true,"archive\\mod\\quest":true,"archive\\mod\\quest\\changedQuests":true,"archive\\mod\\quest\\changedQuests\\open_world":true,"archive\\mod\\quest\\changedQuests\\open_world\\NewGamePlus_open_world_content.questphase":true,"archive\\mod\\quest\\changedQuests\\NewGamePlus_act_1.questphase":true,"archive\\mod\\quest\\changedQuests\\NewGamePlus_additional_game_elements.questphase":true,"archive\\mod\\quest\\changedQuests\\NewGamePlus_cyberpunk2077.questphase":true,"archive\\mod\\quest\\changedQuests\\NewGamePlus_persistent_content.questphase":true,"archive\\mod\\quest\\newgameplus":true,"archive\\mod\\quest\\newgameplus\\bossEncounterTest":true,"archive\\mod\\quest\\newgameplus\\bossEncounterTest\\bossEncounterTest.gamedef":true,"archive\\mod\\quest\\newgameplus\\bossEncounterTest\\bossEncounterTest.quest":true,"archive\\mod\\quest\\newgameplus\\journal":true,"archive\\mod\\quest\\newgameplus\\journal\\NGPlusJournal.journal":true,"archive\\mod\\quest\\newgameplus\\new_q101":true,"archive\\mod\\quest\\newgameplus\\new_q101\\Q101P2VRoom.questphase":true,"archive\\mod\\quest\\newgameplus\\new_q101\\Q101SetJohnnyStuff.questphase":true,"archive\\mod\\quest\\newgameplus\\new_q101\\new_q101.questphase":true,"archive\\mod\\quest\\newgameplus\\new_q101\\q101_01_covered_in_trash.scene":true,"archive\\mod\\quest\\newgameplus\\new_q101\\q101_p1_a_landfill.questphase":true,"archive\\mod\\quest\\newgameplus\\vrTutorialHack":true,"archive\\mod\\quest\\newgameplus\\vrTutorialHack\\VRTutorialForQ001Start.questphase":true,"archive\\mod\\quest\\newgameplus\\FastTrackToQ001.questphase":true,"archive\\mod\\quest\\newgameplus\\FastTrackToQ101New.questphase":true,"archive\\mod\\quest\\newgameplus\\FixHauntedWeaponsPreEP1.questphase":true,"archive\\mod\\quest\\newgameplus\\InitStreetStoriesAndFastTravel.questphase":true,"archive\\mod\\quest\\newgameplus\\MarkQ000AsCompleted.questphase":true,"archive\\mod\\quest\\newgameplus\\NGPlusAdditionalContent.questphase":true,"archive\\mod\\quest\\newgameplus\\NGPlusBossEncounterLoop.questphase":true,"archive\\mod\\quest\\newgameplus\\NGPlusBossEncounterRetrofix.questphase":true,"archive\\mod\\quest\\newgameplus\\RestoreHUD.questphase":true,"archive\\mod\\quest\\newgameplus\\SetBandageOutfit.questphase":true,"archive\\mod\\quest\\newgameplus\\SetPlayerProgression.questphase":true,"archive\\mod\\quest\\NewGamePlus.gamedef":true,"archive\\mod\\quest\\NewGamePlus.quest":true,"archive\\mod\\quest\\NewGamePlus_NoEP1.gamedef":true,"archive\\mod\\quest\\NewGamePlus_Q001.gamedef":true,"archive\\mod\\quest\\NewGamePlus_Q001.quest":true,"archive\\mod\\quest\\NewGamePlus_Q001_NoEP1.gamedef":true} \ No newline at end of file