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.