mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 12:33:06 +00:00
SAV1 skeleton implementation
This commit is contained in:
parent
e4ec1cb45f
commit
b2342cddb9
7 changed files with 378 additions and 12 deletions
|
@ -90,6 +90,7 @@
|
|||
<Compile Include="PersonalInfo\PersonalInfo.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoB2W2.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoBW.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoG1.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoG3.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoG4.cs" />
|
||||
<Compile Include="PersonalInfo\PersonalInfoORAS.cs" />
|
||||
|
@ -115,6 +116,7 @@
|
|||
<Compile Include="Saves\BlockInfo.cs" />
|
||||
<Compile Include="Saves\BoxWallpaper.cs" />
|
||||
<Compile Include="Saves\Inventory.cs" />
|
||||
<Compile Include="Saves\SAV1.cs" />
|
||||
<Compile Include="Saves\SAV3.cs" />
|
||||
<Compile Include="Saves\SAV4.cs" />
|
||||
<Compile Include="Saves\SAV5.cs" />
|
||||
|
@ -462,6 +464,7 @@
|
|||
<None Include="Resources\byte\personal_hgss" />
|
||||
<None Include="Resources\byte\personal_lg" />
|
||||
<None Include="Resources\byte\personal_pt" />
|
||||
<None Include="Resources\byte\personal_rby" />
|
||||
<None Include="Resources\byte\personal_rs" />
|
||||
<None Include="Resources\byte\personal_xy" />
|
||||
<None Include="Resources\byte\wc6.pkl" />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 { } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
312
PKHeX/Saves/SAV1.cs
Normal file
312
PKHeX/Saves/SAV1.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { } }
|
||||
|
|
|
@ -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
|
|||
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>Determines the type of 1st gen save</summary>
|
||||
/// <param name="data">Save data of which to determine the type</param>
|
||||
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>Determines if 1st gen save is non-japanese</summary>
|
||||
/// <param name="data">Save data of which to determine the region</param>
|
||||
/// <returns>True if a valid non-japanese save, False otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>Determines if 1st gen save is japanese</summary>
|
||||
/// <param name="data">Save data of which to determine the region</param>
|
||||
/// <returns>True if a valid japanese save, False otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>Determines the type of 3th gen save</summary>
|
||||
/// <param name="data">Save data of which to determine the type</param>
|
||||
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
|
||||
|
|
Loading…
Reference in a new issue