Add offset table for sav1

calling Japanese ? X : Y on every single access is too excessive, just
compute one layout for each type whenever the first sav1 requires it.

could probably reduce the amount of properties by reusing others + shift
value; offsets are different due to string lengths being different
This commit is contained in:
Kurt 2018-08-05 20:27:25 -07:00
parent fc73eee570
commit 0296f98777
2 changed files with 165 additions and 59 deletions

View file

@ -11,6 +11,7 @@ namespace PKHeX.Core
protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}";
public override string Filter => "SAV File|*.sav|All Files|*.*";
public override string Extension => ".sav";
public override string[] PKMExtensions => PKM.Extensions.Where(f =>
{
int gen = f.Last() - 0x30;
@ -33,11 +34,12 @@ namespace PKHeX.Core
if (Starter != 0)
Version = Yellow ? GameVersion.YW : GameVersion.RB;
Japanese = SaveUtil.GetIsG1SAVJ(Data);
Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT;
Box = Data.Length;
Array.Resize(ref Data, Data.Length + SIZE_RESERVED);
Party = GetPartyOffset(0);
Japanese = SaveUtil.GetIsG1SAVJ(Data);
Personal = PersonalTable.Y;
// Stash boxes after the save file's end.
@ -60,18 +62,18 @@ namespace PKHeX.Core
}
}
var current = GetData(Japanese ? 0x302D : 0x30C0, SIZE_STOREDBOX);
var current = GetData(Offsets.CurrentBox, SIZE_STOREDBOX);
var curBoxPL = new PokeList1(current, capacity, Japanese);
for (int i = 0; i < curBoxPL.Pokemon.Length; i++)
{
var dest = Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED;
var dest = Data.Length - SIZE_RESERVED + (CurrentBox * SIZE_BOX) + (i * SIZE_STORED);
var pkDat = i < curBoxPL.Count
? new PokeList1(curBoxPL[i]).Write()
: new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)];
pkDat.CopyTo(Data, dest);
}
var party = GetData(Japanese ? 0x2ED5 : 0x2F2C, SIZE_STOREDPARTY);
var party = GetData(Offsets.Party, SIZE_STOREDPARTY);
var partyPL = new PokeList1(party, PokeListType.Party, Japanese);
for (int i = 0; i < partyPL.Pokemon.Length; i++)
{
@ -83,19 +85,16 @@ namespace PKHeX.Core
}
byte[] rawDC = new byte[0x38];
Array.Copy(Data, Japanese ? 0x2CA7 : 0x2CF4, rawDC, 0, rawDC.Length);
Array.Copy(Data, Offsets.Daycare, rawDC, 0, rawDC.Length);
byte[] TempDaycare = new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)];
TempDaycare[0] = rawDC[0];
Array.Copy(rawDC, 1, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY + StringLength, StringLength);
Array.Copy(rawDC, 1 + StringLength, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY, StringLength);
Array.Copy(rawDC, 1 + 2 * StringLength, TempDaycare, 2 + 1, PKX.SIZE_1STORED);
Array.Copy(rawDC, 1 + (2 * StringLength), TempDaycare, 2 + 1, PKX.SIZE_1STORED);
PokeList1 daycareList = new PokeList1(TempDaycare, PokeListType.Single, Japanese);
daycareList.Write().CopyTo(Data, GetPartyOffset(7));
Daycare = GetPartyOffset(7);
EventFlag = Japanese ? 0x29E9 : 0x29F3;
ObjectSpawnFlags = Japanese ? 0x2848 : 0x2852; // 2 bytes after Coin
// Enable Pokedex editing
PokeDex = 0;
@ -103,10 +102,11 @@ namespace PKHeX.Core
ClearBoxes();
}
private readonly SAV1Offsets Offsets;
// Event Flags
protected override int EventFlagMax => EventFlag > 0 ? 0xA00 : int.MinValue; // 320 * 8
protected override int EventConstMax => 0;
private readonly int ObjectSpawnFlags;
private const int SIZE_RESERVED = 0x8000; // unpacked box data
@ -131,7 +131,7 @@ namespace PKHeX.Core
// copy to active loc if current box
if (i == CurrentBox)
SetData(boxdata, Japanese ? 0x302D : 0x30C0);
SetData(boxdata, Offsets.CurrentBox);
}
var partyPL = new PokeList1(PokeListType.Party, Japanese);
@ -142,16 +142,16 @@ namespace PKHeX.Core
if (partyPK.Species > 0)
partyPL[pSlot++] = partyPK;
}
partyPL.Write().CopyTo(Data, Japanese ? 0x2ED5 : 0x2F2C);
partyPL.Write().CopyTo(Data, Offsets.Party);
// Daycare is read-only, but in case it ever becomes editable, copy it back in.
byte[] rawDC = GetData(GetDaycareSlotOffset(loc: 0, slot: 0), SIZE_STORED);
byte[] dc = new byte[1 + 2*StringLength + PKX.SIZE_1STORED];
byte[] dc = new byte[1 + (2 * StringLength) + PKX.SIZE_1STORED];
dc[0] = rawDC[0];
Array.Copy(rawDC, 2 + 1 + PKX.SIZE_1PARTY + StringLength, dc, 1, StringLength);
Array.Copy(rawDC, 2 + 1 + PKX.SIZE_1PARTY, dc, 1 + StringLength, StringLength);
Array.Copy(rawDC, 2 + 1, dc, 1 + 2*StringLength, PKX.SIZE_1STORED);
dc.CopyTo(Data, Japanese ? 0x2CA7 : 0x2CF4);
Array.Copy(rawDC, 2 + 1, dc, 1 + (2 * StringLength), PKX.SIZE_1STORED);
dc.CopyTo(Data, Offsets.Daycare);
SetChecksums();
byte[] outData = new byte[Data.Length - SIZE_RESERVED];
@ -202,12 +202,11 @@ namespace PKHeX.Core
public override bool IsPKMPresent(int Offset) => PKX.IsPKMPresentGB(Data, Offset);
// Checksums
protected override void SetChecksums() => Data[CHECKSUM_OFS] = GetRBYChecksum(CHECKSUM_OFS);
public override bool ChecksumsValid => Data[CHECKSUM_OFS] == GetRBYChecksum(CHECKSUM_OFS);
protected override void SetChecksums() => Data[Offsets.ChecksumOfs] = GetRBYChecksum(Offsets.ChecksumOfs);
public override bool ChecksumsValid => Data[Offsets.ChecksumOfs] == GetRBYChecksum(Offsets.ChecksumOfs);
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
private const int CHECKSUM_START = 0x2598;
private int CHECKSUM_OFS => Japanese ? 0x3594 : 0x3523;
private byte GetRBYChecksum(int end)
{
@ -226,32 +225,37 @@ namespace PKHeX.Core
get => GetString(0x2598, OTLength);
set => SetString(value, OTLength).CopyTo(Data, 0x2598);
}
public byte[] OT_Trash { get => GetData(0x2598, StringLength); set { if (value?.Length == StringLength) SetData(value, 0x2598); } }
public override int Gender
{
get => 0;
set { }
}
public override int TID
{
get => BigEndian.ToUInt16(Data, Japanese ? 0x25FB : 0x2605);
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, Japanese ? 0x25FB : 0x2605);
get => BigEndian.ToUInt16(Data, Offsets.TID);
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, Offsets.TID);
}
public override int SID { get => 0; set { } }
public bool Yellow => Starter == 0x54; // Pikachu
public int Starter => Data[Japanese ? 0x29B9 : 0x29C3];
public int Starter => Data[Offsets.Starter];
public byte PikaFriendship
{
get => Data[Japanese ? 0x2712 : 0x271C];
set => Data[Japanese ? 0x2712 : 0x271C] = value;
get => Data[Offsets.PikaFriendship];
set => Data[Offsets.PikaFriendship] = value;
}
private int PlayedTimeOffset => Japanese ? 0x2CA0 : 0x2CED;
public override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {SaveUtil.CRC16_CCITT(Data):X4}";
public override int PlayedHours
{
get => Data[PlayedTimeOffset + 0];
get => Data[Offsets.PlayTime + 0];
set
{
if (value >= byte.MaxValue) // Set 255:00:00.00 and flag
@ -260,50 +264,58 @@ namespace PKHeX.Core
value = byte.MaxValue;
PlayedMinutes = PlayedSeconds = PlayedFrames = 0;
}
Data[PlayedTimeOffset + 0] = (byte) value;
Data[Offsets.PlayTime + 0] = (byte) value;
}
}
public bool PlayedMaximum
{
get => Data[PlayedTimeOffset + 1] != 0;
set => Data[PlayedTimeOffset + 1] = (byte)(value ? 1 : 0);
get => Data[Offsets.PlayTime + 1] != 0;
set => Data[Offsets.PlayTime + 1] = (byte)(value ? 1 : 0);
}
public override int PlayedMinutes
{
get => Data[PlayedTimeOffset + 2];
set => Data[PlayedTimeOffset + 2] = (byte)value;
get => Data[Offsets.PlayTime + 2];
set => Data[Offsets.PlayTime + 2] = (byte)value;
}
public override int PlayedSeconds
{
get => Data[PlayedTimeOffset + 3];
set => Data[PlayedTimeOffset + 3] = (byte)value;
get => Data[Offsets.PlayTime + 3];
set => Data[Offsets.PlayTime + 3] = (byte)value;
}
public int PlayedFrames
{
get => Data[PlayedTimeOffset + 4];
set => Data[PlayedTimeOffset + 4] = (byte)value;
get => Data[Offsets.PlayTime + 4];
set => Data[Offsets.PlayTime + 4] = (byte)value;
}
public int Badges
{
get => Data[Japanese ? 0x25F8 : 0x2602];
set { if (value < 0) return; Data[Japanese ? 0x25F8 : 0x2602] = (byte)value; }
get => Data[Offsets.Badges];
set { if (value < 0) return; Data[Offsets.Badges] = (byte)value; }
}
private byte Options
{
get => Data[Japanese ? 0x25F7 : 0x2601];
set => Data[Japanese ? 0x25F7 : 0x2601] = value;
get => Data[Offsets.Options];
set => Data[Offsets.Options] = value;
}
public bool BattleEffects
{
get => (Options & 0x80) == 0;
set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80));
}
public bool BattleStyleSwitch
{
get => (Options & 0x40) == 0;
set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40));
}
public int Sound
{
get => (Options & 0x30) >> 4;
@ -317,6 +329,7 @@ namespace PKHeX.Core
Options = (byte)((Options & 0xCF) | (new_sound << 4));
}
}
public int TextSpeed
{
get => Options & 0x7;
@ -330,26 +343,29 @@ namespace PKHeX.Core
Options = (byte)((Options & 0xF8) | new_speed);
}
}
public override uint Money
{
get => (uint)BigEndian.BCDToInt32(Data, Japanese ? 0x25EE : 0x25F3, 3);
get => (uint)BigEndian.BCDToInt32(Data, Offsets.Money, 3);
set
{
value = (uint)Math.Min(value, MaxMoney);
BigEndian.Int32ToBCD((int)value, 3).CopyTo(Data, Japanese ? 0x25EE : 0x25F3);
BigEndian.Int32ToBCD((int)value, 3).CopyTo(Data, Offsets.Money);
}
}
public uint Coin
{
get => (uint)BigEndian.BCDToInt32(Data, Japanese ? 0x2846 : 0x2850, 2);
get => (uint)BigEndian.BCDToInt32(Data, Offsets.Coin, 2);
set
{
value = (ushort)Math.Min(value, MaxCoins);
BigEndian.Int32ToBCD((int)value, 2).CopyTo(Data, Japanese ? 0x2846 : 0x2850);
BigEndian.Int32ToBCD((int)value, 2).CopyTo(Data, Offsets.Coin);
}
}
private readonly ushort[] LegalItems = Legal.Pouch_Items_RBY;
public override InventoryPouch[] Inventory
{
get
@ -357,8 +373,8 @@ namespace PKHeX.Core
ushort[] legalItems = LegalItems;
InventoryPouch[] pouch =
{
new InventoryPouchGB(InventoryType.Items, legalItems, 99, Japanese ? 0x25C4 : 0x25C9, 20),
new InventoryPouchGB(InventoryType.PCItems, legalItems, 99, Japanese ? 0x27DC : 0x27E6, 50)
new InventoryPouchGB(InventoryType.Items, legalItems, 99, Offsets.Items, 20),
new InventoryPouchGB(InventoryType.PCItems, legalItems, 99, Offsets.PCItems, 50)
};
foreach (var p in pouch)
{
@ -383,25 +399,30 @@ namespace PKHeX.Core
}
}
}
public override int GetDaycareSlotOffset(int loc, int slot)
{
return Daycare;
}
public override uint? GetDaycareEXP(int loc, int slot)
{
return null;
}
public override bool? IsDaycareOccupied(int loc, int slot)
{
if (loc == 0 && slot == 0)
return Data[Japanese ? 0x2CA7 : 0x2CF4] == 0x01;
return Data[Offsets.Daycare] == 0x01;
else
return null;
}
public override void SetDaycareEXP(int loc, int slot, uint EXP)
{
}
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
{
@ -410,26 +431,31 @@ namespace PKHeX.Core
// Storage
public override int PartyCount
{
get => Data[Japanese ? 0x2ED5 : 0x2F2C];
protected set => Data[Japanese ? 0x2ED5 : 0x2F2C] = (byte)value;
get => Data[Offsets.Party];
protected set => Data[Offsets.Party] = (byte)value;
}
public override int GetBoxOffset(int box)
{
return Data.Length - SIZE_RESERVED + box * SIZE_BOX;
return Data.Length - SIZE_RESERVED + (box * SIZE_BOX);
}
public override int GetPartyOffset(int slot)
{
return Data.Length - SIZE_RESERVED + BoxCount * SIZE_BOX + slot * SIZE_STORED;
return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED);
}
public override int CurrentBox
{
get => Data[Japanese ? 0x2842 : 0x284C] & 0x7F;
set => Data[Japanese ? 0x2842 : 0x284C] = (byte)((Data[Japanese ? 0x2842 : 0x284C] & 0x80) | (value & 0x7F));
get => Data[Offsets.CurrentBoxIndex] & 0x7F;
set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x80) | (value & 0x7F));
}
public override string GetBoxName(int box)
{
return $"BOX {box + 1}";
}
public override void SetBoxName(int box, string value)
{
// Don't allow for custom box names
@ -441,14 +467,13 @@ namespace PKHeX.Core
return new PokeList1(data, PokeListType.Single, Japanese)[0];
return new PK1(data);
}
public override byte[] DecryptPKM(byte[] data)
{
return data;
}
// Pokédex
private int PokedexSeenOffset => Japanese ? 0x25B1 : 0x25B6;
private int PokedexCaughtOffset => Japanese ? 0x259E : 0x25A3;
protected override void SetDex(PKM pkm)
{
int species = pkm.Species;
@ -458,6 +483,7 @@ namespace PKHeX.Core
SetCaught(pkm.Species, true);
SetSeen(pkm.Species, true);
}
private bool CanSetDex(int species)
{
if (species <= 0)
@ -468,32 +494,37 @@ namespace PKHeX.Core
return false;
return true;
}
public override void SetSeen(int species, bool seen)
{
int bit = species - 1;
int ofs = bit >> 3;
SetFlag(PokedexSeenOffset + ofs, bit & 7, seen);
SetFlag(Offsets.DexSeen + ofs, bit & 7, seen);
}
public override void SetCaught(int species, bool caught)
{
int bit = species - 1;
int ofs = bit >> 3;
SetFlag(PokedexCaughtOffset + ofs, bit & 7, caught);
SetFlag(Offsets.DexCaught + ofs, bit & 7, caught);
}
public override bool GetSeen(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
return GetFlag(PokedexSeenOffset + ofs, bit & 7);
return GetFlag(Offsets.DexSeen + ofs, bit & 7);
}
public override bool GetCaught(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
return GetFlag(PokedexCaughtOffset + ofs, bit & 7);
return GetFlag(Offsets.DexCaught + ofs, bit & 7);
}
private const int SpawnFlagCount = 0xF0;
public bool[] EventSpawnFlags
{
get
@ -501,7 +532,7 @@ namespace PKHeX.Core
// RB uses 0xE4 (0xE8) flags, Yellow uses 0xF0 flags. Just grab 0xF0
bool[] data = new bool[SpawnFlagCount];
for (int i = 0; i < data.Length; i++)
data[i] = GetFlag(ObjectSpawnFlags + i >> 3, i & 7);
data[i] = GetFlag(Offsets.ObjectSpawnFlags + i >> 3, i & 7);
return data;
}
set
@ -509,11 +540,12 @@ namespace PKHeX.Core
if (value?.Length != SpawnFlagCount)
return;
for (int i = 0; i < value.Length; i++)
SetFlag(ObjectSpawnFlags + i >> 3, i & 7, value[i]);
SetFlag(Offsets.ObjectSpawnFlags + i >> 3, i & 7, value[i]);
}
}
public override string GetString(int Offset, int Length) => StringConverter.GetString1(Data, Offset, Length, Japanese);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)

View file

@ -0,0 +1,74 @@
namespace PKHeX.Core
{
internal class SAV1Offsets
{
public static readonly SAV1Offsets INT = GetINT();
public static readonly SAV1Offsets JPN = GetJPN();
private static SAV1Offsets GetINT() => new SAV1Offsets
{
DexCaught = 0x25A3,
DexSeen = 0x25B6,
Items = 0x25C9,
Money = 0x25F3,
Options = 0x2601,
Badges = 0x2602,
TID = 0x2605,
PikaFriendship = 0x271C,
PCItems = 0x27E6,
CurrentBoxIndex = 0x284C,
Coin = 0x2850,
ObjectSpawnFlags = 0x2852, // 2 bytes after Coin
Starter = 0x29C3,
EventFlag = 0x29F3,
PlayTime = 0x2CED,
Daycare = 0x2CF4,
Party = 0x2F2C,
CurrentBox = 0x30C0,
ChecksumOfs = 0x3523,
};
private static SAV1Offsets GetJPN() => new SAV1Offsets
{
DexCaught = 0x259E,
DexSeen = 0x25B1,
Items = 0x25C4,
Money = 0x25EE,
Options = 0x25F7,
Badges = 0x25F8,
TID = 0x25FB,
PikaFriendship = 0x2712,
PCItems = 0x27DC,
CurrentBoxIndex = 0x2842,
Coin = 0x2846,
ObjectSpawnFlags = 0x2848, // 2 bytes after Coin
Starter = 0x29B9,
EventFlag = 0x29E9,
PlayTime = 0x2CA0,
Daycare = 0x2CA7,
Party = 0x2ED5,
CurrentBox = 0x302D,
ChecksumOfs = 0x3594,
};
public int DexCaught { get; private set; }
public int DexSeen { get; private set; }
public int Items { get; private set; }
public int Money { get; private set; }
public int Options { get; private set; }
public int Badges { get; private set; }
public int TID { get; private set; }
public int PikaFriendship { get; private set; }
public int PCItems { get; private set; }
public int CurrentBoxIndex { get; private set; }
public int Coin { get; private set; }
public int ObjectSpawnFlags { get; private set; }
public int Starter { get; private set; }
public int EventFlag { get; private set; }
public int PlayTime { get; private set; }
public int Daycare { get; private set; }
public int Party { get; private set; }
public int CurrentBox { get; private set; }
public int ChecksumOfs { get; private set; }
}
}