From 5ef9c637938da4b3010d4929aad3adfab914d33e Mon Sep 17 00:00:00 2001 From: Christian Engelhardt <37042388+cengelha@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:07:16 +0100 Subject: [PATCH] Worlds Part II (#81) * chore(deps): bump test-dependencies * chore(game): add new game version and expedition * fix(Microsoft): adapt to changes in double save identifier * docs: updated changelog * chore(game): adding and updating enums * chore(game): take new difficulty settings into account * chore(game): add new hashed technology * docs(changelog): add difficulty settings and packaged technology * regression: add new Microsoft property to ContainerExtra in ExecuteCanCreate * test(Playstation): adapt expected result --- .exclusion.dic | 1 + CHANGELOG.md | 8 +- .../Container/Container_Property_IsVersion.cs | 4 + libNOM.io/Enums/DifficultyEnum.cs | 148 ++++++++++-------- libNOM.io/Enums/GameVersionEnum.cs | 3 + libNOM.io/Enums/PersistentBaseTypesEnum.cs | 2 + libNOM.io/Enums/SeasonEnum.cs | 1 + libNOM.io/Extensions/Newtonsoft.cs | 3 + libNOM.io/Extensions/Type.cs | 15 ++ libNOM.io/Global/Constants.cs | 12 +- libNOM.io/Global/Json.cs | 2 +- libNOM.io/Interfaces/IContainer.cs | 4 + libNOM.io/Meta/DifficultyPreset.cs | 101 ++++++++---- libNOM.io/Meta/GameVersion.cs | 8 +- libNOM.io/Models/ContainerExtra.cs | 1 + libNOM.io/Models/Difficulty.cs | 2 +- .../PlatformMicrosoft_FileOperation.cs | 1 + .../PlatformMicrosoft_Initialize.cs | 14 +- .../PlatformMicrosoft_Write.cs | 25 ++- libNOM.io/Resources/hashed_technology.bin | Bin 3831 -> 3909 bytes libNOM.io/libNOM.io.csproj | 4 +- libNOM.io/packages.lock.json | 48 +++--- libNOM.test/Playstation.cs | 2 +- libNOM.test/libNOM.test.csproj | 4 +- 24 files changed, 263 insertions(+), 150 deletions(-) create mode 100644 libNOM.io/Extensions/Type.cs diff --git a/.exclusion.dic b/.exclusion.dic index 76c4bbf9..648d6f61 100644 --- a/.exclusion.dic +++ b/.exclusion.dic @@ -67,3 +67,4 @@ Reisende Lehave Yamak Innerhalb +Starborn diff --git a/CHANGELOG.md b/CHANGELOG.md index 068ee9e6..bdfef63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ since 1.0.0. * Read action to read a (save) file and write the plaintext JSON to stdout * Write action to take plaintext JSON from stdin and write it to a (save) file * Parameter for `Transfer` to ignore incomplete user identification +* Support for game version **Worlds Part II 5.50** + * Detection for new packaged technology + * Difficulty setting for NPC population is taken into account +* Titan Expedition ### Changed * Files in backups are now only prefixed with `data`/`meta` and no longer completely renamed to make manual backups a little easier * The static class `libNOM.io.Global.Common` is no longer public accessible @@ -30,6 +34,8 @@ since 1.0.0. * Missing `IsVersion525TheCursedWithCrossSave` in `IContainer` * Use `IContainer` to implement `IComparable` and `IEquatable` of `IContainer` instead of `Container` * Packaged technology disappears due to the hashes no being UTF-8 conform ([#122 in the NomNom repository](/~https://github.com/zencq/NomNom/issues/122)) +* Crash caused by changes in the Microsoft platform ([#232 in the NomNom repository](/~https://github.com/zencq/NomNom/issues/232)) +* Difficulty setting for fishing added in **Aquarius 5.10** was not taken into account ### Security ## 0.13.0 (2024-12-02) @@ -122,7 +128,7 @@ since 1.0.0. ### Added * `GetString` extension for `JToken` that is now used by the one for `JObject` -* Support for game version **Worlds 5.00** +* Support for game version **Worlds Part I 5.00** * Liquidators Expedition ### Changed diff --git a/libNOM.io/Container/Container_Property_IsVersion.cs b/libNOM.io/Container/Container_Property_IsVersion.cs index ad57f9dd..98ae332b 100644 --- a/libNOM.io/Container/Container_Property_IsVersion.cs +++ b/libNOM.io/Container/Container_Property_IsVersion.cs @@ -78,4 +78,8 @@ public partial class Container : IContainer public bool IsVersion520TheCursed => IsVersion(GameVersionEnum.TheCursed); // { get; } public bool IsVersion525TheCursedWithCrossSave => IsVersion(GameVersionEnum.TheCursedWithCrossSave); // { get; } + + public bool IsVersion529TheCursedWithStarbornPhoenix => IsVersion(GameVersionEnum.TheCursedWithStarbornPhoenix); // { get; } + + public bool IsVersion550WorldsPartII => IsVersion(GameVersionEnum.WorldsPartII); // { get; } } diff --git a/libNOM.io/Enums/DifficultyEnum.cs b/libNOM.io/Enums/DifficultyEnum.cs index 9f6c9df0..48399b6a 100644 --- a/libNOM.io/Enums/DifficultyEnum.cs +++ b/libNOM.io/Enums/DifficultyEnum.cs @@ -11,45 +11,55 @@ internal enum ActiveSurvivalBarsDifficultyEnum : uint All, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcHazardDrainDifficultyOption.cs#L7 -internal enum HazardDrainDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcBreakTechOnDamageDifficultyOption.cs#L7 +internal enum BreakTechOnDamageProbabilityEnum : uint { - Slow, + None, + Low, + High, +} + +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcChargingRequirementsDifficultyOption.cs#L7 +internal enum ChargingRequirementsDifficultyEnum : uint +{ + None, + Low, Normal, - Fast, + High, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcEnergyDrainDifficultyOption.cs#L7 -internal enum EnergyDrainDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCombatTimerDifficultyOption.cs#L7 +internal enum CombatTimerDifficultyOptionEnum : uint { + Off, Slow, Normal, Fast, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcSubstanceCollectionDifficultyOption.cs#L7 -internal enum SubstanceCollectionDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCreatureHostilityDifficultyOption.cs#L7 +internal enum CreatureHostilityDifficultyEnum : uint { - High, - Normal, - Low, + NeverAttack, + AttackIfProvoked, + FullEcosystem, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcSprintingCostDifficultyOption.cs#L7 -internal enum SprintingCostDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCurrencyCostDifficultyOption.cs#L7 +internal enum CurrencyCostDifficultyEnum : uint { Free, - Low, - Full, + Cheap, + Normal, + Expensive, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcScannerRechargeDifficultyOption.cs#L7 -internal enum ScannerRechargeDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcDamageGivenDifficultyOption.cs#L7 +internal enum DamageGivenDifficultyEnum : uint { - VeryFast, - Fast, + High, Normal, - Slow, + Low, } // /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcDamageReceivedDifficultyOption.cs#L7 @@ -61,14 +71,6 @@ internal enum DamageReceivedDifficultyEnum : uint High, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcBreakTechOnDamageDifficultyOption.cs#L7 -internal enum BreakTechOnDamageProbabilityEnum : uint -{ - None, - Low, - High, -} - // /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcDeathConsequencesDifficultyOption.cs#L7 internal enum DeathConsequencesDifficultyEnum : uint { @@ -78,35 +80,25 @@ internal enum DeathConsequencesDifficultyEnum : uint DestroySave, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcChargingRequirementsDifficultyOption.cs#L7 -internal enum ChargingRequirementsDifficultyEnum : uint -{ - None, - Low, - Normal, - High, -} - -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcFuelUseDifficultyOption.cs#L7 -internal enum FuelUseDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcEnergyDrainDifficultyOption.cs#L7 +internal enum EnergyDrainDifficultyEnum : uint { - Free, - Cheap, + Slow, Normal, - Expensive, + Fast, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcLaunchFuelCostDifficultyOption.cs#L7 -internal enum LaunchFuelCostDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcFishingDifficultyOption.cs#L7 +public enum FishingDifficultyEnum : uint // added in Aquarius 5.10 { - Free, - Low, - Normal, - High, + AutoCatch, + LongCatchWindow, + NormalCatchWindow, + ShortCatchWindow, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCurrencyCostDifficultyOption.cs#L7 -internal enum CurrencyCostDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcFuelUseDifficultyOption.cs#L7 +internal enum FuelUseDifficultyEnum : uint { Free, Cheap, @@ -114,12 +106,12 @@ internal enum CurrencyCostDifficultyEnum : uint Expensive, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcItemShopAvailabilityDifficultyOption.cs#L7 -internal enum ItemShopAvailabilityDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcHazardDrainDifficultyOption.cs#L7 +internal enum HazardDrainDifficultyEnum : uint { - High, + Slow, Normal, - Low, + Fast, } // /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcInventoryStackLimitsDifficultyOption.cs#L7 @@ -130,29 +122,28 @@ internal enum InventoryStackLimitsDifficultyEnum : uint Low, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcDamageGivenDifficultyOption.cs#L7 -internal enum DamageGivenDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcItemShopAvailabilityDifficultyOption.cs#L7 +internal enum ItemShopAvailabilityDifficultyEnum : uint { High, Normal, Low, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCombatTimerDifficultyOption.cs#L7 -internal enum CombatTimerDifficultyOptionEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcLaunchFuelCostDifficultyOption.cs#L7 +internal enum LaunchFuelCostDifficultyEnum : uint { - Off, - Slow, + Free, + Low, Normal, - Fast, + High, } -// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcCreatureHostilityDifficultyOption.cs#L7 -internal enum CreatureHostilityDifficultyEnum : uint +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcNPCPopulationDifficultyOption.cs#L7 +internal enum NPCPopulationDifficultyEnum : uint // added in Worlds Part II 5.50 { - NeverAttack, - AttackIfProvoked, - FullEcosystem, + Full, + Abandoned, } // /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcReputationGainDifficultyOption.cs#L7 @@ -163,3 +154,28 @@ internal enum ReputationGainDifficultyEnum : uint Normal, Slow, } + +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcScannerRechargeDifficultyOption.cs#L7 +internal enum ScannerRechargeDifficultyEnum : uint +{ + VeryFast, + Fast, + Normal, + Slow, +} + +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcSprintingCostDifficultyOption.cs#L7 +internal enum SprintingCostDifficultyEnum : uint +{ + Free, + Low, + Full, +} + +// /~https://github.com/monkeyman192/MBINCompiler/blob/development/libMBIN/Source/NMS/GameComponents/GcSubstanceCollectionDifficultyOption.cs#L7 +internal enum SubstanceCollectionDifficultyEnum : uint +{ + High, + Normal, + Low, +} diff --git a/libNOM.io/Enums/GameVersionEnum.cs b/libNOM.io/Enums/GameVersionEnum.cs index 537c0855..90c055ef 100644 --- a/libNOM.io/Enums/GameVersionEnum.cs +++ b/libNOM.io/Enums/GameVersionEnum.cs @@ -65,4 +65,7 @@ public enum GameVersionEnum TheCursed = 520, [Description(nameof(TheCursed))] TheCursedWithCrossSave = 525, + [Description(nameof(TheCursed))] + TheCursedWithStarbornPhoenix = 529, + WorldsPartII = 550, } diff --git a/libNOM.io/Enums/PersistentBaseTypesEnum.cs b/libNOM.io/Enums/PersistentBaseTypesEnum.cs index a5d1725c..c234b721 100644 --- a/libNOM.io/Enums/PersistentBaseTypesEnum.cs +++ b/libNOM.io/Enums/PersistentBaseTypesEnum.cs @@ -17,4 +17,6 @@ internal enum PersistentBaseTypesEnum : uint SpaceBase, GeneratedPlanetBase, GeneratedPlanetBaseEdits, + PlayerShipBase, + FriendsShipBase, } diff --git a/libNOM.io/Enums/SeasonEnum.cs b/libNOM.io/Enums/SeasonEnum.cs index f01b43fc..99608f19 100644 --- a/libNOM.io/Enums/SeasonEnum.cs +++ b/libNOM.io/Enums/SeasonEnum.cs @@ -43,5 +43,6 @@ public enum SeasonEnum LiquidatorsRedux, AquariusRedux, CursedRedux, + Titan, // 17th Future, } diff --git a/libNOM.io/Extensions/Newtonsoft.cs b/libNOM.io/Extensions/Newtonsoft.cs index a8071ced..b370f3ec 100644 --- a/libNOM.io/Extensions/Newtonsoft.cs +++ b/libNOM.io/Extensions/Newtonsoft.cs @@ -233,6 +233,9 @@ internal static IEnumerable SelectTokensWithIntersection(this JObject self var result = default(T); var type = typeof(T); + if (type.IsNullable()) + type = type.GenericTypeArguments[0]; + if (type.IsSubclassOf(typeof(JToken)) || type == typeof(JToken)) { // integer diff --git a/libNOM.io/Extensions/Type.cs b/libNOM.io/Extensions/Type.cs new file mode 100644 index 00000000..b192660c --- /dev/null +++ b/libNOM.io/Extensions/Type.cs @@ -0,0 +1,15 @@ +namespace libNOM.io.Extensions; + + +internal static class TypeExtensions +{ + /// + /// Checks whether this is or not. + /// + /// + /// + internal static bool IsNullable(this Type self) + { + return !self.IsGenericTypeDefinition && self.IsGenericType && self.GetGenericTypeDefinition() == typeof(Nullable<>); + } +} diff --git a/libNOM.io/Global/Constants.cs b/libNOM.io/Global/Constants.cs index faaae109..d11184ad 100644 --- a/libNOM.io/Global/Constants.cs +++ b/libNOM.io/Global/Constants.cs @@ -52,11 +52,11 @@ public static class Constants internal const int CACHE_EXPIRATION = 250; // milliseconds - internal static readonly Difficulty DIFFICULTY_PRESET_NORMAL = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Normal, EnergyDrainDifficultyEnum.Normal, SubstanceCollectionDifficultyEnum.Normal, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.Normal, BreakTechOnDamageProbabilityEnum.Low, DeathConsequencesDifficultyEnum.ItemGrave, ChargingRequirementsDifficultyEnum.Normal, FuelUseDifficultyEnum.Normal, LaunchFuelCostDifficultyEnum.Normal, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Normal, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Normal, CombatTimerDifficultyOptionEnum.Normal, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, ReputationGainDifficultyEnum.Normal); - internal static readonly Difficulty DIFFICULTY_PRESET_CREATIVE = new(ActiveSurvivalBarsDifficultyEnum.None, HazardDrainDifficultyEnum.Slow, EnergyDrainDifficultyEnum.Slow, SubstanceCollectionDifficultyEnum.Normal, SprintingCostDifficultyEnum.Free, ScannerRechargeDifficultyEnum.Fast, DamageReceivedDifficultyEnum.None, BreakTechOnDamageProbabilityEnum.None, DeathConsequencesDifficultyEnum.None, ChargingRequirementsDifficultyEnum.None, FuelUseDifficultyEnum.Free, LaunchFuelCostDifficultyEnum.Free, true, CurrencyCostDifficultyEnum.Free, ItemShopAvailabilityDifficultyEnum.High, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Off, CombatTimerDifficultyOptionEnum.Off, CreatureHostilityDifficultyEnum.NeverAttack, true, false, true, ReputationGainDifficultyEnum.Fast); - internal static readonly Difficulty DIFFICULTY_PRESET_RELAXED = new(ActiveSurvivalBarsDifficultyEnum.HealthAndHazard, HazardDrainDifficultyEnum.Slow, EnergyDrainDifficultyEnum.Slow, SubstanceCollectionDifficultyEnum.High, SprintingCostDifficultyEnum.Low, ScannerRechargeDifficultyEnum.VeryFast, DamageReceivedDifficultyEnum.Low, BreakTechOnDamageProbabilityEnum.None, DeathConsequencesDifficultyEnum.None, ChargingRequirementsDifficultyEnum.Low, FuelUseDifficultyEnum.Cheap, LaunchFuelCostDifficultyEnum.Low, false, CurrencyCostDifficultyEnum.Cheap, ItemShopAvailabilityDifficultyEnum.High, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.High, CombatTimerDifficultyOptionEnum.Slow, CombatTimerDifficultyOptionEnum.Slow, CreatureHostilityDifficultyEnum.AttackIfProvoked, true, true, true, ReputationGainDifficultyEnum.Fast); - internal static readonly Difficulty DIFFICULTY_PRESET_SURVIVAL = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Fast, EnergyDrainDifficultyEnum.Fast, SubstanceCollectionDifficultyEnum.Low, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.High, BreakTechOnDamageProbabilityEnum.High, DeathConsequencesDifficultyEnum.DestroyItems, ChargingRequirementsDifficultyEnum.High, FuelUseDifficultyEnum.Expensive, LaunchFuelCostDifficultyEnum.High, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Low, InventoryStackLimitsDifficultyEnum.Normal, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Fast, CombatTimerDifficultyOptionEnum.Fast, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, ReputationGainDifficultyEnum.Normal); - internal static readonly Difficulty DIFFICULTY_PRESET_PERMADEATH = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Fast, EnergyDrainDifficultyEnum.Fast, SubstanceCollectionDifficultyEnum.Low, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.High, BreakTechOnDamageProbabilityEnum.High, DeathConsequencesDifficultyEnum.DestroySave, ChargingRequirementsDifficultyEnum.High, FuelUseDifficultyEnum.Expensive, LaunchFuelCostDifficultyEnum.High, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Low, InventoryStackLimitsDifficultyEnum.Normal, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Fast, CombatTimerDifficultyOptionEnum.Fast, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, ReputationGainDifficultyEnum.Normal); + internal static readonly Difficulty DIFFICULTY_PRESET_NORMAL = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Normal, EnergyDrainDifficultyEnum.Normal, SubstanceCollectionDifficultyEnum.Normal, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.Normal, BreakTechOnDamageProbabilityEnum.Low, DeathConsequencesDifficultyEnum.ItemGrave, NPCPopulationDifficultyEnum.Full, ChargingRequirementsDifficultyEnum.Normal, FuelUseDifficultyEnum.Normal, LaunchFuelCostDifficultyEnum.Normal, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Normal, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Normal, CombatTimerDifficultyOptionEnum.Normal, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, FishingDifficultyEnum.NormalCatchWindow, ReputationGainDifficultyEnum.Normal); + internal static readonly Difficulty DIFFICULTY_PRESET_CREATIVE = new(ActiveSurvivalBarsDifficultyEnum.None, HazardDrainDifficultyEnum.Slow, EnergyDrainDifficultyEnum.Slow, SubstanceCollectionDifficultyEnum.Normal, SprintingCostDifficultyEnum.Free, ScannerRechargeDifficultyEnum.Fast, DamageReceivedDifficultyEnum.None, BreakTechOnDamageProbabilityEnum.None, DeathConsequencesDifficultyEnum.None, NPCPopulationDifficultyEnum.Full, ChargingRequirementsDifficultyEnum.None, FuelUseDifficultyEnum.Free, LaunchFuelCostDifficultyEnum.Free, true, CurrencyCostDifficultyEnum.Free, ItemShopAvailabilityDifficultyEnum.High, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Off, CombatTimerDifficultyOptionEnum.Off, CreatureHostilityDifficultyEnum.NeverAttack, true, false, true, FishingDifficultyEnum.NormalCatchWindow, ReputationGainDifficultyEnum.Fast); + internal static readonly Difficulty DIFFICULTY_PRESET_RELAXED = new(ActiveSurvivalBarsDifficultyEnum.HealthAndHazard, HazardDrainDifficultyEnum.Slow, EnergyDrainDifficultyEnum.Slow, SubstanceCollectionDifficultyEnum.High, SprintingCostDifficultyEnum.Low, ScannerRechargeDifficultyEnum.VeryFast, DamageReceivedDifficultyEnum.Low, BreakTechOnDamageProbabilityEnum.None, DeathConsequencesDifficultyEnum.None, NPCPopulationDifficultyEnum.Full, ChargingRequirementsDifficultyEnum.Low, FuelUseDifficultyEnum.Cheap, LaunchFuelCostDifficultyEnum.Low, false, CurrencyCostDifficultyEnum.Cheap, ItemShopAvailabilityDifficultyEnum.High, InventoryStackLimitsDifficultyEnum.High, DamageGivenDifficultyEnum.High, CombatTimerDifficultyOptionEnum.Slow, CombatTimerDifficultyOptionEnum.Slow, CreatureHostilityDifficultyEnum.AttackIfProvoked, true, true, true, FishingDifficultyEnum.NormalCatchWindow, ReputationGainDifficultyEnum.Fast); + internal static readonly Difficulty DIFFICULTY_PRESET_SURVIVAL = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Fast, EnergyDrainDifficultyEnum.Fast, SubstanceCollectionDifficultyEnum.Low, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.High, BreakTechOnDamageProbabilityEnum.High, DeathConsequencesDifficultyEnum.DestroyItems, NPCPopulationDifficultyEnum.Full, ChargingRequirementsDifficultyEnum.High, FuelUseDifficultyEnum.Expensive, LaunchFuelCostDifficultyEnum.High, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Low, InventoryStackLimitsDifficultyEnum.Normal, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Fast, CombatTimerDifficultyOptionEnum.Fast, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, FishingDifficultyEnum.NormalCatchWindow, ReputationGainDifficultyEnum.Normal); + internal static readonly Difficulty DIFFICULTY_PRESET_PERMADEATH = new(ActiveSurvivalBarsDifficultyEnum.All, HazardDrainDifficultyEnum.Fast, EnergyDrainDifficultyEnum.Fast, SubstanceCollectionDifficultyEnum.Low, SprintingCostDifficultyEnum.Full, ScannerRechargeDifficultyEnum.Normal, DamageReceivedDifficultyEnum.High, BreakTechOnDamageProbabilityEnum.High, DeathConsequencesDifficultyEnum.DestroySave, NPCPopulationDifficultyEnum.Full, ChargingRequirementsDifficultyEnum.High, FuelUseDifficultyEnum.Expensive, LaunchFuelCostDifficultyEnum.High, false, CurrencyCostDifficultyEnum.Normal, ItemShopAvailabilityDifficultyEnum.Low, InventoryStackLimitsDifficultyEnum.Normal, DamageGivenDifficultyEnum.Normal, CombatTimerDifficultyOptionEnum.Fast, CombatTimerDifficultyOptionEnum.Fast, CreatureHostilityDifficultyEnum.FullEcosystem, false, true, false, FishingDifficultyEnum.NormalCatchWindow, ReputationGainDifficultyEnum.Normal); internal const string FILE_TIMESTAMP_FORMAT = "yyyyMMddHHmmssfff"; @@ -100,6 +100,7 @@ public static class Constants { "DIFFICULTY_DAMAGE_RECEIVED", ["6f=.LyC.:fe.hXp.cYk", "PlayerStateData.DifficultyState.Settings.DamageReceived.DamageReceivedDifficulty", "{0}.6f=.LyC.:fe.hXp.cYk", "{0}.PlayerStateData.DifficultyState.Settings.DamageReceived.DamageReceivedDifficulty"] }, { "DIFFICULTY_BREAK_TECH_ON_DAMAGE", ["6f=.LyC.:fe.gd>.ef4", "PlayerStateData.DifficultyState.Settings.BreakTechOnDamage.BreakTechOnDamageProbability", "{0}.6f=.LyC.:fe.gd>.ef4", "{0}.PlayerStateData.DifficultyState.Settings.BreakTechOnDamage.BreakTechOnDamageProbability"] }, { "DIFFICULTY_DEATH_CONSEQUENCES", ["6f=.LyC.:fe.n7p.q2@", "PlayerStateData.DifficultyState.Settings.DeathConsequences.DeathConsequencesDifficulty", "{0}.6f=.LyC.:fe.n7p.q2@", "{0}.PlayerStateData.DifficultyState.Settings.DeathConsequences.DeathConsequencesDifficulty"] }, + { "DIFFICULTY_NPC_POPULATION", ["", "", "{0}.6f=.LyC.:fe.Z52.SdQ", "{0}.PlayerStateData.DifficultyState.Settings.NPCPopulation.NPCPopulationDifficulty"] }, { "DIFFICULTY_CHARGING_REQUIREMENTS", ["6f=.LyC.:fe.nhq.428", "PlayerStateData.DifficultyState.Settings.ChargingRequirements.ChargingRequirementsDifficulty", "{0}.6f=.LyC.:fe.nhq.428", "{0}.PlayerStateData.DifficultyState.Settings.ChargingRequirements.ChargingRequirementsDifficulty"] }, { "DIFFICULTY_FUEL_USE", ["6f=.LyC.:fe.jnM.Eg1", "PlayerStateData.DifficultyState.Settings.FuelUse.FuelUseDifficulty", "{0}.6f=.LyC.:fe.jnM.Eg1", "{0}.PlayerStateData.DifficultyState.Settings.FuelUse.FuelUseDifficulty"] }, { "DIFFICULTY_LAUNCH_FUEL_COST", ["6f=.LyC.:fe.A9D.iqY", "PlayerStateData.DifficultyState.Settings.LaunchFuelCost.LaunchFuelCostDifficulty", "{0}.6f=.LyC.:fe.A9D.iqY", "{0}.PlayerStateData.DifficultyState.Settings.LaunchFuelCost.LaunchFuelCostDifficulty"] }, @@ -114,6 +115,7 @@ public static class Constants { "DIFFICULTY_INVENTORIES_ALWAYS_IN_RANGE", ["6f=.LyC.:fe.pS0", "PlayerStateData.DifficultyState.Settings.InventoriesAlwaysInRange", "{0}.6f=.LyC.:fe.pS0", "{0}.PlayerStateData.DifficultyState.Settings.InventoriesAlwaysInRange"] }, { "DIFFICULTY_WARP_DRIVE_REQUIREMENTS", ["6f=.LyC.:fe.aw9", "PlayerStateData.DifficultyState.Settings.WarpDriveRequirements", "{0}.6f=.LyC.:fe.aw9", "{0}.PlayerStateData.DifficultyState.Settings.WarpDriveRequirements"] }, { "DIFFICULTY_BASE_AUTO_POWER", ["6f=.LyC.:fe.uo4", "PlayerStateData.DifficultyState.Settings.BaseAutoPower", "{0}.6f=.LyC.:fe.uo4", "{0}.PlayerStateData.DifficultyState.Settings.BaseAutoPower"] }, + { "DIFFICULTY_FISHING", ["", "", "{0}.6f=.LyC.:fe.B;B.Ifu", "{0}.PlayerStateData.DifficultyState.Settings.Fishing.FishingDifficulty"] }, { "DIFFICULTY_REPUTATION_GAIN", ["6f=.LyC.:fe.vo>.S@3", "PlayerStateData.DifficultyState.Settings.ReputationGain.ReputationGainDifficulty", "{0}.6f=.LyC.:fe.vo>.S@3", "{0}.PlayerStateData.DifficultyState.Settings.ReputationGain.ReputationGainDifficulty"] }, { "FREIGHTER_POSITION", ["6f=.lpm[*]", "PlayerStateData.FreighterMatrixPos[*]", "{0}.6f=.lpm[*]", "{0}.PlayerStateData.FreighterMatrixPos[*]"] }, { "PERSISTENT_PLAYER_BASE_ALL_TYPES", ["6f=.F?0[*].peI.DPp", "PlayerStateData.PersistentPlayerBases[*].BaseType.PersistentBaseTypes", "{0}.6f=.F?0[*].peI.DPp", "{0}.PlayerStateData.PersistentPlayerBases[*].BaseType.PersistentBaseTypes"] }, diff --git a/libNOM.io/Global/Json.cs b/libNOM.io/Global/Json.cs index 94dde196..3661f1d2 100644 --- a/libNOM.io/Global/Json.cs +++ b/libNOM.io/Global/Json.cs @@ -50,7 +50,7 @@ internal static string[] GetPaths(string identifier, JObject? jsonObject, SaveCo while (index >= 0) // to not store unchanged paths multiple times { if (paths.ContainsIndex(index) && !string.IsNullOrEmpty(paths[index])) // skip empty strings for Vanilla save structure - return string.IsNullOrEmpty(contextKey) ? [paths[index]] : [string.Format(paths[index], contextKey)]; // [] to have a consistent return type + return string.IsNullOrEmpty(contextKey) ? [paths[index]] : [string.Format(paths[index], contextKey)]; index -= 2; // 2 obfuscation states per save structure } diff --git a/libNOM.io/Interfaces/IContainer.cs b/libNOM.io/Interfaces/IContainer.cs index c8904b6d..7715c6c3 100644 --- a/libNOM.io/Interfaces/IContainer.cs +++ b/libNOM.io/Interfaces/IContainer.cs @@ -232,6 +232,10 @@ public interface IContainer : IComparable, IEquatable public bool IsVersion525TheCursedWithCrossSave { get; } + public bool IsVersion529TheCursedWithStarbornPhoenix { get; } + + public bool IsVersion550WorldsPartII { get; } + #endregion #region Miscellaneous diff --git a/libNOM.io/Meta/DifficultyPreset.cs b/libNOM.io/Meta/DifficultyPreset.cs index cdade41b..3301f3ed 100644 --- a/libNOM.io/Meta/DifficultyPreset.cs +++ b/libNOM.io/Meta/DifficultyPreset.cs @@ -21,12 +21,12 @@ internal static DifficultyPresetTypeEnum Get(Container container, JObject? jsonO // Since Waypoint the difficulty is handed differently and therefore needs to be checked in more detail. if (container.IsVersion400Waypoint && container.GameMode == PresetGameModeEnum.Normal) - return GetCurrentDifficulty(jsonObject) switch + return GetCurrentDifficulty(jsonObject, container.ActiveContext) switch { - var difficulty when difficulty.Equals(Constants.DIFFICULTY_PRESET_NORMAL) => DifficultyPresetTypeEnum.Normal, - var difficulty when difficulty.Equals(Constants.DIFFICULTY_PRESET_CREATIVE) => DifficultyPresetTypeEnum.Creative, - var difficulty when difficulty.Equals(Constants.DIFFICULTY_PRESET_RELAXED) => DifficultyPresetTypeEnum.Relaxed, - var difficulty when difficulty.Equals(Constants.DIFFICULTY_PRESET_SURVIVAL) => DifficultyPresetTypeEnum.Survival, + var difficulty when IsPreset(difficulty, Constants.DIFFICULTY_PRESET_NORMAL) => DifficultyPresetTypeEnum.Normal, + var difficulty when IsPreset(difficulty, Constants.DIFFICULTY_PRESET_CREATIVE) => DifficultyPresetTypeEnum.Creative, + var difficulty when IsPreset(difficulty, Constants.DIFFICULTY_PRESET_RELAXED) => DifficultyPresetTypeEnum.Relaxed, + var difficulty when IsPreset(difficulty, Constants.DIFFICULTY_PRESET_SURVIVAL) => DifficultyPresetTypeEnum.Survival, _ => DifficultyPresetTypeEnum.Custom, }; @@ -40,7 +40,7 @@ internal static DifficultyPresetTypeEnum Get(Container container, string? json) if (string.IsNullOrWhiteSpace(json)) return DifficultyPresetTypeEnum.Invalid; - // Since Omega there can be two difficulties in one save and therefore not determined reliable from a string. Also SeasonData is now stored before DifficultyState. + // Since Omega 4.50 there can be two difficulties in one save and therefore not determined reliable from a string. Also SeasonData is now stored before DifficultyState. if (container.IsVersion450Omega) { return DifficultyPresetTypeEnum.Custom; @@ -70,7 +70,7 @@ var jsonSubstring when IsPreset(jsonSubstring, Constants.DIFFICULTY_PRESET_SURVI internal static void Set(Container container, Difficulty preset) { var jsonObject = container.GetJsonObject(); - var parameters = new (JToken, string)[] { + var parameters = new List<(JToken, string)> { // Survival Elements (preset.ActiveSurvivalBars.ToString(), "DIFFICULTY_ACTIVE_SURVIVAL_BARS"), @@ -138,6 +138,18 @@ internal static void Set(Container container, Difficulty preset) (preset.ReputationGain.ToString(), "DIFFICULTY_REPUTATION_GAIN"), }; + if (container.IsVersion510Aquarius) + { + // Fishing Timing + parameters.Add((preset.Fishing.ToString(), "DIFFICULTY_FISHING")); + } + + if (container.IsVersion550WorldsPartII) + { + // Universe Population + parameters.Add((preset.NPCPopulation.ToString(), "DIFFICULTY_NPC_POPULATION")); + } + foreach (var (value, pathIdentifier) in parameters) jsonObject.SetValue(value, pathIdentifier); } @@ -146,77 +158,100 @@ internal static void Set(Container container, Difficulty preset) #region Helper - private static Difficulty GetCurrentDifficulty(JObject jsonObject) + private static Difficulty GetCurrentDifficulty(JObject jsonObject, SaveContextQueryEnum context) { // Survival Elements - var activeSurvivalBars = jsonObject.GetValue("DIFFICULTY_ACTIVE_SURVIVAL_BARS"); + var activeSurvivalBars = jsonObject.GetValue("DIFFICULTY_ACTIVE_SURVIVAL_BARS", context); // Survival Difficulty - var hazardDrain = jsonObject.GetValue("DIFFICULTY_HAZARD_DRAIN"); - var energyDrain = jsonObject.GetValue("DIFFICULTY_ENERGY_DRAIN"); + var hazardDrain = jsonObject.GetValue("DIFFICULTY_HAZARD_DRAIN", context); + var energyDrain = jsonObject.GetValue("DIFFICULTY_ENERGY_DRAIN", context); // Natural Resources - var substanceCollection = jsonObject.GetValue("DIFFICULTY_SUBSTANCE_COLLECTION"); + var substanceCollection = jsonObject.GetValue("DIFFICULTY_SUBSTANCE_COLLECTION", context); // Sprinting - var sprintingCost = jsonObject.GetValue("DIFFICULTY_SPRINTING_COST"); + var sprintingCost = jsonObject.GetValue("DIFFICULTY_SPRINTING_COST", context); // Scanner Recharge - var scannerRecharge = jsonObject.GetValue("DIFFICULTY_SCANNER_RECHARGE"); + var scannerRecharge = jsonObject.GetValue("DIFFICULTY_SCANNER_RECHARGE", context); // Damage Levels - var damageReceived = jsonObject.GetValue("DIFFICULTY_DAMAGE_RECEIVED"); + var damageReceived = jsonObject.GetValue("DIFFICULTY_DAMAGE_RECEIVED", context); // Technology Damage - var breakTechOnDamage = jsonObject.GetValue("DIFFICULTY_BREAK_TECH_ON_DAMAGE"); + var breakTechOnDamage = jsonObject.GetValue("DIFFICULTY_BREAK_TECH_ON_DAMAGE", context); // Death Consequences - var deathConsequences = jsonObject.GetValue("DIFFICULTY_DEATH_CONSEQUENCES"); + var deathConsequences = jsonObject.GetValue("DIFFICULTY_DEATH_CONSEQUENCES", context); + + // Universe Population + var npcPopulation = jsonObject.GetValue("DIFFICULTY_NPC_POPULATION", context); // Fuel Usage - var chargingRequirements = jsonObject.GetValue("DIFFICULTY_CHARGING_REQUIREMENTS"); - var fuelUse = jsonObject.GetValue("DIFFICULTY_FUEL_USE"); - var launchFuelCost = jsonObject.GetValue("DIFFICULTY_LAUNCH_FUEL_COST"); + var chargingRequirements = jsonObject.GetValue("DIFFICULTY_CHARGING_REQUIREMENTS", context); + var fuelUse = jsonObject.GetValue("DIFFICULTY_FUEL_USE", context); + var launchFuelCost = jsonObject.GetValue("DIFFICULTY_LAUNCH_FUEL_COST", context); // Crafting - var craftingIsFree = jsonObject.GetValue("DIFFICULTY_CRAFTING_IS_FREE"); + var craftingIsFree = jsonObject.GetValue("DIFFICULTY_CRAFTING_IS_FREE", context); // Purchases - var currencyCost = jsonObject.GetValue("DIFFICULTY_CURRENCY_COST"); + var currencyCost = jsonObject.GetValue("DIFFICULTY_CURRENCY_COST", context); // Goods Availability - var itemShopAvailability = jsonObject.GetValue("DIFFICULTY_ITEM_SHOP_AVAILABILITY"); + var itemShopAvailability = jsonObject.GetValue("DIFFICULTY_ITEM_SHOP_AVAILABILITY", context); // Inventory Stack Limits - var inventoryStackLimits = jsonObject.GetValue("DIFFICULTY_INVENTORY_STACK_LIMITS"); + var inventoryStackLimits = jsonObject.GetValue("DIFFICULTY_INVENTORY_STACK_LIMITS", context); // Enemy Strength - var damageGiven = jsonObject.GetValue("DIFFICULTY_DAMAGE_GIVEN"); + var damageGiven = jsonObject.GetValue("DIFFICULTY_DAMAGE_GIVEN", context); // On-Foot Combat - var groundCombatTimers = jsonObject.GetValue("DIFFICULTY_GROUND_COMBAT_TIMERS"); + var groundCombatTimers = jsonObject.GetValue("DIFFICULTY_GROUND_COMBAT_TIMERS", context); // Space Combat - var spaceCombatTimers = jsonObject.GetValue("DIFFICULTY_SPACE_COMBAT_TIMERS"); + var spaceCombatTimers = jsonObject.GetValue("DIFFICULTY_SPACE_COMBAT_TIMERS", context); // Creatures - var creatureHostility = jsonObject.GetValue("DIFFICULTY_CREATURE_HOSTILITY"); + var creatureHostility = jsonObject.GetValue("DIFFICULTY_CREATURE_HOSTILITY", context); // Inventory Transfer Range - var inventoriesAlwaysInRange = jsonObject.GetValue("DIFFICULTY_INVENTORIES_ALWAYS_IN_RANGE"); + var inventoriesAlwaysInRange = jsonObject.GetValue("DIFFICULTY_INVENTORIES_ALWAYS_IN_RANGE", context); // Hyperdrive System Access - var warpDriveRequirements = jsonObject.GetValue("DIFFICULTY_WARP_DRIVE_REQUIREMENTS"); + var warpDriveRequirements = jsonObject.GetValue("DIFFICULTY_WARP_DRIVE_REQUIREMENTS", context); // Base Power - var baseAutoPower = jsonObject.GetValue("DIFFICULTY_BASE_AUTO_POWER"); + var baseAutoPower = jsonObject.GetValue("DIFFICULTY_BASE_AUTO_POWER", context); + + // Fishing Timing + var fishing = jsonObject.GetValue("DIFFICULTY_FISHING", context); // Reputation & Standing Gain - var reputationGain = jsonObject.GetValue("DIFFICULTY_REPUTATION_GAIN"); + var reputationGain = jsonObject.GetValue("DIFFICULTY_REPUTATION_GAIN", context); - return new(activeSurvivalBars, hazardDrain, energyDrain, substanceCollection, sprintingCost, scannerRecharge, damageReceived, breakTechOnDamage, deathConsequences, chargingRequirements, fuelUse, launchFuelCost, craftingIsFree, currencyCost, itemShopAvailability, inventoryStackLimits, damageGiven, groundCombatTimers, spaceCombatTimers, creatureHostility, inventoriesAlwaysInRange, warpDriveRequirements, baseAutoPower, reputationGain); + return new(activeSurvivalBars, hazardDrain, energyDrain, substanceCollection, sprintingCost, scannerRecharge, damageReceived, breakTechOnDamage, deathConsequences, npcPopulation, chargingRequirements, fuelUse, launchFuelCost, craftingIsFree, currencyCost, itemShopAvailability, inventoryStackLimits, damageGiven, groundCombatTimers, spaceCombatTimers, creatureHostility, inventoriesAlwaysInRange, warpDriveRequirements, baseAutoPower, fishing, reputationGain); + } + + private static bool IsPreset(Difficulty difficulty, Difficulty preset) + { + foreach (var property in typeof(Difficulty).GetProperties()) + { + var value = property.GetValue(difficulty); + + // This is a case for settings that have been added in a later version. + if (property.PropertyType.IsNullable() && value is null) + continue; + + if (!value!.Equals(property.GetValue(preset))) + return false; + } + return true; } + // Newly added settings can be ignored here as it will never be reached (see comment about Omega 4.50 in calling method). private static bool IsPreset(string json, Difficulty preset) { string[] settings = [ diff --git a/libNOM.io/Meta/GameVersion.cs b/libNOM.io/Meta/GameVersion.cs index 0a23f6aa..399f9dcb 100644 --- a/libNOM.io/Meta/GameVersion.cs +++ b/libNOM.io/Meta/GameVersion.cs @@ -42,8 +42,8 @@ internal static GameVersionEnum Get(Platform platform, int length, uint format) // EXTERNAL RELEASE: Add new game version. internal static GameVersionEnum Get(int baseVersion) => baseVersion switch { - // >= 4168 => GameVersionEnum.WorldsPartII, // 5.50 - // >= 4167 => GameVersionEnum.TheCursedWithStarbornPhoenix, // 5.29 + >= 4168 => GameVersionEnum.WorldsPartII, // 5.50 + >= 4167 => GameVersionEnum.TheCursedWithStarbornPhoenix, // 5.29 >= 4164 => GameVersionEnum.TheCursedWithCrossSave, // 5.25 >= 4161 => GameVersionEnum.TheCursed, // 5.20 >= 4155 => GameVersionEnum.Aquarius, // 5.10 @@ -82,6 +82,8 @@ internal static GameVersionEnum Get(Platform platform, int length, uint format) // EXTERNAL RELEASE: Add new game version. internal static GameVersionEnum Get(int baseVersion, JObject jsonObject) => baseVersion switch { + >= 4168 => GameVersionEnum.WorldsPartII, // 5.50 + >= 4167 => GameVersionEnum.TheCursedWithStarbornPhoenix, // 5.29 >= 4164 => GameVersionEnum.TheCursedWithCrossSave, // 5.25 >= 4161 => GameVersionEnum.TheCursed, // 5.20 >= 4155 => GameVersionEnum.Aquarius, // 5.10 @@ -116,6 +118,8 @@ internal static GameVersionEnum Get(Platform platform, int length, uint format) // EXTERNAL RELEASE: Add new game version. internal static GameVersionEnum Get(int baseVersion, string json) => baseVersion switch { + >= 4168 => GameVersionEnum.WorldsPartII, // 5.50 + >= 4167 => GameVersionEnum.TheCursedWithStarbornPhoenix, // 5.29 >= 4164 => GameVersionEnum.TheCursedWithCrossSave, // 5.25 >= 4161 => GameVersionEnum.TheCursed, // 5.20 >= 4155 => GameVersionEnum.Aquarius, // 5.10 diff --git a/libNOM.io/Models/ContainerExtra.cs b/libNOM.io/Models/ContainerExtra.cs index 14a9e91c..443dd76c 100644 --- a/libNOM.io/Models/ContainerExtra.cs +++ b/libNOM.io/Models/ContainerExtra.cs @@ -64,6 +64,7 @@ internal record class ContainerExtra #region Microsoft + internal bool? MicrosoftHasSecondIdentifier { get; init; } internal string? MicrosoftSyncTime { get; init; } // Ticks as Hexadecimal as String with surrounding double quotes internal byte? MicrosoftBlobContainerExtension { get; init; } internal MicrosoftBlobSyncStateEnum? MicrosoftSyncState { get; init; } diff --git a/libNOM.io/Models/Difficulty.cs b/libNOM.io/Models/Difficulty.cs index a025cad8..babf2056 100644 --- a/libNOM.io/Models/Difficulty.cs +++ b/libNOM.io/Models/Difficulty.cs @@ -1,4 +1,4 @@ namespace libNOM.io.Models; -internal record class Difficulty(ActiveSurvivalBarsDifficultyEnum ActiveSurvivalBars, HazardDrainDifficultyEnum HazardDrain, EnergyDrainDifficultyEnum EnergyDrain, SubstanceCollectionDifficultyEnum SubstanceCollection, SprintingCostDifficultyEnum SprintingCost, ScannerRechargeDifficultyEnum ScannerRecharge, DamageReceivedDifficultyEnum DamageReceived, BreakTechOnDamageProbabilityEnum BreakTechOnDamage, DeathConsequencesDifficultyEnum DeathConsequences, ChargingRequirementsDifficultyEnum ChargingRequirements, FuelUseDifficultyEnum FuelUse, LaunchFuelCostDifficultyEnum LaunchFuelCost, bool CraftingIsFree, CurrencyCostDifficultyEnum CurrencyCost, ItemShopAvailabilityDifficultyEnum ItemShopAvailability, InventoryStackLimitsDifficultyEnum InventoryStackLimits, DamageGivenDifficultyEnum DamageGiven, CombatTimerDifficultyOptionEnum GroundCombatTimers, CombatTimerDifficultyOptionEnum SpaceCombatTimers, CreatureHostilityDifficultyEnum CreatureHostility, bool InventoriesAlwaysInRange, bool WarpDriveRequirements, bool BaseAutoPower, ReputationGainDifficultyEnum ReputationGain); +internal sealed record class Difficulty(ActiveSurvivalBarsDifficultyEnum ActiveSurvivalBars, HazardDrainDifficultyEnum HazardDrain, EnergyDrainDifficultyEnum EnergyDrain, SubstanceCollectionDifficultyEnum SubstanceCollection, SprintingCostDifficultyEnum SprintingCost, ScannerRechargeDifficultyEnum ScannerRecharge, DamageReceivedDifficultyEnum DamageReceived, BreakTechOnDamageProbabilityEnum BreakTechOnDamage, DeathConsequencesDifficultyEnum DeathConsequences, NPCPopulationDifficultyEnum? NPCPopulation, ChargingRequirementsDifficultyEnum ChargingRequirements, FuelUseDifficultyEnum FuelUse, LaunchFuelCostDifficultyEnum LaunchFuelCost, bool CraftingIsFree, CurrencyCostDifficultyEnum CurrencyCost, ItemShopAvailabilityDifficultyEnum ItemShopAvailability, InventoryStackLimitsDifficultyEnum InventoryStackLimits, DamageGivenDifficultyEnum DamageGiven, CombatTimerDifficultyOptionEnum GroundCombatTimers, CombatTimerDifficultyOptionEnum SpaceCombatTimers, CreatureHostilityDifficultyEnum CreatureHostility, bool InventoriesAlwaysInRange, bool WarpDriveRequirements, bool BaseAutoPower, FishingDifficultyEnum? Fishing, ReputationGainDifficultyEnum ReputationGain); diff --git a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_FileOperation.cs b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_FileOperation.cs index 41beadaf..9017b465 100644 --- a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_FileOperation.cs +++ b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_FileOperation.cs @@ -61,6 +61,7 @@ private void ExecuteCanCreate(Container Destination) // Update container and its extra with dummy data. Destination.Extra = Destination.Extra with { + MicrosoftHasSecondIdentifier = !Destination.IsVersion550WorldsPartII, MicrosoftSyncTime = string.Empty, MicrosoftBlobContainerExtension = 0, MicrosoftSyncState = MicrosoftBlobSyncStateEnum.Created, diff --git a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Initialize.cs b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Initialize.cs index 4f295cd2..ca54e40e 100644 --- a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Initialize.cs +++ b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Initialize.cs @@ -154,10 +154,10 @@ 8. FOOTER (268435456) ( 8) private int ParseBlobContainerIndex(ReadOnlySpan bytes, int offset, out string saveIdentifier, out ContainerExtra extra) { /** - 9. SAVE IDENTIFIER LENGTH ( 4) - 10. SAVE IDENTIFIER (VAR) (UTF-16) - 11. SAVE IDENTIFIER LENGTH ( 4) - 12. SAVE IDENTIFIER (VAR) (UTF-16) + 9. SAVE IDENTIFIER 1 LENGTH ( 4) + 10. SAVE IDENTIFIER 1 (VAR) (UTF-16) + 11. SAVE IDENTIFIER 2 LENGTH ( 4) + 12. SAVE IDENTIFIER 2 (VAR) (UTF-16) // SAVE IDENTIFIER 2 no longer used since Worlds Part II 5.50 13. SYNC HEX LENGTH ( 4) 14. SYNC HEX (VAR) (UTF-16) 15. BLOB CONTAINER FILE EXTENSION ( 1) @@ -168,7 +168,10 @@ 19. EMPTY ( 8) 20. TOTAL SIZE OF FILES ( 8) (BLOB CONTAINER EXCLUDED) */ - offset += bytes.ReadString(offset, out saveIdentifier) * 2; // saveIdentifier two times in a row + // Two slots for identification but only the first one is relevant. + offset += bytes.ReadString(offset, out saveIdentifier); + offset += bytes.ReadString(offset, out var secondIdentifier); + offset += bytes.ReadString(offset, out var syncTime); var directoryGuid = bytes.GetGuid(offset + 5); @@ -178,6 +181,7 @@ 20. TOTAL SIZE OF FILES ( 8) (BLOB CONTAINER EXCLUDED) LastWriteTime = DateTimeOffset.FromFileTime(bytes.Cast(offset + 21)).ToLocalTime(), SizeDisk = (uint)(bytes.Cast(offset + 37)), + MicrosoftHasSecondIdentifier = !string.IsNullOrEmpty(secondIdentifier), MicrosoftSyncTime = syncTime, MicrosoftBlobContainerExtension = bytes[offset], MicrosoftSyncState = (MicrosoftBlobSyncStateEnum)(bytes.Cast(offset + 1)), diff --git a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Write.cs b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Write.cs index 4f965632..ffa810a3 100644 --- a/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Write.cs +++ b/libNOM.io/PlatformMicrosoft/PlatformMicrosoft_Write.cs @@ -230,13 +230,13 @@ private void WriteContainersIndex() writer.Write(CONTAINERSINDEX_FOOTER); if (HasAccountData) - AppendMicrosoftMeta(writer, AccountContainer!.Identifier, AccountContainer!.Extra); + AppendMicrosoftMeta(writer, AccountContainer!); if (hasSettings) - AppendMicrosoftMeta(writer, "Settings", _settingsContainer!); + AppendMicrosoftMeta(writer, _settingsContainer!, "Settings"); foreach (var container in collection) - AppendMicrosoftMeta(writer, container.Identifier, container.Extra); + AppendMicrosoftMeta(writer, container); buffer = buffer.AsSpan()[..(int)(writer.BaseStream.Position)].ToArray(); } @@ -246,14 +246,25 @@ private void WriteContainersIndex() _containersindex.Refresh(); } - private static void AppendMicrosoftMeta(BinaryWriter writer, string identifier, ContainerExtra extra) + private static void AppendMicrosoftMeta(BinaryWriter writer, Container container) => AppendMicrosoftMeta(writer, container.Extra, container.Identifier); + + private static void AppendMicrosoftMeta(BinaryWriter writer, ContainerExtra extra, string identifier) { // Make sure to get the latest data. extra.MicrosoftBlobDataFile?.Refresh(); extra.MicrosoftBlobMetaFile?.Refresh(); - AppendDynamicText(writer, identifier, 2); - AppendDynamicText(writer, extra.MicrosoftSyncTime!, 1); + if (extra.MicrosoftHasSecondIdentifier!.Value) + { + AppendDynamicText(writer, identifier, count: 2); + } + else + { + AppendDynamicText(writer, identifier); + writer.Write((int)(0)); // length (0) of second identifier is still necessary + } + + AppendDynamicText(writer, extra.MicrosoftSyncTime!); writer.Write(extra.MicrosoftBlobContainerExtension!.Value); writer.Write((int)(extra.MicrosoftSyncState!.Value)); writer.Write(extra.MicrosoftBlobDirectoryGuid!.Value.ToByteArray()); @@ -268,7 +279,7 @@ private static void AppendMicrosoftMeta(BinaryWriter writer, string identifier, /// /// /// How many times it should be added. - private static void AppendDynamicText(BinaryWriter writer, string identifier, int count) + private static void AppendDynamicText(BinaryWriter writer, string identifier, int count = 1) { for (var i = 0; i < count; i++) { diff --git a/libNOM.io/Resources/hashed_technology.bin b/libNOM.io/Resources/hashed_technology.bin index 1c90bd1d7512583a3e03db0c85563e84ee1ff5b0..472c9c5c01ebaf1cef7f4374b6a9da94fc4fab6b 100644 GIT binary patch delta 123 zcmew^dsJ>hjo9OuQUe1+qvbI%3=It{&m9DF4JTGE;9k%GW?Q&AMogAqoXFL(0?aoF zoH%(o<3rX)A9t-6o7 - 0.14.0-beta.17 + 0.14.0-beta.20 cengelha Provides reading and writing save files from the game No Man's Sky for all possible platforms as well as related actions. Copyright (c) Christian Engelhardt 2021 @@ -93,7 +93,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libNOM.io/packages.lock.json b/libNOM.io/packages.lock.json index 9950dbe9..9988c8a3 100644 --- a/libNOM.io/packages.lock.json +++ b/libNOM.io/packages.lock.json @@ -51,12 +51,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.13.4, )", - "resolved": "0.13.4", - "contentHash": "uQtznykCV1+zL7vWqu/0jwsB7yk1o9T0ym4YJW+D3wxlOioKkxQglmSeF92w3e3rpJnXpeSr+DVdpiAm4EgMUg==", + "requested": "[0.13.5, )", + "resolved": "0.13.5", + "contentHash": "puaMk8OQEYYw4nN/d9MRmWlS8SzW4nzb5gFCSM8dE1Szjx0S36W7C7s/cPyJmPAqJ6jLU931ofXYPsJppoXvjA==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "13.0.1" + "Octokit": "14.0.0" } }, "Microsoft.SourceLink.GitHub": { @@ -172,8 +172,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "tjXTa2FXzbd3n17VWpi8UXe05EIJqHcWJW8C2kukftIve00duWiZL8x4i1vlZQ0zQ4RbRANbRc7J5K7Co/1spQ==" + "resolved": "14.0.0", + "contentHash": "jGOuTH1l+TCpJH+fwYOp7USzHDuGfN1jKbLz3J2COwyn+wL08eynvpnM6rY2qkzIEXum3PN2p2QkP3BW/p9Qcw==" }, "System.Buffers": { "type": "Transitive", @@ -239,12 +239,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.13.4, )", - "resolved": "0.13.4", - "contentHash": "uQtznykCV1+zL7vWqu/0jwsB7yk1o9T0ym4YJW+D3wxlOioKkxQglmSeF92w3e3rpJnXpeSr+DVdpiAm4EgMUg==", + "requested": "[0.13.5, )", + "resolved": "0.13.5", + "contentHash": "puaMk8OQEYYw4nN/d9MRmWlS8SzW4nzb5gFCSM8dE1Szjx0S36W7C7s/cPyJmPAqJ6jLU931ofXYPsJppoXvjA==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "13.0.1" + "Octokit": "14.0.0" } }, "Microsoft.SourceLink.GitHub": { @@ -330,8 +330,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "tjXTa2FXzbd3n17VWpi8UXe05EIJqHcWJW8C2kukftIve00duWiZL8x4i1vlZQ0zQ4RbRANbRc7J5K7Co/1spQ==" + "resolved": "14.0.0", + "contentHash": "jGOuTH1l+TCpJH+fwYOp7USzHDuGfN1jKbLz3J2COwyn+wL08eynvpnM6rY2qkzIEXum3PN2p2QkP3BW/p9Qcw==" }, "System.Buffers": { "type": "Transitive", @@ -390,12 +390,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.13.4, )", - "resolved": "0.13.4", - "contentHash": "uQtznykCV1+zL7vWqu/0jwsB7yk1o9T0ym4YJW+D3wxlOioKkxQglmSeF92w3e3rpJnXpeSr+DVdpiAm4EgMUg==", + "requested": "[0.13.5, )", + "resolved": "0.13.5", + "contentHash": "puaMk8OQEYYw4nN/d9MRmWlS8SzW4nzb5gFCSM8dE1Szjx0S36W7C7s/cPyJmPAqJ6jLU931ofXYPsJppoXvjA==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "13.0.1" + "Octokit": "14.0.0" } }, "Microsoft.SourceLink.GitHub": { @@ -481,8 +481,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "tjXTa2FXzbd3n17VWpi8UXe05EIJqHcWJW8C2kukftIve00duWiZL8x4i1vlZQ0zQ4RbRANbRc7J5K7Co/1spQ==" + "resolved": "14.0.0", + "contentHash": "jGOuTH1l+TCpJH+fwYOp7USzHDuGfN1jKbLz3J2COwyn+wL08eynvpnM6rY2qkzIEXum3PN2p2QkP3BW/p9Qcw==" }, "System.Memory": { "type": "Transitive", @@ -526,12 +526,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.13.4, )", - "resolved": "0.13.4", - "contentHash": "uQtznykCV1+zL7vWqu/0jwsB7yk1o9T0ym4YJW+D3wxlOioKkxQglmSeF92w3e3rpJnXpeSr+DVdpiAm4EgMUg==", + "requested": "[0.13.5, )", + "resolved": "0.13.5", + "contentHash": "puaMk8OQEYYw4nN/d9MRmWlS8SzW4nzb5gFCSM8dE1Szjx0S36W7C7s/cPyJmPAqJ6jLU931ofXYPsJppoXvjA==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "13.0.1" + "Octokit": "14.0.0" } }, "Microsoft.SourceLink.GitHub": { @@ -617,8 +617,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "tjXTa2FXzbd3n17VWpi8UXe05EIJqHcWJW8C2kukftIve00duWiZL8x4i1vlZQ0zQ4RbRANbRc7J5K7Co/1spQ==" + "resolved": "14.0.0", + "contentHash": "jGOuTH1l+TCpJH+fwYOp7USzHDuGfN1jKbLz3J2COwyn+wL08eynvpnM6rY2qkzIEXum3PN2p2QkP3BW/p9Qcw==" }, "System.Memory": { "type": "Transitive", diff --git a/libNOM.test/Playstation.cs b/libNOM.test/Playstation.cs index a34ebc06..a9c453f2 100644 --- a/libNOM.test/Playstation.cs +++ b/libNOM.test/Playstation.cs @@ -460,7 +460,7 @@ public void T137_Read_0x7D2_SaveWizard_7() { new(3, "Slot2Manual", true, true, false, true, true, false, false, false, SaveContextQueryEnum.DontCare, nameof(PresetGameModeEnum.Normal), DifficultyPresetTypeEnum.Custom, SeasonEnum.None, 4146, 4658, GameVersionEnum.Echoes, "Eggsave", "Aboard the Space Anomaly", 23349), - new(9, "Slot5Manual", true, true, false, true, true, true, true, true, SaveContextQueryEnum.Main, nameof(PresetGameModeEnum.Normal), DifficultyPresetTypeEnum.Custom, SeasonEnum.None, 4148, 4660, GameVersionEnum.Omega, "Purfex", "Within HydroFarm Paradise", 2721229), + new(9, "Slot5Manual", true, true, false, true, true, true, true, true, SaveContextQueryEnum.Main, nameof(PresetGameModeEnum.Normal), DifficultyPresetTypeEnum.Normal, SeasonEnum.None, 4148, 4660, GameVersionEnum.Omega, "Purfex", "Within HydroFarm Paradise", 2721229), new(10, "Slot6Auto", true, true, false, true, true, true, true, true, SaveContextQueryEnum.Main, nameof(PresetGameModeEnum.Normal), DifficultyPresetTypeEnum.Custom, SeasonEnum.None, 4148, 4660, GameVersionEnum.Omega, "Purfex", "Within HydroFarm Paradise", 2742841), }; diff --git a/libNOM.test/libNOM.test.csproj b/libNOM.test/libNOM.test.csproj index 065daacf..751a0dc4 100644 --- a/libNOM.test/libNOM.test.csproj +++ b/libNOM.test/libNOM.test.csproj @@ -54,8 +54,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive