Minimize BAK file allocations (#3426)

Stop allocating an entire shadow copy of the save file whenever we create a new savefile object from file.

Prior commits added the clear SaveFileMetadata class to cleanly track the file path. Backups are copied from the original path.
This commit is contained in:
Kurt 2022-02-09 16:48:55 -08:00 committed by GitHub
parent 04856122b7
commit bc2549b24e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 60 additions and 69 deletions

View file

@ -148,6 +148,19 @@ namespace PKHeX.Core
public sealed override int MaxBallID => Legal.MaxBallID_3;
public sealed override int MaxGameID => Legal.MaxGameID_3;
/// <summary>
/// Force loads a new <see cref="SAV3"/> object to the requested <see cref="version"/>.
/// </summary>
/// <param name="version"> Version to retrieve for</param>
/// <returns>New <see cref="SaveFile"/> object.</returns>
public SAV3 ForceLoad(GameVersion version) => version switch
{
GameVersion.R or GameVersion.S or GameVersion.RS => new SAV3RS(Data),
GameVersion.E => new SAV3E(Data),
GameVersion.FR or GameVersion.LG or GameVersion.FRLG => new SAV3FRLG(Data),
_ => throw new ArgumentOutOfRangeException(nameof(version)),
};
public sealed override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public sealed override int BoxCount => 14;
@ -182,7 +195,7 @@ namespace PKHeX.Core
WriteUInt16LittleEndian(sector[0xFF6..], chk);
}
if (State.BAK.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes
if (Data.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes
return;
// Hall of Fame Checksums
@ -208,7 +221,7 @@ namespace PKHeX.Core
return false;
}
if (State.BAK.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
if (Data.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
return true;
if (!IsSectorValidExtra(0x1C000))
@ -246,7 +259,7 @@ namespace PKHeX.Core
list.Add($"Sector {i} @ {i*SIZE_SECTOR:X5} invalid.");
}
if (State.BAK.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
if (Data.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
{
if (!IsSectorValidExtra(0x1C000))
list.Add("HoF first sector invalid.");

View file

@ -14,7 +14,7 @@ namespace PKHeX.Core
public override string Extension => this.GCExtension();
public override PersonalTable Personal => PersonalTable.RS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_COLO;
public SAV3GCMemoryCard? MemoryCard { get; }
public SAV3GCMemoryCard? MemoryCard { get; init; }
// 3 Save files are stored
// 0x0000-0x6000 contains memory card data
@ -33,17 +33,19 @@ namespace PKHeX.Core
private readonly StrategyMemo StrategyMemo;
public const int MaxShadowID = 0x80; // 128
private int Memo;
public SAV3Colosseum(byte[] data, SAV3GCMemoryCard memCard) : this(data, memCard.Data) => MemoryCard = memCard;
public SAV3Colosseum(byte[] data) : this(data, (byte[])data.Clone()) { }
private readonly byte[] BAK;
public SAV3Colosseum() : base(SaveUtil.SIZE_G3COLO)
{
BAK = Array.Empty<byte>();
StrategyMemo = Initialize();
ClearBoxes();
}
private SAV3Colosseum(byte[] data, byte[] bak) : base(data, bak)
public SAV3Colosseum(byte[] data) : base(data)
{
BAK = data;
InitializeData();
StrategyMemo = Initialize();
}
@ -119,7 +121,7 @@ namespace PKHeX.Core
byte[] newSAV = EncryptColosseum(slot, digest);
// Put save slot back in original save data
byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[]) State.BAK.Clone();
byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone();
Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length);
return newFile;
}
@ -128,8 +130,7 @@ namespace PKHeX.Core
protected override SaveFile CloneInternal()
{
var data = GetInnerData();
var sav = MemoryCard is not null ? new SAV3Colosseum(data, MemoryCard) : new SAV3Colosseum(data);
return sav;
return new SAV3Colosseum(data) { MemoryCard = MemoryCard };
}
protected override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;

View file

@ -14,11 +14,10 @@ namespace PKHeX.Core
public override string Extension => this.GCExtension();
public override PersonalTable Personal => PersonalTable.RS;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public SAV3GCMemoryCard? MemoryCard { get; }
public SAV3GCMemoryCard? MemoryCard { get; init; }
public readonly bool Japanese = false; // todo?
public SAV3RSBox(byte[] data, SAV3GCMemoryCard memCard) : this(data, memCard.Data) => MemoryCard = memCard;
public SAV3RSBox(byte[] data) : this(data, (byte[])data.Clone()) { }
public SAV3RSBox(byte[] data, SAV3GCMemoryCard memCard) : this(data) => MemoryCard = memCard;
public SAV3RSBox() : base(SaveUtil.SIZE_G3BOX)
{
@ -27,7 +26,7 @@ namespace PKHeX.Core
ClearBoxes();
}
private SAV3RSBox(byte[] data, byte[] bak) : base(data, bak)
public SAV3RSBox(byte[] data) : base(data)
{
Blocks = ReadBlocks(data);
InitializeData();

View file

@ -11,7 +11,7 @@ namespace PKHeX.Core
{
protected internal override string ShortSummary => $"{OT} ({Version}) #{SaveCount:0000}";
public override string Extension => this.GCExtension();
public SAV3GCMemoryCard? MemoryCard { get; }
public SAV3GCMemoryCard? MemoryCard { get; init; }
private const int SLOT_SIZE = 0x28000;
private const int SLOT_START = 0x6000;
@ -27,11 +27,11 @@ namespace PKHeX.Core
public int MaxShadowID => ShadowInfo.Count;
private int OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_PouchCologne, OFS_PouchDisc;
private readonly int[] subOffsets = new int[16];
public SAV3XD(byte[] data, SAV3GCMemoryCard memCard) : this(data, memCard.Data) => MemoryCard = memCard;
public SAV3XD(byte[] data) : this(data, (byte[])data.Clone()) { }
private readonly byte[] BAK;
public SAV3XD() : base(SaveUtil.SIZE_G3XD)
{
BAK = Array.Empty<byte>();
// create fake objects
StrategyMemo = new StrategyMemo();
ShadowInfo = new ShadowInfoTableXD(false);
@ -39,8 +39,9 @@ namespace PKHeX.Core
ClearBoxes();
}
private SAV3XD(byte[] data, byte[] bak) : base(data, bak)
public SAV3XD(byte[] data) : base(data)
{
BAK = data;
InitializeData(out StrategyMemo, out ShadowInfo);
Initialize();
}
@ -141,7 +142,7 @@ namespace PKHeX.Core
byte[] newSAV = GeniusCrypto.Encrypt(Data, 0x10, 0x27FD8, keys);
// Put save slot back in original save data
byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[]) State.BAK.Clone();
byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[]) BAK.Clone();
Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length);
return newFile;
}
@ -150,8 +151,7 @@ namespace PKHeX.Core
protected override SaveFile CloneInternal()
{
var data = GetInnerData();
var sav = MemoryCard is not null ? new SAV3XD(data, MemoryCard) : new SAV3XD(data);
return sav;
return new SAV3XD(data) { MemoryCard = MemoryCard };
}
protected override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED;

View file

@ -19,9 +19,8 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray
Initialize();
}
private SAV8LA(byte[] data, IReadOnlyList<SCBlock> blocks) : base(data)
private SAV8LA(IReadOnlyList<SCBlock> blocks) : base(Array.Empty<byte>())
{
Data = Array.Empty<byte>();
AllBlocks = blocks;
Blocks = new SaveBlockAccessor8LA(this);
Initialize();
@ -112,7 +111,7 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray
var blockCopy = new SCBlock[AllBlocks.Count];
for (int i = 0; i < AllBlocks.Count; i++)
blockCopy[i] = AllBlocks[i].Clone();
return new SAV8LA(State.BAK, blockCopy);
return new SAV8LA(blockCopy);
}
public override int MaxMoveID => Legal.MaxMoveID_8a;

View file

@ -17,7 +17,7 @@ namespace PKHeX.Core
Initialize();
}
private SAV8SWSH(byte[] data, IReadOnlyList<SCBlock> blocks) : base(data)
private SAV8SWSH(IReadOnlyList<SCBlock> blocks) : base(Array.Empty<byte>())
{
Data = Array.Empty<byte>();
AllBlocks = blocks;
@ -109,7 +109,7 @@ namespace PKHeX.Core
var blockCopy = new SCBlock[AllBlocks.Count];
for (int i = 0; i < AllBlocks.Count; i++)
blockCopy[i] = AllBlocks[i].Clone();
return new SAV8SWSH(State.BAK, blockCopy);
return new SAV8SWSH(blockCopy);
}
private int m_spec, m_item, m_move, m_abil;

View file

@ -16,21 +16,17 @@ namespace PKHeX.Core
public SaveFileState State { get; }
public SaveFileMetadata Metadata { get; private set; }
protected SaveFile(byte[] data, byte[] bak, bool exportable = true)
protected SaveFile(byte[] data, bool exportable = true)
{
Data = data;
State = new SaveFileState(bak, exportable);
State = new SaveFileState(exportable);
Metadata = new SaveFileMetadata(this);
}
protected SaveFile(byte[] data, bool exportable = true) : this(data, (byte[])data.Clone(), exportable)
{
}
protected SaveFile(int size = 0)
{
Data = size == 0 ? Array.Empty<byte>() : new byte[size];
State = new SaveFileState(Array.Empty<byte>(), false);
State = new SaveFileState(false);
Metadata = new SaveFileMetadata(this);
}

View file

@ -18,14 +18,8 @@
/// </remarks>
public readonly bool Exportable;
/// <summary>
/// Original Binary Data the save file was loaded with.
/// </summary>
public readonly byte[] BAK;
public SaveFileState(byte[] bak, bool exportable = true)
public SaveFileState(bool exportable = true)
{
BAK = bak;
Exportable = exportable;
}
}

View file

@ -650,9 +650,9 @@ namespace PKHeX.Core
switch (memCard.SelectedGameVersion)
{
// Side Games
case COLO: sav = new SAV3Colosseum(data, memCard); break;
case XD: sav = new SAV3XD(data, memCard); break;
case RSBOX: sav = new SAV3RSBox(data, memCard); break;
case COLO: sav = new SAV3Colosseum(data) { MemoryCard = memCard }; break;
case XD: sav = new SAV3XD(data) { MemoryCard = memCard }; break;
case RSBOX: sav = new SAV3RSBox(data, memCard) { MemoryCard = memCard }; break;
// No pattern matched
default: return null;
@ -821,19 +821,5 @@ namespace PKHeX.Core
/// <param name="size">Size in bytes of the save data</param>
/// <returns>A boolean indicating whether or not the save data size is valid.</returns>
public static bool IsSizeValid(int size) => Sizes.Contains(size) || Handlers.Any(z => z.IsRecognized(size));
/// <summary>
/// Force loads the provided <see cref="sav"/> to the requested <see cref="version"/>.
/// </summary>
/// <param name="sav">SaveFile data to force</param>
/// <param name="version"> Version to retrieve for</param>
/// <returns>New <see cref="SaveFile"/> object.</returns>
public static SAV3 GetG3SaveOverride(SaveFile sav, GameVersion version) => version switch // Reset save file info
{
R or S or RS => new SAV3RS(sav.State.BAK),
E => new SAV3E(sav.State.BAK),
FR or LG or FRLG => new SAV3FRLG(sav.State.BAK),
_ => throw new ArgumentOutOfRangeException(nameof(version)),
};
}
}

View file

@ -778,14 +778,14 @@ namespace PKHeX.WinForms.Controls
public bool ExportBackup()
{
if (!SAV.State.Exportable)
if (!SAV.State.Exportable || SAV.Metadata.FilePath is not { } file)
return false;
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.State.BAK);
File.Copy(file, path);
WinFormsUtil.Alert(MsgSaveBackup, path);
return true;
@ -1110,7 +1110,8 @@ namespace PKHeX.WinForms.Controls
{
// Generational Interface
ToggleSecrets(sav, HideSecretDetails);
B_VerifyCHK.Visible = Menu_ExportBAK.Visible = SAV.State.Exportable;
B_VerifyCHK.Visible = SAV.State.Exportable;
Menu_ExportBAK.Visible = SAV.State.Exportable && SAV.Metadata.FilePath is not null;
if (sav is SAV4BR br)
{

View file

@ -821,8 +821,11 @@ namespace PKHeX.WinForms
// If backup folder exists, save a backup.
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);
{
var src = sav.Metadata.FilePath;
if (src is { } x && File.Exists(x))
File.Copy(x, backupName);
}
if (!FileUtil.IsFileLocked(path))
return true;
@ -846,18 +849,17 @@ namespace PKHeX.WinForms
if (dialog.Result is GameVersion.Invalid)
return false;
var s = SaveUtil.GetG3SaveOverride(sav, dialog.Result);
var origin = s3.Metadata.FilePath;
if (origin is not null)
s.Metadata.SetExtraInfo(origin);
sav = s;
if (sav is SAV3FRLG frlg)
var s = s3.ForceLoad(dialog.Result);
if (s is SAV3FRLG frlg)
{
bool result = frlg.ResetPersonal(dialog.Result);
if (!result)
return false;
}
var origin = sav.Metadata.FilePath;
if (origin is not null)
s.Metadata.SetExtraInfo(origin);
sav = s;
}
else if (s3 is SAV3FRLG frlg) // IndeterminateSubVersion
{