diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs index 0fca50707..8dd751ba8 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs @@ -9,7 +9,9 @@ namespace PKHeX.Core; /// /// Calculates differences in the Event Blocks between two . /// -public sealed class EventBlockDiff : IEventWorkDiff where T : class, IEventFlagArray, IEventWorkArray where T2 : unmanaged, IEquatable +public sealed class EventBlockDiff : IEventWorkDiff + where TSave : class, IEventFlagArray, IEventWorkArray + where TWorkValue : unmanaged, IEquatable { public List SetFlags { get; } = []; public List ClearedFlags { get; } = []; @@ -19,7 +21,7 @@ public sealed class EventBlockDiff : IEventWorkDiff where T : class, IEve private const int MAX_SAVEFILE_SIZE = 0x10_0000; // 1 MB - public EventBlockDiff(T s1, T s2) => Diff(s1, s2); + public EventBlockDiff(TSave s1, TSave s2) => Diff(s1, s2); public EventBlockDiff(string f1, string f2) { @@ -37,16 +39,16 @@ public sealed class EventBlockDiff : IEventWorkDiff where T : class, IEve Diff(t1, t2); } - private static T? GetBlock(SaveFile s1) + private static TSave? GetBlock(SaveFile s1) { - if (s1 is T t1) + if (s1 is TSave t1) return t1; if (s1 is IEventFlagProvider37 p1) - return p1.EventWork as T; + return p1.EventWork as TSave; return null; } - private static EventWorkDiffCompatibility SanityCheckSaveInfo(T s1, T s2) + private static EventWorkDiffCompatibility SanityCheckSaveInfo(TSave s1, TSave s2) { if (s1.GetType() != s2.GetType()) return DifferentGameGroup; @@ -57,7 +59,7 @@ public sealed class EventBlockDiff : IEventWorkDiff where T : class, IEve return Valid; } - private void Diff(T s1, T s2) + private void Diff(TSave s1, TSave s2) { Message = SanityCheckSaveInfo(s1, s2); if (Message != Valid) diff --git a/PKHeX.Core/Saves/Access/ISaveBlock8LA.cs b/PKHeX.Core/Saves/Access/ISaveBlock8LA.cs index 432d91e9f..524999e30 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock8LA.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock8LA.cs @@ -13,5 +13,5 @@ public interface ISaveBlock8LA MyItem8a Items { get; } Epoch1970Value AdventureStart { get; } Epoch1900DateTimeValue LastSaved { get; } - PlayTime8a Played { get; } + PlayTime8b Played { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor8LA.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor8LA.cs index f3ff34bc2..c2a5870ae 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor8LA.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor8LA.cs @@ -17,7 +17,7 @@ public sealed class SaveBlockAccessor8LA(SAV8LA sav) : SCBlockAccessor, ISaveBlo public Coordinates8a Coordinates { get; } = new(sav, Block(sav, KCoordinates)); public Epoch1900DateTimeValue LastSaved { get; } = new(Block(sav, KLastSaved)); public PlayerFashion8a FashionPlayer { get; } = new(sav, Block(sav, KFashionPlayer)); - public PlayTime8a Played { get; } = new(sav, Block(sav, KPlayTime)); + public PlayTime8b Played { get; } = new(sav, Block(sav, KPlayTime)); public int DetectRevision() => HasBlock(0x8184EFB4) ? 1 : 0; diff --git a/PKHeX.Core/Saves/SAV8BS.cs b/PKHeX.Core/Saves/SAV8BS.cs index b45667e07..f83db7e4c 100644 --- a/PKHeX.Core/Saves/SAV8BS.cs +++ b/PKHeX.Core/Saves/SAV8BS.cs @@ -261,9 +261,9 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; } public override uint Money { get => MyStatus.Money; set => MyStatus.Money = value; } - public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = (ushort)value; } - public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = (byte)value; } - public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = (byte)value; } + public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } + public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } + public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } // Inventory public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } diff --git a/PKHeX.Core/Saves/SAV8LA.cs b/PKHeX.Core/Saves/SAV8LA.cs index a18368f4c..afae78206 100644 --- a/PKHeX.Core/Saves/SAV8LA.cs +++ b/PKHeX.Core/Saves/SAV8LA.cs @@ -115,7 +115,7 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe public Epoch1970Value AdventureStart => Blocks.AdventureStart; public Coordinates8a Coordinates => Blocks.Coordinates; public Epoch1900DateTimeValue LastSaved => Blocks.LastSaved; - public PlayTime8a Played => Blocks.Played; + public PlayTime8b Played => Blocks.Played; public AreaSpawnerSet8a AreaSpawners => new(Blocks.GetBlock(SaveBlockAccessor8LA.KSpawners)); #endregion @@ -123,9 +123,9 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe public override uint Money { get => (uint)Blocks.GetBlockValue(SaveBlockAccessor8LA.KMoney); set => Blocks.SetBlockValue(SaveBlockAccessor8LA.KMoney, value); } public override int MaxMoney => 9_999_999; - public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = (ushort)value; } - public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = (byte)value; } - public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = (byte)value; } + public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } + public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } + public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } protected override Span BoxBuffer => BoxInfo.Data; protected override Span PartyBuffer => PartyInfo.Data; diff --git a/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs b/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs index 291b3bcb2..db85e8f25 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs @@ -3,11 +3,12 @@ using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -public sealed class PlayTime6 : SaveBlock +/// +/// Simple 4-byte block storing time played in a save file. +/// +public abstract class PlayTime(TSave sav, Memory raw) : SaveBlock(sav, raw) + where TSave : SaveFile { - public PlayTime6(SAV6 sav, Memory raw) : base(sav, raw) { } - public PlayTime6(SAV7 sav, Memory raw) : base(sav, raw) { } - public int PlayedHours { get => ReadUInt16LittleEndian(Data); @@ -26,39 +27,53 @@ public sealed class PlayTime6 : SaveBlock set => Data[3] = (byte)value; } - private uint LastSaved { get => ReadUInt32LittleEndian(Data[0x4..]); set => WriteUInt32LittleEndian(Data[0x4..], value); } - private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; } - private int LastSavedMonth { get => (int)((LastSaved >> 12) & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | (((uint)value & 0xF) << 12); } - private int LastSavedDay { get => (int)((LastSaved >> 16) & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } - private int LastSavedHour { get => (int)((LastSaved >> 21) & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } - private int LastSavedMinute { get => (int)((LastSaved >> 26) & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } - public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : + public string PlayedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : +} + +/// +/// Object storing the playtime of a save file as well as the last saved date. +/// +/// Type of Save File +/// Type of Epoch for the timestamp. +public abstract class PlayTimeLastSaved(TSave sav, Memory raw) : PlayTime(sav, raw) + where TSave : SaveFile + where TEpoch : EpochDateTime +{ + protected abstract TEpoch LastSaved { get; } + public string LastSavedTime => $"{LastSaved.Year:0000}-{LastSaved.Month:00}-{LastSaved.Day:00} {LastSaved.Hour:00}ː{LastSaved.Minute:00}"; // not : public DateTime? LastSavedDate { - get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) + get => !DateUtil.IsDateValid(LastSaved.Year, LastSaved.Month, LastSaved.Day) ? null - : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); + : LastSaved.Timestamp; set { // Only update the properties if a value is provided. if (value is { } dt) { - LastSavedYear = dt.Year; - LastSavedMonth = dt.Month; - LastSavedDay = dt.Day; - LastSavedHour = dt.Hour; - LastSavedMinute = dt.Minute; + LastSaved.Timestamp = dt; } else // Clear the date. { // If code tries to access MetDate again, null will be returned. - LastSavedYear = 0; - LastSavedMonth = 0; - LastSavedDay = 0; - LastSavedHour = 0; - LastSavedMinute = 0; + LastSaved.Year = 0; + LastSaved.Month = 0; + LastSaved.Day = 0; + LastSaved.Hour = 0; + LastSaved.Minute = 0; } } } } + +/// +/// PlayTime object with a zero-epoch Last Saved timestamp. +/// +public sealed class PlayTime6 : PlayTimeLastSaved +{ + public PlayTime6(SAV6 sav, Memory raw) : base(sav, raw) { } + public PlayTime6(SAV7 sav, Memory raw) : base(sav, raw) { } + + protected override Epoch0000DateTime LastSaved => new(Raw.Slice(0x4, 4)); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs index f5a4ed7a3..72802dc3c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs @@ -1,55 +1,14 @@ using System; -using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -public sealed class PlayTime7b : SaveBlock +/// +/// PlayTime object with a 1900-epoch Last Saved timestamp. +/// +public sealed class PlayTime7b : PlayTimeLastSaved { public PlayTime7b(SAV7b sav, Memory raw) : base(sav, raw) { } public PlayTime7b(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - public int PlayedHours - { - get => ReadUInt16LittleEndian(Data); - set => WriteUInt16LittleEndian(Data, (ushort)value); - } - - public int PlayedMinutes - { - get => Data[2]; - set => Data[2] = (byte)value; - } - - public int PlayedSeconds - { - get => Data[3]; - set => Data[3] = (byte)value; - } - - private Epoch1900DateTimeValue LastSaved => new(Raw.Slice(0x4, 4)); - public string LastSavedTime => LastSaved.DisplayValue; - - public DateTime? LastSavedDate - { - get => !DateUtil.IsDateValid(LastSaved.Year, LastSaved.Month, LastSaved.Day) - ? null - : LastSaved.Timestamp; - set - { - // Only update the properties if a value is provided. - if (value is { } dt) - { - LastSaved.Timestamp = dt; - } - else // Clear the date. - { - // If code tries to access MetDate again, null will be returned. - LastSaved.Year = 0; - LastSaved.Month = 0; - LastSaved.Day = 0; - LastSaved.Hour = 0; - LastSaved.Minute = 0; - } - } - } + protected override Epoch1900DateTimeValue LastSaved => new(Raw.Slice(0x4, 4)); } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs index 154cca3a7..a91cd2cb1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs @@ -1,22 +1,14 @@ using System; using System.ComponentModel; -using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; /// -/// Playtime storage +/// PlayTime object without a Last Saved timestamp. /// [TypeConverter(typeof(ExpandableObjectConverter))] -public sealed class PlayTime8b(SAV8BS sav, Memory raw) : SaveBlock(sav, raw) +public sealed class PlayTime8b : PlayTime { - public ushort PlayedHours - { - get => ReadUInt16LittleEndian(Data); - set => WriteUInt16LittleEndian(Data, value); - } - - public byte PlayedMinutes { get => Data[2]; set => Data[2] = value; } - public byte PlayedSeconds { get => Data[3]; set => Data[3] = value; } - public string LastSavedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : + public PlayTime8b(SAV8BS sav, Memory raw) : base(sav, raw) { } + public PlayTime8b(SAV8LA sav, SCBlock block) : base(sav, block.Data) { } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/LA/PlayTime8a.cs b/PKHeX.Core/Saves/Substructures/Gen8/LA/PlayTime8a.cs deleted file mode 100644 index f33460abf..000000000 --- a/PKHeX.Core/Saves/Substructures/Gen8/LA/PlayTime8a.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.ComponentModel; -using static System.Buffers.Binary.BinaryPrimitives; - -namespace PKHeX.Core; - -/// -/// Stores the amount of time the save file has been played. -/// -[TypeConverter(typeof(ExpandableObjectConverter))] -public sealed class PlayTime8a(SAV8LA sav, SCBlock block) : SaveBlock(sav, block.Data) -{ - public ushort PlayedHours - { - get => ReadUInt16LittleEndian(Data); - set => WriteUInt16LittleEndian(Data, value); - } - - public byte PlayedMinutes { get => Data[2]; set => Data[2] = value; } - public byte PlayedSeconds { get => Data[3]; set => Data[3] = value; } - public string PlayedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : -} diff --git a/PKHeX.Core/Saves/Substructures/Gen9/PlayTime9.cs b/PKHeX.Core/Saves/Substructures/Gen9/PlayTime9.cs index fe9603580..fc8509700 100644 --- a/PKHeX.Core/Saves/Substructures/Gen9/PlayTime9.cs +++ b/PKHeX.Core/Saves/Substructures/Gen9/PlayTime9.cs @@ -1,4 +1,3 @@ -using System; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; diff --git a/PKHeX.Core/Saves/Substructures/Time/Epoch0000DateTime.cs b/PKHeX.Core/Saves/Substructures/Time/Epoch0000DateTime.cs new file mode 100644 index 000000000..4c4e64fd6 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Time/Epoch0000DateTime.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; + +namespace PKHeX.Core; + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class Epoch0000DateTime(Memory Data): EpochDateTime(Data) +{ + // Data should be 4 or 8 bytes where we only care about the first 4 bytes i.e. 32 bits + // First 12 bits are year from 0000, next 4 bits are month, next 5 are days, next 5 are hours, next 6 bits are minutes + + private static DateTime Epoch => new(0, 1, 1); + + public override int Year { get => (int)(RawDate & 0xFFF); set => RawDate = (RawDate & 0xFFFFF000) | (uint)(value); } + public override int Month { get => (int)((RawDate >> 12) & 0xF); set => RawDate = (RawDate & 0xFFFF0FFF) | (((uint)value & 0xF) << 12); } + + public override DateTime Timestamp + { + get => new(Year, Month, Day, Hour, Minute, 0); + set + { + Year = value.Year; + Month = value.Month; + Day = value.Day; + Hour = value.Hour; + Minute = value.Minute; + } + } + + public override string DisplayValue => $"{Timestamp.Year:0000}-{Timestamp.Month:00}-{Timestamp.Day:00} {Timestamp.Hour:00}ː{Timestamp.Minute:00}ː{Timestamp.Second:00}"; // not : + + /// + /// time_t + /// + public override ulong TotalSeconds + { + get => (ulong)(Timestamp - Epoch).TotalSeconds; + set => Timestamp = Epoch.AddSeconds(value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Time/Epoch1900DateTimeValue.cs b/PKHeX.Core/Saves/Substructures/Time/Epoch1900DateTimeValue.cs index 565862627..993ff0f1f 100644 --- a/PKHeX.Core/Saves/Substructures/Time/Epoch1900DateTimeValue.cs +++ b/PKHeX.Core/Saves/Substructures/Time/Epoch1900DateTimeValue.cs @@ -8,22 +8,17 @@ namespace PKHeX.Core; /// Stores the to indicate the seconds since 1900 that an event occurred. /// [TypeConverter(typeof(ExpandableObjectConverter))] -public sealed class Epoch1900DateTimeValue(Memory Data) +public sealed class Epoch1900DateTimeValue(Memory Data) : EpochDateTime(Data) { // Data should be 4 or 8 bytes where we only care about the first 4 or 4.5 bytes i.e. 32 or 36 bits // First 12 bits are year from 1900, next 4 bits are 0 indexed month, next 5 are days, next 5 are hours, next 6 bits are minutes, (optional) last 4 bits are seconds - private Span Span => Data.Span; public Epoch1900DateTimeValue(SCBlock block) : this(block.Data) { } private static DateTime Epoch => new(1900, 1, 1); - private uint RawDate { get => ReadUInt32LittleEndian(Span); set => WriteUInt32LittleEndian(Span, value); } - public int Year { get => (int)(RawDate & 0xFFF) + Epoch.Year; set => RawDate = (RawDate & 0xFFFFF000) | (uint)(value - Epoch.Year); } - public int Month { get => (int)((RawDate >> 12) & 0xF) + 1; set => RawDate = (RawDate & 0xFFFF0FFF) | (((uint)(value - 1) & 0xF) << 12); } - public int Day { get => (int)((RawDate >> 16) & 0x1F); set => RawDate = (RawDate & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } - public int Hour { get => (int)((RawDate >> 21) & 0x1F); set => RawDate = (RawDate & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } - public int Minute { get => (int)((RawDate >> 26) & 0x3F); set => RawDate = (RawDate & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } + public override int Year { get => (int)(RawDate & 0xFFF) + Epoch.Year; set => RawDate = (RawDate & 0xFFFFF000) | (uint)(value - Epoch.Year); } + public override int Month { get => (int)((RawDate >> 12) & 0xF) + 1; set => RawDate = (RawDate & 0xFFFF0FFF) | (((uint)(value - 1) & 0xF) << 12); } public bool HasSeconds => Span.Length > 4; public int Second { get => HasSeconds ? Span[4] : 0; @@ -32,7 +27,7 @@ public sealed class Epoch1900DateTimeValue(Memory Data) } } - public DateTime Timestamp + public override DateTime Timestamp { get => new(Year, Month, Day, Hour, Minute, Second); set @@ -46,12 +41,12 @@ public sealed class Epoch1900DateTimeValue(Memory Data) } } - public string DisplayValue => $"{Timestamp.Year:0000}-{Timestamp.Month:00}-{Timestamp.Day:00} {Timestamp.Hour:00}ː{Timestamp.Minute:00}" + (HasSeconds ? $"ː{Timestamp.Second:00}" : ""); // not : + public override string DisplayValue => $"{Timestamp.Year:0000}-{Timestamp.Month:00}-{Timestamp.Day:00} {Timestamp.Hour:00}ː{Timestamp.Minute:00}" + (HasSeconds ? $"ː{Timestamp.Second:00}" : ""); // not : /// /// time_t (seconds since 1900 Epoch) /// - public ulong TotalSeconds + public override ulong TotalSeconds { get => (ulong)(Timestamp - Epoch).TotalSeconds; set => Timestamp = Epoch.AddSeconds(value); diff --git a/PKHeX.Core/Saves/Substructures/Time/EpochDateTime.cs b/PKHeX.Core/Saves/Substructures/Time/EpochDateTime.cs new file mode 100644 index 000000000..4037bff26 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Time/EpochDateTime.cs @@ -0,0 +1,18 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public abstract class EpochDateTime(Memory Data) +{ + protected Span Span => Data.Span; + protected uint RawDate { get => ReadUInt32LittleEndian(Span); set => WriteUInt32LittleEndian(Span, value); } + public abstract int Year { get; set;} + public abstract int Month { get; set;} + public int Day { get => (int)((RawDate >> 16) & 0x1F); set => RawDate = (RawDate & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } + public int Hour { get => (int)((RawDate >> 21) & 0x1F); set => RawDate = (RawDate & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } + public int Minute { get => (int)((RawDate >> 26) & 0x3F); set => RawDate = (RawDate & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } + public abstract DateTime Timestamp { get; set; } + public abstract string DisplayValue { get; } + public abstract ulong TotalSeconds { get; set; } +}