diff --git a/CHANGELOG.md b/CHANGELOG.md index 7844ad2..849d7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,14 @@ All notable changes to this project will be documented in this file. It uses the * A setting to choose between writing always or only if a container is unsynced ### Changed * Renamed the settings _LastWriteTime_ and _Mapping_ +* Names of the IsVersion flags now include the version number as well +* DifficultyPresetTypeEnum has been added and therefore PresetGameModeEnum is now + only used internal ### Deprecated ### Removed ### Fixed * The _Mapping_ settings is now only used to determine input/output and not for modifying things internally -* A number of different issues reported on [Discord](https://discord.gg/nomnom-762409407488720918) and the [NomNom repository](https://github.com/zencq/NomNom/issues) +* A number of different issues reported on [Discord](https://discord.gg/nomnom-762409407488720918) and the [NomNom repository](https://github.com/zencq/NomNom/milestone/10) ### Security ## 0.5.3 (2023-06-24) diff --git a/GAMEUPDATE.md b/GAMEUPDATE.md index 67dd3da..afcdd16 100644 --- a/GAMEUPDATE.md +++ b/GAMEUPDATE.md @@ -4,11 +4,12 @@ ### Enums * Check whether game enums have been updated: + * `DifficultyPresetTypeEnum` + * `ParticipantTypeEnum` * `PersistentBaseTypesEnum` * `PresetGameModeEnum` * Extend `VersionEnum` and if there is a new Expedition, update the `SeasonEnum` as well. -* In rare occasions there might be a new game mode as well (`PresetGameModeEnum`). * If necessary add a `Description` attribute to it. ### Container @@ -19,6 +20,7 @@ ### Global * For new game modes the `GetGameModeEnum` needs to be updated. +* For new difficulty preset the `DifficultyPresetTypeEnum` needs to be updated. * Will probably never be the case but if the formula for the version numbers changes, the `CalculateBaseVersion` and `CalculateVersion` need an updated as well. diff --git a/libNOM.io/Container.cs b/libNOM.io/Container.cs index 649e631..ad282df 100644 --- a/libNOM.io/Container.cs +++ b/libNOM.io/Container.cs @@ -104,7 +104,7 @@ public partial class Container : IComparable, IEquatable public DateTimeOffset? LastWriteTime // { get; set; } { get => Extra.LastWriteTime ?? (Exists ? DataFile?.LastWriteTime : null); - set => Extra.LastWriteTime = value; + set => Extra = Extra with { LastWriteTime = value }; } public FileInfo? MetaFile { get; internal set; } @@ -136,7 +136,13 @@ public partial class Container : IComparable, IEquatable public int BaseVersion { get => Extra.BaseVersion; - internal set => Extra.BaseVersion = value; + internal set => Extra = Extra with { BaseVersion = value }; + } + + public DifficultyPresetTypeEnum GameDifficultyPresetEnum // { get; internal set; } + { + get => (DifficultyPresetTypeEnum)(Extra.DifficultyPreset); + internal set => Extra = Extra with { DifficultyPreset = (byte)(value) }; } public PresetGameModeEnum GameModeEnum // { get; internal set; } @@ -148,67 +154,86 @@ internal set { SaveVersion = Calculate.CalculateVersion(BaseVersion, value, SeasonEnum); } - Extra.GameMode = (short)(value); + Extra = Extra with { GameMode = (short)(value) }; } } public GameVersionEnum GameVersionEnum { get; internal set; } = GameVersionEnum.Unknown; - public bool IsBeyondWithVehicleCam => IsVersion(GameVersionEnum.BeyondWithVehicleCam); // { get; } + public bool Is211BeyondWithVehicleCam => IsVersion(GameVersionEnum.BeyondWithVehicleCam); // { get; } + + public bool Is220Synthesis => IsVersion(GameVersionEnum.Synthesis); // { get; } + + public bool Is226SynthesisWithJetpack => IsVersion(GameVersionEnum.SynthesisWithJetpack); // { get; } + + public bool Is230LivingShip => IsVersion(GameVersionEnum.LivingShip); // { get; } - public bool IsSynthesis => IsVersion(GameVersionEnum.Synthesis); // { get; } + public bool Is240ExoMech => IsVersion(GameVersionEnum.ExoMech); // { get; } - public bool IsSynthesisWithJetpack => IsVersion(GameVersionEnum.SynthesisWithJetpack); // { get; } + public bool Is250Crossplay => IsVersion(GameVersionEnum.Crossplay); // { get; } - public bool IsLivingShip => IsVersion(GameVersionEnum.LivingShip); // { get; } + public bool Is260Desolation => IsVersion(GameVersionEnum.Desolation); // { get; } - public bool IsExoMech => IsVersion(GameVersionEnum.ExoMech); // { get; } + public bool Is300Origins => IsVersion(GameVersionEnum.Origins); // { get; } - public bool IsCrossplay => IsVersion(GameVersionEnum.Crossplay); // { get; } + public bool Is310NextGeneration => IsVersion(GameVersionEnum.NextGeneration); // { get; } - public bool IsDesolation => IsVersion(GameVersionEnum.Desolation); // { get; } + public bool Is320Companions => IsVersion(GameVersionEnum.Companions); // { get; } - public bool IsOrigins => IsVersion(GameVersionEnum.Origins); // { get; } + public bool Is330Expeditions => IsVersion(GameVersionEnum.Expeditions); // { get; } - public bool IsNextGeneration => IsVersion(GameVersionEnum.NextGeneration); // { get; } + public bool Is340Beachhead => IsVersion(GameVersionEnum.Beachhead); // { get; } - public bool IsCompanions => IsVersion(GameVersionEnum.Companions); // { get; } + public bool Is350Prisms => IsVersion(GameVersionEnum.Prisms); // { get; } - public bool IsExpeditions => IsVersion(GameVersionEnum.Expeditions); // { get; } + public bool Is351PrismsWithBytebeatAuthor => IsVersion(GameVersionEnum.PrismsWithBytebeatAuthor); // { get; } - public bool IsBeachhead => IsVersion(GameVersionEnum.Beachhead); // { get; } + public bool Is360Frontiers => IsVersion(GameVersionEnum.Frontiers); // { get; } - public bool IsPrisms => IsVersion(GameVersionEnum.Prisms); // { get; } + public bool Is370Emergence => IsVersion(GameVersionEnum.Emergence); // { get; } - public bool IsPrismsWithBytebeatAuthor => IsVersion(GameVersionEnum.PrismsWithBytebeatAuthor); // { get; } + public bool Is380Sentinel => IsVersion(GameVersionEnum.Sentinel); // { get; } - public bool IsFrontiers => IsVersion(GameVersionEnum.Frontiers); // { get; } + public bool Is381SentinelWithWeaponResource => IsVersion(GameVersionEnum.SentinelWithWeaponResource); // { get; } - public bool IsEmergence => IsVersion(GameVersionEnum.Emergence); // { get; } + public bool Is384SentinelWithVehicleAI => IsVersion(GameVersionEnum.SentinelWithVehicleAI); // { get; } - public bool IsSentinel => IsVersion(GameVersionEnum.Sentinel); // { get; } + public bool Is385Outlaws => IsVersion(GameVersionEnum.Outlaws); // { get; } - public bool IsSentinelWithWeaponResource => IsVersion(GameVersionEnum.SentinelWithWeaponResource); // { get; } + public bool Is390Leviathan => IsVersion(GameVersionEnum.Leviathan); // { get; } - public bool IsSentinelWithVehicleAI => IsVersion(GameVersionEnum.SentinelWithVehicleAI); // { get; } + public bool Is394Endurance => IsVersion(GameVersionEnum.Endurance); // { get; } - public bool IsOutlaws => IsVersion(GameVersionEnum.Outlaws); // { get; } + public bool Is400Waypoint => IsVersion(GameVersionEnum.Waypoint); // { get; } - public bool IsLeviathan => IsVersion(GameVersionEnum.Leviathan); // { get; } + public bool Is404WaypointWithAgileStat => IsVersion(GameVersionEnum.WaypointWithAgileStat); // { get; } - public bool IsEndurance => IsVersion(GameVersionEnum.Endurance); // { get; } + public bool Is405WaypointWithSuperchargedSlots => IsVersion(GameVersionEnum.WaypointWithSuperchargedSlots); // { get; } - public bool IsWaypoint => IsVersion(GameVersionEnum.Waypoint); // { get; } + public bool Is410Fractal => IsVersion(GameVersionEnum.Fractal); // { get; } - public bool IsWaypointWithAgileStat => IsVersion(GameVersionEnum.WaypointWithAgileStat); // { get; } + public bool Is420Interceptor => IsVersion(GameVersionEnum.Interceptor); // { get; } - public bool IsWaypointWithSuperchargedSlots => IsVersion(GameVersionEnum.WaypointWithSuperchargedSlots); // { get; } + public bool Is430Singularity => IsVersion(GameVersionEnum.Singularity); // { get; } - public bool IsFractal => IsVersion(GameVersionEnum.Fractal); // { get; } + public bool Is440Echoes => IsVersion(GameVersionEnum.Echoes); // { get; } - public bool IsInterceptor => IsVersion(GameVersionEnum.Interceptor); // { get; } + internal SaveFormatEnum SaveFormatEnum + { + get + { + if (Is400Waypoint) + return SaveFormatEnum.Waypoint; + + if (Is360Frontiers) + return SaveFormatEnum.Frontiers; - public bool IsSingularity => IsVersion(GameVersionEnum.Singularity); // { get; } + if (BaseVersion > Constants.THRESHOLD_VANILLA) + return SaveFormatEnum.Foundation; + + return SaveFormatEnum.Vanilla; + } + } public string SaveName // { get; set; } { @@ -218,7 +243,7 @@ internal set if (_jsonObject is not null) SetJsonValue(value, "6f=.Pk4", "PlayerStateData.SaveName"); - Extra.SaveName = value; + Extra = Extra with { SaveName = value }; } } @@ -230,7 +255,7 @@ internal set if (_jsonObject is not null) SetJsonValue(value, "6f=.n:R", "PlayerStateData.SaveSummary"); - Extra.SaveSummary = value; + Extra = Extra with { SaveSummary = value }; } } @@ -245,7 +270,7 @@ internal set { SaveVersion = Calculate.CalculateVersion(BaseVersion, GameModeEnum, value); } - Extra.Season = (short)(value); + Extra = Extra with { Season = (short)(value) }; } } @@ -257,7 +282,7 @@ internal set if (_jsonObject is not null) SetJsonValue(value, "6f=.Lg8", "PlayerStateData.TotalPlayTime"); - Extra.TotalPlayTime = value; + Extra = Extra with { TotalPlayTime = value }; } } @@ -539,7 +564,7 @@ public override int GetHashCode() public override string ToString() { - return $"{nameof(Container)} {MetaIndex} {Identifier}"; + return $"{nameof(Container)} {MetaIndex:00} {Identifier}{(Exists ? " (exists)" : string.Empty)}"; } #endregion diff --git a/libNOM.io/Enums/DifficultyPresetTypeEnum.cs b/libNOM.io/Enums/DifficultyPresetTypeEnum.cs new file mode 100644 index 0000000..7818397 --- /dev/null +++ b/libNOM.io/Enums/DifficultyPresetTypeEnum.cs @@ -0,0 +1,16 @@ +namespace libNOM.io.Enums; + +/// +/// Specifies available game modes. +/// +/// +public enum DifficultyPresetTypeEnum : byte +{ + Invalid, + Custom, + Normal, + Creative, + Relaxed, + Survival, + Permadeath, +} diff --git a/libNOM.io/Enums/GameVersionEnum.cs b/libNOM.io/Enums/GameVersionEnum.cs index 33304dd..a67d218 100644 --- a/libNOM.io/Enums/GameVersionEnum.cs +++ b/libNOM.io/Enums/GameVersionEnum.cs @@ -6,7 +6,7 @@ namespace libNOM.io.Enums; /// /// Specifies all versions of big game updates and smaller ones if necessary for specific features. /// -public enum GameVersionEnum +public enum GameVersionEnum : uint { Unknown = 0, Vanilla = 100, @@ -53,4 +53,5 @@ public enum GameVersionEnum Interceptor = 420, Mac = 425, Singularity = 430, + Echoes = 440, } diff --git a/libNOM.io/Enums/LoadingStrategyEnum.cs b/libNOM.io/Enums/LoadingStrategyEnum.cs index 9ea7169..6388325 100644 --- a/libNOM.io/Enums/LoadingStrategyEnum.cs +++ b/libNOM.io/Enums/LoadingStrategyEnum.cs @@ -4,7 +4,7 @@ /// /// Specifies strategies how to load and keep containers in a . /// -public enum LoadingStrategyEnum +public enum LoadingStrategyEnum : uint { /// /// No save information and data loaded. diff --git a/libNOM.io/Enums/MicrosoftBlobSyncStateEnum.cs b/libNOM.io/Enums/MicrosoftBlobSyncStateEnum.cs index 5016c3a..759a23b 100644 --- a/libNOM.io/Enums/MicrosoftBlobSyncStateEnum.cs +++ b/libNOM.io/Enums/MicrosoftBlobSyncStateEnum.cs @@ -4,7 +4,7 @@ /// /// Specifies states used in the containers.index of the . /// -internal enum MicrosoftBlobSyncStateEnum +internal enum MicrosoftBlobSyncStateEnum : uint { Unknown_Zero = 0, Synced = 1, diff --git a/libNOM.io/Enums/MicrosoftIndexSyncStateEnum.cs b/libNOM.io/Enums/MicrosoftIndexSyncStateEnum.cs index 95e9255..35d93e0 100644 --- a/libNOM.io/Enums/MicrosoftIndexSyncStateEnum.cs +++ b/libNOM.io/Enums/MicrosoftIndexSyncStateEnum.cs @@ -4,7 +4,7 @@ /// /// Specifies states used in the containers.index of the . /// -internal enum MicrosoftIndexSyncStateEnum +internal enum MicrosoftIndexSyncStateEnum : uint { Unknown_Zero = 0, Unknown_One = 1, diff --git a/libNOM.io/Enums/ParticipantTypeEnum.cs b/libNOM.io/Enums/ParticipantTypeEnum.cs index 23a68ca..04eb270 100644 --- a/libNOM.io/Enums/ParticipantTypeEnum.cs +++ b/libNOM.io/Enums/ParticipantTypeEnum.cs @@ -4,7 +4,7 @@ /// /// /// -public enum ParticipantTypeEnum +public enum ParticipantTypeEnum : uint { None, MissionGiver, diff --git a/libNOM.io/Enums/PersistentBaseTypesEnum.cs b/libNOM.io/Enums/PersistentBaseTypesEnum.cs index ed9ec32..242cfac 100644 --- a/libNOM.io/Enums/PersistentBaseTypesEnum.cs +++ b/libNOM.io/Enums/PersistentBaseTypesEnum.cs @@ -5,7 +5,7 @@ /// Specifies types a persistent base can have. /// /// -internal enum PersistentBaseTypesEnum +internal enum PersistentBaseTypesEnum : uint { HomePlanetBase, FreighterBase, diff --git a/libNOM.io/Enums/PlatformEnum.cs b/libNOM.io/Enums/PlatformEnum.cs index 120a5ab..abc4145 100644 --- a/libNOM.io/Enums/PlatformEnum.cs +++ b/libNOM.io/Enums/PlatformEnum.cs @@ -7,7 +7,7 @@ namespace libNOM.io.Enums; /// Specifies platforms the game is available on. /// PlayStation is last as it has the least specific identification characteristics (only Steam like save file in worst case). /// -public enum PlatformEnum +public enum PlatformEnum : uint { Unknown, [Description("Apple App Store")] diff --git a/libNOM.io/Enums/PresetGameModeEnum.cs b/libNOM.io/Enums/PresetGameModeEnum.cs index 8368f1e..a520d5d 100644 --- a/libNOM.io/Enums/PresetGameModeEnum.cs +++ b/libNOM.io/Enums/PresetGameModeEnum.cs @@ -4,7 +4,7 @@ /// Specifies available game modes. /// /// -public enum PresetGameModeEnum +public enum PresetGameModeEnum : uint { Unspecified, Normal, diff --git a/libNOM.io/Enums/SaveFormatEnum.cs b/libNOM.io/Enums/SaveFormatEnum.cs new file mode 100644 index 0000000..df2ab56 --- /dev/null +++ b/libNOM.io/Enums/SaveFormatEnum.cs @@ -0,0 +1,13 @@ +namespace libNOM.io.Enums; + + +/// +/// ... +/// +internal enum SaveFormatEnum : uint +{ + Vanilla, + Foundation, + Frontiers, + Waypoint, +} diff --git a/libNOM.io/Enums/SaveTypeEnum.cs b/libNOM.io/Enums/SaveTypeEnum.cs index 46659cf..2c0edad 100644 --- a/libNOM.io/Enums/SaveTypeEnum.cs +++ b/libNOM.io/Enums/SaveTypeEnum.cs @@ -4,7 +4,7 @@ /// /// Specifies types per slot a save can have. /// -public enum SaveTypeEnum +public enum SaveTypeEnum : uint { Auto, Manual, diff --git a/libNOM.io/Enums/SeasonEnum.cs b/libNOM.io/Enums/SeasonEnum.cs index 75eb2c8..b7b1a3d 100644 --- a/libNOM.io/Enums/SeasonEnum.cs +++ b/libNOM.io/Enums/SeasonEnum.cs @@ -4,7 +4,7 @@ /// /// Specifies all known Expeditions incl. a placeholder for the next one. /// -public enum SeasonEnum +public enum SeasonEnum : uint { None = 0, Pioneers = None, // 1st diff --git a/libNOM.io/Globals/Json.cs b/libNOM.io/Globals/Json.cs index a2dfff7..58f5fc4 100644 --- a/libNOM.io/Globals/Json.cs +++ b/libNOM.io/Globals/Json.cs @@ -675,6 +675,9 @@ internal static GameVersionEnum GetGameVersionEnum(Container container, JObject GameVersion = CreativeVersion/BaseVersion (Obfuscated = Deobfuscated) ??? = ????/???? (??? = ?) + Echoes + 440 = ????/???? (??? = ?) + Singularity 438 = 4657/4145 437 = 4657/4145 @@ -846,6 +849,8 @@ internal static GameVersionEnum GetGameVersionEnum(Container container, JObject var usesMapping = jsonObject.UsesMapping(); + //return GameVersionEnum.Echoes; + if (container.BaseVersion >= 4144) // 4.20, 4.25, 4.30 { // Only used in actual Expedition saves. diff --git a/libNOM.io/Globals/LZ4.cs b/libNOM.io/Globals/LZ4.cs index 3cd5374..13af721 100644 --- a/libNOM.io/Globals/LZ4.cs +++ b/libNOM.io/Globals/LZ4.cs @@ -35,6 +35,7 @@ internal static int Decode(this byte[] self, out byte[] target, int targetLength if (targetLength > 0) { target = new byte[targetLength]; + // TODO ReadOnlySpan bytesWritten = LZ4Codec.Decode(self, 0, self.Length, target, 0, target.Length); } diff --git a/libNOM.io/Platform.cs b/libNOM.io/Platform.cs index 8b17fd3..278fe04 100644 --- a/libNOM.io/Platform.cs +++ b/libNOM.io/Platform.cs @@ -132,7 +132,7 @@ public abstract class Platform : IPlatform, IEquatable #region Container - public Container? GetAccountContainer() + public Container GetAccountContainer() { return AccountContainer; } @@ -187,7 +187,7 @@ public IEnumerable GetWatcherContainers() protected int GetMetaSize(Container container) { var size = (int)(container.Extra.Size); - return (size == META_LENGTH_TOTAL_WAYPOINT || size == META_LENGTH_TOTAL_VANILLA) ? size : (container.IsWaypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA); + return (size == META_LENGTH_TOTAL_WAYPOINT || size == META_LENGTH_TOTAL_VANILLA) ? size : (container.Is400Waypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA); } #endregion @@ -568,12 +568,13 @@ protected uint[] LoadMeta(Container container) /// protected uint[] LoadMeta(Container container, byte[] read) { - if (read.IsNullOrEmpty()) - return Array.Empty(); - // 2. Decrypt // 3. Decompress - return DecompressMeta(container, DecryptMeta(container, read)); + var result = DecompressMeta(container, DecryptMeta(container, read)); + // 4. Update Container Information + UpdateContainerWithMetaInformation(container, read, result); + + return result; } /// @@ -611,6 +612,8 @@ protected virtual uint[] DecompressMeta(Container container, uint[] meta) return meta; } + protected abstract void UpdateContainerWithMetaInformation(Container container, byte[] raw, uint[] converted); + /// protected virtual byte[] LoadData(Container container, uint[] meta) { @@ -629,7 +632,11 @@ protected virtual byte[] LoadData(Container container, uint[] meta, byte[] read) { // 2. Decrypt // 3. Decompress - return DecompressData(container, meta, DecryptData(container, meta, read)); + var result = DecompressData(container, meta, DecryptData(container, meta, read)); + // 4. Update Container Information + UpdateContainerWithDataInformation(container, read, result); + + return result; } /// @@ -668,11 +675,11 @@ protected virtual byte[] DecompressData(Container container, uint[] meta, byte[] { #if NETSTANDARD2_0 // No compression for account data and before Frontiers. - if (!container.IsSave || data.Take(4).GetUInt32().FirstOrDefault() != Globals.Constants.HEADER_SAVE_STREAMING_CHUNK) + if (!container.IsSave || !data.Any() || data.Take(4).GetUInt32().FirstOrDefault() != Globals.Constants.HEADER_SAVE_STREAMING_CHUNK) return data; #else // No compression for account data and before Frontiers. - if (!container.IsSave || data[..4].GetUInt32().FirstOrDefault() != Globals.Constants.HEADER_SAVE_STREAMING_CHUNK) + if (!container.IsSave || !data.Any() || data[..4].GetUInt32().FirstOrDefault() != Globals.Constants.HEADER_SAVE_STREAMING_CHUNK) return data; #endif var concurrent = new ConcurrentDictionary(); @@ -711,6 +718,8 @@ protected virtual byte[] DecompressData(Container container, uint[] meta, byte[] return result.ToArray(); } + protected abstract void UpdateContainerWithDataInformation(Container container, byte[] raw, byte[] converted); + #endregion #region Process @@ -945,7 +954,7 @@ protected virtual byte[] CreateData(Container container) /// protected virtual byte[] CompressData(Container container, byte[] data) { - if (!container.IsSave || !container.IsFrontiers) + if (!container.IsSave || !container.Is360Frontiers) return data; var concurrent = new ConcurrentDictionary(); @@ -1519,10 +1528,10 @@ protected void TransferOwnership(Container container, ContainerTransferData sour if (sourceTransferData.TransferBase) // 1.1 TransferBaseOwnership(jsonObject, sourceTransferData); - if (container.IsPrismsWithBytebeatAuthor && sourceTransferData.TransferBytebeat) // 3.51 + if (container.Is351PrismsWithBytebeatAuthor && sourceTransferData.TransferBytebeat) // 3.51 TransferBytebeatOwnership(jsonObject, sourceTransferData); - if (container.IsFrontiers && sourceTransferData.TransferSettlement) // 3.6 + if (container.Is360Frontiers && sourceTransferData.TransferSettlement) // 3.6 TransferGeneralOwnership(jsonObject, usesMapping ? $"PlayerStateData.SettlementStatesV2..[?(@.UID == '{sourceTransferData.UserIdentification!.UID}')]" : $"6f=.GQA..[?(@.K7E == '{sourceTransferData.UserIdentification!.UID}')]"); container.SetJsonObject(jsonObject); diff --git a/libNOM.io/PlatformExtra.cs b/libNOM.io/PlatformExtra.cs index 454d850..2024e51 100644 --- a/libNOM.io/PlatformExtra.cs +++ b/libNOM.io/PlatformExtra.cs @@ -53,6 +53,8 @@ internal record class PlatformExtra internal string SaveSummary = string.Empty; + internal byte DifficultyPreset; + #endregion #region Microsoft diff --git a/libNOM.io/PlatformMicrosoft.cs b/libNOM.io/PlatformMicrosoft.cs index ec30537..d4bda93 100644 --- a/libNOM.io/PlatformMicrosoft.cs +++ b/libNOM.io/PlatformMicrosoft.cs @@ -444,28 +444,25 @@ protected override byte[] LoadContainer(Container container) return Array.Empty(); } - protected override uint[] DecryptMeta(Container container, byte[] meta) + protected override void UpdateContainerWithMetaInformation(Container container, byte[] raw, uint[] converted) { - var value = base.DecryptMeta(container, meta); - var season = BitConverter.ToInt16(meta, 1 * sizeof(uint) + 2); + var season = BitConverter.ToInt16(raw, 1 * sizeof(uint) + 2); container.Extra = container.Extra with { #if NETSTANDARD2_0 - Bytes = meta.Skip(META_LENGTH_KNOWN).ToArray(), + Bytes = raw.Skip(META_LENGTH_KNOWN).ToArray(), #else - Bytes = meta[META_LENGTH_KNOWN..], + Bytes = raw[META_LENGTH_KNOWN..], #endif - SizeDecompressed = value[4], - BaseVersion = (int)(value[0]), - GameMode = BitConverter.ToInt16(meta, 1 * sizeof(uint)), + SizeDecompressed = converted[4], + BaseVersion = (int)(converted[0]), + GameMode = BitConverter.ToInt16(raw, 1 * sizeof(uint)), Season = (short)(season <= 0 ? 0 : season), - TotalPlayTime = value[2], + TotalPlayTime = converted[2], }; container.SaveVersion = Calculate.CalculateVersion(container.Extra.BaseVersion, container.Extra.GameMode, container.Extra.Season); - - return value; } protected override byte[] DecompressData(Container container, uint[] meta, byte[] data) @@ -475,6 +472,12 @@ protected override byte[] DecompressData(Container container, uint[] meta, byte[ return target; } + protected override void UpdateContainerWithDataInformation(Container container, byte[] raw, byte[] converted) + { + if (container.Extra.SizeDecompressed == 0) + container.Extra.SizeDecompressed = (uint)(converted.Length); + } + #endregion #region Write @@ -595,7 +598,7 @@ protected override byte[] CreateMeta(Container container, byte[] data) { writer.Write(container.Extra.BaseVersion); // 4 - writer.Write(container.IsWaypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 + writer.Write(container.Is400Waypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 writer.Write(container.Extra.Season); // 2 writer.Write((long)(container.Extra.TotalPlayTime)); // 8 @@ -1016,7 +1019,7 @@ protected override void CreatePlatformExtra(Container destination, Container sou MicrosoftBlobDirectoryGuid = Guid.NewGuid(), MicrosoftBlobContainerExtension = 0, LastWriteTime = source.LastWriteTime, - Bytes = new byte[(source.IsWaypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], + Bytes = new byte[(source.Is400Waypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], MicrosoftSyncState = MicrosoftBlobSyncStateEnum.Created, }; } diff --git a/libNOM.io/PlatformPlaystation.cs b/libNOM.io/PlatformPlaystation.cs index dc67d1b..72df12d 100644 --- a/libNOM.io/PlatformPlaystation.cs +++ b/libNOM.io/PlatformPlaystation.cs @@ -7,24 +7,6 @@ namespace libNOM.io; -#region Container - -internal record class PlatformExtraPlaystation -{ - internal byte[]? Bytes; - internal int Size; - internal int Offset; - internal int SizeCompressed; - internal int SizeDecompressed; -} - -public partial class Container -{ - internal PlatformExtraPlaystation? Playstation { get; set; } -} - -#endregion - public partial class PlatformPlaystation : Platform { #region Constant @@ -36,6 +18,8 @@ public partial class PlatformPlaystation : Platform private const uint META_HEADER = 0xCA55E77E; private int META_OFFSET => _usesSaveWizard ? 0x40 : 0x0; // 64 : 0 private int META_SIZE => _usesSaveWizard ? 0x30 : 0x20; // 48 : 32 + protected override int META_LENGTH_TOTAL_VANILLA => 0x68; // 104 byte + protected override int META_LENGTH_TOTAL_WAYPOINT => 0x168; // 360 byte private const int MEMORYDAT_ANCHORFILE_INDEX = 1; private int MEMORYDAT_META_INDEX_OFFSET => _usesSaveWizard ? 8 : 3; @@ -194,7 +178,7 @@ protected override void InitializeComponent(DirectoryInfo? directory, PlatformSe #region Generate - protected override Container CreateContainer(int metaIndex, object? extra) + private protected override Container CreateContainer(int metaIndex, PlatformExtra? extra) { if (_usesSaveStreaming) { @@ -262,6 +246,7 @@ protected override byte[] LoadContainer(Container container) // Load meta data outside the if as it sets whether the container exists. var meta = LoadMeta(container); + //container.Exists &= meta?.Any() == true; if (container.Exists) { var data = LoadData(container, meta); @@ -300,20 +285,21 @@ protected override byte[] ReadMeta(Container container) return Array.Empty(); } - protected override uint[] DecryptMeta(Container container, byte[] meta) + protected override void UpdateContainerWithMetaInformation(Container container, byte[] raw, uint[] converted) { - var metaInt = base.DecryptMeta(container, meta); - if (_usesSaveStreaming) { if (_usesSaveWizard) { - container.Playstation = new PlatformExtraPlaystation + if (!converted.Any()) + return; + + container.Extra = new PlatformExtra { - Offset = 0x70, - Size = (int)(metaInt[19]), - SizeCompressed = (int)(metaInt[1]), - SizeDecompressed = (int)(metaInt[19]), + PlaystationOffset = 0x70, + Size = converted[19], + SizeDisk = converted[1], + SizeDecompressed = converted[19], }; } //else @@ -321,23 +307,28 @@ protected override uint[] DecryptMeta(Container container, byte[] meta) } else { - container.Exists = metaInt[2] != 0; - container.LastWriteTime = DateTimeOffset.FromUnixTimeSeconds(metaInt[6]).ToLocalTime(); - container.Playstation = new PlatformExtraPlaystation + if (!converted.Any()) + { + container.Exists = false; + return; + } + + container.Extra = new PlatformExtra { - Offset = (int)(metaInt[MEMORYDAT_META_INDEX_OFFSET]), - Size = (int)(metaInt[MEMORYDAT_META_INDEX_SIZE]), // either compressed or decompressed size depending on SaveWizard usage - SizeCompressed = (int)(metaInt[2]), - SizeDecompressed = (int)(metaInt[7]), + PlaystationOffset = (int)(converted[MEMORYDAT_META_INDEX_OFFSET]), + Size = converted[MEMORYDAT_META_INDEX_SIZE], // either compressed or decompressed size depending on SaveWizard usage + SizeDisk = converted[2], + SizeDecompressed = converted[7], }; - } - return metaInt; + container.Exists = converted[2] != 0; + container.LastWriteTime = DateTimeOffset.FromUnixTimeSeconds(converted[6]).ToLocalTime(); + } } protected override byte[] LoadData(Container container, uint[] meta) { - return LoadData(container, meta, container.Playstation?.Bytes ?? ReadData(container)); + return LoadData(container, meta, container.Extra.Bytes ?? ReadData(container)); } protected override byte[] ReadData(Container container) @@ -345,13 +336,13 @@ protected override byte[] ReadData(Container container) if (!_usesSaveStreaming || (_usesSaveWizard && container.IsSave)) { using var reader = new BinaryReader(File.Open(container.DataFile!.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - reader.BaseStream.Seek(container.Playstation!.Offset, SeekOrigin.Begin); + reader.BaseStream.Seek(container.Extra.PlaystationOffset!.Value, SeekOrigin.Begin); - var data = reader.ReadBytes(container.Playstation!.Size); + var data = reader.ReadBytes((int)(container.Extra.Size)); // Store raw bytes as the block size is dynamic and moves if SaveWizard is used. Therefore the entire file needs to be rebuild. if (!_usesSaveStreaming) - container.Playstation!.Bytes = data; + container.Extra.Bytes = data; return data; } @@ -365,35 +356,39 @@ protected override byte[] DecompressData(Container container, uint[] meta, byte[ if (_usesSaveWizard) return data; - // Same as for Steam. if (_usesSaveStreaming) { - // No compression for account data. - if (!container.IsSave) + // Same as for Steam. + return base.DecompressData(container, meta, data); + } + else + { + _ = Globals.LZ4.Decode(data, out byte[] target, (int)(container.Extra.SizeDecompressed)); + return target; + } + } + + protected override void UpdateContainerWithDataInformation(Container container, byte[] raw, byte[] converted) + { + if (_usesSaveStreaming) + { + if (container.IsSave) { - // Create PlayStation container in case it will be copied. - container.Playstation = new PlatformExtraPlaystation + container.Extra = container.Extra with { - SizeCompressed = data.Length, - SizeDecompressed = data.Length, + SizeDecompressed = (uint)(converted.Length), + SizeDisk = (uint)(raw.Length), }; - return data; } - - var result = DecompressSaveStreamingData(data); - - // Create PlayStation container in case it will be copied. - container.Playstation = new PlatformExtraPlaystation + // No compression for account data. + else { - SizeCompressed = data.Length, - SizeDecompressed = result.Length, - }; - return result; - } - else - { - _ = Globals.LZ4.Decode(data, out byte[] target, container.Playstation!.SizeDecompressed); - return target; + container.Extra = container.Extra with + { + SizeDecompressed = (uint)(raw.Length), + SizeDisk = (uint)(raw.Length), + }; + } } } @@ -405,7 +400,7 @@ public override void Write(Container container, DateTimeOffset writeTime) { if (_usesSaveStreaming) { - base.Write(container, writeTime: writeTime); + base.Write(container, writeTime); } else { @@ -422,7 +417,13 @@ public override void Write(Container container, DateTimeOffset writeTime) container.Exists = true; container.IsSynced = true; - (container.Playstation!.Bytes, _) = CreateData(container); // properties SizeCompressed and SizeDecompressed are set inside + var data = CreateData(container); // updates container.Extra inside + container.Extra = container.Extra with + { + Bytes = data, + }; + + //container.Extra.Bytes = CreateData(container); } if (Settings.SetLastWriteTime) @@ -440,6 +441,20 @@ public override void Write(Container container, DateTimeOffset writeTime) container.WriteCallback.Invoke(); } } + protected override byte[] CreateData(Container container) + { + var plain = container.GetJsonObject()!.GetBytes(Settings.UseMapping); + var encrypted = EncryptData(container, CompressData(container, plain)); + + container.Extra = container.Extra with + { + Size = _usesSaveWizard ? (uint)(plain.Length) : (uint)(encrypted.Length), // override because of this + SizeDecompressed = (uint)(plain.Length), + SizeDisk = (uint)(encrypted.Length), + }; + + return encrypted; + } protected override byte[] CompressData(Container container, byte[] data) { @@ -448,10 +463,10 @@ protected override byte[] CompressData(Container container, byte[] data) if (!container.IsSave) return data; - var result = CompressSaveStreamingData(data); + var result = base.CompressData(container, data); - container.Playstation!.SizeDecompressed = data.Length; - container.Playstation!.SizeCompressed = result.Length; + //container.Extra.SizeDecompressed = (uint)(data.Length); + //container.Extra.SizeDisk = (uint)(result.Length); // SaveWizard will do the compression itself. if (_usesSaveWizard) @@ -461,8 +476,10 @@ protected override byte[] CompressData(Container container, byte[] data) } else { - container.Playstation!.SizeDecompressed = data.Length; - container.Playstation!.SizeCompressed = Globals.LZ4.Encode(data, out byte[] target); + //container.Extra.SizeDecompressed = (uint)(data.Length); + //container.Extra.SizeDisk = (uint)(Globals.LZ4.Encode(data, out byte[] target)); + + _ = Globals.LZ4.Encode(data, out byte[] target); // SaveWizard will do the compression itself. if (_usesSaveWizard) @@ -486,7 +503,7 @@ protected override void WriteData(Container container, byte[] data) } } - protected override byte[] CreateMeta(Container container, byte[] data, int decompressedSize) + protected override byte[] CreateMeta(Container container, byte[] data) { if (_usesSaveStreaming) { @@ -500,7 +517,7 @@ protected override byte[] CreateMeta(Container container, byte[] data, int decom writer.Write(2); writer.Write(META_OFFSET); writer.Write(1); - writer.Write(container.Playstation!.SizeCompressed); + writer.Write(container.Extra.SizeDisk); writer.Seek(META_OFFSET + 0x4, SeekOrigin.Begin); @@ -508,7 +525,7 @@ protected override byte[] CreateMeta(Container container, byte[] data, int decom writer.Seek(0x5C, SeekOrigin.Begin); - writer.Write(container.Playstation!.SizeDecompressed); + writer.Write(container.Extra.SizeDecompressed); writer.Seek(4, SeekOrigin.Current); @@ -528,11 +545,12 @@ protected override byte[] CreateMeta(Container container, byte[] data, int decom // 5. META INDEX ( 4) // 6. TIMESTAMP ( 4) // 7. DECOMPRESSED SIZE ( 4) - // 8. EMPTY (32) - // 16. SAVEWIZARD OFFSET ( 4) - // 17. 1 ( 4) - // 18. UNKNOWN ( 8) - // (48) + // (32) // Vanilla + + // 8. SAVEWIZARD OFFSET ( 4) + // 9. 1 ( 4) + // 10. EMPTY ( 8) + // (48) // SaveWizard var buffer = new byte[META_SIZE]; @@ -540,18 +558,18 @@ protected override byte[] CreateMeta(Container container, byte[] data, int decom { var legacyOffset = !container.IsSave ? 0x20000 : (uint)(MEMORYDAT_OFFSET_CONTAINER + (container.CollectionIndex * MEMORYDAT_SIZE_CONTAINER)); var legacyLength = !container.IsSave ? MEMORYDAT_SIZE_ACCOUNTDATA : MEMORYDAT_SIZE_CONTAINER; - var unixSeconds = (uint)(container.LastWriteTime.ToUniversalTime().ToUnixTimeSeconds()); + var unixSeconds = (uint)(container.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); using var writer = new BinaryWriter(new MemoryStream(buffer)); writer.Write(META_HEADER); writer.Write(Globals.Constants.SAVE_FORMAT_2); - writer.Write(container.Playstation!.SizeCompressed); + writer.Write(container.Extra.SizeDisk); writer.Write(legacyOffset); writer.Write(legacyLength); writer.Write(container.MetaIndex); writer.Write(unixSeconds); - writer.Write(container.Playstation!.SizeDecompressed); + writer.Write(container.Extra.SizeDecompressed); if (_usesSaveWizard) { @@ -560,11 +578,11 @@ protected override byte[] CreateMeta(Container container, byte[] data, int decom { var precedingContainer = SaveContainerCollection.Where(i => i.Exists && i.CollectionIndex < container.CollectionIndex); - offset += AccountContainer!.Playstation!.SizeDecompressed; - offset += precedingContainer.Sum(i => SAVEWIZARD_HEADER.Length + i.Playstation!.SizeDecompressed); + offset += AccountContainer!.Extra.SizeDecompressed; + offset += precedingContainer.Sum(i => SAVEWIZARD_HEADER.Length + i.Extra.SizeDecompressed); offset += SAVEWIZARD_HEADER.Length; } - writer.Write(offset); + writer.Write((uint)(offset)); writer.Write(1); } } @@ -609,7 +627,7 @@ private void WriteMemoryDat() // AccountData { - var meta = CreateMeta(AccountContainer!, AccountContainer!.Playstation!.Bytes!, AccountContainer!.Playstation!.SizeDecompressed); + var meta = CreateMeta(AccountContainer!, AccountContainer!.Extra!.Bytes!); writer.Write(meta); writer.BaseStream.Seek(META_SIZE, SeekOrigin.Current); // skip index 1 as not used } @@ -617,7 +635,7 @@ private void WriteMemoryDat() // Container foreach (var container in SaveContainerCollection) { - var meta = CreateMeta(container, container.Playstation!.Bytes!, container.Playstation!.SizeDecompressed); + var meta = CreateMeta(container, container.Extra.Bytes!); writer.Write(meta); } } @@ -628,11 +646,11 @@ private void WriteMemoryDat() if (_usesSaveWizard) { // AccountData - result = result.Concat(SAVEWIZARD_HEADER_BINARY).Concat(AccountContainer!.Playstation!.Bytes!); + result = result.Concat(SAVEWIZARD_HEADER_BINARY).Concat(AccountContainer!.Extra.Bytes!); // Container foreach (var container in SaveContainerCollection.Where(i => i.Exists)) - result = result.Concat(SAVEWIZARD_HEADER_BINARY).Concat(container.Playstation!.Bytes!); + result = result.Concat(SAVEWIZARD_HEADER_BINARY).Concat(container.Extra.Bytes!); } else { @@ -640,7 +658,7 @@ private void WriteMemoryDat() buffer = new byte[MEMORYDAT_OFFSET_CONTAINER - MEMORYDAT_OFFSET_DATA]; using (var writer = new BinaryWriter(new MemoryStream(buffer))) { - writer.Write(AccountContainer!.Playstation!.Bytes!); + writer.Write(AccountContainer!.Extra.Bytes!); } result = result.Concat(buffer); @@ -651,7 +669,7 @@ private void WriteMemoryDat() if (container.Exists) { using var writer = new BinaryWriter(new MemoryStream(buffer)); - writer.Write(container.Playstation!.Bytes!); + writer.Write(container.Extra.Bytes!); } result = result.Concat(buffer); } @@ -686,9 +704,6 @@ protected override void Copy(IEnumerable<(Container Source, Container Destinatio } else if (Destination.Exists || !Destination.Exists && CanCreate) { - if (GuardPlatformExtra(Source)) - throw new InvalidOperationException("Cannot copy as the source container has no platform extra."); - if (!Source.IsLoaded) BuildContainerFull(Source); @@ -707,7 +722,7 @@ protected override void Copy(IEnumerable<(Container Source, Container Destinatio // Properties requied to properly build the container below. Destination.BaseVersion = Source.BaseVersion; - Destination.VersionEnum = Source.VersionEnum; + Destination.GameVersionEnum = Source.GameVersionEnum; Destination.SeasonEnum = Source.SeasonEnum; Destination.SetJsonObject(Source.GetJsonObject()); @@ -724,18 +739,13 @@ protected override void Copy(IEnumerable<(Container Source, Container Destinatio UpdateUserIdentification(); } - protected override bool GuardPlatformExtra(Container source) - { - return source.Playstation is null; - } - protected override void CopyPlatformExtra(Container destination, Container source) { - destination.Playstation = new PlatformExtraPlaystation + destination.Extra = new PlatformExtra { - Bytes = source.Playstation!.Bytes, - SizeCompressed = source.Playstation!.SizeCompressed, - SizeDecompressed = source.Playstation!.SizeDecompressed, + Bytes = source.Extra.Bytes, + SizeDisk = source.Extra.SizeDisk, + SizeDecompressed = source.Extra.SizeDecompressed, }; } @@ -835,14 +845,14 @@ protected override void Transfer(ContainerTransferData sourceTransferData, int d // Properties requied to properly build the container below. Destination.BaseVersion = Source.BaseVersion; - Destination.VersionEnum = Source.VersionEnum; + Destination.GameVersionEnum = Source.GameVersionEnum; Destination.SeasonEnum = Source.SeasonEnum; Destination.SetJsonObject(Source.GetJsonObject()); TransferOwnership(Destination, sourceTransferData); // Update bytes in platform extra as it is what will be written later. - (Destination.Playstation!.Bytes, _) = CreateData(Destination); + Destination.Extra!.Bytes = CreateData(Destination); BuildContainerFull(Destination); } @@ -858,7 +868,7 @@ protected override void Transfer(ContainerTransferData sourceTransferData, int d protected override void CreatePlatformExtra(Container destination, Container source) { - destination.Playstation = new(); + destination.Extra = new(); } #endregion @@ -875,7 +885,7 @@ private void RefreshContainerCollection() for (var containerIndex = 0; containerIndex < COUNT_SAVES_TOTAL; containerIndex++) { // Reset bytes to read the file again. - SaveContainerCollection[containerIndex].Playstation!.Bytes = null; + SaveContainerCollection[containerIndex].Extra.Bytes = null; // Rebuild new container to set its properties. if (Settings.LoadingStrategy < LoadingStrategyEnum.Full) diff --git a/libNOM.io/PlatformSteam.cs b/libNOM.io/PlatformSteam.cs index 7282ce2..987c1ca 100644 --- a/libNOM.io/PlatformSteam.cs +++ b/libNOM.io/PlatformSteam.cs @@ -230,27 +230,6 @@ protected override uint[] DecryptMeta(Container container, byte[] meta) hash += 0x61C88647; } - // Do not write wrong data. - if (value[0] == META_HEADER) - { - var mode = BitConverter.GetBytes(value[18]); - container.Extra = container.Extra with - { -#if NETSTANDARD2_0 - Bytes = value.Skip(META_LENGTH_KNOWN / sizeof(uint)).GetBytes(), -#else - Bytes = value[(META_LENGTH_KNOWN / sizeof(uint))..].GetBytes(), -#endif - - SizeDecompressed = value[14], - BaseVersion = (int)(value[17]), - GameMode = BitConverter.ToInt16(mode, 0), - Season = BitConverter.ToInt16(mode, 2), - TotalPlayTime = value[19], - }; - container.SaveVersion = Calculate.CalculateVersion(container.Extra.BaseVersion, container.Extra.GameMode, container.Extra.Season); - } - return value; } @@ -274,6 +253,42 @@ private static uint RotateLeft(uint value, int bits) return (value << bits) | (value >> (32 - bits)); } + protected override void UpdateContainerWithMetaInformation(Container container, byte[] raw, uint[] converted) + { + // Do not write wrong data. + if (converted[0] == META_HEADER) + { + var mode = BitConverter.GetBytes(converted[18]); + container.Extra = container.Extra with + { +#if NETSTANDARD2_0 + Bytes = converted.Skip(META_LENGTH_KNOWN / sizeof(uint)).GetBytes(), +#else + Bytes = converted[(META_LENGTH_KNOWN / sizeof(uint))..].GetBytes(), +#endif + + SizeDecompressed = converted[14], + BaseVersion = (int)(converted[17]), + GameMode = BitConverter.ToInt16(mode, 0), + Season = BitConverter.ToInt16(mode, 2), + TotalPlayTime = converted[19], + }; + container.SaveVersion = Calculate.CalculateVersion(container.Extra.BaseVersion, container.Extra.GameMode, container.Extra.Season); + } + + if (container.Extra.Size == 0) + container.Extra.Size = (uint)(raw.Length); + } + + protected override void UpdateContainerWithDataInformation(Container container, byte[] raw, byte[] converted) + { + if (container.Extra.SizeDecompressed == 0) + container.Extra.SizeDecompressed = (uint)(converted.Length); + + if (container.Extra.SizeDisk == 0) + container.Extra.SizeDisk = (uint)(raw.Length); + } + #endregion #region Write @@ -309,9 +324,9 @@ protected override byte[] CreateMeta(Container container, byte[] data) // Editing account data is possible since Frontiers and therefore has always the new format. using var writer = new BinaryWriter(new MemoryStream(buffer)); writer.Write(META_HEADER); // 4 - writer.Write((container.IsAccount || container.IsFrontiers) ? Globals.Constants.SAVE_FORMAT_3 : Globals.Constants.SAVE_FORMAT_2); // 4 + writer.Write((container.IsAccount || container.Is360Frontiers) ? Globals.Constants.SAVE_FORMAT_3 : Globals.Constants.SAVE_FORMAT_2); // 4 - if (container.IsSave && container.IsFrontiers) // SAVE_FORMAT_3 + if (container.IsSave && container.Is360Frontiers) // SAVE_FORMAT_3 { // SPOOKY HASH and SHA256 HASH not used. writer.Seek(0x30, SeekOrigin.Current); @@ -322,7 +337,7 @@ protected override byte[] CreateMeta(Container container, byte[] data) writer.Seek(0x8, SeekOrigin.Current); writer.Write(container.Extra.BaseVersion); // 4 - writer.Write(container.IsWaypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 + writer.Write(container.Is400Waypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 writer.Write(container.Extra.Season); // 2 writer.Write(container.Extra.TotalPlayTime); // 4 } @@ -399,7 +414,7 @@ protected override void CreatePlatformExtra(Container destination, Container sou { destination.Extra = new() { - Bytes = new byte[(source.IsWaypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], + Bytes = new byte[(source.Is400Waypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], Size = source.Extra.Size, SizeDecompressed = source.Extra.SizeDecompressed, SizeDisk = source.DataFile?.Exists == true ? (uint)(source.DataFile!.Length) : 0, diff --git a/libNOM.io/PlatformSwitch.cs b/libNOM.io/PlatformSwitch.cs index dc210b7..d77bf8a 100644 --- a/libNOM.io/PlatformSwitch.cs +++ b/libNOM.io/PlatformSwitch.cs @@ -120,15 +120,18 @@ public PlatformSwitch(DirectoryInfo directory, PlatformSettings platformSettings private protected override Container CreateContainer(int metaIndex, PlatformExtra? extra) { var data = new FileInfo(Path.Combine(Location!.FullName, $"savedata{metaIndex:D2}.hg")); + var meta = new FileInfo(Path.Combine(Location.FullName, $"manifest{metaIndex:D2}.hg")); + return new Container(metaIndex) { DataFile = data, Extra = new() { /// Additional values will be set in . + Size = meta.Exists ? (uint)(meta.Length) : 0, SizeDisk = data.Exists ? (uint)(data.Length) : 0, }, - MetaFile = new FileInfo(Path.Combine(Location!.FullName, $"manifest{metaIndex:D2}.hg")), + MetaFile = meta, }; } @@ -136,28 +139,36 @@ private protected override Container CreateContainer(int metaIndex, PlatformExtr #region Load - protected override uint[] DecryptMeta(Container container, byte[] meta) + protected override void UpdateContainerWithMetaInformation(Container container, byte[] raw, uint[] converted) { - var value = base.DecryptMeta(container, meta); - container.Extra = new() { #if NETSTANDARD2_0 - Bytes = meta.Skip(META_LENGTH_KNOWN).ToArray(), + Bytes = raw.Skip(META_LENGTH_KNOWN).ToArray(), #else - Bytes = meta[META_LENGTH_KNOWN..], + Bytes = raw[META_LENGTH_KNOWN..], #endif - Size = (uint)(meta.Length), - SizeDecompressed = meta[2], - LastWriteTime = DateTimeOffset.FromUnixTimeSeconds(value[4]).ToLocalTime(), - BaseVersion = (int)(value[5]), - GameMode = BitConverter.ToInt16(meta, 6 * sizeof(uint)), - Season = BitConverter.ToInt16(meta, 6 * sizeof(uint) + 2), - TotalPlayTime = value[7], + Size = (uint)(raw.Length), + SizeDecompressed = converted[2], + LastWriteTime = DateTimeOffset.FromUnixTimeSeconds(converted[4]).ToLocalTime(), + BaseVersion = (int)(converted[5]), + GameMode = BitConverter.ToInt16(raw, 6 * sizeof(uint)), + Season = BitConverter.ToInt16(raw, 6 * sizeof(uint) + 2), + TotalPlayTime = converted[7], }; - return value; + if (container.Extra.Size == 0) + container.Extra.Size = (uint)(raw.Length); + } + + protected override void UpdateContainerWithDataInformation(Container container, byte[] raw, byte[] converted) + { + if (container.Extra.SizeDecompressed == 0) + container.Extra.SizeDecompressed = (uint)(converted.Length); + + if (container.Extra.SizeDisk == 0) + container.Extra.SizeDisk = (uint)(raw.Length); } #endregion @@ -203,7 +214,7 @@ protected override byte[] CreateMeta(Container container, byte[] data) writer.Write(container.MetaIndex); // 4 writer.Write(unixSeconds); // 4 writer.Write(container.Extra.BaseVersion); // 4 - writer.Write(container.IsWaypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 + writer.Write(container.Is400Waypoint && container.GameModeEnum < PresetGameModeEnum.Permadeath ? (short)(PresetGameModeEnum.Normal) : container.Extra.GameMode); // 2 writer.Write(container.Extra.Season); // 2 writer.Write(container.Extra.TotalPlayTime); // 4 } @@ -222,7 +233,7 @@ protected override void CreatePlatformExtra(Container destination, Container sou { destination.Extra = new PlatformExtra { - Bytes = new byte[(source.IsWaypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], + Bytes = new byte[(source.Is400Waypoint ? META_LENGTH_TOTAL_WAYPOINT : META_LENGTH_TOTAL_VANILLA) - META_LENGTH_KNOWN], BaseVersion = source.Extra.BaseVersion, TotalPlayTime = source.Extra.TotalPlayTime, }; diff --git a/libNOM.io/libNOM.io.csproj b/libNOM.io/libNOM.io.csproj index 361147e..a55e141 100644 --- a/libNOM.io/libNOM.io.csproj +++ b/libNOM.io/libNOM.io.csproj @@ -10,6 +10,7 @@ + @@ -52,9 +53,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libNOM.io/packages.lock.json b/libNOM.io/packages.lock.json index 4d59a26..021b1a8 100644 --- a/libNOM.io/packages.lock.json +++ b/libNOM.io/packages.lock.json @@ -25,9 +25,9 @@ }, "K4os.Compression.LZ4": { "type": "Direct", - "requested": "[1.3.5, )", - "resolved": "1.3.5", - "contentHash": "TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==", + "requested": "[1.3.6, )", + "resolved": "1.3.6", + "contentHash": "RxGhoJBjZCgGeZgDqOP4Krs1cR9PHInbz6d2N19Dic0Y6ZACzVKbR3uSpqfEZf4RiUbHk9aiog2eS22nQPTc2A==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -45,12 +45,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.9.1.2, )", - "resolved": "0.9.1.2", - "contentHash": "/QoTJOMTHuwGhUov06pyP5rz6LxX/VI6xEsPTGJr5fnEV7P8IIgRIJ9Z/PJ75vgghNJiJih4Y9JUpJ4d+XfoUA==", + "requested": "[0.9.1.20, )", + "resolved": "0.9.1.20", + "contentHash": "1yAEICxt6RmwT/pUVQxBucjT7g3gAK6ngKKoZIeL8Q9dPMoDSJtIlP9BPlC6Boc/v/T7cSPpSG0yX7b3Zt8GUg==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "6.1.0" + "Octokit": "7.1.0" } }, "Microsoft.SourceLink.GitHub": { @@ -166,8 +166,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "6.1.0", - "contentHash": "ZGXknnmO+PaKkw9BU/ysSjuDnvIFpzQjIdUQy62k7nllmqh3sxkkU8XHgfThav3UuPKW7sEMsVJqRWYfYU3dUw==" + "resolved": "7.1.0", + "contentHash": "v2qhKBe1Zlssa4XOmpOXNDFBrOAMw2tBmr8L8YMSrEi+PlF/L/PboZcXHSkZuhIxIeOGxEFiwkuJLkvG4aHNHQ==" }, "System.Buffers": { "type": "Transitive", @@ -224,9 +224,9 @@ }, "K4os.Compression.LZ4": { "type": "Direct", - "requested": "[1.3.5, )", - "resolved": "1.3.5", - "contentHash": "TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==", + "requested": "[1.3.6, )", + "resolved": "1.3.6", + "contentHash": "RxGhoJBjZCgGeZgDqOP4Krs1cR9PHInbz6d2N19Dic0Y6ZACzVKbR3uSpqfEZf4RiUbHk9aiog2eS22nQPTc2A==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } @@ -243,12 +243,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.9.1.2, )", - "resolved": "0.9.1.2", - "contentHash": "/QoTJOMTHuwGhUov06pyP5rz6LxX/VI6xEsPTGJr5fnEV7P8IIgRIJ9Z/PJ75vgghNJiJih4Y9JUpJ4d+XfoUA==", + "requested": "[0.9.1.20, )", + "resolved": "0.9.1.20", + "contentHash": "1yAEICxt6RmwT/pUVQxBucjT7g3gAK6ngKKoZIeL8Q9dPMoDSJtIlP9BPlC6Boc/v/T7cSPpSG0yX7b3Zt8GUg==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "6.1.0" + "Octokit": "7.1.0" } }, "Microsoft.SourceLink.GitHub": { @@ -334,8 +334,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "6.1.0", - "contentHash": "ZGXknnmO+PaKkw9BU/ysSjuDnvIFpzQjIdUQy62k7nllmqh3sxkkU8XHgfThav3UuPKW7sEMsVJqRWYfYU3dUw==" + "resolved": "7.1.0", + "contentHash": "v2qhKBe1Zlssa4XOmpOXNDFBrOAMw2tBmr8L8YMSrEi+PlF/L/PboZcXHSkZuhIxIeOGxEFiwkuJLkvG4aHNHQ==" }, "System.Buffers": { "type": "Transitive", @@ -378,9 +378,9 @@ }, "K4os.Compression.LZ4": { "type": "Direct", - "requested": "[1.3.5, )", - "resolved": "1.3.5", - "contentHash": "TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==" + "requested": "[1.3.6, )", + "resolved": "1.3.6", + "contentHash": "RxGhoJBjZCgGeZgDqOP4Krs1cR9PHInbz6d2N19Dic0Y6ZACzVKbR3uSpqfEZf4RiUbHk9aiog2eS22nQPTc2A==" }, "LazyCache": { "type": "Direct", @@ -394,12 +394,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.9.1.2, )", - "resolved": "0.9.1.2", - "contentHash": "/QoTJOMTHuwGhUov06pyP5rz6LxX/VI6xEsPTGJr5fnEV7P8IIgRIJ9Z/PJ75vgghNJiJih4Y9JUpJ4d+XfoUA==", + "requested": "[0.9.1.20, )", + "resolved": "0.9.1.20", + "contentHash": "1yAEICxt6RmwT/pUVQxBucjT7g3gAK6ngKKoZIeL8Q9dPMoDSJtIlP9BPlC6Boc/v/T7cSPpSG0yX7b3Zt8GUg==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "6.1.0" + "Octokit": "7.1.0" } }, "Microsoft.SourceLink.GitHub": { @@ -485,8 +485,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "6.1.0", - "contentHash": "ZGXknnmO+PaKkw9BU/ysSjuDnvIFpzQjIdUQy62k7nllmqh3sxkkU8XHgfThav3UuPKW7sEMsVJqRWYfYU3dUw==" + "resolved": "7.1.0", + "contentHash": "v2qhKBe1Zlssa4XOmpOXNDFBrOAMw2tBmr8L8YMSrEi+PlF/L/PboZcXHSkZuhIxIeOGxEFiwkuJLkvG4aHNHQ==" }, "System.Memory": { "type": "Transitive", @@ -514,9 +514,9 @@ }, "K4os.Compression.LZ4": { "type": "Direct", - "requested": "[1.3.5, )", - "resolved": "1.3.5", - "contentHash": "TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==" + "requested": "[1.3.6, )", + "resolved": "1.3.6", + "contentHash": "RxGhoJBjZCgGeZgDqOP4Krs1cR9PHInbz6d2N19Dic0Y6ZACzVKbR3uSpqfEZf4RiUbHk9aiog2eS22nQPTc2A==" }, "LazyCache": { "type": "Direct", @@ -530,12 +530,12 @@ }, "libNOM.map": { "type": "Direct", - "requested": "[0.9.1.2, )", - "resolved": "0.9.1.2", - "contentHash": "/QoTJOMTHuwGhUov06pyP5rz6LxX/VI6xEsPTGJr5fnEV7P8IIgRIJ9Z/PJ75vgghNJiJih4Y9JUpJ4d+XfoUA==", + "requested": "[0.9.1.20, )", + "resolved": "0.9.1.20", + "contentHash": "1yAEICxt6RmwT/pUVQxBucjT7g3gAK6ngKKoZIeL8Q9dPMoDSJtIlP9BPlC6Boc/v/T7cSPpSG0yX7b3Zt8GUg==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Octokit": "6.1.0" + "Octokit": "7.1.0" } }, "Microsoft.SourceLink.GitHub": { @@ -621,8 +621,8 @@ }, "Octokit": { "type": "Transitive", - "resolved": "6.1.0", - "contentHash": "ZGXknnmO+PaKkw9BU/ysSjuDnvIFpzQjIdUQy62k7nllmqh3sxkkU8XHgfThav3UuPKW7sEMsVJqRWYfYU3dUw==" + "resolved": "7.1.0", + "contentHash": "v2qhKBe1Zlssa4XOmpOXNDFBrOAMw2tBmr8L8YMSrEi+PlF/L/PboZcXHSkZuhIxIeOGxEFiwkuJLkvG4aHNHQ==" }, "System.Memory": { "type": "Transitive", diff --git a/libNOM.test/Common.cs b/libNOM.test/Common.cs index 009506d..17b86b8 100644 --- a/libNOM.test/Common.cs +++ b/libNOM.test/Common.cs @@ -13,7 +13,10 @@ public class CommonTestInitializeCleanup protected const uint SAVE_FORMAT_2 = 0x7D1; // 2001 protected const uint SAVE_FORMAT_3 = 0x7D2; // 2002 - protected const int FILESYSTEMWATCHER_SLEEP = 2000; + protected const int FILESYSTEMWATCHER_SLEEP = 5000; + + protected const int OFFSET_INDEX = 2; + protected static readonly int[] MUSICVOLUME_INDICES = new[] { 1, 7 }; protected const string MUSICVOLUME_JSON_PATH = "UserSettingsData.MusicVolume"; protected const int MUSICVOLUME_NEW_AMOUNT = 100; @@ -25,6 +28,12 @@ public class CommonTestInitializeCleanup #region Assert + protected static void AssertAllAreEqual(IEnumerable expected, params IEnumerable[] actual) + { + foreach (var value in actual) + Assert.IsTrue(expected.SequenceEqual(value)); + } + protected static void AssertAllAreEqual(int expected, params int[] actual) { foreach (var value in actual) @@ -49,6 +58,13 @@ protected static void AssertAllAreEqual(string expected, params string[] actual) Assert.AreEqual(expected, value); } + protected static void AssertAllNotZero(params IEnumerable[] actual) + { + foreach (var value in actual) + if (value.Any(i => i == 0)) + throw new AssertFailedException(); + } + protected static void AssertAllNotZero(params uint[] actual) { foreach (var value in actual) @@ -70,6 +86,13 @@ protected static void AssertAllZero(params IEnumerable[] actual) throw new AssertFailedException(); } + protected static void AssertAllZero(params uint[] actual) + { + foreach (var value in actual) + if (value != 0) + throw new AssertFailedException(); + } + protected static void AssertAllZero(params IEnumerable[] actual) { foreach (var value in actual) diff --git a/libNOM.test/Microsoft.cs b/libNOM.test/Microsoft.cs index 5d01738..b8a1f32 100644 --- a/libNOM.test/Microsoft.cs +++ b/libNOM.test/Microsoft.cs @@ -630,7 +630,6 @@ public void T10_Write_Default_ContainersIndex() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -683,7 +682,6 @@ public void T11_Write_Default() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -745,7 +743,6 @@ public void T12_Write_Default_Account() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -798,7 +795,6 @@ public void T13_Write_SetLastWriteTime_False() LoadingStrategy = LoadingStrategyEnum.Hollow, SetLastWriteTime = false, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -846,7 +842,6 @@ public void T14_Write_WriteAlways_True() LoadingStrategy = LoadingStrategyEnum.Hollow, WriteAlways = true, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -892,7 +887,6 @@ public void T15_Write_WriteAlways_False() LoadingStrategy = LoadingStrategyEnum.Hollow, WriteAlways = false, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act diff --git a/libNOM.test/Playstation.cs b/libNOM.test/Playstation.cs new file mode 100644 index 0000000..1430873 --- /dev/null +++ b/libNOM.test/Playstation.cs @@ -0,0 +1,1586 @@ +using CommunityToolkit.Diagnostics; +using libNOM.io; +using libNOM.io.Enums; +using libNOM.io.Globals; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System.Text; + +namespace libNOM.test; + + +[TestClass] +[DeploymentItem("..\\..\\..\\Resources\\TESTSUITE_ARCHIVE.zip")] +public class PlaystationTest : CommonTestInitializeCleanup +{ + #region Constant + + protected const uint META_HEADER = 0xCA55E77E; + protected static int META_LENGTH_TOTAL_VANILLA => (_usesSaveWizard ? 0x30 : 0x20); // 48 : 32 + protected static int META_LENGTH_TOTAL_WAYPOINT => (_usesSaveWizard ? 0x70 : 0x0); // 28 : 0 + protected static int META_OFFSET => _usesSaveWizard ? 0x40 : 0x0; // 64 : 0 + + protected static int MEMORYDAT_META_INDEX_OFFSET => _usesSaveWizard ? 8 : 3; + protected static int MEMORYDAT_META_INDEX_SIZE => _usesSaveWizard ? 7 : 2; + protected const uint MEMORYDAT_OFFSET_CONTAINER = 0xE0000U; + protected static uint MEMORYDAT_OFFSET_DATA => _usesSaveWizard ? 0x1040U : 0x20000U; + protected const uint MEMORYDAT_SIZE_ACCOUNTDATA = 0x40000U; + protected const uint MEMORYDAT_SIZE_CONTAINER = 0x300000U; + protected const uint MEMORYDAT_SIZE_TOTAL = 0x2000000U; // 32 MB + + protected const string SAVEWIZARD_HEADER = "NOMANSKY"; + protected static readonly byte[] SAVEWIZARD_HEADER_BINARY = Encoding.UTF8.GetBytes(SAVEWIZARD_HEADER); + + #endregion + + #region Field + + private static bool _usesSaveStreaming; + private static bool _usesSaveWizard; + + #endregion + + #region Meta + + /// + /// + private static uint[] DecryptMeta(Container container) + { + byte[] meta = Array.Empty(); + + using var reader = new BinaryReader(File.Open(container.MetaFile!.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + if (_usesSaveStreaming) + { + if (_usesSaveWizard) + { + meta = reader.ReadBytes(META_LENGTH_TOTAL_WAYPOINT); + } + } + else + { + reader.BaseStream.Seek(META_OFFSET + (container.MetaIndex * META_LENGTH_TOTAL_VANILLA), SeekOrigin.Begin); + meta = reader.ReadBytes(META_LENGTH_TOTAL_VANILLA); + } + + return GetUInt32(meta); + } + + private static void AssertCommonMeta(Container container, uint[] metaA, uint[] metaB) + { + Assert.AreEqual(metaA.Length, metaB.Length); + + if (_usesSaveStreaming) + { + if (metaA.Length == META_LENGTH_TOTAL_WAYPOINT / sizeof(uint)) + { + if (_usesSaveWizard) + { + AssertAllAreEqual(BitConverter.ToUInt32(SAVEWIZARD_HEADER_BINARY, 0), metaA[0], metaB[0]); + AssertAllAreEqual(BitConverter.ToUInt32(SAVEWIZARD_HEADER_BINARY, 4), metaA[1], metaB[1]); + AssertAllAreEqual(2, metaA[2], metaB[2]); + AssertAllAreEqual((uint)(META_OFFSET), metaA[3], metaB[3]); + AssertAllAreEqual(1, metaA[4], metaB[4]); + + AssertAllZero(metaA.Skip(6).Take(11), metaB.Skip(6).Take(11)); + AssertAllAreEqual(SAVE_FORMAT_3, metaA[17], metaB[17]); + AssertAllZero(metaA.Skip(18).Take(5), metaB.Skip(6).Take(5)); + + AssertAllZero(metaA[24], metaB[24]); + AssertAllAreEqual(1, metaA[25], metaB[25]); + AssertAllZero(metaA.Skip(26), metaB.Skip(26)); + } + } + else + throw new AssertFailedException(); + } + else + { + if (metaA.Length == META_LENGTH_TOTAL_VANILLA / sizeof(uint)) + { + // Nothing to do as already done in AssertMemoryDat(). + } + else + throw new AssertFailedException(); + } + } + + #endregion + + #region memory.dat + + /// + private static void AssertMemoryDat(byte[] memoryDatA, byte[] memoryDatB) + { + if (_usesSaveWizard) + { + AssertAllAreEqual(SAVEWIZARD_HEADER_BINARY, memoryDatA.Take(SAVEWIZARD_HEADER.Length), memoryDatB.Take(SAVEWIZARD_HEADER.Length)); + AssertAllAreEqual(SAVE_FORMAT_2, BitConverter.ToInt32(memoryDatA, 8), BitConverter.ToInt32(memoryDatB, 8)); + AssertAllAreEqual(0x40, BitConverter.ToInt32(memoryDatA, 12), BitConverter.ToInt32(memoryDatB, 12)); + AssertAllAreEqual(11, BitConverter.ToInt32(memoryDatA, 16), BitConverter.ToInt32(memoryDatB, 16)); + AssertAllAreEqual(memoryDatA.Skip(20).Take(0x40 - 20), memoryDatB.Skip(20).Take(0x40 - 20)); + } + else + { + AssertAllAreEqual(memoryDatA.Length, memoryDatB.Length); + AssertAllAreEqual(MEMORYDAT_SIZE_TOTAL, memoryDatA.Length, memoryDatB.Length); + } + + var metaA = new uint[12][]; // 5 slots and 1 account + var metaB = new uint[12][]; + + for (int i = 0; i < metaA.Length; i++) + { + metaA[i] = GetUInt32(memoryDatA.Skip(META_OFFSET + i * META_LENGTH_TOTAL_VANILLA).Take(META_LENGTH_TOTAL_VANILLA).ToArray()); + metaB[i] = GetUInt32(memoryDatB.Skip(META_OFFSET + i * META_LENGTH_TOTAL_VANILLA).Take(META_LENGTH_TOTAL_VANILLA).ToArray()); + + if (i == 1) + { + AssertAllZero(metaA[i], metaB[i]); + continue; + } + + AssertAllAreEqual(META_HEADER, metaA[i][0], metaB[i][0]); + AssertAllAreEqual(SAVE_FORMAT_2, metaA[i][1], metaB[i][1]); + if (i == 0) + { + AssertAllAreEqual(0x20000U, metaA[i][3], metaB[i][3]); // MEMORYDAT_OFFSET_DATA + AssertAllAreEqual(MEMORYDAT_SIZE_ACCOUNTDATA, metaA[i][4], metaB[i][4]); + } + else if (i < 4 || _usesSaveWizard) + { + AssertAllAreEqual(MEMORYDAT_OFFSET_CONTAINER + (i - OFFSET_INDEX) * MEMORYDAT_SIZE_CONTAINER, metaA[i][3], metaB[i][3]); + AssertAllAreEqual(MEMORYDAT_SIZE_CONTAINER, metaA[i][4], metaB[i][4]); + AssertAllAreEqual(i, metaA[i][5], metaB[i][5]); + } + else + { + AssertAllAreEqual(uint.MaxValue, metaA[i][5], metaB[i][5]); + continue; + } + + if (_usesSaveWizard) + { + AssertAllAreEqual(1, metaA[i][9], metaB[i][9]); + AssertAllZero(metaA[i].Skip(10), metaB[i].Skip(10)); + } + + if (_usesSaveWizard) + { + // We currently do not need the full save here. + //var saveA = memoryDatA.Skip((int)(metaA[i][MEMORYDAT_META_INDEX_OFFSET])).Take((int)(metaA[i][MEMORYDAT_META_INDEX_SIZE])).ToArray(); + //var saveB = memoryDatB.Skip((int)(metaB[i][MEMORYDAT_META_INDEX_OFFSET])).Take((int)(metaB[i][MEMORYDAT_META_INDEX_SIZE])).ToArray(); + + var headerA = memoryDatA.Skip((int)(metaA[i][MEMORYDAT_META_INDEX_OFFSET]) - SAVEWIZARD_HEADER.Length).Take(SAVEWIZARD_HEADER.Length).ToArray(); + var headerB = memoryDatB.Skip((int)(metaB[i][MEMORYDAT_META_INDEX_OFFSET]) - SAVEWIZARD_HEADER.Length).Take(SAVEWIZARD_HEADER.Length).ToArray(); + AssertAllAreEqual(SAVEWIZARD_HEADER_BINARY, headerA, headerB); + } + else + { + var saveA = memoryDatA.Skip((int)(metaA[i][MEMORYDAT_META_INDEX_OFFSET])).Take((int)(metaA[i][4])).ToArray(); + var saveB = memoryDatB.Skip((int)(metaB[i][MEMORYDAT_META_INDEX_OFFSET])).Take((int)(metaB[i][4])).ToArray(); + + AssertAllNotZero(saveA.Take(0x10), saveA.Skip((int)(metaA[i][2]) - 0x11).Take(0x10), saveB.Take(0x10), saveB.Skip((int)(metaB[i][2]) - 0x11).Take(0x10)); + AssertAllZero(saveA[(int)(metaA[i][2]) - 1], saveB[(int)(metaB[i][2]) - 1]); + } + } + } + + #endregion + + [TestMethod] + public void T01_Read_0x7D1_Homebrew_1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameModeEnum, SeasonEnum SeasonEnum, int BaseVersion, GameVersionEnum GameVersionEnum)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4119, GameVersionEnum.BeyondWithVehicleCam), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4119, GameVersionEnum.BeyondWithVehicleCam), // 1Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameModeEnum, container.GameModeEnum); + Assert.AreEqual(results[i].SeasonEnum, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].GameVersionEnum, container.GameVersionEnum); + } + } + + [TestMethod] + public void T02_Read_0x7D1_Homebrew_2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "2"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4119, GameVersionEnum.BeyondWithVehicleCam), // 1Auto + (1, true, true, PresetGameModeEnum.Normal, SeasonEnum.None, 4098, GameVersionEnum.Unknown), // 1Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T03_Read_0x7D1_SaveWizard_1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "SaveWizard", "1"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 1Manual + (2, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 2Auto + (3, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 2Manual + (4, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 3Auto + (5, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4133, GameVersionEnum.PrismsWithBytebeatAuthor), // 3Manual + (6, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 4Auto + (7, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4124, GameVersionEnum.LivingShip), // 4Manual + (8, true, false, PresetGameModeEnum.Permadeath, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 5Auto + (9, true, false, PresetGameModeEnum.Permadeath, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 5Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T04_Read_0x7D1_SaveWizard_2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "SaveWizard", "2"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4126, GameVersionEnum.Origins), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 1Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T05_Read_0x7D1_SaveWizard_3() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "SaveWizard", "3"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4126, GameVersionEnum.Origins), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4126, GameVersionEnum.Origins), // 1Manual + (2, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 2Auto + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T10_Read_0x7D2_Homebrew_1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameModeEnum, SeasonEnum SeasonEnum, int BaseVersion, GameVersionEnum GameVersionEnum)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4119, GameVersionEnum.BeyondWithVehicleCam), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Emergence), // 1Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsTrue(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameModeEnum, container.GameModeEnum); + Assert.AreEqual(results[i].SeasonEnum, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].GameVersionEnum, container.GameVersionEnum); + } + } + + [TestMethod] + public void T11_Read_0x7D2_SaveWizard_1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "1"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Creative, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 1Auto + (2, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 2Auto + (3, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 2Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T12_Read_0x7D2_SaveWizard_2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "2"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (2, true, false, PresetGameModeEnum.Unspecified, SeasonEnum.None, 4141, GameVersionEnum.WaypointWithAgileStat), // 2Auto + (3, true, false, PresetGameModeEnum.Unspecified, SeasonEnum.None, 4141, GameVersionEnum.WaypointWithAgileStat), // 2Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T13_Read_0x7D2_SaveWizard_3() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "3"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 1Manual + (2, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 2Auto + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T14_Read_0x7D2_SaveWizard_4() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "4"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (0, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 1Auto + (1, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4134, GameVersionEnum.PrismsWithBytebeatAuthor), // 1Manual + (2, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 2Auto + (3, true, false, PresetGameModeEnum.Normal, SeasonEnum.None, 4135, GameVersionEnum.Frontiers), // 2Manual + (4, true, false, PresetGameModeEnum.Seasonal, SeasonEnum.Cartographers, 4135, GameVersionEnum.Frontiers), // 3Auto + (5, true, false, PresetGameModeEnum.Seasonal, SeasonEnum.Cartographers, 4135, GameVersionEnum.Frontiers), // 3Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T15_Read_0x7D2_SaveWizard_5() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "5"); + var results = new (int CollectionIndex, bool Exists, bool IsOld, PresetGameModeEnum GameMode, SeasonEnum Season, int BaseVersion, GameVersionEnum Version)[] + { + (11, true, false, PresetGameModeEnum.Unspecified, SeasonEnum.None, 4145, GameVersionEnum.Interceptor), // 6Manual + }; + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Full, + }; + var userIdentification = ReadUserIdentification(path); + + // Act + var platform = new PlatformPlaystation(path, settings); + + // Assert + Assert.IsFalse(platform.HasAccountData); + Assert.AreEqual(results.Length, platform.GetExistingContainers().Count()); + Assert.AreEqual(userIdentification[0], platform.PlatformUserIdentification.LID); + Assert.AreEqual(userIdentification[1], platform.PlatformUserIdentification.UID); + Assert.AreEqual(userIdentification[2], platform.PlatformUserIdentification.USN); + Assert.AreEqual(userIdentification[3], platform.PlatformUserIdentification.PTK); + + for (var i = 0; i < results.Length; i++) + { + var container = platform.GetSaveContainer(results[i].CollectionIndex)!; + Assert.AreEqual(results[i].Exists, container.Exists); + Assert.AreEqual(results[i].IsOld, container.IsOld); + Assert.AreEqual(results[i].GameMode, container.GameModeEnum); + Assert.AreEqual(results[i].Season, container.SeasonEnum); + Assert.AreEqual(results[i].BaseVersion, container.BaseVersion); + Assert.AreEqual(results[i].Version, container.GameVersionEnum); + } + } + + [TestMethod] + public void T20_Write_Default_0x7D1_Homebrew() + { + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var nowUnix = now.ToUnixTimeSeconds(); + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + var memoryDatA = File.ReadAllBytes(Path.Combine(path, "memory.dat")); + var metaA = DecryptMeta(containerA); + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int Units, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + platformA.Write(containerA, now); + (int Units, long UnixSeconds) valuesSet = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + var memoryDatB = File.ReadAllBytes(Path.Combine(path, "memory.dat")); + var metaB = DecryptMeta(containerB); + + platformB.Load(containerB); + (int Units, long UnixSeconds) valuesReload = (containerB.GetJsonValue(UNITS_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + AssertMemoryDat(memoryDatA, memoryDatB); + + Assert.AreEqual(979316666, valuesOrigin.Units); + Assert.AreEqual(1601202709, valuesOrigin.UnixSeconds); // 2020-09-27 10:31:49 +00:00 + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesSet.Units); + Assert.AreEqual(nowUnix, valuesSet.UnixSeconds); + + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesReload.Units); + Assert.AreEqual(nowUnix, valuesReload.UnixSeconds); + + AssertCommonMeta(containerA, metaA, metaB); + AssertAllAreEqual(4119, (uint)(containerA.BaseVersion), (uint)(containerB.BaseVersion)); + AssertAllAreEqual(2, (uint)(containerA.MetaIndex), (uint)(containerB.MetaIndex), metaA[5], metaB[5]); + } + + [TestMethod] + public void T21_Write_Default_0x7D1_SaveWizard() + { + _usesSaveStreaming = false; + _usesSaveWizard = true; + + var now = DateTimeOffset.UtcNow; + var nowUnix = now.ToUnixTimeSeconds(); + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "SaveWizard", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + var memoryDatA = File.ReadAllBytes(Path.Combine(path, "memory.dat")); + var metaA = DecryptMeta(containerA); + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int Units, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + platformA.Write(containerA, now); + (int Units, long UnixSeconds) valuesSet = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + var memoryDatB = File.ReadAllBytes(Path.Combine(path, "memory.dat")); + var metaB = DecryptMeta(containerB); + + platformB.Load(containerB); + (int Units, long UnixSeconds) valuesReload = (containerB.GetJsonValue(UNITS_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + AssertMemoryDat(memoryDatA, memoryDatB); + + Assert.AreEqual(1283166770, valuesOrigin.Units); + Assert.AreEqual(1624402816, valuesOrigin.UnixSeconds); // 2021-06-23 23:00:16 +00:00 + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesSet.Units); + Assert.AreEqual(nowUnix, valuesSet.UnixSeconds); + + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesReload.Units); + Assert.AreEqual(nowUnix, valuesReload.UnixSeconds); + + AssertCommonMeta(containerA, metaA, metaB); + AssertAllAreEqual(4134, (uint)(containerA.BaseVersion), (uint)(containerB.BaseVersion)); + AssertAllAreEqual(2, (uint)(containerA.MetaIndex), (uint)(containerB.MetaIndex), metaA[5], metaB[5]); + } + + [TestMethod] + public void T22_Write_Default_0x7D2_Homebrew() + { + _usesSaveStreaming = true; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var nowUnix = now.ToUnixTimeSeconds(); + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + var metaA = DecryptMeta(containerA); + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int Units, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + platformA.Write(containerA, now); + (int Units, long UnixSeconds) valuesSet = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + var metaB = DecryptMeta(containerB); + + platformB.Load(containerB); + (int Units, long UnixSeconds) valuesReload = (containerB.GetJsonValue(UNITS_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(1970043489, valuesOrigin.Units); + Assert.AreEqual(1641647860, valuesOrigin.UnixSeconds); // 2022-01-08 13:17:40 +00:00 + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesSet.Units); + Assert.AreEqual(nowUnix, valuesSet.UnixSeconds); + + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesReload.Units); + Assert.AreEqual(nowUnix, valuesReload.UnixSeconds); + + AssertCommonMeta(containerA, metaA, metaB); + } + + [TestMethod] + public void T23_Write_Default_0x7D2_Homebrew_Account() + { + _usesSaveStreaming = true; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var nowUnix = now.ToUnixTimeSeconds(); + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetAccountContainer(); + var metaA = DecryptMeta(containerA); + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int MusicVolume, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(MUSICVOLUME_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(MUSICVOLUME_NEW_AMOUNT, MUSICVOLUME_JSON_PATH); + platformA.Write(containerA, now); + (int MusicVolume, long UnixSeconds) valuesSet = (containerA.GetJsonValue(MUSICVOLUME_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetAccountContainer(); + var metaB = DecryptMeta(containerB); + + platformB.Load(containerB); + (int MusicVolume, long UnixSeconds) valuesReload = (containerB.GetJsonValue(MUSICVOLUME_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(80, valuesOrigin.MusicVolume); + Assert.AreEqual(1641647860, valuesOrigin.UnixSeconds); // 2022-01-08 13:17:40 +00:00 + Assert.AreEqual(MUSICVOLUME_NEW_AMOUNT, valuesSet.MusicVolume); + Assert.AreEqual(nowUnix, valuesSet.UnixSeconds); + + Assert.AreEqual(MUSICVOLUME_NEW_AMOUNT, valuesReload.MusicVolume); + Assert.AreEqual(nowUnix, valuesReload.UnixSeconds); + + AssertCommonMeta(containerA, metaA, metaB); + } + + [TestMethod] + public void T24_Write_Default_0x7D2_SaveWizard() + { + _usesSaveStreaming = true; + _usesSaveWizard = true; + + var now = DateTimeOffset.UtcNow; + var nowUnix = now.ToUnixTimeSeconds(); + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "SaveWizard", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + var metaA = DecryptMeta(containerA); + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int Units, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + platformA.Write(containerA, now); + (int Units, long UnixSeconds) valuesSet = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + var metaB = DecryptMeta(containerB); + + platformB.Load(containerB); + (int Units, long UnixSeconds) valuesReload = (containerB.GetJsonValue(UNITS_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(0, valuesOrigin.Units); + Assert.AreEqual(1631364996, valuesOrigin.UnixSeconds); // 2021-09-11 12:56:36 +00:00 + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesSet.Units); + Assert.AreEqual(nowUnix, valuesSet.UnixSeconds); + + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesReload.Units); + Assert.AreEqual(nowUnix, valuesReload.UnixSeconds); + + AssertCommonMeta(containerA, metaA, metaB); + } + + [TestMethod] + public void T25_Write_SetLastWriteTime_False() + { + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + SetLastWriteTime = false, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + platformA.Load(containerA); + (int Units, long UnixSeconds) valuesOrigin = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + containerA.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + platformA.Write(containerA, now); + (int Units, long UnixSeconds) valuesSet = (containerA.GetJsonValue(UNITS_JSON_PATH), containerA.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + + platformB.Load(containerB); + (int Units, long UnixSeconds) valuesReload = (containerB.GetJsonValue(UNITS_JSON_PATH), containerB.LastWriteTime!.Value.ToUniversalTime().ToUnixTimeSeconds()); +#pragma warning restore IDE0042 // Deconstruct variable declaration + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(979316666, valuesOrigin.Units); + Assert.AreEqual(1601202709, valuesOrigin.UnixSeconds); // 2020-09-27 10:31:49 +00:00 + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesSet.Units); + Assert.AreEqual(1601202709, valuesSet.UnixSeconds); + + Assert.AreEqual(UNITS_NEW_AMOUNT, valuesReload.Units); + Assert.AreEqual(1601202709, valuesReload.UnixSeconds); + } + + [TestMethod] + public void T26_Write_WriteAlways_True_0x7D1() + { + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + SetLastWriteTime = false, // no interfering + WriteAlways = true, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + + platformA.Load(containerA); + containerA.DataFile!.Refresh(); + var timeOrigin = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + platformA.Write(containerA); + containerA.DataFile!.Refresh(); + var timeSet = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + + platformB.Load(containerB); + containerB.DataFile!.Refresh(); + var timeReload = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreNotEqual(timeOrigin, timeSet); + Assert.AreNotEqual(timeOrigin, timeReload); + + Assert.AreEqual(timeSet, timeReload); + } + + [TestMethod] + public void T27_Write_WriteAlways_True_0x7D2() + { + _usesSaveStreaming = true; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + SetLastWriteTime = false, // no interfering + WriteAlways = true, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + + platformA.Load(containerA); + containerA.DataFile!.Refresh(); + var lengthOrigin = containerA.DataFile!.Length; + + platformA.Write(containerA); + containerA.DataFile!.Refresh(); + var lengthSet = containerA.DataFile!.Length; + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + + platformB.Load(containerB); + containerB.DataFile!.Refresh(); + var lengthReload = containerA.DataFile!.Length; + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreNotEqual(lengthOrigin, lengthSet); + Assert.AreNotEqual(lengthOrigin, lengthReload); + + Assert.AreEqual(lengthSet, lengthReload); + } + + [TestMethod] + public void T28_Write_WriteAlways_False_0x7D1() + { + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + SetLastWriteTime = false, // no interfering + WriteAlways = false, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + + platformA.Load(containerA); + containerA.DataFile!.Refresh(); + var timeOrigin = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + platformA.Write(containerA); + containerA.DataFile!.Refresh(); + var timeSet = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + + platformB.Load(containerB); + containerB.DataFile!.Refresh(); + var timeReload = containerA.DataFile!.LastWriteTimeUtc.Ticks; + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(timeOrigin, timeSet); + Assert.AreEqual(timeOrigin, timeReload); + } + + [TestMethod] + public void T29_Write_WriteAlways_False_0x7D2() + { + _usesSaveStreaming = true; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + SetLastWriteTime = false, // no interfering + WriteAlways = false, + }; + var writeCallback = false; + + // Act + var platformA = new PlatformPlaystation(path, settings); + var containerA = platformA.GetSaveContainer(0)!; + + containerA.WriteCallback += () => + { + writeCallback = true; + }; + + platformA.Load(containerA); + containerA.DataFile!.Refresh(); + var lengthOrigin = containerA.DataFile!.Length; + + platformA.Write(containerA); + containerA.DataFile!.Refresh(); + var lengthSet = containerA.DataFile!.Length; + + var platformB = new PlatformPlaystation(path, settings); + var containerB = platformB.GetSaveContainer(0)!; + + platformB.Load(containerB); + containerB.DataFile!.Refresh(); + var lengthReload = containerA.DataFile!.Length; + + // Assert + Assert.IsTrue(writeCallback); + + Assert.AreEqual(lengthOrigin, lengthSet); + Assert.AreEqual(lengthOrigin, lengthReload); + } + + [TestMethod] + public void T30_FileSystemWatcher_0x7D1() + { + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var pathSave = Path.Combine(path, "memory.dat"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + Watcher = true, + }; + + // Act + var bytes = File.ReadAllBytes(pathSave); + var platform = new PlatformPlaystation(path, settings); + + var container = platform.GetSaveContainer(0)!; + platform.Load(container); + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers1 = platform.GetWatcherContainers(); + var count1 = watchers1.Count(); + var synced1 = container.IsSynced; + + container.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + var synced2 = container.IsSynced; + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers2 = platform.GetWatcherContainers(); + var count2 = watchers2.Count(); + var synced3 = container.IsSynced; + + var watcherContainer2 = watchers2.FirstOrDefault(); + Guard.IsNotNull(watcherContainer2); + platform.OnWatcherDecision(watcherContainer2, false); + var synced4 = container.IsSynced; + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers3 = platform.GetWatcherContainers(); + var count3 = watchers3.Count(); + var synced5 = container.IsSynced; + + var watcherContainer3 = watchers3.FirstOrDefault(); + Guard.IsNotNull(watcherContainer3); + platform.OnWatcherDecision(watcherContainer3, true); + var synced6 = container.IsSynced; + + // Assert + Assert.AreEqual(0, count1); + Assert.IsTrue(synced1); + + Assert.IsFalse(synced2); + + Assert.AreEqual(1, count2); + Assert.IsFalse(synced3); + + Assert.AreEqual(container, watcherContainer2); + Assert.IsFalse(synced4); + + Assert.AreEqual(1, count3); + Assert.IsFalse(synced5); + + Assert.AreEqual(container, watcherContainer3); + Assert.IsTrue(synced6); + } + + [TestMethod] + public void T31_FileSystemWatcher_0x7D2() + { + _usesSaveStreaming = true; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D2", "Homebrew", "1"); + var pathSave = Path.Combine(path, "savedata02.hg"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + Watcher = true, + }; + + // Act + var bytes = File.ReadAllBytes(pathSave); + var platform = new PlatformPlaystation(path, settings); + + var container = platform.GetSaveContainer(0)!; + platform.Load(container); + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers1 = platform.GetWatcherContainers(); + var count1 = watchers1.Count(); + var synced1 = container.IsSynced; + + container.SetJsonValue(UNITS_NEW_AMOUNT, UNITS_JSON_PATH); + var synced2 = container.IsSynced; + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers2 = platform.GetWatcherContainers(); + var count2 = watchers2.Count(); + var synced3 = container.IsSynced; + + var watcherContainer2 = watchers2.FirstOrDefault(); + Guard.IsNotNull(watcherContainer2); + platform.OnWatcherDecision(watcherContainer2, false); + var synced4 = container.IsSynced; + + File.WriteAllBytes(pathSave, bytes); + Thread.Sleep(FILESYSTEMWATCHER_SLEEP); + var watchers3 = platform.GetWatcherContainers(); + var count3 = watchers3.Count(); + var synced5 = container.IsSynced; + + var watcherContainer3 = watchers3.FirstOrDefault(); + Guard.IsNotNull(watcherContainer3); + platform.OnWatcherDecision(watcherContainer3, true); + var synced6 = container.IsSynced; + + // Assert + Assert.AreEqual(0, count1); + Assert.IsTrue(synced1); + + Assert.IsFalse(synced2); + + Assert.AreEqual(1, count2); + Assert.IsFalse(synced3); + + Assert.AreEqual(container, watcherContainer2); + Assert.IsFalse(synced4); + + Assert.AreEqual(1, count3); + Assert.IsFalse(synced5); + + Assert.AreEqual(container, watcherContainer3); + Assert.IsTrue(synced6); + } + + [TestMethod] + public void T32_Copy_0x7D1() + { + // Arrange + _usesSaveStreaming = false; + _usesSaveWizard = false; + + var now = DateTimeOffset.UtcNow; + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "0x7D1", "Homebrew", "1"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + var container1 = platform.GetSaveContainer(1)!; // 1Manual + var container2 = platform.GetSaveContainer(2)!; // 2Auto + var container4 = platform.GetSaveContainer(4)!; // 3Auto + var container6 = platform.GetSaveContainer(6)!; // 4Auto + + platform.Copy(container0, container2); // 1Auto -> 2Auto (overwrite) + platform.Copy(container0, container1); // 1Auto -> 1Manual (create) + platform.Copy(container6, container4); // 4Auto -> 3Auto (delete) + + // Assert + Assert.IsTrue(container2.Exists); + Assert.AreEqual(container0.GameModeEnum, container2.GameModeEnum); + Assert.AreEqual(container0.SeasonEnum, container2.SeasonEnum); + Assert.AreEqual(container0.BaseVersion, container2.BaseVersion); + Assert.AreEqual(container0.GameVersionEnum, container2.GameVersionEnum); + + Assert.IsTrue(container1.Exists); + Assert.AreEqual(container0.GameModeEnum, container1.GameModeEnum); + Assert.AreEqual(container0.SeasonEnum, container1.SeasonEnum); + Assert.AreEqual(container0.BaseVersion, container1.BaseVersion); + Assert.AreEqual(container0.GameVersionEnum, container1.GameVersionEnum); + + Assert.IsFalse(container4.Exists); + } + + [TestMethod] + public void T33_Copy_0x7D2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "4"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + var container1 = platform.GetSaveContainer(1)!; // 1Manual + var container2 = platform.GetSaveContainer(2)!; // 2Auto + var container4 = platform.GetSaveContainer(4)!; // 3Auto + var container6 = platform.GetSaveContainer(6)!; // 4Auto + + platform.Copy(container0, container2); // 1Auto -> 2Auto (overwrite) + platform.Copy(container0, container1); // 1Auto -> 1Manual (create) + platform.Copy(container6, container4); // 4Auto -> 3Auto (delete) + + // Assert + Assert.IsTrue(container2.Exists); + Assert.AreEqual(container0.GameModeEnum, container2.GameModeEnum); + Assert.AreEqual(container0.SeasonEnum, container2.SeasonEnum); + Assert.AreEqual(container0.BaseVersion, container2.BaseVersion); + Assert.AreEqual(container0.GameVersionEnum, container2.GameVersionEnum); + + Assert.IsTrue(container1.Exists); + Assert.AreEqual(container0.GameModeEnum, container1.GameModeEnum); + Assert.AreEqual(container0.SeasonEnum, container1.SeasonEnum); + Assert.AreEqual(container0.BaseVersion, container1.BaseVersion); + Assert.AreEqual(container0.GameVersionEnum, container1.GameVersionEnum); + + Assert.IsFalse(container4.Exists); + } + + [TestMethod] + public void T34_Delete_0x7D1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "4"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + + platform.Delete(container0); + + // Assert + Assert.IsFalse(container0.Exists); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container0.IncompatibilityTag); + } + + [TestMethod] + public void T35_Delete_0x7D2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "4"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + + platform.Delete(container0); + + // Assert + Assert.IsFalse(container0.Exists); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container0.IncompatibilityTag); + } + + [TestMethod] + public void T36_Move_0x7D1() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "4"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + var container1 = platform.GetSaveContainer(1)!; // 1Manual + var container2 = platform.GetSaveContainer(2)!; // 2Auto + var container4 = platform.GetSaveContainer(4)!; // 3Auto + var container5 = platform.GetSaveContainer(5)!; // 3Manual + var container9 = platform.GetSaveContainer(9)!; // 5Manual + + var gameModeEnum2 = container2.GameModeEnum; + var seasonEnum2 = container2.SeasonEnum; + var baseVersion2 = container2.BaseVersion; + var versionEnum2 = container2.GameVersionEnum; + platform.Copy(container4, container5); + platform.Move(container2, container5); // overwrite + + platform.Move(container1, container0); // delete + + var gameModeEnum4 = container4.GameModeEnum; + var seasonEnum4 = container4.SeasonEnum; + var baseVersion4 = container4.BaseVersion; + var versionEnum4 = container4.GameVersionEnum; + platform.Move(container4, container9); // move + + // Assert + Assert.IsFalse(container2.Exists); + Assert.IsTrue(container5.Exists); + Assert.AreEqual(gameModeEnum2, container5.GameModeEnum); + Assert.AreEqual(seasonEnum2, container5.SeasonEnum); + Assert.AreEqual(baseVersion2, container5.BaseVersion); + Assert.AreEqual(versionEnum2, container5.GameVersionEnum); + + Assert.IsFalse(container0.Exists); + Assert.IsFalse(container1.Exists); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container0.IncompatibilityTag); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container1.IncompatibilityTag); + + Assert.IsFalse(container4.Exists); + Assert.IsTrue(container9.Exists); + Assert.AreEqual(gameModeEnum4, container9.GameModeEnum); + Assert.AreEqual(seasonEnum4, container9.SeasonEnum); + Assert.AreEqual(baseVersion4, container9.BaseVersion); + Assert.AreEqual(versionEnum4, container9.GameVersionEnum); + } + + [TestMethod] + public void T37_Move_0x7D2() + { + // Arrange + var path = Path.Combine(nameof(Properties.Resources.TESTSUITE_ARCHIVE), "Platform", "Playstation", "4"); + var settings = new PlatformSettings + { + LoadingStrategy = LoadingStrategyEnum.Hollow, + }; + + // Act + var platform = new PlatformPlaystation(path, settings); + + var container0 = platform.GetSaveContainer(0)!; // 1Auto + var container1 = platform.GetSaveContainer(1)!; // 1Manual + var container2 = platform.GetSaveContainer(2)!; // 2Auto + var container4 = platform.GetSaveContainer(4)!; // 3Auto + var container5 = platform.GetSaveContainer(5)!; // 3Manual + var container9 = platform.GetSaveContainer(9)!; // 5Manual + + var gameModeEnum2 = container2.GameModeEnum; + var seasonEnum2 = container2.SeasonEnum; + var baseVersion2 = container2.BaseVersion; + var versionEnum2 = container2.GameVersionEnum; + platform.Copy(container4, container5); + platform.Move(container2, container5); // overwrite + + platform.Move(container1, container0); // delete + + var gameModeEnum4 = container4.GameModeEnum; + var seasonEnum4 = container4.SeasonEnum; + var baseVersion4 = container4.BaseVersion; + var versionEnum4 = container4.GameVersionEnum; + platform.Move(container4, container9); // move + + // Assert + Assert.IsFalse(container2.Exists); + Assert.IsTrue(container5.Exists); + Assert.AreEqual(gameModeEnum2, container5.GameModeEnum); + Assert.AreEqual(seasonEnum2, container5.SeasonEnum); + Assert.AreEqual(baseVersion2, container5.BaseVersion); + Assert.AreEqual(versionEnum2, container5.GameVersionEnum); + + Assert.IsFalse(container0.Exists); + Assert.IsFalse(container1.Exists); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container0.IncompatibilityTag); + Assert.AreEqual(libNOM.io.Globals.Constants.INCOMPATIBILITY_006, container1.IncompatibilityTag); + + Assert.IsFalse(container4.Exists); + Assert.IsTrue(container9.Exists); + Assert.AreEqual(gameModeEnum4, container9.GameModeEnum); + Assert.AreEqual(seasonEnum4, container9.SeasonEnum); + Assert.AreEqual(baseVersion4, container9.BaseVersion); + Assert.AreEqual(versionEnum4, container9.GameVersionEnum); + } + + [TestMethod] + public void TransferToGog() + { + // Arrange + // Act + + // ... Read User/Read User/Transfer/Compare + + // Assert + } + + [TestMethod] + public void TransferToMicrosoft() + { + // Arrange + // Act + // Assert + } + + [TestMethod] + public void TransferToPlaystation() + { + // Arrange + // Act + // Assert + } + + [TestMethod] + public void TransferToSteam() + { + // Arrange + // Act + // Assert + } + + [TestMethod] + public void TransferToSwitch() + { + // Arrange + // Act + // Assert + } +} diff --git a/libNOM.test/Resources/TESTSUITE_ARCHIVE.zip b/libNOM.test/Resources/TESTSUITE_ARCHIVE.zip index b7c03ef..480987b 100644 Binary files a/libNOM.test/Resources/TESTSUITE_ARCHIVE.zip and b/libNOM.test/Resources/TESTSUITE_ARCHIVE.zip differ diff --git a/libNOM.test/Steam.cs b/libNOM.test/Steam.cs index 49ed6d9..e4758b0 100644 --- a/libNOM.test/Steam.cs +++ b/libNOM.test/Steam.cs @@ -91,7 +91,7 @@ private static void AssertCommonMeta(Container container, uint[] metaA, uint[] m if (metaA.Length == META_SIZE) { - if (container.IsAccount || container.IsSave && !container.IsFrontiers) + if (container.IsAccount || container.IsSave && !container.Is360Frontiers) { // Editing account data is possible since Frontiers and therefore has always the new format but otherwise uses the old format. AssertAllAreEqual(container.IsAccount ? SAVE_FORMAT_3 : SAVE_FORMAT_2, metaA[1], metaB[1]); @@ -100,7 +100,7 @@ private static void AssertCommonMeta(Container container, uint[] metaA, uint[] m AssertAllNotZero(metaA.Skip(6).Take(8), metaB.Skip(6).Take(8)); AssertAllZero(metaA.Skip(14), metaB.Skip(14)); } - else if (container.IsFrontiers) + else if (container.Is360Frontiers) { AssertAllAreEqual(SAVE_FORMAT_3, metaA[1], metaB[1]); @@ -326,7 +326,6 @@ public void T10_Write_Default_0x7D1() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -378,7 +377,6 @@ public void T11_Write_Default_0x7D2_Frontiers() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -436,7 +434,6 @@ public void T12_Write_Default_0x7D2_Waypoint() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -495,7 +492,6 @@ public void T13_Write_Default_0x7D2_Frontiers_Account() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -547,7 +543,6 @@ public void T14_Write_Default_0x7D2_Waypoint_Account() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -600,7 +595,6 @@ public void T15_Write_SetLastWriteTime_False() LoadingStrategy = LoadingStrategyEnum.Hollow, SetLastWriteTime = false, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -648,7 +642,6 @@ public void T16_Write_WriteAlways_True() LoadingStrategy = LoadingStrategyEnum.Hollow, WriteAlways = true, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -694,7 +687,6 @@ public void T17_Write_WriteAlways_False() LoadingStrategy = LoadingStrategyEnum.Hollow, WriteAlways = false, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act diff --git a/libNOM.test/Switch.cs b/libNOM.test/Switch.cs index 7aef2e4..bb0e4ef 100644 --- a/libNOM.test/Switch.cs +++ b/libNOM.test/Switch.cs @@ -210,7 +210,6 @@ public void T10_Write_Default_0x7D2_Frontiers() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act @@ -270,7 +269,6 @@ public void T12_Write_Default_0x7D2_Frontiers_Account() { LoadingStrategy = LoadingStrategyEnum.Hollow, }; - var userIdentification = ReadUserIdentification(path); var writeCallback = false; // Act diff --git a/libNOM.test/libNOM.test.csproj b/libNOM.test/libNOM.test.csproj index f9951b2..8c66b54 100644 --- a/libNOM.test/libNOM.test.csproj +++ b/libNOM.test/libNOM.test.csproj @@ -19,7 +19,7 @@ - +