mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-15 00:37:11 +00:00
4d1ef52b34
Will make it easier to add projects in the future, like unit tests
490 lines
19 KiB
C#
490 lines
19 KiB
C#
using System;
|
|
using System.Linq;
|
|
|
|
namespace PKHeX
|
|
{
|
|
// Base Class for Save Files
|
|
public abstract class SaveFile
|
|
{
|
|
internal static bool SetUpdateDex = true;
|
|
internal static bool SetUpdatePKM = true;
|
|
|
|
// General Object Properties
|
|
public byte[] Data;
|
|
public bool Edited;
|
|
public string FileName, FilePath;
|
|
public abstract string BAKName { get; }
|
|
public byte[] BAK { get; protected set; }
|
|
public bool Exportable { get; protected set; }
|
|
public abstract SaveFile Clone();
|
|
public abstract string Filter { get; }
|
|
public byte[] Footer { protected get; set; } = new byte[0]; // .dsv
|
|
public bool Japanese { protected get; set; }
|
|
|
|
// General PKM Properties
|
|
protected abstract Type PKMType { get; }
|
|
public abstract PKM getPKM(byte[] data);
|
|
public abstract PKM BlankPKM { get; }
|
|
public abstract byte[] decryptPKM(byte[] data);
|
|
public abstract int SIZE_STORED { get; }
|
|
public abstract int SIZE_PARTY { get; }
|
|
public abstract int MaxEV { get; }
|
|
public ushort[] HeldItems { get; protected set; }
|
|
|
|
// General SAV Properties
|
|
public virtual byte[] Write(bool DSV)
|
|
{
|
|
setChecksums();
|
|
if (Footer.Length > 0 && DSV)
|
|
return Data.Concat(Footer).ToArray();
|
|
return Data;
|
|
}
|
|
public virtual string MiscSaveChecks() { return ""; }
|
|
public virtual string MiscSaveInfo() { return ""; }
|
|
public virtual GameVersion Version { get; protected set; }
|
|
public abstract bool ChecksumsValid { get; }
|
|
public abstract string ChecksumInfo { get; }
|
|
public abstract int Generation { get; }
|
|
public PersonalTable Personal { get; set; }
|
|
|
|
public bool ORASDEMO => Data.Length == SaveUtil.SIZE_G6ORASDEMO;
|
|
public bool ORAS => Version == GameVersion.OR || Version == GameVersion.AS;
|
|
public bool XY => Version == GameVersion.X || Version == GameVersion.Y;
|
|
public bool B2W2 => Version == GameVersion.B2W2;
|
|
public bool BW => Version == GameVersion.BW;
|
|
public bool HGSS => Version == GameVersion.HGSS;
|
|
public bool Pt => Version == GameVersion.Pt;
|
|
public bool DP => Version == GameVersion.DP;
|
|
public bool E => Version == GameVersion.E;
|
|
public bool FRLG => Version == GameVersion.FRLG;
|
|
public bool RS => Version == GameVersion.RS;
|
|
|
|
public virtual int MaxMoveID => int.MaxValue;
|
|
public virtual int MaxSpeciesID => int.MaxValue;
|
|
public virtual int MaxAbilityID => int.MaxValue;
|
|
public virtual int MaxItemID => int.MaxValue;
|
|
public virtual int MaxBallID => int.MaxValue;
|
|
public virtual int MaxGameID => int.MaxValue;
|
|
|
|
// Flags
|
|
public bool HasWondercards => WondercardData > -1;
|
|
public bool HasSuperTrain => SuperTrain > -1;
|
|
public bool HasBerryField => BerryField > -1;
|
|
public bool HasHoF => HoF > -1;
|
|
public bool HasSecretBase => SecretBase > -1;
|
|
public bool HasPuff => Puff > -1;
|
|
public bool HasPSS => PSS > -1;
|
|
public bool HasOPower => OPower > -1;
|
|
public bool HasJPEG => JPEGData != null;
|
|
public bool HasBox => Box > -1;
|
|
public virtual bool HasParty => Party > -1;
|
|
public bool HasBattleBox => BattleBox > -1;
|
|
public bool HasFused => Fused > -1;
|
|
public bool HasGTS => GTS > -1;
|
|
public bool HasDaycare => Daycare > -1;
|
|
public virtual bool HasPokeDex => PokeDex > -1;
|
|
public virtual bool HasBoxWallpapers => PCLayout > -1;
|
|
public virtual bool HasSUBE => SUBE > -1 && !ORAS;
|
|
public virtual bool HasGeolocation => false;
|
|
public bool HasPokeBlock => ORAS && !ORASDEMO;
|
|
public bool HasEvents => EventFlags != null;
|
|
public bool HasLink => ORAS && !ORASDEMO || XY;
|
|
|
|
// Counts
|
|
protected virtual int GiftCountMax { get; } = int.MinValue;
|
|
protected virtual int GiftFlagMax { get; } = 0x800;
|
|
protected virtual int EventFlagMax { get; } = int.MinValue;
|
|
protected virtual int EventConstMax { get; } = int.MinValue;
|
|
public virtual int DaycareSeedSize { get; } = 0;
|
|
public abstract int OTLength { get; }
|
|
public abstract int NickLength { get; }
|
|
|
|
// Offsets
|
|
protected int Box { get; set; } = int.MinValue;
|
|
protected int Party { get; set; } = int.MinValue;
|
|
protected int Trainer1 { get; set; } = int.MinValue;
|
|
protected int Daycare { get; set; } = int.MinValue;
|
|
protected int WondercardData { get; set; } = int.MinValue;
|
|
protected int PCLayout { get; set; } = int.MinValue;
|
|
protected int EventFlag { get; set; } = int.MinValue;
|
|
protected int EventConst { get; set; } = int.MinValue;
|
|
|
|
public int GTS { get; protected set; } = int.MinValue;
|
|
public int BattleBox { get; protected set; } = int.MinValue;
|
|
public int Fused { get; protected set; } = int.MinValue;
|
|
public int SUBE { get; protected set; } = int.MinValue;
|
|
public int PokeDex { get; protected set; } = int.MinValue;
|
|
public int SuperTrain { get; protected set; } = int.MinValue;
|
|
public int SecretBase { get; protected set; } = int.MinValue;
|
|
public int Puff { get; protected set; } = int.MinValue;
|
|
public int PSS { get; protected set; } = int.MinValue;
|
|
public int BerryField { get; protected set; } = int.MinValue;
|
|
public int OPower { get; protected set; } = int.MinValue;
|
|
public int HoF { get; protected set; } = int.MinValue;
|
|
|
|
// SAV Properties
|
|
public PKM[] BoxData
|
|
{
|
|
get
|
|
{
|
|
PKM[] data = new PKM[BoxCount*30];
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
data[i] = getStoredSlot(getBoxOffset(i/30) + SIZE_STORED*(i%30));
|
|
data[i].Identifier = $"{getBoxName(i/30)}:{(i%30 + 1).ToString("00")}";
|
|
data[i].Box = i/30 + 1;
|
|
data[i].Slot = i%30 + 1;
|
|
}
|
|
return data;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
throw new ArgumentNullException();
|
|
if (value.Length != BoxCount*30)
|
|
throw new ArgumentException($"Expected {BoxCount*30}, got {value.Length}");
|
|
if (value.Any(pk => PKMType != pk.GetType()))
|
|
throw new ArgumentException($"Not {PKMType} array.");
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
setStoredSlot(value[i], getBoxOffset(i/30) + SIZE_STORED*(i%30));
|
|
}
|
|
}
|
|
public PKM[] PartyData
|
|
{
|
|
get
|
|
{
|
|
PKM[] data = new PKM[PartyCount];
|
|
for (int i = 0; i < data.Length; i++)
|
|
data[i] = getPartySlot(getPartyOffset(i));
|
|
return data;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
throw new ArgumentNullException();
|
|
if (value.Length == 0 || value.Length > 6)
|
|
throw new ArgumentException("Expected 1-6, got " + value.Length);
|
|
if (value.Any(pk => PKMType != pk.GetType()))
|
|
throw new ArgumentException($"Not {PKMType} array.");
|
|
if (value[0].Species == 0)
|
|
throw new ArgumentException("Can't have an empty first slot." + value.Length);
|
|
|
|
PKM[] newParty = value.Where(pk => pk.Species != 0).ToArray();
|
|
|
|
PartyCount = newParty.Length;
|
|
Array.Resize(ref newParty, 6);
|
|
|
|
for (int i = PartyCount; i < newParty.Length; i++)
|
|
newParty[i] = BlankPKM;
|
|
for (int i = 0; i < newParty.Length; i++)
|
|
setPartySlot(newParty[i], getPartyOffset(i));
|
|
}
|
|
}
|
|
public PKM[] BattleBoxData
|
|
{
|
|
get
|
|
{
|
|
if (Generation < 5)
|
|
return new PKM[0];
|
|
|
|
PKM[] data = new PKM[6];
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
data[i] = getStoredSlot(BattleBox + SIZE_STORED * i);
|
|
if (data[i].Species == 0)
|
|
return data.Take(i).ToArray();
|
|
}
|
|
return data;
|
|
}
|
|
}
|
|
|
|
public bool[] EventFlags
|
|
{
|
|
get
|
|
{
|
|
if (EventFlagMax < 0)
|
|
return null;
|
|
|
|
bool[] Flags = new bool[EventFlagMax];
|
|
for (int i = 0; i < Flags.Length; i++)
|
|
Flags[i] = (Data[EventFlag + i / 8] >> i % 8 & 0x1) == 1;
|
|
return Flags;
|
|
}
|
|
set
|
|
{
|
|
if (EventFlagMax < 0)
|
|
return;
|
|
if (value.Length != EventFlagMax)
|
|
return;
|
|
|
|
byte[] data = new byte[value.Length / 8];
|
|
for (int i = 0; i < value.Length; i++)
|
|
if (value[i])
|
|
data[i >> 3] |= (byte)(1 << (i & 7));
|
|
|
|
data.CopyTo(Data, EventFlag);
|
|
}
|
|
}
|
|
public ushort[] EventConsts
|
|
{
|
|
get
|
|
{
|
|
if (EventConstMax < 0)
|
|
return null;
|
|
|
|
ushort[] Constants = new ushort[EventConstMax];
|
|
for (int i = 0; i < Constants.Length; i++)
|
|
Constants[i] = BitConverter.ToUInt16(Data, EventConst + i * 2);
|
|
return Constants;
|
|
}
|
|
set
|
|
{
|
|
if (EventConstMax < 0)
|
|
return;
|
|
if (value.Length != EventConstMax)
|
|
return;
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
BitConverter.GetBytes(value[i]).CopyTo(Data, EventConst + i * 2);
|
|
}
|
|
}
|
|
|
|
// Inventory
|
|
public abstract InventoryPouch[] Inventory { get; set; }
|
|
protected int OFS_PouchHeldItem { get; set; } = int.MinValue;
|
|
protected int OFS_PouchKeyItem { get; set; } = int.MinValue;
|
|
protected int OFS_PouchMedicine { get; set; } = int.MinValue;
|
|
protected int OFS_PouchTMHM { get; set; } = int.MinValue;
|
|
protected int OFS_PouchBerry { get; set; } = int.MinValue;
|
|
protected int OFS_PouchBalls { get; set; } = int.MinValue;
|
|
protected int OFS_BattleItems { get; set; } = int.MinValue;
|
|
protected int OFS_MailItems { get; set; } = int.MinValue;
|
|
|
|
// Mystery Gift
|
|
protected virtual bool[] MysteryGiftReceivedFlags { get { return null; } set { } }
|
|
protected virtual MysteryGift[] MysteryGiftCards { get { return null; } set { } }
|
|
public virtual MysteryGiftAlbum GiftAlbum
|
|
{
|
|
get
|
|
{
|
|
return new MysteryGiftAlbum
|
|
{
|
|
Flags = MysteryGiftReceivedFlags,
|
|
Gifts = MysteryGiftCards
|
|
};
|
|
}
|
|
set
|
|
{
|
|
MysteryGiftReceivedFlags = value.Flags;
|
|
MysteryGiftCards = value.Gifts;
|
|
}
|
|
}
|
|
|
|
public virtual bool BattleBoxLocked { get { return false; } set { } }
|
|
public virtual string JPEGTitle => null;
|
|
public virtual byte[] JPEGData => null;
|
|
public virtual int Country { get { return -1; } set { } }
|
|
public virtual int ConsoleRegion { get { return -1; } set { } }
|
|
public virtual int SubRegion { get { return -1; } set { } }
|
|
|
|
// Trainer Info
|
|
public abstract int Gender { get; set; }
|
|
public virtual int Language { get { return -1; } set { } }
|
|
public virtual int Game { get { return -1; } set { } }
|
|
public abstract ushort TID { get; set; }
|
|
public abstract ushort SID { get; set; }
|
|
public abstract string OT { get; set; }
|
|
public abstract int PlayedHours { get; set; }
|
|
public abstract int PlayedMinutes { get; set; }
|
|
public abstract int PlayedSeconds { get; set; }
|
|
public virtual int SecondsToStart { get; set; }
|
|
public virtual int SecondsToFame { get; set; }
|
|
public abstract uint Money { get; set; }
|
|
public abstract int BoxCount { get; }
|
|
public abstract int PartyCount { get; protected set; }
|
|
public abstract int CurrentBox { get; set; }
|
|
public abstract string Extension { get; }
|
|
|
|
// Varied Methods
|
|
protected abstract void setChecksums();
|
|
public abstract int getBoxOffset(int box);
|
|
public abstract int getPartyOffset(int slot);
|
|
public abstract int getBoxWallpaper(int box);
|
|
public abstract string getBoxName(int box);
|
|
public abstract void setBoxName(int box, string val);
|
|
|
|
// Daycare
|
|
public int DaycareIndex = 0;
|
|
public abstract int getDaycareSlotOffset(int loc, int slot);
|
|
public abstract uint? getDaycareEXP(int loc, int slot);
|
|
public virtual ulong? getDaycareRNGSeed(int loc) { return null; }
|
|
public virtual bool? getDaycareHasEgg(int loc) { return false; }
|
|
public abstract bool? getDaycareOccupied(int loc, int slot);
|
|
|
|
public abstract void setDaycareEXP(int loc, int slot, uint EXP);
|
|
public virtual void setDaycareRNGSeed(int loc, ulong seed) { }
|
|
public virtual void setDaycareHasEgg(int loc, bool hasEgg) { }
|
|
public abstract void setDaycareOccupied(int loc, int slot, bool occupied);
|
|
|
|
// Storage
|
|
public PKM getPartySlot(int offset)
|
|
{
|
|
return getPKM(decryptPKM(getData(offset, SIZE_PARTY)));
|
|
}
|
|
public PKM getStoredSlot(int offset)
|
|
{
|
|
return getPKM(decryptPKM(getData(offset, SIZE_STORED)));
|
|
}
|
|
public void setPartySlot(PKM pkm, int offset, bool? trade = null, bool? dex = null)
|
|
{
|
|
if (pkm == null) return;
|
|
if (pkm.GetType() != PKMType)
|
|
throw new InvalidCastException($"PKM Format needs to be {PKMType} when setting to a Gen{Generation} Save File.");
|
|
if (trade ?? SetUpdatePKM)
|
|
setPKM(pkm);
|
|
if (dex ?? SetUpdateDex)
|
|
setDex(pkm);
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
if (getPartyOffset(i) == offset)
|
|
if (PartyCount <= i)
|
|
PartyCount = i + 1;
|
|
|
|
setData(pkm.EncryptedPartyData, offset);
|
|
Console.WriteLine("");
|
|
Edited = true;
|
|
}
|
|
public void setStoredSlot(PKM pkm, int offset, bool? trade = null, bool? dex = null)
|
|
{
|
|
if (pkm == null) return;
|
|
if (pkm.GetType() != PKMType)
|
|
throw new InvalidCastException($"PKM Format needs to be {PKMType} when setting to a Gen{Generation} Save File.");
|
|
if (trade ?? SetUpdatePKM)
|
|
setPKM(pkm);
|
|
if (dex ?? SetUpdateDex)
|
|
setDex(pkm);
|
|
|
|
setData(pkm.EncryptedBoxData, offset);
|
|
Edited = true;
|
|
}
|
|
public void setPartySlot(byte[] data, int offset, bool? trade = null, bool? dex = null)
|
|
{
|
|
if (data == null) return;
|
|
PKM pkm = getPKM(decryptPKM(data));
|
|
if (trade ?? SetUpdatePKM)
|
|
setPKM(pkm);
|
|
if (dex ?? SetUpdateDex)
|
|
setDex(pkm);
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
if (getPartyOffset(i) == offset)
|
|
if (PartyCount <= i)
|
|
PartyCount = i + 1;
|
|
|
|
setData(pkm.EncryptedPartyData, offset);
|
|
Edited = true;
|
|
}
|
|
public void setStoredSlot(byte[] data, int offset, bool? trade = null, bool? dex = null)
|
|
{
|
|
if (data == null) return;
|
|
PKM pkm = getPKM(decryptPKM(data));
|
|
if (trade ?? SetUpdatePKM)
|
|
setPKM(pkm);
|
|
if (dex ?? SetUpdateDex)
|
|
setDex(pkm);
|
|
|
|
setData(pkm.EncryptedBoxData, offset);
|
|
Edited = true;
|
|
}
|
|
public void deletePartySlot(int slot)
|
|
{
|
|
if (PartyCount <= slot) // beyond party range (or empty data already present)
|
|
return;
|
|
// Move all party slots down one
|
|
for (int i = slot + 1; i < 6; i++) // Slide slots down
|
|
{
|
|
int slotTo = getPartyOffset(i - 1);
|
|
int slotFrom = getPartyOffset(i);
|
|
setData(getData(slotFrom, SIZE_PARTY), slotTo);
|
|
}
|
|
setStoredSlot(BlankPKM, getPartyOffset(5), false, false);
|
|
PartyCount -= 1;
|
|
}
|
|
|
|
public void sortBoxes(int BoxStart = 0, int BoxEnd = -1)
|
|
{
|
|
PKM[] BD = BoxData;
|
|
var Section = BD.Skip(BoxStart*30);
|
|
if (BoxEnd > BoxStart)
|
|
Section = Section.Take(30*(BoxEnd - BoxStart));
|
|
|
|
var Sorted = Section
|
|
.OrderBy(p => p.Species == 0) // empty slots at end
|
|
.ThenBy(p => p.IsEgg) // eggs to the end
|
|
.ThenBy(p => p.Species) // species sorted
|
|
.ThenBy(p => p.IsNicknamed).ToArray();
|
|
|
|
Array.Copy(Sorted, 0, BD, BoxStart*30, Sorted.Length);
|
|
BoxData = BD;
|
|
}
|
|
public void resetBoxes(int BoxStart = 0, int BoxEnd = -1)
|
|
{
|
|
if (BoxEnd < 0)
|
|
BoxEnd = BoxCount;
|
|
for (int i = BoxStart; i < BoxEnd; i++)
|
|
{
|
|
int offset = getBoxOffset(i);
|
|
for (int p = 0; p < 30; p++)
|
|
setStoredSlot(BlankPKM, offset + SIZE_STORED * p);
|
|
}
|
|
}
|
|
|
|
public byte[] getPCBin() { return BoxData.SelectMany(pk => pk.EncryptedBoxData).ToArray(); }
|
|
public byte[] getBoxBin(int box) { return BoxData.Skip(box*30).Take(30).SelectMany(pk => pk.EncryptedBoxData).ToArray(); }
|
|
public bool setPCBin(byte[] data)
|
|
{
|
|
if (data.Length != getPCBin().Length)
|
|
return false;
|
|
|
|
// split up data to individual pkm
|
|
byte[][] pkdata = new byte[data.Length/SIZE_STORED][];
|
|
for (int i = 0; i < data.Length; i += SIZE_STORED)
|
|
pkdata[i/SIZE_STORED] = data.Skip(i).Take(SIZE_STORED).ToArray();
|
|
|
|
PKM[] pkms = BoxData;
|
|
for (int i = 0; i < pkms.Length; i++)
|
|
pkms[i].Data = decryptPKM(pkdata[i]);
|
|
BoxData = pkms;
|
|
return true;
|
|
}
|
|
public bool setBoxBin(byte[] data, int box)
|
|
{
|
|
if (data.Length != getBoxBin(box).Length)
|
|
return false;
|
|
|
|
byte[][] pkdata = new byte[data.Length / SIZE_STORED][];
|
|
for (int i = 0; i < data.Length; i += SIZE_STORED)
|
|
pkdata[i/SIZE_STORED] = data.Skip(i).Take(SIZE_STORED).ToArray();
|
|
|
|
PKM[] pkms = BoxData;
|
|
for (int i = 0; i < 30; i++)
|
|
pkms[box*30 + i].Data = decryptPKM(pkdata[i]);
|
|
BoxData = pkms;
|
|
return true;
|
|
}
|
|
|
|
protected virtual void setPKM(PKM pkm) { }
|
|
protected virtual void setDex(PKM pkm) { }
|
|
|
|
public byte[] getData(int Offset, int Length)
|
|
{
|
|
return Data.Skip(Offset).Take(Length).ToArray();
|
|
}
|
|
public void setData(byte[] input, int Offset)
|
|
{
|
|
input.CopyTo(Data, Offset);
|
|
Edited = true;
|
|
}
|
|
}
|
|
}
|