Extract metadata/state tracking from SaveFile obj

This commit is contained in:
Kurt 2020-12-05 05:36:23 -08:00
parent 44473d1593
commit e72c80613e
56 changed files with 332 additions and 250 deletions

View File

@ -50,9 +50,8 @@ public sealed class FakePKMEditor : IPKMView
public sealed class FakeSaveFile : SaveFile
{
public static readonly FakeSaveFile Default = new FakeSaveFile();
protected override string BAKText => "Fake Save File";
public override SaveFile Clone() => this;
public override string Filter => string.Empty;
protected internal override string ShortSummary => "Fake Save File";
protected override SaveFile CloneInternal() => this;
public override string Extension => string.Empty;
public override bool ChecksumsValid => true;
public override string ChecksumInfo => string.Empty;

View File

@ -0,0 +1,9 @@
namespace PKHeX.Core
{
public interface INamedFolderPath
{
string Path { get; }
string DisplayText { get; }
bool Custom { get; }
}
}

View File

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.IO;
namespace PKHeX.Core
{
/// <summary>
/// Bindable Summary of a <see cref="SaveFile"/> for use in a sortable table.
/// </summary>
public sealed class SavePreview
{
public readonly SaveFile Save;
public readonly string FilePath;
public SavePreview(SaveFile sav, string parent, string path)
{
Save = sav;
Folder = parent;
FilePath = path;
}
public SavePreview(SaveFile sav, List<INamedFolderPath> paths)
{
var meta = sav.Metadata;
var dir = meta.FileFolder;
const string notFound = "???";
var parent = dir == null ? notFound : paths.Find(z => dir.StartsWith(z.Path))?.DisplayText ?? notFound;
Save = sav;
Folder = parent;
FilePath = meta.FilePath ?? notFound;
}
public string OT => Save.OT;
public int G => Save.Generation;
public GameVersion Game => Save.Version;
public string Played => Save.PlayTimeString.PadLeft(9, '0');
public string FileTime => File.GetLastWriteTimeUtc(FilePath).ToString("yyyy.MM.dd:hh:mm:ss");
public string TID => Save.Generation >= 7 ? Save.TrainerID7.ToString("000000") : Save.TID.ToString("00000");
public string SID => Save.Generation >= 7 ? Save.TrainerSID7.ToString("0000") : Save.SID.ToString("00000");
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string Folder { get; }
public string Name => Path.GetFileName(FilePath);
}
}

View File

@ -74,7 +74,7 @@ public static bool InitFromSaveFileData(SaveFile sav)
ActiveTrainer = sav;
if (sav.Generation >= 3)
return AllowGBCartEra = false;
bool vc = !sav.Exportable || (sav.FileName?.EndsWith("dat") ?? false); // default to true for non-exportable
bool vc = !sav.State.Exportable || (sav.Metadata.FileName?.EndsWith("dat") ?? false); // default to true for non-exportable
return AllowGBCartEra = !vc; // physical cart selected
}
}

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV1 : SaveFile, ILangDeviantSave
{
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => "SAV File|*.sav|All Files|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : 1;
@ -184,7 +183,7 @@ private int GetBoxRawDataOffset(int i)
}
// Configuration
public override SaveFile Clone() => new SAV1(Write(), Version);
protected override SaveFile CloneInternal() => new SAV1(Write(), Version);
protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;

View File

@ -16,7 +16,7 @@ public sealed class SAV1Stadium : SAV_STADIUM
public override IReadOnlyList<ushort> HeldItems => Array.Empty<ushort>();
public override GameVersion Version { get; protected set; } = GameVersion.Stadium;
public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
protected override SaveFile CloneInternal() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
public override int Generation => 1;
private int StringLength => Japanese ? StringLengthJ : StringLengthU;

View File

@ -17,7 +17,7 @@ public sealed class SAV1StadiumJ : SAV_STADIUM
public override IReadOnlyList<ushort> HeldItems => Array.Empty<ushort>();
public override GameVersion Version { get; protected set; } = GameVersion.StadiumJ;
public override SaveFile Clone() => new SAV1StadiumJ((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV1StadiumJ((byte[])Data.Clone());
public override int Generation => 1;
private const int StringLength = 6; // Japanese Only

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV2 : SaveFile, ILangDeviantSave
{
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => "SAV File|*.sav|All Files|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : !Korean ? 1 : 2;
@ -247,7 +246,7 @@ protected override byte[] GetFinalData()
}
// Configuration
public override SaveFile Clone() => new SAV2(Write(), Version);
protected override SaveFile CloneInternal() => new SAV2(Write(), Version);
protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;

View File

@ -16,7 +16,7 @@ public sealed class SAV2Stadium : SAV_STADIUM
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_GSC;
public override GameVersion Version { get; protected set; } = GameVersion.Stadium2;
public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
protected override SaveFile CloneInternal() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
public override int Generation => 2;
private const int StringLength = 12;

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV3 : SaveFile, ILangDeviantSave
{
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => "SAV File|*.sav|All Files|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : 1;
@ -285,7 +284,7 @@ protected override byte[] GetFinalData()
public int GetBlockOffset(int block) => BlockOfs[block];
// Configuration
public override SaveFile Clone() => new SAV3(Write(), Version);
protected override SaveFile CloneInternal() => new SAV3(Write(), Version);
protected override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
@ -331,7 +330,7 @@ protected override void SetChecksums()
BitConverter.GetBytes(chk).CopyTo(Data, ofs + 0xFF6);
}
if (BAK.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes
if (State.BAK.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes
return;
// Hall of Fame Checksums
@ -355,7 +354,7 @@ public override bool ChecksumsValid
return false;
}
if (BAK.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
if (State.BAK.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
return true;
if (!IsChunkValidHoF(0x1C000))
@ -391,7 +390,7 @@ public override string ChecksumInfo
list.Add($"Block {BlockOrder[i]:00} @ {i*SIZE_BLOCK:X5} invalid.");
}
if (BAK.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
if (State.BAK.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
{
if (!IsChunkValidHoF(0x1C000))
list.Add("HoF Block 1 invalid.");

View File

@ -10,8 +10,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV3Colosseum : SaveFile, IDisposable, IGCSaveFile
{
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => this.GCFilter();
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => this.GCExtension();
public override PersonalTable Personal => PersonalTable.RS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_COLO;
@ -120,17 +119,16 @@ private byte[] GetInnerData()
byte[] newSAV = EncryptColosseum(Data, digest);
// Put save slot back in original save data
byte[] newFile = MC != null ? MC.SelectedSaveData : (byte[])BAK.Clone();
byte[] newFile = MC != null ? MC.SelectedSaveData : (byte[]) State.BAK.Clone();
Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length);
return newFile;
}
// Configuration
public override SaveFile Clone()
protected override SaveFile CloneInternal()
{
var data = GetInnerData();
var sav = IsMemoryCardSave ? new SAV3Colosseum(data, MC!) : new SAV3Colosseum(data);
sav.Header = (byte[])Header.Clone();
return sav;
}

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV3RSBox : SaveFile, IGCSaveFile
{
protected override string BAKText => $"{Version} #{SaveCount:0000}";
public override string Filter => this.GCFilter();
protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}";
public override string Extension => this.GCExtension();
public override PersonalTable Personal => PersonalTable.RS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
@ -95,11 +94,10 @@ private byte[] GetInnerData()
}
// Configuration
public override SaveFile Clone()
protected override SaveFile CloneInternal()
{
var data = GetInnerData();
var sav = IsMemoryCardSave ? new SAV3RSBox(data, MC!) : new SAV3RSBox(data);
sav.Header = (byte[])Header.Clone();
return sav;
}

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV3XD : SaveFile, IGCSaveFile
{
protected override string BAKText => $"{OT} ({Version}) #{SaveCount:0000}";
public override string Filter => this.GCFilter();
protected internal override string ShortSummary => $"{OT} ({Version}) #{SaveCount:0000}";
public override string Extension => this.GCExtension();
public bool IsMemoryCardSave => MC != null;
private readonly SAV3GCMemoryCard? MC;
@ -144,17 +143,16 @@ private byte[] GetInnerData()
byte[] newSAV = GCSaveUtil.Encrypt(Data, 0x10, 0x27FD8, keys);
// Put save slot back in original save data
byte[] newFile = MC != null ? MC.SelectedSaveData : (byte[])BAK.Clone();
byte[] newFile = MC != null ? MC.SelectedSaveData : (byte[]) State.BAK.Clone();
Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length);
return newFile;
}
// Configuration
public override SaveFile Clone()
protected override SaveFile CloneInternal()
{
var data = GetInnerData();
var sav = IsMemoryCardSave ? new SAV3XD(data, MC!) : new SAV3XD(data);
sav.Header = (byte[]) Header.Clone();
return sav;
}

View File

@ -12,8 +12,7 @@ namespace PKHeX.Core
/// </remarks>
public abstract class SAV4 : SaveFile
{
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => (Footer.Length > 0 ? "DeSmuME DSV|*.dsv|" : string.Empty) + "SAV File|*.sav|All Files|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
// Blocks & Offsets
@ -59,16 +58,15 @@ protected SAV4(byte[] data) : base(data)
}
// Configuration
public override SaveFile Clone()
protected override SaveFile CloneInternal()
{
var sav = CloneInternal();
var sav = CloneInternal4();
SetData(sav.General, General, 0);
SetData(sav.Storage, Storage, 0);
sav.Footer = (byte[])Footer.Clone();
return sav;
}
protected abstract SAV4 CloneInternal();
protected abstract SAV4 CloneInternal4();
public override void CopyChangesFrom(SaveFile sav)
{

View File

@ -10,8 +10,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV4BR : SaveFile
{
protected override string BAKText => $"{Version} #{SaveCount:0000}";
public override string Filter => "PbrSaveData|*";
protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}";
public override string Extension => string.Empty;
public override PersonalTable Personal => PersonalTable.DP;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_DP;
@ -67,7 +66,7 @@ protected override byte[] GetFinalData()
}
// Configuration
public override SaveFile Clone() => new SAV4BR(Write());
protected override SaveFile CloneInternal() => new SAV4BR(Write());
public readonly List<int> SaveSlots = new List<int>(SAVE_COUNT);
public readonly List<string> SaveNames = new List<string>(SAVE_COUNT);

View File

@ -10,7 +10,7 @@ public sealed class SAV4DP : SAV4Sinnoh
{
public SAV4DP() => Initialize();
public SAV4DP(byte[] data) : base(data) => Initialize();
protected override SAV4 CloneInternal() => Exportable ? new SAV4DP(Data) : new SAV4DP();
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP(Data) : new SAV4DP();
public override PersonalTable Personal => PersonalTable.DP;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_DP;

View File

@ -10,7 +10,7 @@ public sealed class SAV4HGSS : SAV4
{
public SAV4HGSS() => Initialize();
public SAV4HGSS(byte[] data) : base(data) => Initialize();
protected override SAV4 CloneInternal() => Exportable ? new SAV4HGSS(Data) : new SAV4HGSS();
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4HGSS(Data) : new SAV4HGSS();
public override PersonalTable Personal => PersonalTable.HGSS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_HGSS;

View File

@ -9,7 +9,7 @@ public sealed class SAV4Pt : SAV4Sinnoh
{
public SAV4Pt() => Initialize();
public SAV4Pt(byte[] data) : base(data) => Initialize();
protected override SAV4 CloneInternal() => Exportable ? new SAV4Pt(Data) : new SAV4Pt();
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt(Data) : new SAV4Pt();
public override PersonalTable Personal => PersonalTable.Pt;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_Pt;

View File

@ -11,8 +11,7 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW
protected override PKM GetPKM(byte[] data) => new PK5(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
protected override string BAKText => $"{OT} ({(GameVersion)Game}) - {PlayTimeString}";
public override string Filter => (Footer.Length != 0 ? "DeSmuME DSV|*.dsv|" : string.Empty) + "SAV File|*.sav|All Files|*.*";
protected internal override string ShortSummary => $"{OT} ({(GameVersion)Game}) - {PlayTimeString}";
public override string Extension => ".sav";
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_BW;
@ -185,7 +184,7 @@ public byte[] CGearSkinData
chkbytes.CopyTo(Data, CGearSkinInfoOffset);
CGearSkinPresent = true;
Edited = true;
State.Edited = true;
}
}

View File

@ -18,7 +18,7 @@ public SAV5B2W2(byte[] data) : base(data)
public override PersonalTable Personal => PersonalTable.B2W2;
public SaveBlockAccessor5B2W2 Blocks { get; }
public override SaveFile Clone() => new SAV5B2W2((byte[])Data.Clone()) { Footer = (byte[])Footer.Clone() };
protected override SaveFile CloneInternal() => new SAV5B2W2((byte[]) Data.Clone());
protected override int EventConstMax => 0x1AF; // this doesn't seem right?
protected override int EventFlagMax => 0xBF8;
public override int MaxItemID => Legal.MaxItemID_5_B2W2;

View File

@ -18,7 +18,7 @@ public SAV5BW(byte[] data) : base(data)
public override PersonalTable Personal => PersonalTable.BW;
public SaveBlockAccessor5BW Blocks { get; }
public override SaveFile Clone() => new SAV5BW((byte[])Data.Clone()) { Footer = (byte[])Footer.Clone() };
protected override SaveFile CloneInternal() => new SAV5BW((byte[])Data.Clone());
protected override int EventConstMax => 0x13E;
protected override int EventFlagMax => 0xB60;
public override int MaxItemID => Legal.MaxItemID_5_BW;

View File

@ -8,8 +8,7 @@ namespace PKHeX.Core
public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IRegionOrigin, IGameSync
{
// Save Data Attributes
protected override string BAKText => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Filter => "Main SAV|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Extension => string.Empty;
protected SAV6(byte[] data, int biOffset) : base(data, biOffset) { }

View File

@ -27,7 +27,7 @@ public SAV6AO() : base(SaveUtil.SIZE_G6ORAS, SaveBlockAccessor6AO.boAO)
public override PersonalTable Personal => PersonalTable.AO;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItem_AO;
public SaveBlockAccessor6AO Blocks { get; }
public override SaveFile Clone() => new SAV6AO((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV6AO((byte[])Data.Clone());
public override int MaxMoveID => Legal.MaxMoveID_6_AO;
public override int MaxItemID => Legal.MaxItemID_6_AO;
public override int MaxAbilityID => Legal.MaxAbilityID_6_AO;

View File

@ -22,7 +22,7 @@ public SAV6AODemo() : base(SaveUtil.SIZE_G6ORASDEMO, SaveBlockAccessor6AODemo.bo
public override PersonalTable Personal => PersonalTable.AO;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItem_AO;
public override SaveFile Clone() => new SAV6AODemo((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV6AODemo((byte[])Data.Clone());
public override int MaxMoveID => Legal.MaxMoveID_6_AO;
public override int MaxItemID => Legal.MaxItemID_6_AO;
public override int MaxAbilityID => Legal.MaxAbilityID_6_AO;

View File

@ -27,7 +27,7 @@ public SAV6XY() : base(SaveUtil.SIZE_G6XY, SaveBlockAccessor6XY.boXY)
public override PersonalTable Personal => PersonalTable.XY;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItem_XY;
public SaveBlockAccessor6XY Blocks { get; }
public override SaveFile Clone() => new SAV6XY((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV6XY((byte[])Data.Clone());
public override int MaxMoveID => Legal.MaxMoveID_6_XY;
public override int MaxItemID => Legal.MaxItemID_6_XY;
public override int MaxAbilityID => Legal.MaxAbilityID_6_XY;
@ -133,7 +133,8 @@ public void UnlockAllFriendSafariSlots()
if (Data[ofs] != 0) // no friend data == 0x00
Data[ofs] = 0x3D;
}
Edited = true;
State.Edited = true;
}
public override GameVersion Version

View File

@ -11,8 +11,7 @@ namespace PKHeX.Core
public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IRegionOrigin
{
// Save Data Attributes
protected override string BAKText => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Filter => "Main SAV|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Extension => string.Empty;
public override IReadOnlyList<string> PKMExtensions => PKM.Extensions.Where(f =>
@ -32,7 +31,7 @@ protected SAV7(int size, int biOffset) : base(size, biOffset)
protected void ReloadBattleTeams()
{
var demo = this is SAV7SM && Data.IsRangeAll((byte)0, BoxLayout.Offset, 0x4C4); // up to Battle Box values
if (demo || !Exportable)
if (demo || !State.Exportable)
{
BoxLayout.ClearBattleTeams();
}

View File

@ -37,7 +37,7 @@ private void Initialize()
public override PersonalTable Personal => PersonalTable.SM;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_SM;
public override SaveFile Clone() => new SAV7SM((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV7SM((byte[])Data.Clone());
#region Blocks
public SaveBlockAccessor7SM Blocks { get; }

View File

@ -35,7 +35,7 @@ private void Initialize()
public override PersonalTable Personal => PersonalTable.USUM;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_USUM;
public override SaveFile Clone() => new SAV7USUM((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV7USUM((byte[])Data.Clone());
protected override int EventFlagMax => 4928;
public override int MaxMoveID => Legal.MaxMoveID_7_USUM;
public override int MaxSpeciesID => Legal.MaxSpeciesID_7_USUM;

View File

@ -9,8 +9,7 @@ namespace PKHeX.Core
/// </summary>
public sealed class SAV7b : SAV_BEEF, IGameSync
{
protected override string BAKText => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}";
public override string Filter => "savedata|*.bin";
protected internal override string ShortSummary => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}";
public override string Extension => ".bin";
public override IReadOnlyList<string> PKMExtensions => PKM.Extensions.Where(f => f[1] == 'b' && f[f.Length - 1] == '7').ToArray();
@ -24,7 +23,7 @@ public sealed class SAV7b : SAV_BEEF, IGameSync
public override PersonalTable Personal => PersonalTable.GG;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_GG;
public override SaveFile Clone() => new SAV7b((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV7b((byte[])Data.Clone());
public SaveBlockAccessor7b Blocks { get; }
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;

View File

@ -10,8 +10,7 @@ namespace PKHeX.Core
public abstract class SAV8 : SaveFile, ISaveBlock8Main
{
// Save Data Attributes
protected override string BAKText => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Filter => "Main SAV|*.*";
protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Extension => string.Empty;
public override IReadOnlyList<string> PKMExtensions => PKM.Extensions.Where(f =>

View File

@ -39,7 +39,7 @@ public override void CopyChangesFrom(SaveFile sav)
var newB = z.AllBlocks;
for (int i = 0; i < mine.Count; i++)
newB[i].Data.CopyTo(mine[i].Data, 0);
Edited = true;
State.Edited = true;
}
public int SaveRevision { get; }
@ -83,7 +83,7 @@ public override void CopyChangesFrom(SaveFile sav)
public T GetValue<T>(uint key) where T : struct
{
if (!Exportable)
if (!State.Exportable)
return default;
var value = Blocks.GetBlockValue(key);
if (value is T v)
@ -93,13 +93,13 @@ public override void CopyChangesFrom(SaveFile sav)
public void SetValue<T>(uint key, T value) where T : struct
{
if (!Exportable)
if (!State.Exportable)
return;
Blocks.SetBlockValue(key, value);
}
#endregion
public override SaveFile Clone() => new SAV8SWSH(BAK, AllBlocks.Select(z => z.Clone()).ToArray());
protected override SaveFile CloneInternal() => new SAV8SWSH(State.BAK, AllBlocks.Select(z => z.Clone()).ToArray());
private int m_spec, m_item, m_move, m_abil;
public override int MaxMoveID => m_move;

View File

@ -5,8 +5,7 @@
/// </summary>
public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave
{
protected sealed override string BAKText => $"{OT} ({Version})";
public sealed override string Filter => "SAV File|*.sav|All Files|*.*";
protected internal sealed override string ShortSummary => $"{OT} ({Version})";
public sealed override string Extension => ".sav";
public abstract int SaveRevision { get; }

View File

@ -5,6 +5,19 @@
namespace PKHeX.Core
{
public class SaveFileState
{
public bool Edited { get; set; }
public readonly bool Exportable;
public readonly byte[] BAK;
public SaveFileState(byte[] bak, bool exportable = true)
{
BAK = bak;
Exportable = exportable;
}
}
/// <summary>
/// Base Class for Save Files
/// </summary>
@ -12,42 +25,42 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
{
// General Object Properties
public byte[] Data;
public bool Edited;
public readonly bool Exportable;
public readonly byte[] BAK;
public SaveFileState State { get; }
public SaveFileMetadata Metadata { get; private set; }
protected SaveFile(byte[] data, byte[] bak, bool exportable = true)
{
Data = data;
BAK = bak;
Exportable = exportable;
State = new SaveFileState(bak, exportable);
Metadata = new SaveFileMetadata(this);
}
protected SaveFile(byte[] data, bool exportable = true) : this(data, (byte[])data.Clone(), exportable) { }
protected SaveFile()
protected SaveFile(byte[] data, bool exportable = true) : this(data, (byte[])data.Clone(), exportable)
{
Data = BAK = Array.Empty<byte>();
Exportable = false;
}
protected SaveFile(int size)
protected SaveFile(int size = 0)
{
Data = new byte[size];
BAK = Data;
Exportable = false;
Data = size == 0 ? Array.Empty<byte>() : new byte[size];
State = new SaveFileState(Array.Empty<byte>(), false);
Metadata = new SaveFileMetadata(this);
}
public string? FileName, FilePath, FileFolder;
public string BAKName => $"{FileName} [{BAKText}].bak";
protected abstract string BAKText { get; }
public abstract SaveFile Clone();
public abstract string Filter { get; }
public byte[] Footer { protected get; set; } = Array.Empty<byte>(); // .dsv
public byte[] Header { protected get; set; } = Array.Empty<byte>(); // .gci
public virtual string PlayTimeString => $"{PlayedHours}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not :
protected internal abstract string ShortSummary { get; }
public abstract string Extension { get; }
protected abstract SaveFile CloneInternal();
public SaveFile Clone()
{
var sav = CloneInternal();
sav.Metadata = Metadata;
return sav;
}
public virtual string PlayTimeString => $"{PlayedHours}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not :
public virtual IReadOnlyList<string> PKMExtensions => PKM.Extensions.Where(f =>
{
int gen = f.Last() - 0x30;
@ -58,11 +71,7 @@ protected SaveFile(int size)
public byte[] Write(ExportFlags flags = ExportFlags.None)
{
byte[] data = GetFinalData();
if (Footer.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeFooter))
return data.Concat(Footer).ToArray();
if (Header.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeHeader))
return Header.Concat(data).ToArray();
return data;
return Metadata.Finalize(data, flags);
}
protected virtual byte[] GetFinalData()
@ -87,7 +96,7 @@ protected virtual byte[] GetFinalData()
public void SetData(byte[] dest, byte[] input, int offset)
{
input.CopyTo(dest, offset);
Edited = true;
State.Edited = true;
}
public abstract string GetString(byte[] data, int offset, int length);

View File

@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Linq;
namespace PKHeX.Core
{
public class SaveFileMetadata
{
private readonly SaveFile SAV;
public string? FileName;
public string? FilePath;
public string? FileFolder;
private byte[] Footer = Array.Empty<byte>(); // .dsv
private byte[] Header = Array.Empty<byte>(); // .gci
public string BAKName => $"{FileName} [{SAV.ShortSummary}].bak";
public SaveFileMetadata(SaveFile sav) => SAV = sav;
public bool HasHeader => Header.Length != 0;
public bool HasFooter => Footer.Length != 0;
public string Filter => $"{SAV.GetType().Name}|{GetSuggestedExtension()}|All Files|*.*";
public byte[] Finalize(byte[] data, ExportFlags flags)
{
if (Footer.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeFooter))
return data.Concat(Footer).ToArray();
if (Header.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeHeader))
return Header.Concat(data).ToArray();
return data;
}
/// <summary>
/// Sets the details of any trimmed header and footer arrays to a <see cref="SaveFile"/> object.
/// </summary>
public void SetExtraInfo(byte[] header, byte[] footer)
{
Header = header;
Footer = footer;
}
/// <summary>
/// Sets the details of a path to a <see cref="SaveFile"/> object.
/// </summary>
/// <param name="path">Full Path of the file</param>
public void SetExtraInfo(string path)
{
var sav = SAV;
if (!sav.State.Exportable) // Blank save file
{
sav.Metadata.FileFolder = sav.Metadata.FilePath = string.Empty;
sav.Metadata.FileName = "Blank Save File";
return;
}
sav.Metadata.FilePath = path;
sav.Metadata.FileFolder = Path.GetDirectoryName(path);
sav.Metadata.FileName = string.Empty;
var bakName = Util.CleanFileName(sav.Metadata.BAKName);
sav.Metadata.FileName = Path.GetFileName(path);
if (sav.Metadata.FileName?.EndsWith(bakName) == true)
sav.Metadata.FileName = sav.Metadata.FileName.Substring(0, sav.Metadata.FileName.Length - bakName.Length);
}
public string GetSuggestedExtension()
{
var sav = SAV;
var fn = sav.Metadata.FileName;
if (fn != null)
return Path.GetExtension(fn);
if ((sav.Generation == 4 || sav.Generation == 5) && sav.Metadata.HasFooter)
return ".dsv";
return sav.Extension;
}
}
}

View File

@ -11,11 +11,10 @@ public sealed class Bank3 : BulkStorage
public override PersonalTable Personal => PersonalTable.RS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public override SaveFile Clone() => new Bank3((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new Bank3((byte[])Data.Clone());
public override string PlayTimeString => Checksums.CRC16(Data, 0, Data.Length).ToString("X4");
protected override string BAKText => PlayTimeString;
protected internal override string ShortSummary => PlayTimeString;
public override string Extension => ".gst";
public override string Filter { get; } = "PokeStock G3 Storage|*.gst*";
public override int BoxCount => 64;
private const int BoxNameSize = 9;

View File

@ -11,11 +11,10 @@ public sealed class Bank4 : BulkStorage
public override PersonalTable Personal => PersonalTable.HGSS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_HGSS;
public override SaveFile Clone() => new Bank4((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new Bank4((byte[])Data.Clone());
public override string PlayTimeString => Checksums.CRC16(Data, 0, Data.Length).ToString("X4");
protected override string BAKText => PlayTimeString;
protected internal override string ShortSummary => PlayTimeString;
public override string Extension => ".stk";
public override string Filter { get; } = "PokeStock G4 Storage|*.stk*";
public override int BoxCount => 64;
private const int BoxNameSize = 0x18;

View File

@ -12,9 +12,9 @@ public sealed class Bank7 : BulkStorage
public override PersonalTable Personal => PersonalTable.USUM;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_SM;
public override SaveFile Clone() => new Bank7((byte[])Data.Clone(), PKMType, BoxStart, SlotsPerBox);
protected override SaveFile CloneInternal() => new Bank7((byte[])Data.Clone(), PKMType, BoxStart, SlotsPerBox);
public override string PlayTimeString => $"{Year:00}{Month:00}{Day:00}_{Hours:00}ː{Minutes:00}";
protected override string BAKText => PlayTimeString;
protected internal override string ShortSummary => PlayTimeString;
private const int GroupNameSize = 0x20;
private const int BankNameSize = 0x24;
private const int GroupNameSpacing = GroupNameSize + 2;

View File

@ -21,11 +21,10 @@ protected BulkStorage(byte[] data, Type t, int start, int slotsPerBox = 30) : ba
protected readonly int SlotsPerBox;
protected override string BAKText => $"{Checksums.CRC16(Data, Box, Data.Length - Box):X4}";
public override string Filter { get; } = "All Files|*.*";
public override string Extension { get; } = ".bin";
public sealed override bool ChecksumsValid { get; } = true;
public sealed override string ChecksumInfo { get; } = "No Info.";
protected internal override string ShortSummary => $"{Checksums.CRC16(Data, Box, Data.Length - Box):X4}";
public override string Extension => ".bin";
public sealed override bool ChecksumsValid => true;
public sealed override string ChecksumInfo => "No Info.";
private readonly PKM blank;
public sealed override Type PKMType => blank.GetType();

View File

@ -21,11 +21,10 @@ public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision
public override PersonalTable Personal => PersonalTable.Pt;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_Pt;
public override SaveFile Clone() => new SAV4Ranch((byte[])Data.Clone());
protected override SaveFile CloneInternal() => new SAV4Ranch((byte[])Data.Clone());
public override string PlayTimeString => Checksums.CRC16(Data, 0, Data.Length).ToString("X4");
protected override string BAKText => $"{OT} {PlayTimeString}";
protected internal override string ShortSummary => $"{OT} {PlayTimeString}";
public override string Extension => ".bin";
public override string Filter { get; } = "Ranch G4 Storage|*.bin*";
protected override PKM GetPKM(byte[] data) => new PK4(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);

View File

@ -20,7 +20,7 @@ public void SetReceivedFlags(bool[] value)
if (value.Length != MaxReceivedFlag)
return;
ArrayUtil.SetBitFlagArray(Data, Offset + FlagStart, value);
SAV.Edited = true;
SAV.State.Edited = true;
}
public DataMysteryGift[] GetGifts()

View File

@ -22,7 +22,7 @@ public bool[] MysteryGiftReceivedFlags
if (value.Length != MaxReceivedFlag)
return;
ArrayUtil.SetBitFlagArray(Data, Offset + FlagStart, value);
SAV.Edited = true;
SAV.State.Edited = true;
}
}

View File

@ -30,7 +30,7 @@ public PokeListHeader(SAV7b sav, int offset) : base(sav)
{
Offset = offset;
PokeListInfo = LoadPointerData();
if (!sav.Exportable)
if (!sav.State.Exportable)
{
for (int i = 0; i < COUNT; i++)
PokeListInfo[i] = SLOT_EMPTY;

View File

@ -16,7 +16,7 @@ public string Number
{
for (int i = 0; i < 3; i++)
Data[0x01 + i] = (byte)(value.Length > i ? value[i] : '\0');
SAV.Edited = true;
SAV.State.Edited = true;
}
}

View File

@ -12,7 +12,7 @@ public sealed class TeamIndexes8 : SaveBlock, ITeamIndexSet
public void LoadBattleTeams()
{
if (!SAV.Exportable)
if (!SAV.State.Exportable)
{
ClearBattleTeams();
return;

View File

@ -117,7 +117,7 @@ public string Number
{
for (int i = 0; i < 3; i++)
Data[0x39 + i] = (byte) (value.Length > i ? value[i] : '\0');
SAV.Edited = true;
SAV.State.Edited = true;
}
}

View File

@ -32,7 +32,7 @@ private Zukan7(SaveFile sav, int dex, int langflag, Func<int, int, int, int> for
{
DexFormIndexFetcher = form;
FormBaseSpecies = GetFormIndexBaseSpeciesList();
Debug.Assert(!SAV.Exportable || BitConverter.ToUInt32(SAV.Data, PokeDex) == MAGIC);
Debug.Assert(!SAV.State.Exportable || BitConverter.ToUInt32(SAV.Data, PokeDex) == MAGIC);
}
public Func<int, int, int, int> DexFormIndexFetcher { get; }

View File

@ -212,7 +212,7 @@ public static string[] GetBoxNames(SaveFile sav)
{
int count = sav.BoxCount;
var result = new string[count];
if (!sav.Exportable)
if (!sav.State.Exportable)
{
for (int i = 0; i < count; i++)
result[i] = $"Box {i + 1}";

View File

@ -22,30 +22,7 @@ public static class SaveExtensions
/// <param name="pk">PKM input that is to be injected into the Save File.</param>
/// <returns>Indication whether or not the PKM is compatible.</returns>
public static bool IsPKMCompatibleWithModifications(this SaveFile sav, PKM pk) => PKMConverter.IsPKMCompatibleWithModifications(pk, sav);
/// <summary>
/// Sets the details of a path to a <see cref="SaveFile"/> object.
/// </summary>
/// <param name="sav">Save File to set path details to.</param>
/// <param name="path">Full Path of the file</param>
public static void SetFileInfo(this SaveFile sav, string path)
{
if (!sav.Exportable) // Blank save file
{
sav.FileFolder = sav.FilePath = string.Empty;
sav.FileName = "Blank Save File";
return;
}
sav.FilePath = path;
sav.FileFolder = Path.GetDirectoryName(path);
sav.FileName = string.Empty;
var bakName = Util.CleanFileName(sav.BAKName);
sav.FileName = Path.GetFileName(path);
if (sav.FileName?.EndsWith(bakName) == true)
sav.FileName = sav.FileName.Substring(0, sav.FileName.Length - bakName.Length);
}
/// <summary>
/// Gets suggested export options for the save file.
/// </summary>

View File

@ -173,7 +173,7 @@ public static bool DetectSaveFile(out string path, out SaveFile? sav, IReadOnlyL
return false;
}
path = result.FilePath!;
path = result.Metadata.FilePath!;
sav = result;
return File.Exists(path);
}

View File

@ -487,7 +487,7 @@ private static GameVersion GetIsG8SAV(byte[] data)
var sav = GetVariantSAV(data);
if (sav == null)
return null;
sav.SetFileInfo(path);
sav.Metadata.SetExtraInfo(path);
return sav;
}
@ -501,8 +501,8 @@ private static GameVersion GetIsG8SAV(byte[] data)
var sav = GetVariantSAVInternal(data);
if (sav == null)
return null;
sav.Header = header;
sav.Footer = footer;
sav.Metadata.SetExtraInfo(header, footer);
return sav;
}
@ -569,8 +569,8 @@ private static GameVersion GetIsG8SAV(byte[] data)
// No pattern matched
default: return null;
}
sav.Header = header;
sav.Footer = footer;
sav.Metadata.SetExtraInfo(header, footer);
return sav;
}
@ -780,12 +780,9 @@ private static void CheckHeaderFooter(ref byte[] input, out byte[] header, out b
input = input.SliceEnd(0xA4);
return;
}
int start = input.Length - FOOTER_DSV.Length;
for (int i = 0; i < FOOTER_DSV.Length; i++)
{
if (FOOTER_DSV[i] != input[start + i])
return;
}
if (!GetHasFooterDSV(input))
return;
footer = input.SliceEnd(SIZE_G4RAW);
input = input.Slice(0, SIZE_G4RAW);
@ -814,6 +811,24 @@ private static void CheckHeaderFooter(ref byte[] input, out byte[] header, out b
static bool IsGameMatchHeader(IEnumerable<string> headers, byte[] data) => headers.Contains(Encoding.ASCII.GetString(data, 0, 4));
}
private static bool GetHasFooterDSV(byte[] input)
{
var signature = FOOTER_DSV;
if (!GetHasSignature(input, signature, input.Length - signature.Length))
return false;
return true;
}
private static bool GetHasSignature(byte[] input, byte[] signature, int start)
{
for (int i = 0; i < signature.Length; i++)
{
if (signature[i] != input[start + i])
return false;
}
return true;
}
/// <summary>
/// Force loads the provided <see cref="sav"/> to the requested <see cref="ver"/>.
/// </summary>
@ -824,13 +839,13 @@ public static SAV3 GetG3SaveOverride(SaveFile sav, GameVersion ver)
{
return ver switch // Reset save file info
{
R => new SAV3(sav.BAK, RS),
S => new SAV3(sav.BAK, RS),
RS => new SAV3(sav.BAK, RS),
E => new SAV3(sav.BAK, E),
FRLG => new SAV3(sav.BAK, FRLG),
FR => new SAV3(sav.BAK, FRLG),
LG => new SAV3(sav.BAK, FRLG),
R => new SAV3(sav.State.BAK, RS),
S => new SAV3(sav.State.BAK, RS),
RS => new SAV3(sav.State.BAK, RS),
E => new SAV3(sav.State.BAK, E),
FRLG => new SAV3(sav.State.BAK, FRLG),
FR => new SAV3(sav.State.BAK, FRLG),
LG => new SAV3(sav.State.BAK, FRLG),
_ => throw new ArgumentException(nameof(ver))
};
}

View File

@ -456,13 +456,13 @@ private void UpdateStringSeed(object sender, EventArgs e)
{
var value = filterText.PadLeft(SAV.DaycareSeedSize, '0');
SAV.SetDaycareRNGSeed(SAV.DaycareIndex, value);
SAV.Edited = true;
SAV.State.Edited = true;
}
else if (tb == TB_GameSync && SAV is IGameSync sync)
{
var value = filterText.PadLeft(sync.GameSyncIDSize, '0');
sync.GameSyncID = value;
SAV.Edited = true;
SAV.State.Edited = true;
}
else if (SAV is ISecureValueStorage s)
{
@ -471,7 +471,7 @@ private void UpdateStringSeed(object sender, EventArgs e)
s.TimeStampCurrent = value;
else if (tb == TB_Secure2)
s.TimeStampPrevious = value;
SAV.Edited = true;
SAV.State.Edited = true;
}
}
@ -728,7 +728,7 @@ private void ClickVerifyCHK(object sender, EventArgs e)
return;
}
if (SAV.Edited)
if (SAV.State.Edited)
{
WinFormsUtil.Alert(MsgSaveChecksumFailEdited);
return;
@ -775,14 +775,14 @@ public bool ExportSaveFile()
public bool ExportBackup()
{
if (!SAV.Exportable)
if (!SAV.State.Exportable)
return false;
using var sfd = new SaveFileDialog {FileName = Util.CleanFileName(SAV.BAKName)};
using var sfd = new SaveFileDialog {FileName = Util.CleanFileName(SAV.Metadata.BAKName)};
if (sfd.ShowDialog() != DialogResult.OK)
return false;
string path = sfd.FileName;
File.WriteAllBytes(path, SAV.BAK);
File.WriteAllBytes(path, SAV.State.BAK);
WinFormsUtil.Alert(MsgSaveBackup, path);
return true;
@ -1009,7 +1009,7 @@ private bool ToggleViewBox(SaveFile sav)
private bool ToggleViewParty(SaveFile sav, int BoxTab)
{
if (!sav.HasParty || !sav.Exportable)
if (!sav.HasParty || !sav.State.Exportable)
{
if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle))
tabBoxMulti.TabPages.Remove(Tab_PartyBattle);
@ -1028,7 +1028,7 @@ private bool ToggleViewParty(SaveFile sav, int BoxTab)
private bool ToggleViewDaycare(SaveFile sav, int BoxTab, int PartyTab)
{
if ((!sav.HasDaycare && SL_Extra.SlotCount == 0) || !sav.Exportable)
if ((!sav.HasDaycare && SL_Extra.SlotCount == 0) || !sav.State.Exportable)
{
if (tabBoxMulti.TabPages.Contains(Tab_Other))
tabBoxMulti.TabPages.Remove(Tab_Other);
@ -1050,7 +1050,7 @@ private bool ToggleViewDaycare(SaveFile sav, int BoxTab, int PartyTab)
private void ToggleViewSubEditors(SaveFile sav)
{
if (!sav.Exportable || sav is BulkStorage)
if (!sav.State.Exportable || sav is BulkStorage)
{
GB_SAVtools.Visible = false;
B_JPEG.Visible = false;
@ -1093,7 +1093,7 @@ private void ToggleViewSubEditors(SaveFile sav)
SL_Extra.SAV = sav;
SL_Extra.Initialize(sav.GetExtraSlots(HaX), InitializeDragDrop);
}
GB_SAVtools.Visible = sav.Exportable && FLP_SAVtools.Controls.OfType<Control>().Any(c => c.Enabled);
GB_SAVtools.Visible = sav.State.Exportable && FLP_SAVtools.Controls.OfType<Control>().Any(c => c.Enabled);
foreach (Control c in FLP_SAVtools.Controls.OfType<Control>())
c.Visible = c.Enabled;
var list = FLP_SAVtools.Controls.OfType<Control>().ToArray();
@ -1106,7 +1106,7 @@ private void ToggleViewMisc(SaveFile sav)
{
// Generational Interface
ToggleSecrets(sav, HideSecretDetails);
B_VerifyCHK.Enabled = SAV.Exportable;
B_VerifyCHK.Enabled = SAV.State.Exportable;
if (sav is SAV4BR br)
{
@ -1138,7 +1138,7 @@ private void ToggleViewMisc(SaveFile sav)
private void ToggleSecrets(SaveFile sav, bool hide)
{
var shouldShow = sav.Exportable && !hide;
var shouldShow = sav.State.Exportable && !hide;
TB_Secure1.Visible = TB_Secure2.Visible = L_Secure1.Visible = L_Secure2.Visible = shouldShow && sav is ISecureValueStorage;
TB_GameSync.Visible = L_GameSync.Visible = shouldShow && sav is IGameSync;
}

View File

@ -226,7 +226,7 @@ private void LoadBlankSaveFile(GameVersion ver)
if (sav.Version == GameVersion.Invalid) // will fail to load
sav = SaveUtil.GetBlankSAV((GameVersion)GameInfo.VersionDataSource.Max(z => z.Value), tr, lang);
OpenSAV(sav, string.Empty);
C_SAV!.SAV.Edited = false; // Prevents form close warning from showing until changes are made
C_SAV!.SAV.State.Edited = false; // Prevents form close warning from showing until changes are made
}
private void FormLoadCheckForUpdates()
@ -486,7 +486,7 @@ private void MainMenuFolder(object sender, EventArgs e)
{
if (this.OpenWindowExists<SAV_FolderList>())
return;
var form = new SAV_FolderList(s => OpenSAV(SaveUtil.GetVariantSAV(s.FilePath!), s.FilePath!));
var form = new SAV_FolderList(s => OpenSAV(SaveUtil.GetVariantSAV(s.Metadata.FilePath!), s.Metadata.FilePath!));
form.Show();
}
@ -729,11 +729,11 @@ private bool OpenSAV(SaveFile? sav, string path)
return true;
}
sav.SetFileInfo(path);
sav.Metadata.SetExtraInfo(path);
if (!SanityCheckSAV(ref sav))
return true;
if (C_SAV.SAV.Edited && Settings.Default.ModifyUnset)
if (C_SAV.SAV.State.Edited && Settings.Default.ModifyUnset)
{
var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramSaveFileConfirm);
if (prompt != DialogResult.Yes)
@ -747,7 +747,7 @@ private bool OpenSAV(SaveFile? sav, string path)
dragout.Size = new Size(SpriteUtil.Spriter.Width, SpriteUtil.Spriter.Height);
// clean fields
Menu_ExportSAV.Enabled = sav.Exportable;
Menu_ExportSAV.Enabled = sav.State.Exportable;
// No changes made yet
Menu_Undo.Enabled = false;
@ -807,7 +807,7 @@ private void ResetSAVPKMEditors(SaveFile sav)
PKME_Tabs.FlickerInterface();
foreach (var p in Plugins)
p.NotifySaveLoaded();
sav.Edited = false;
sav.State.Edited = false;
}
private static string GetProgramTitle()
@ -830,9 +830,9 @@ private static string GetProgramTitle(SaveFile sav)
var ver = GameInfo.GetVersionName(sav.Version);
if (Settings.Default.HideSAVDetails)
return title + $"[{ver}]";
if (!sav.Exportable) // Blank save file
return title + $"{sav.FileName} [{sav.OT} ({ver})]";
return title + Path.GetFileNameWithoutExtension(Util.CleanFileName(sav.BAKName)); // more descriptive
if (!sav.State.Exportable) // Blank save file
return title + $"{sav.Metadata.FileName} [{sav.OT} ({ver})]";
return title + Path.GetFileNameWithoutExtension(Util.CleanFileName(sav.Metadata.BAKName)); // more descriptive
}
private static bool TryBackupExportCheck(SaveFile sav, string path)
@ -841,9 +841,9 @@ private static bool TryBackupExportCheck(SaveFile sav, string path)
return false;
// If backup folder exists, save a backup.
string backupName = Path.Combine(BackupPath, Util.CleanFileName(sav.BAKName));
if (sav.Exportable && Directory.Exists(BackupPath) && !File.Exists(backupName))
File.WriteAllBytes(backupName, sav.BAK);
string backupName = Path.Combine(BackupPath, Util.CleanFileName(sav.Metadata.BAKName));
if (sav.State.Exportable && Directory.Exists(BackupPath) && !File.Exists(backupName))
File.WriteAllBytes(backupName, sav.State.BAK);
if (!FileUtil.IsFileLocked(path))
return true;
@ -856,7 +856,7 @@ private static bool SanityCheckSAV(ref SaveFile sav)
{
ParseSettings.InitFromSaveFileData(sav); // physical GB, no longer used in logic
if (sav.Exportable && sav is SAV3 s3)
if (sav.State.Exportable && sav is SAV3 s3)
{
if (s3.IndeterminateGame || ModifierKeys == Keys.Control)
{
@ -1156,7 +1156,7 @@ private void DragoutDrop(object sender, DragEventArgs e)
private void Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (C_SAV.SAV.Edited || PKME_Tabs.PKMIsUnsaved)
if (C_SAV.SAV.State.Edited || PKME_Tabs.PKMIsUnsaved)
{
var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramCloseConfirm);
if (prompt != DialogResult.Yes)

View File

@ -15,7 +15,7 @@ namespace PKHeX.WinForms
public partial class SAV_FolderList : Form
{
private readonly Action<SaveFile> OpenSaveFile;
private readonly List<CustomFolderPath> Paths;
private readonly List<INamedFolderPath> Paths;
private readonly SortableBindingList<SavePreview> Recent;
private readonly SortableBindingList<SavePreview> Backup;
private readonly List<Label> TempTranslationLabels = new List<Label>();
@ -73,9 +73,9 @@ public SAV_FolderList(Action<SaveFile> openSaveFile)
CenterToParent();
}
private static List<CustomFolderPath> GetPathList(IReadOnlyList<string> drives)
private static List<INamedFolderPath> GetPathList(IReadOnlyList<string> drives)
{
var locs = new List<CustomFolderPath>
var locs = new List<INamedFolderPath>
{
new CustomFolderPath(Main.BackupPath, "PKHeX Backups")
};
@ -118,7 +118,7 @@ private void AddCustomizeButton()
var custom = Paths.Where(z => z.Custom).ToList();
if (custom.Count == 0)
Paths.Add(new CustomFolderPath("DISPLAY_TEXT", "FOLDER_PATH", true));
var lines = custom.Select(z => z.Write());
var lines = custom.Select(z => ((CustomFolderPath)z).ToString());
File.WriteAllLines(loc, lines);
}
Process.Start(loc);
@ -173,11 +173,11 @@ private static IEnumerable<CustomFolderPath> GetSwitchPaths(IEnumerable<string>
return paths.Select(z => new CustomFolderPath(z));
}
private sealed class CustomFolderPath
private sealed class CustomFolderPath : INamedFolderPath
{
public readonly string Path;
public readonly string DisplayText;
public readonly bool Custom;
public string Path { get; }
public string DisplayText { get; }
public bool Custom { get; }
public CustomFolderPath(string z, bool custom = false)
{
@ -206,44 +206,11 @@ public CustomFolderPath(string path, string display, bool custom = false)
Custom = custom;
}
public string Write() => $"{DisplayText}\t{Path}";
}
private string GetParentFolderName(SaveFile first)
{
var parent = Paths.Find(z => first.FileFolder?.StartsWith(z.Path) == true);
return parent?.DisplayText ?? "???";
public override string ToString() => $"{DisplayText}\t{Path}";
}
private sealed class SaveList<T> : SortableBindingList<T> where T : class { }
private sealed class SavePreview
{
public readonly SaveFile Save;
public SavePreview(SaveFile sav, string parent)
{
Save = sav;
Folder = parent;
}
public string OT => Save.OT;
public int G => Save.Generation;
public GameVersion Game => Save.Version;
public string Played => Save.PlayTimeString.PadLeft(9, '0');
public string FileTime => File.GetLastWriteTimeUtc(Save.FilePath!).ToString("yyyy.MM.dd:hh:mm:ss");
public string TID => Save.Generation >= 7 ? Save.TrainerID7.ToString("000000") : Save.TID.ToString("00000");
public string SID => Save.Generation >= 7 ? Save.TrainerSID7.ToString("0000") : Save.SID.ToString("00000");
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string Folder { get; }
public string? Name => Path.GetFileName(Save.FilePath);
}
private ContextMenuStrip GetContextMenu(DataGridView dgv)
{
var mnuOpen = new ToolStripMenuItem
@ -289,7 +256,7 @@ private void ClickOpenFolder(DataGridView dgv)
return;
}
var path = sav.Save.FilePath;
var path = sav.Save.Metadata.FilePath;
Process.Start("explorer.exe", $"/select, \"{path}\"");
}
@ -327,7 +294,7 @@ private SaveList<SavePreview> PopulateData(DataGridView dgData, IEnumerable<Save
}
var first = enumerator.Current;
var sav1 = new SavePreview(first, GetParentFolderName(first));
var sav1 = new SavePreview(first, Paths);
LoadEntryInitial(dgData, list, sav1);
int ctr = 1; // refresh every 7 until 15+ are loaded
@ -338,7 +305,7 @@ private SaveList<SavePreview> PopulateData(DataGridView dgData, IEnumerable<Save
while (enumerator.MoveNext())
{
var next = enumerator.Current;
var sav = new SavePreview(next, GetParentFolderName(next));
var sav = new SavePreview(next, Paths);
dgData.Invoke(new Action(() => LoadEntry(dgData, list, sav)));
ctr++;
if (ctr < 15 && ctr % 7 == 0)

View File

@ -49,7 +49,7 @@ private void SaveData()
Data.MasterFlag = CHK_Master.Checked;
SaveCurrent();
Origin.Data = SAV.Data;
Origin.Edited = true;
Origin.State.Edited = true;
}
private void LoadCurrent()

View File

@ -203,7 +203,7 @@ private void B_Cancel_Click(object sender, EventArgs e)
private void B_Save_Click(object sender, EventArgs e)
{
Editor.Save();
Origin.Edited = true;
Origin.State.Edited = true;
Close();
}
@ -298,14 +298,14 @@ private void B_ApplyFlag_Click(object sender, EventArgs e)
{
var index = (int)NUD_Flag.Value;
SAV.SetFlag(index, c_CustomFlag.Checked);
Origin.Edited = true;
Origin.State.Edited = true;
}
private void B_ApplyWork_Click(object sender, EventArgs e)
{
var index = CB_Stats.SelectedIndex;
SAV.SetWork(index, (int)NUD_Stat.Value);
Origin.Edited = true;
Origin.State.Edited = true;
}
}
}

View File

@ -279,7 +279,7 @@ public static bool OpenSAVPKMDialog(IEnumerable<string> extensions, out string?
Error(msg);
if (sav != null)
ofd.FileName = sav.FileName;
ofd.FileName = sav.Metadata.FileName;
if (ofd.ShowDialog() != DialogResult.OK)
{
@ -346,13 +346,13 @@ public static bool ExportSAVDialog(SaveFile sav, int currentBox = 0)
{
using var sfd = new SaveFileDialog
{
Filter = sav.Filter,
FileName = sav.FileName,
Filter = sav.Metadata.Filter,
FileName = sav.Metadata.FileName,
FilterIndex = 1000, // default to last, All Files
RestoreDirectory = true
};
if (Directory.Exists(sav.FileFolder))
sfd.InitialDirectory = sav.FileFolder;
if (Directory.Exists(sav.Metadata.FileFolder))
sfd.InitialDirectory = sav.Metadata.FileFolder;
if (sfd.ShowDialog() != DialogResult.OK)
return false;
@ -377,8 +377,8 @@ private static void ExportSAV(SaveFile sav, string path)
try
{
File.WriteAllBytes(path, sav.Write(flags));
sav.Edited = false;
sav.SetFileInfo(path);
sav.State.Edited = false;
sav.Metadata.SetExtraInfo(path);
Alert(MsgSaveExportSuccessPath, path);
}
catch (Exception x)