Skip to content

Commit

Permalink
Deduplicate Gen 6-8 PlayTime Logic (#4208)
Browse files Browse the repository at this point in the history
Slightly overengineered but a fun experiment to de-duplicate some logic.
  • Loading branch information
foohyfooh authored Mar 10, 2024
1 parent 25c7b3c commit c651c6f
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 131 deletions.
16 changes: 9 additions & 7 deletions PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ namespace PKHeX.Core;
/// <summary>
/// Calculates differences in the Event Blocks between two <see cref="SaveFile"/>.
/// </summary>
public sealed class EventBlockDiff<T, T2> : IEventWorkDiff where T : class, IEventFlagArray, IEventWorkArray<T2> where T2 : unmanaged, IEquatable<T2>
public sealed class EventBlockDiff<TSave, TWorkValue> : IEventWorkDiff
where TSave : class, IEventFlagArray, IEventWorkArray<TWorkValue>
where TWorkValue : unmanaged, IEquatable<TWorkValue>
{
public List<int> SetFlags { get; } = [];
public List<int> ClearedFlags { get; } = [];
Expand All @@ -19,7 +21,7 @@ public sealed class EventBlockDiff<T, T2> : 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)
{
Expand All @@ -37,16 +39,16 @@ public EventBlockDiff(string f1, string f2)
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;
Expand All @@ -57,7 +59,7 @@ private static EventWorkDiffCompatibility SanityCheckSaveInfo(T s1, T s2)
return Valid;
}

private void Diff(T s1, T s2)
private void Diff(TSave s1, TSave s2)
{
Message = SanityCheckSaveInfo(s1, s2);
if (Message != Valid)
Expand Down
2 changes: 1 addition & 1 deletion PKHeX.Core/Saves/Access/ISaveBlock8LA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public interface ISaveBlock8LA
MyItem8a Items { get; }
Epoch1970Value AdventureStart { get; }
Epoch1900DateTimeValue LastSaved { get; }
PlayTime8a Played { get; }
PlayTime8b Played { get; }
}
2 changes: 1 addition & 1 deletion PKHeX.Core/Saves/Access/SaveBlockAccessor8LA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 3 additions & 3 deletions PKHeX.Core/Saves/SAV8BS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
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<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
Expand Down
8 changes: 4 additions & 4 deletions PKHeX.Core/Saves/SAV8LA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,17 @@ protected override SAV8LA CloneInternal()
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

public override uint SecondsToStart { get => (uint)AdventureStart.Seconds; set => AdventureStart.Seconds = value; }
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<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
Expand Down
61 changes: 38 additions & 23 deletions PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

namespace PKHeX.Core;

public sealed class PlayTime6 : SaveBlock<SaveFile>
/// <summary>
/// Simple 4-byte block storing time played in a save file.
/// </summary>
public abstract class PlayTime<TSave>(TSave sav, Memory<byte> raw) : SaveBlock<TSave>(sav, raw)
where TSave : SaveFile
{
public PlayTime6(SAV6 sav, Memory<byte> raw) : base(sav, raw) { }
public PlayTime6(SAV7 sav, Memory<byte> raw) : base(sav, raw) { }

public int PlayedHours
{
get => ReadUInt16LittleEndian(Data);
Expand All @@ -26,39 +27,53 @@ public int PlayedSeconds
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 :
}

/// <summary>
/// Object storing the playtime of a save file as well as the last saved date.
/// </summary>
/// <typeparam name="TSave">Type of Save File</typeparam>
/// <typeparam name="TEpoch">Type of Epoch for the <see cref="LastSaved"/> timestamp.</typeparam>
public abstract class PlayTimeLastSaved<TSave, TEpoch>(TSave sav, Memory<byte> raw) : PlayTime<TSave>(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;
}
}
}
}

/// <summary>
/// PlayTime object with a zero-epoch Last Saved timestamp.
/// </summary>
public sealed class PlayTime6 : PlayTimeLastSaved<SaveFile, Epoch0000DateTime>
{
public PlayTime6(SAV6 sav, Memory<byte> raw) : base(sav, raw) { }
public PlayTime6(SAV7 sav, Memory<byte> raw) : base(sav, raw) { }

protected override Epoch0000DateTime LastSaved => new(Raw.Slice(0x4, 4));
}
51 changes: 5 additions & 46 deletions PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,14 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;

namespace PKHeX.Core;

public sealed class PlayTime7b : SaveBlock<SaveFile>
/// <summary>
/// PlayTime object with a 1900-epoch Last Saved timestamp.
/// </summary>
public sealed class PlayTime7b : PlayTimeLastSaved<SaveFile, Epoch1900DateTimeValue>
{
public PlayTime7b(SAV7b sav, Memory<byte> 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));
}
16 changes: 4 additions & 12 deletions PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
using System;
using System.ComponentModel;
using static System.Buffers.Binary.BinaryPrimitives;

namespace PKHeX.Core;

/// <summary>
/// Playtime storage
/// PlayTime object without a Last Saved timestamp.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class PlayTime8b(SAV8BS sav, Memory<byte> raw) : SaveBlock<SAV8BS>(sav, raw)
public sealed class PlayTime8b : PlayTime<SaveFile>
{
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<byte> raw) : base(sav, raw) { }
public PlayTime8b(SAV8LA sav, SCBlock block) : base(sav, block.Data) { }
}
22 changes: 0 additions & 22 deletions PKHeX.Core/Saves/Substructures/Gen8/LA/PlayTime8a.cs

This file was deleted.

1 change: 0 additions & 1 deletion PKHeX.Core/Saves/Substructures/Gen9/PlayTime9.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;

namespace PKHeX.Core;
Expand Down
40 changes: 40 additions & 0 deletions PKHeX.Core/Saves/Substructures/Time/Epoch0000DateTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.ComponentModel;

namespace PKHeX.Core;

[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class Epoch0000DateTime(Memory<byte> 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 :

/// <summary>
/// time_t
/// </summary>
public override ulong TotalSeconds
{
get => (ulong)(Timestamp - Epoch).TotalSeconds;
set => Timestamp = Epoch.AddSeconds(value);
}
}
17 changes: 6 additions & 11 deletions PKHeX.Core/Saves/Substructures/Time/Epoch1900DateTimeValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,17 @@ namespace PKHeX.Core;
/// Stores the <see cref="Timestamp"/> to indicate the seconds since 1900 that an event occurred.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class Epoch1900DateTimeValue(Memory<byte> Data)
public sealed class Epoch1900DateTimeValue(Memory<byte> 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<byte> 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;
Expand All @@ -32,7 +27,7 @@ public int Second {
}
}

public DateTime Timestamp
public override DateTime Timestamp
{
get => new(Year, Month, Day, Hour, Minute, Second);
set
Expand All @@ -46,12 +41,12 @@ public DateTime Timestamp
}
}

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 :

/// <summary>
/// time_t (seconds since 1900 Epoch)
/// </summary>
public ulong TotalSeconds
public override ulong TotalSeconds
{
get => (ulong)(Timestamp - Epoch).TotalSeconds;
set => Timestamp = Epoch.AddSeconds(value);
Expand Down
Loading

0 comments on commit c651c6f

Please sign in to comment.