diff --git a/PKHeX/PKHeX.csproj b/PKHeX/PKHeX.csproj index 4af47f6a3..db1be6497 100644 --- a/PKHeX/PKHeX.csproj +++ b/PKHeX/PKHeX.csproj @@ -90,6 +90,7 @@ + @@ -115,6 +116,7 @@ + @@ -462,6 +464,7 @@ + diff --git a/PKHeX/PKM/PK1.cs b/PKHeX/PKM/PK1.cs index ffe725310..07d4365a8 100644 --- a/PKHeX/PKM/PK1.cs +++ b/PKHeX/PKM/PK1.cs @@ -186,7 +186,7 @@ namespace PKHeX #endregion } - class PokemonList + class PokemonList1 { internal const int CAPACITY_DAYCARE = 1; internal const int CAPACITY_PARTY = 6; @@ -225,7 +225,7 @@ namespace PKHeX return new[] { (byte)0 }.Concat(Enumerable.Repeat((byte)0xFF, cap + 1)).Concat(Enumerable.Repeat((byte)0, getEntrySize(c) * cap)).Concat(Enumerable.Repeat((byte)0x50, (is_JP ? PK1.STRLEN_J : PK1.STRLEN_U) * 2 * cap)).ToArray(); } - public PokemonList(byte[] d, CapacityType c = CapacityType.Single, bool jp = false) + public PokemonList1(byte[] d, CapacityType c = CapacityType.Single, bool jp = false) { Japanese = jp; Data = d ?? getEmptyList(c, Japanese); @@ -248,13 +248,13 @@ namespace PKHeX } } - public PokemonList(CapacityType c = CapacityType.Single, bool jp = false) + public PokemonList1(CapacityType c = CapacityType.Single, bool jp = false) : this(null, c, jp) { Count = 1; } - public PokemonList(PK1 pk) + public PokemonList1(PK1 pk) : this(CapacityType.Single, pk.Japanese) { this[0] = pk; diff --git a/PKHeX/PersonalInfo/PersonalInfoG1.cs b/PKHeX/PersonalInfo/PersonalInfoG1.cs index f2ae6ff06..7d4d8bbc0 100644 --- a/PKHeX/PersonalInfo/PersonalInfoG1.cs +++ b/PKHeX/PersonalInfo/PersonalInfoG1.cs @@ -47,6 +47,15 @@ namespace PKHeX public int Move4 { get { return Data[0x0D]; } set { Data[0x0D] = (byte)value; } } public override int EXPGrowth { get { return Data[0x13]; } set { Data[0x13] = (byte)value; } } + // EV Yields are just aliases for base stats in Gen I + public override int EV_HP { get { return HP; } set { } } + public override int EV_ATK { get { return ATK; } set { } } + public override int EV_DEF { get { return DEF; } set { } } + public override int EV_SPE { get { return SPE; } set { } } + public int EV_SPC { get { return SPC; } set { } } + public override int EV_SPA { get { return EV_SPC; } set { } } + public override int EV_SPD { get { return EV_SPC; } set { } } + // Future game values, unused public override int[] Items { get { return new[] { 0, 0 }; } set { } } public override int[] EggGroups { get { return new[] { 0, 0 }; } set { } } @@ -56,12 +65,5 @@ namespace PKHeX public override int BaseFriendship { get { return 0; } set { } } public override int EscapeRate { get { return 0; } set { } } public override int Color { get { return 0; } set { } } - private int EVYield { get { return 0; } set { } } - public override int EV_HP { get { return 0; } set { } } - public override int EV_ATK { get { return 0; } set { } } - public override int EV_DEF { get { return 0; } set { } } - public override int EV_SPE { get { return 0; } set { } } - public override int EV_SPA { get { return 0; } set { } } - public override int EV_SPD { get { return 0; } set { } } } } diff --git a/PKHeX/Saves/Inventory.cs b/PKHeX/Saves/Inventory.cs index 036bb9e39..c49391632 100644 --- a/PKHeX/Saves/Inventory.cs +++ b/PKHeX/Saves/Inventory.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace PKHeX { @@ -23,7 +24,8 @@ namespace PKHeX public readonly InventoryType Type; public readonly ushort[] LegalItems; public readonly int MaxCount; - private readonly int Offset; + public int Count => Items.Count(it => it.Count > 0); + public readonly int Offset; private readonly int PouchDataSize; public uint SecurityKey { private get; set; } // = 0 // Gen3 Only public InventoryItem[] Items; diff --git a/PKHeX/Saves/SAV1.cs b/PKHeX/Saves/SAV1.cs new file mode 100644 index 000000000..30e0cad31 --- /dev/null +++ b/PKHeX/Saves/SAV1.cs @@ -0,0 +1,312 @@ +using System; +using System.Globalization; +using System.Linq; + +namespace PKHeX +{ + public sealed class SAV1 : SaveFile + { + public override string BAKName => $"{FileName} [{OT} ({Version})" +/* - {LastSavedTime}*/ "].bak"; + public override string Filter => "SAV File|*.sav"; + public override string Extension => ".sav"; + + public SAV1(byte[] data = null) + { + Data = data == null ? new byte[SaveUtil.SIZE_G1RAW] : (byte[])data.Clone(); + BAK = (byte[])Data.Clone(); + Exportable = !Data.SequenceEqual(new byte[Data.Length]); + + if (data == null) + Version = GameVersion.RBY; + else Version = SaveUtil.getIsG1SAV(Data); + if (Version == GameVersion.Invalid) + return; + + Japanese = SaveUtil.getIsG1SAVJ(data); + + OFS_PouchHeldItem = (Japanese ? 0x25C4 : 0x25C9); + OFS_PCItem = (Japanese ? 0x27DC : 0x27E6); + Personal = PersonalTable.RBY; + + if (!Exportable) + resetBoxes(); + } + + private const int SIZE_RESERVED = 0x10000; // unpacked box data + public override byte[] Write(bool DSV) + { + + setChecksums(); + return Data; + } + + + // Configuration + public override SaveFile Clone() { return new SAV1(Data); } + + public override int SIZE_STORED => PKX.SIZE_1STORED; + public override int SIZE_PARTY => PKX.SIZE_1PARTY; + public override PKM BlankPKM => new PK1(); + protected override Type PKMType => typeof(PK1); + + public override int MaxMoveID => 165; + public override int MaxSpeciesID => 151; + public override int MaxAbilityID => 0; + public override int MaxItemID => 255; + public override int MaxBallID => 0; + public override int MaxGameID => 99; // What do I set this to...? + + public override int BoxCount => Japanese ? 8 : 12; + public override int MaxEV => 65535; + public override int Generation => 1; + protected override int GiftCountMax => 0; + public override int OTLength => Japanese ? 5 : 10; + public override int NickLength => Japanese ? 5 : 10; + + public override bool HasParty => true; + + // Checksums + protected override void setChecksums() + { + int CHECKSUM_OFS = Japanese ? 0x3594 : 0x3523; + Data[CHECKSUM_OFS] = 0; + uint chksum = 0; + for (int i = 0x2598; i < CHECKSUM_OFS; i++) + { + chksum += Data[i]; + } + + chksum = ~chksum; + chksum &= 0xFF; + + Data[CHECKSUM_OFS] = (byte)chksum; + } + public override bool ChecksumsValid + { + get + { + int CHECKSUM_OFS = Japanese ? 0x3594 : 0x3523; + Data[CHECKSUM_OFS] = 0; + uint chksum = 0; + for (int i = 0x2598; i < CHECKSUM_OFS; i++) + { + chksum += Data[i]; + } + + chksum = ~chksum; + chksum &= 0xFF; + + return Data[CHECKSUM_OFS] == (byte)chksum; + } + } + public override string ChecksumInfo + { + get + { + return ChecksumsValid ? "Checksum valid." : "Checksum invalid"; + } + } + + // Trainer Info + public override GameVersion Version { get; protected set; } + + private int StringLength => Japanese ? PK1.STRLEN_J : PK1.STRLEN_U; + + public override string OT + { + get { return PKX.getG3Str(Data.Skip(0x2598).Take(StringLength).ToArray(), Japanese); } + set + { + byte[] strdata = PKX.setG1Str(value, Japanese); + if (strdata.Length > StringLength) + throw new ArgumentOutOfRangeException("OT Name too long for given save file."); + strdata.CopyTo(Data, 0x2598); + } + } + public override int Gender + { + get { return 0; } + set { } + } + public override ushort TID + { + get { return Util.SwapEndianness(BitConverter.ToUInt16(Data, Japanese ? 0x25FB : 0x2605)); } + set { BitConverter.GetBytes(Util.SwapEndianness(value)).CopyTo(Data, Japanese ? 0x25FB : 0x2605); } + } + public override ushort SID + { + get { return 0; } + set { } + } + public override int PlayedHours + { + get { return BitConverter.ToUInt16(Data, Japanese ? 0x2CA0 : 0x2CED); } + set { BitConverter.GetBytes((ushort)value).CopyTo(Data, Japanese ? 0x2CA0 : 0x2CED); } + } + public override int PlayedMinutes + { + get { return Data[Japanese ? 0x2CA2 : 0x2CEF]; } + set { Data[Japanese ? 0x2CA2 : 0x2CEF] = (byte)value; } + } + public override int PlayedSeconds + { + get { return Data[Japanese ? 0x2CA3 : 0x2CF0]; } + set { Data[Japanese ? 0x2CA3 : 0x2CF0] = (byte)value; } + } + + public override uint Money + { + get { return uint.Parse((Util.SwapEndianness(BitConverter.ToUInt32(Data, Japanese ? 0x25EE : 0x25F3)) >> 8).ToString("X6")); } + set + { + BitConverter.GetBytes(Util.SwapEndianness(uint.Parse(value.ToString("000000"), NumberStyles.HexNumber))).Skip(1).ToArray().CopyTo(Data, Japanese ? 0x25EE : 0x25F3); + } + } + public uint Coin + { + get + { + return uint.Parse(Util.SwapEndianness(BitConverter.ToUInt16(Data, Japanese ? 0x2846 : 0x2850)).ToString("X4")); + } + set + { + BitConverter.GetBytes(Util.SwapEndianness(ushort.Parse(value.ToString("0000"), NumberStyles.HexNumber))).ToArray().CopyTo(Data, Japanese ? 0x2846 : 0x2850); + } + } + + private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries; + public override InventoryPouch[] Inventory + { + get + { + ushort[] legalItems = LegalItems; + InventoryPouch[] pouch = + { + new InventoryPouch(InventoryType.Items, legalItems, 99, OFS_PouchHeldItem + 2, 20), + new InventoryPouch(InventoryType.Items, legalItems, 99, OFS_PCItem + 2, 50), + }; + foreach (var p in pouch) + { + p.getPouch(ref Data); + int ofs = 0; + for (int i = 0; i < p.Count; i++) + { + while (p.Items[ofs].Count == 0) + ofs++; + p.Items[i] = p.Items[ofs++]; + } + while (ofs < p.MaxCount) + p.Items[ofs++] = new InventoryItem { Count = 0, Index = 0 }; + } + return pouch; + } + set + { + foreach (var p in value) + { + int ofs = 0; + for (int i = 0; i < p.Count; i++) + { + while (p.Items[ofs].Count == 0) + ofs++; + p.Items[i] = p.Items[ofs++]; + } + while (ofs < p.MaxCount) + p.Items[ofs++] = new InventoryItem { Count = 0, Index = 0 }; + p.setPouch(ref Data); + Data[p.Offset - 2] = (byte)p.Count; + } + } + } + public override int getDaycareSlotOffset(int loc = 0, int slot = 0) + { + return Daycare; + } + public override ulong? getDaycareRNGSeed(int loc) + { + return null; + } + public override uint? getDaycareEXP(int loc = 0, int slot = 0) + { + return null; + } + public override bool? getDaycareOccupied(int loc, int slot) + { + return null; + } + public override void setDaycareEXP(int loc, int slot, uint EXP) + { + + } + public override void setDaycareOccupied(int loc, int slot, bool occupied) + { + + } + + // Storage + public override int PartyCount + { + get { return Data[Japanese ? 0x2ED5 : 0x2F2C]; } + protected set + { + Data[Japanese ? 0x2ED5 : 0x2F2C] = (byte)value; + } + } + public override int getBoxOffset(int box) + { + throw new NotImplementedException(); + } + public override int getPartyOffset(int slot) + { + throw new NotImplementedException(); + } + public override int CurrentBox + { + get { return Data[Japanese ? 0x2842 : 0x284C]; } + set { Data[Japanese ? 0x2842 : 0x284C] = (byte)value; } + } + public override int getBoxWallpaper(int box) + { + return 0; + } + public override string getBoxName(int box) + { + int boxNum = box + 1; + return $"Box {boxNum}"; + } + public override void setBoxName(int box, string value) + { + + } + public override PKM getPKM(byte[] data) + { + if (data.Length == PKX.SIZE_1JLIST || data.Length == PKX.SIZE_1ULIST) + return new PokemonList1(data, PokemonList1.CapacityType.Single, Japanese)[0]; + return new PK1(data); + } + public override byte[] decryptPKM(byte[] data) + { + return data; + } + + protected override void setDex(PKM pkm) + { + if (pkm.Species == 0) + return; + if (pkm.Species > MaxSpeciesID) + return; + if (Version == GameVersion.Unknown) + return; + + int bit = pkm.Species - 1; + int ofs = bit >> 3; + byte bitval = (byte)(1 << (bit % 7)); + + // Set the Captured Flag + Data[(Japanese ? 0x259E : 0x25A3) + ofs] |= bitval; + + // Set the Seen Flag + Data[(Japanese ? 0x25B1 : 0x25B6) + ofs] |= bitval; + } + } +} diff --git a/PKHeX/Saves/SaveFile.cs b/PKHeX/Saves/SaveFile.cs index 8db12ea12..84533da8c 100644 --- a/PKHeX/Saves/SaveFile.cs +++ b/PKHeX/Saves/SaveFile.cs @@ -58,6 +58,7 @@ namespace PKHeX public bool E => Version == GameVersion.E; public bool FRLG => Version == GameVersion.FRLG; public bool RS => Version == GameVersion.RS; + public bool RBY => Version == GameVersion.RBY; public virtual int MaxMoveID => int.MaxValue; public virtual int MaxSpeciesID => int.MaxValue; @@ -260,6 +261,7 @@ namespace PKHeX protected int OFS_PouchBalls { get; set; } = int.MinValue; protected int OFS_BattleItems { get; set; } = int.MinValue; protected int OFS_MailItems { get; set; } = int.MinValue; + protected int OFS_PCItem { get; set; } = int.MinValue; // Mystery Gift protected virtual bool[] MysteryGiftReceivedFlags { get { return null; } set { } } diff --git a/PKHeX/Saves/SaveUtil.cs b/PKHeX/Saves/SaveUtil.cs index f4c54989b..f10c94de9 100644 --- a/PKHeX/Saves/SaveUtil.cs +++ b/PKHeX/Saves/SaveUtil.cs @@ -45,6 +45,8 @@ namespace PKHeX internal const int SIZE_G4RAW = 0x80000; internal const int SIZE_G3RAW = 0x20000; internal const int SIZE_G3RAWHALF = 0x10000; + internal const int SIZE_G1RAW = 0x8000; + internal const int SIZE_G1BAT = 0x802C; internal static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); @@ -53,6 +55,8 @@ namespace PKHeX /// Version Identifier or Invalid if type cannot be determined. public static int getSAVGeneration(byte[] data) { + if (getIsG1SAV(data) != GameVersion.Invalid) + return 1; if (getIsG3SAV(data) != GameVersion.Invalid) return 3; if (getIsG4SAV(data) != GameVersion.Invalid) @@ -63,6 +67,47 @@ namespace PKHeX return 6; return -1; } + /// Determines the type of 1st gen save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + public static GameVersion getIsG1SAV(byte[] data) + { + if (data.Length != SIZE_G1RAW && data.Length != SIZE_G1BAT) + return GameVersion.Invalid; + + // Check if it's not an american save or a japanese save + if (!(getIsG1SAVU(data) || getIsG1SAVJ(data))) + return GameVersion.Invalid; + // I can't actually detect which game version, because it's not stored anywhere. + // If you can think of anything to do here, please implement :) + return GameVersion.RBY; + } + /// Determines if 1st gen save is non-japanese + /// Save data of which to determine the region + /// True if a valid non-japanese save, False otherwise. + public static bool getIsG1SAVU(byte[] data) + { + foreach (int ofs in new[] { 0x2F2C, 0x30C0 }) + { + byte num_entries = data[ofs]; + if (num_entries > 20 || data[ofs + 1 + num_entries] != 0xFF) + return false; + } + return true; + } + /// Determines if 1st gen save is japanese + /// Save data of which to determine the region + /// True if a valid japanese save, False otherwise. + public static bool getIsG1SAVJ(byte[] data) + { + foreach (int ofs in new[] { 0x2ED5, 0x302D }) + { + byte num_entries = data[ofs]; + if (num_entries > 30 || data[ofs + 1 + num_entries] != 0xFF) + return false; + } + return true; + } /// Determines the type of 3th gen save /// Save data of which to determine the type /// Version Identifier or Invalid if type cannot be determined.