2016-09-02 21:20:39 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
public sealed class SAV2 : SaveFile
|
|
|
|
|
{
|
2016-09-04 22:54:16 +00:00
|
|
|
|
public override string BAKName => $"{FileName} [{OT} ({Version}) - {PlayTimeString}].bak";
|
2016-10-17 14:49:40 +00:00
|
|
|
|
public override string Filter => "SAV File|*.sav|All Files|*.*";
|
2016-09-02 21:20:39 +00:00
|
|
|
|
public override string Extension => ".sav";
|
2017-01-05 06:22:50 +00:00
|
|
|
|
public override string[] PKMExtensions => PKM.Extensions.Where(f =>
|
|
|
|
|
{
|
|
|
|
|
int gen = f.Last() - 0x30;
|
|
|
|
|
return 1 <= gen && gen <= 2;
|
|
|
|
|
}).ToArray();
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
2017-01-15 00:43:16 +00:00
|
|
|
|
public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2016-09-04 01:57:54 +00:00
|
|
|
|
Data = data == null ? new byte[SaveUtil.SIZE_G2RAW_U] : (byte[])data.Clone();
|
2016-09-02 21:20:39 +00:00
|
|
|
|
BAK = (byte[])Data.Clone();
|
|
|
|
|
Exportable = !Data.SequenceEqual(new byte[Data.Length]);
|
|
|
|
|
|
2017-01-15 00:43:16 +00:00
|
|
|
|
if (data == null)
|
|
|
|
|
Version = GameVersion.C;
|
|
|
|
|
else if (versionOverride != GameVersion.Any)
|
|
|
|
|
Version = versionOverride;
|
|
|
|
|
else
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Version = SaveUtil.GetIsG2SAV(Data);
|
2017-01-15 00:43:16 +00:00
|
|
|
|
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (Version == GameVersion.Invalid)
|
|
|
|
|
return;
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid;
|
2016-09-04 01:57:54 +00:00
|
|
|
|
if (Japanese && Data.Length < SaveUtil.SIZE_G2RAW_J)
|
|
|
|
|
Array.Resize(ref Data, SaveUtil.SIZE_G2RAW_J);
|
|
|
|
|
|
2016-09-02 21:20:39 +00:00
|
|
|
|
Box = Data.Length;
|
|
|
|
|
Array.Resize(ref Data, Data.Length + SIZE_RESERVED);
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Party = GetPartyOffset(0);
|
2016-09-04 06:54:11 +00:00
|
|
|
|
|
2016-09-02 21:20:39 +00:00
|
|
|
|
Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C;
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
GetSAVOffsets();
|
2016-09-04 06:54:11 +00:00
|
|
|
|
|
2016-09-04 21:00:52 +00:00
|
|
|
|
LegalItems = Legal.Pouch_Items_GSC;
|
|
|
|
|
LegalBalls = Legal.Pouch_Ball_GSC;
|
|
|
|
|
LegalKeyItems = Version == GameVersion.C ? Legal.Pouch_Key_C : Legal.Pouch_Key_GS;
|
|
|
|
|
LegalTMHMs = Legal.Pouch_TMHM_GSC;
|
|
|
|
|
HeldItems = Legal.HeldItems_GSC;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
// Stash boxes after the save file's end.
|
|
|
|
|
byte[] TempBox = new byte[SIZE_STOREDBOX];
|
|
|
|
|
for (int i = 0; i < BoxCount; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i < (Japanese ? 6 : 7))
|
|
|
|
|
Array.Copy(Data, 0x4000 + i * (TempBox.Length + 2), TempBox, 0, TempBox.Length);
|
|
|
|
|
else
|
|
|
|
|
Array.Copy(Data, 0x6000 + (i - (Japanese ? 6 : 7)) * (TempBox.Length + 2), TempBox, 0, TempBox.Length);
|
|
|
|
|
PokemonList2 PL2 = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese);
|
|
|
|
|
for (int j = 0; j < PL2.Pokemon.Length; j++)
|
|
|
|
|
{
|
|
|
|
|
if (j < PL2.Count)
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new PokemonList2(PL2[j]).GetBytes();
|
|
|
|
|
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)];
|
|
|
|
|
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Array.Copy(Data, CurrentBoxOffset, TempBox, 0, TempBox.Length);
|
|
|
|
|
PokemonList2 curBoxPL = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese);
|
|
|
|
|
for (int i = 0; i < curBoxPL.Pokemon.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i < curBoxPL.Count)
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new PokemonList2(curBoxPL[i]).GetBytes();
|
|
|
|
|
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)];
|
|
|
|
|
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
byte[] TempParty = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Party, Japanese)];
|
|
|
|
|
Array.Copy(Data, PartyOffset, TempParty, 0, TempParty.Length);
|
|
|
|
|
PokemonList2 partyList = new PokemonList2(TempParty, PokemonList2.CapacityType.Party, Japanese);
|
|
|
|
|
for (int i = 0; i < partyList.Pokemon.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i < partyList.Count)
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new PokemonList2(partyList[i]).GetBytes();
|
2017-06-18 01:37:19 +00:00
|
|
|
|
pkDat.CopyTo(Data, GetPartyOffset(i));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)];
|
2017-06-18 01:37:19 +00:00
|
|
|
|
pkDat.CopyTo(Data, GetPartyOffset(i));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-04 01:57:54 +00:00
|
|
|
|
|
|
|
|
|
// Daycare currently undocumented for all Gen II games.
|
|
|
|
|
|
2016-09-02 21:20:39 +00:00
|
|
|
|
// Enable Pokedex editing
|
|
|
|
|
PokeDex = 0;
|
|
|
|
|
|
|
|
|
|
if (!Exportable)
|
2017-06-18 01:37:19 +00:00
|
|
|
|
ClearBoxes();
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private const int SIZE_RESERVED = 0x8000; // unpacked box data
|
2017-06-18 01:37:19 +00:00
|
|
|
|
|
|
|
|
|
protected override byte[] Write(bool DSV)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < BoxCount; i++)
|
|
|
|
|
{
|
2016-09-04 01:57:54 +00:00
|
|
|
|
PokemonList2 boxPL = new PokemonList2(Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
int slot = 0;
|
|
|
|
|
for (int j = 0; j < boxPL.Pokemon.Length; j++)
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
PK2 boxPK = (PK2) GetPKM(GetData(GetBoxOffset(i) + j*SIZE_STORED, SIZE_STORED));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (boxPK.Species > 0)
|
|
|
|
|
boxPL[slot++] = boxPK;
|
|
|
|
|
}
|
2016-09-04 01:57:54 +00:00
|
|
|
|
if (i < (Japanese ? 6 : 7))
|
|
|
|
|
boxPL.GetBytes().CopyTo(Data, 0x4000 + i * (SIZE_STOREDBOX + 2));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
else
|
2016-09-04 01:57:54 +00:00
|
|
|
|
boxPL.GetBytes().CopyTo(Data, 0x6000 + (i - (Japanese ? 6 : 7)) * (SIZE_STOREDBOX + 2));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (i == CurrentBox)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
boxPL.GetBytes().CopyTo(Data, CurrentBoxOffset);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-04 01:57:54 +00:00
|
|
|
|
PokemonList2 partyPL = new PokemonList2(PokemonList2.CapacityType.Party, Japanese);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
int pSlot = 0;
|
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
PK2 partyPK = (PK2)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (partyPK.Species > 0)
|
|
|
|
|
partyPL[pSlot++] = partyPK;
|
|
|
|
|
}
|
2016-09-04 01:57:54 +00:00
|
|
|
|
partyPL.GetBytes().CopyTo(Data, PartyOffset);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
SetChecksums();
|
2016-09-04 01:57:54 +00:00
|
|
|
|
if (Version == GameVersion.C && !Japanese)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(Data, 0x2009, Data, 0x1209, 0xB7A);
|
|
|
|
|
}
|
|
|
|
|
if (Version == GameVersion.C && Japanese)
|
|
|
|
|
{
|
2017-02-25 07:01:07 +00:00
|
|
|
|
Array.Copy(Data, 0x2009, Data, 0x7209, 0xADA);
|
2016-09-04 01:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
if (Version == GameVersion.GS && !Japanese)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(Data, 0x2009, Data, 0x15C7, 0x222F - 0x2009);
|
|
|
|
|
Array.Copy(Data, 0x222F, Data, 0x3D69, 0x23D9 - 0x222F);
|
|
|
|
|
Array.Copy(Data, 0x23D9, Data, 0x0C6B, 0x2856 - 0x23D9);
|
|
|
|
|
Array.Copy(Data, 0x2856, Data, 0x7E39, 0x288A - 0x2856);
|
|
|
|
|
Array.Copy(Data, 0x288A, Data, 0x10E8, 0x2D69 - 0x288A);
|
|
|
|
|
}
|
2016-09-04 06:15:09 +00:00
|
|
|
|
if (Version == GameVersion.GS && Japanese)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(Data, 0x2009, Data, 0x7209, 0xC83);
|
|
|
|
|
}
|
2016-09-02 21:20:39 +00:00
|
|
|
|
byte[] outData = new byte[Data.Length - SIZE_RESERVED];
|
|
|
|
|
Array.Copy(Data, outData, outData.Length);
|
|
|
|
|
return outData;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private void GetSAVOffsets()
|
2016-09-04 06:54:11 +00:00
|
|
|
|
{
|
|
|
|
|
OptionsOffset = 0x2000;
|
|
|
|
|
Trainer1 = 0x2009;
|
|
|
|
|
switch (Version)
|
|
|
|
|
{
|
|
|
|
|
case GameVersion.GS:
|
|
|
|
|
DaylightSavingsOffset = Japanese ? 0x2029 : 0x2042;
|
|
|
|
|
TimePlayedOffset = Japanese ? 0x2034 : 0x2053;
|
|
|
|
|
PaletteOffset = Japanese ? 0x204C : 0x206B;
|
|
|
|
|
MoneyOffset = Japanese ? 0x23BC : 0x23DB;
|
|
|
|
|
JohtoBadgesOffset = Japanese ? 0x23C5 : 0x23E4;
|
|
|
|
|
CurrentBoxIndexOffset = Japanese ? 0x2705 : 0x2724;
|
|
|
|
|
BoxNamesOffset = Japanese ? 0x2708 : 0x2727;
|
|
|
|
|
PartyOffset = Japanese ? 0x283E : 0x288A;
|
|
|
|
|
PokedexCaughtOffset = Japanese ? 0x29CE : 0x2A4C;
|
|
|
|
|
PokedexSeenOffset = Japanese ? 0x29EE : 0x2A6C;
|
|
|
|
|
CurrentBoxOffset = Japanese ? 0x2D10 : 0x2D6C;
|
|
|
|
|
GenderOffset = -1; // No gender in GSC
|
|
|
|
|
break;
|
|
|
|
|
case GameVersion.C:
|
|
|
|
|
DaylightSavingsOffset = Japanese ? 0x2029 : 0x2042;
|
|
|
|
|
TimePlayedOffset = Japanese ? 0x2034 : 0x2052;
|
|
|
|
|
PaletteOffset = Japanese ? 0x204C : 0x206A;
|
|
|
|
|
MoneyOffset = Japanese ? 0x23BE : 0x23DC;
|
|
|
|
|
JohtoBadgesOffset = Japanese ? 0x23C7 : 0x23E5;
|
|
|
|
|
CurrentBoxIndexOffset = Japanese ? 0x26E2 : 0x2700;
|
|
|
|
|
BoxNamesOffset = Japanese ? 0x26E5 : 0x2703;
|
|
|
|
|
PartyOffset = Japanese ? 0x281A : 0x2865;
|
|
|
|
|
PokedexCaughtOffset = Japanese ? 0x29AA : 0x2A27;
|
|
|
|
|
PokedexSeenOffset = Japanese ? 0x29CA : 0x2A47;
|
|
|
|
|
CurrentBoxOffset = 0x2D10;
|
|
|
|
|
GenderOffset = Japanese ? 0x8000 : 0x3E3D;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
// Configuration
|
2016-09-11 21:54:20 +00:00
|
|
|
|
public override SaveFile Clone() { return new SAV2(Write(DSV: false)); }
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
public override int SIZE_STORED => Japanese ? PKX.SIZE_2JLIST : PKX.SIZE_2ULIST;
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override int SIZE_PARTY => Japanese ? PKX.SIZE_2JLIST : PKX.SIZE_2ULIST;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
public override PKM BlankPKM => new PK2(null, null, Japanese);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override Type PKMType => typeof(PK2);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
2017-01-05 06:22:50 +00:00
|
|
|
|
private int SIZE_BOX => BoxSlotCount*SIZE_STORED;
|
|
|
|
|
private int SIZE_STOREDBOX => PokemonList2.GetDataLength(Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese);
|
|
|
|
|
|
2017-01-27 03:18:20 +00:00
|
|
|
|
public override int MaxMoveID => Legal.MaxMoveID_2;
|
2016-10-24 04:59:27 +00:00
|
|
|
|
public override int MaxSpeciesID => Legal.MaxSpeciesID_2;
|
2017-01-27 03:18:20 +00:00
|
|
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_2;
|
|
|
|
|
public override int MaxItemID => Legal.MaxItemID_2;
|
2017-05-13 03:32:36 +00:00
|
|
|
|
public override int MaxBallID => 0; // unused
|
|
|
|
|
public override int MaxGameID => 99; // unused
|
2016-09-09 03:20:32 +00:00
|
|
|
|
public override int MaxMoney => 999999;
|
2017-02-04 20:13:54 +00:00
|
|
|
|
public override int MaxCoins => 9999;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
public override int BoxCount => Japanese ? 9 : 14;
|
|
|
|
|
public override int MaxEV => 65535;
|
|
|
|
|
public override int MaxIV => 15;
|
|
|
|
|
public override int Generation => 2;
|
|
|
|
|
protected override int GiftCountMax => 0;
|
|
|
|
|
public override int OTLength => Japanese ? 5 : 7;
|
|
|
|
|
public override int NickLength => Japanese ? 5 : 10;
|
|
|
|
|
public override int BoxSlotCount => Japanese ? 30 : 20;
|
|
|
|
|
|
|
|
|
|
public override bool HasParty => true;
|
|
|
|
|
|
|
|
|
|
// Offsets
|
2016-09-04 06:54:11 +00:00
|
|
|
|
private int OptionsOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int DaylightSavingsOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int TimePlayedOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int PaletteOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int MoneyOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int JohtoBadgesOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int CurrentBoxIndexOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int BoxNamesOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int PartyOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int PokedexSeenOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int PokedexCaughtOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int CurrentBoxOffset { get; set; } = int.MinValue;
|
|
|
|
|
private int GenderOffset { get; set; } = int.MinValue;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
// Checksums
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private ushort GetChecksum()
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-02-25 07:01:07 +00:00
|
|
|
|
int end;
|
|
|
|
|
switch (Version)
|
2016-09-04 06:15:09 +00:00
|
|
|
|
{
|
2017-02-25 07:01:07 +00:00
|
|
|
|
case GameVersion.C:
|
|
|
|
|
end = Japanese ? 0x2AE2 : 0x2B82;
|
|
|
|
|
break;
|
|
|
|
|
default: // GS
|
|
|
|
|
end = Japanese ? 0x2C8B : 0x2D68;
|
|
|
|
|
break;
|
2016-09-04 06:15:09 +00:00
|
|
|
|
}
|
2017-02-25 07:01:07 +00:00
|
|
|
|
return (ushort)Data.Skip(0x2009).Take(end - 0x2009 + 1).Sum(a => a);
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetChecksums()
|
2017-02-25 07:01:07 +00:00
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
ushort accum = GetChecksum();
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (Version == GameVersion.GS && !Japanese)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
{
|
|
|
|
|
BitConverter.GetBytes(accum).CopyTo(Data, 0x2D69);
|
|
|
|
|
BitConverter.GetBytes(accum).CopyTo(Data, 0x7E6D);
|
|
|
|
|
}
|
2017-02-25 07:01:07 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BitConverter.GetBytes(accum).CopyTo(Data, 0x2D0D);
|
|
|
|
|
BitConverter.GetBytes(accum).CopyTo(Data, 0x7F0D);
|
|
|
|
|
}
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override bool ChecksumsValid
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
ushort accum = GetChecksum();
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (Version == GameVersion.GS && !Japanese)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
return accum == BitConverter.ToUInt16(Data, 0x2D69); // US Gold/Silver
|
2017-02-25 07:01:07 +00:00
|
|
|
|
return accum == BitConverter.ToUInt16(Data, 0x2D0D); // Japanese Crystal
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-04 01:57:54 +00:00
|
|
|
|
|
2016-09-04 06:54:11 +00:00
|
|
|
|
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
|
|
|
|
// Trainer Info
|
|
|
|
|
public override GameVersion Version { get; protected set; }
|
|
|
|
|
|
|
|
|
|
public override string OT
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
get => GetString(0x200B, OTLength);
|
|
|
|
|
set => SetString(value, OTLength).CopyTo(Data, 0x200B);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override int Gender
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Version == GameVersion.C ? Data[GenderOffset] : 0;
|
2016-09-04 01:57:54 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (Version != GameVersion.C)
|
|
|
|
|
return;
|
|
|
|
|
Data[GenderOffset] = (byte) value;
|
|
|
|
|
Data[PaletteOffset] = (byte) value;
|
|
|
|
|
}
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override ushort TID
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => BigEndian.ToUInt16(Data, 0x2009); set => BigEndian.GetBytes(value).CopyTo(Data, 0x2009);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override ushort SID
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => 0;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
set { }
|
|
|
|
|
}
|
|
|
|
|
public override int PlayedHours
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => BigEndian.ToUInt16(Data, TimePlayedOffset);
|
|
|
|
|
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, TimePlayedOffset);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override int PlayedMinutes
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Data[TimePlayedOffset + 2];
|
|
|
|
|
set => Data[TimePlayedOffset + 2] = (byte)value;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public override int PlayedSeconds
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Data[TimePlayedOffset + 3];
|
|
|
|
|
set => Data[TimePlayedOffset + 3] = (byte)value;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Badges
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => BitConverter.ToUInt16(Data, JohtoBadgesOffset);
|
2016-09-04 01:57:54 +00:00
|
|
|
|
set { if (value < 0) return; BitConverter.GetBytes(value).CopyTo(Data, JohtoBadgesOffset); }
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
private byte Options
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Data[0x2000];
|
|
|
|
|
set => Data[0x2000] = value;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public bool BattleEffects
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => (Options & 0x80) == 0;
|
|
|
|
|
set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public bool BattleStyleSwitch
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => (Options & 0x40) == 0;
|
|
|
|
|
set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
public int Sound
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => (Options & 0x30) >> 4;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
var new_sound = value;
|
2016-09-04 01:57:54 +00:00
|
|
|
|
if (new_sound > 0)
|
|
|
|
|
new_sound = 2; // Stereo
|
2016-09-02 21:20:39 +00:00
|
|
|
|
if (new_sound < 0)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
new_sound = 0; // Mono
|
2016-09-02 21:20:39 +00:00
|
|
|
|
Options = (byte)((Options & 0xCF) | (new_sound << 4));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public int TextSpeed
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Options & 0x7; set
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
var new_speed = value;
|
|
|
|
|
if (new_speed > 7)
|
|
|
|
|
new_speed = 7;
|
|
|
|
|
if (new_speed < 0)
|
|
|
|
|
new_speed = 0;
|
|
|
|
|
Options = (byte)((Options & 0xF8) | new_speed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public override uint Money
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => BigEndian.ToUInt32(Data, MoneyOffset - 1) & 0xFFFFFF;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
2017-03-05 05:52:39 +00:00
|
|
|
|
byte[] data = BigEndian.GetBytes((uint) Math.Min(value, MaxMoney));
|
|
|
|
|
Array.Copy(data, 1, Data, MoneyOffset, 3);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public uint Coin
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => BigEndian.ToUInt16(Data, MoneyOffset + 7);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
2017-02-08 23:51:18 +00:00
|
|
|
|
value = (ushort)Math.Min(value, MaxCoins);
|
2017-03-05 05:52:39 +00:00
|
|
|
|
BigEndian.GetBytes((ushort)value).CopyTo(Data, MoneyOffset + 7);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-04 01:57:54 +00:00
|
|
|
|
private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
public override InventoryPouch[] Inventory
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2016-09-04 01:57:54 +00:00
|
|
|
|
InventoryPouch[] pouch;
|
|
|
|
|
if (Version == GameVersion.C)
|
|
|
|
|
{
|
|
|
|
|
pouch = new[]
|
|
|
|
|
{
|
2017-02-04 20:13:54 +00:00
|
|
|
|
new InventoryPouch(InventoryType.TMHMs, LegalTMHMs, 99, Japanese ? 0x23C9 : 0x23E7, 57),
|
2016-09-04 01:57:54 +00:00
|
|
|
|
new InventoryPouch(InventoryType.Items, LegalItems, 99, Japanese ? 0x2402 : 0x2420, 20),
|
|
|
|
|
new InventoryPouch(InventoryType.KeyItems, LegalKeyItems, 1, Japanese ? 0x242C : 0x244A, 26),
|
|
|
|
|
new InventoryPouch(InventoryType.Balls, LegalBalls, 99, Japanese ? 0x2447 : 0x2465, 12),
|
2016-10-28 02:59:19 +00:00
|
|
|
|
new InventoryPouch(InventoryType.PCItems, LegalItems.Concat(LegalKeyItems).Concat(LegalBalls).Concat(LegalTMHMs).ToArray(), 99, Japanese ? 0x2461 : 0x247F, 50)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2016-09-04 01:57:54 +00:00
|
|
|
|
pouch = new[]
|
|
|
|
|
{
|
2017-02-04 20:13:54 +00:00
|
|
|
|
new InventoryPouch(InventoryType.TMHMs, LegalTMHMs, 99, Japanese ? 0x23C7 : 0x23E6, 57),
|
2016-09-04 06:15:09 +00:00
|
|
|
|
new InventoryPouch(InventoryType.Items, LegalItems, 99, Japanese ? 0x2400 : 0x241F, 20),
|
|
|
|
|
new InventoryPouch(InventoryType.KeyItems, LegalKeyItems, 99, Japanese ? 0x242A : 0x2449, 26),
|
|
|
|
|
new InventoryPouch(InventoryType.Balls, LegalBalls, 99, Japanese ? 0x2445 : 0x2464, 12),
|
2016-10-28 02:59:19 +00:00
|
|
|
|
new InventoryPouch(InventoryType.PCItems, LegalItems.Concat(LegalKeyItems).Concat(LegalBalls).Concat(LegalTMHMs).ToArray(), 99, Japanese ? 0x245F : 0x247E, 50)
|
2016-09-04 01:57:54 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2016-09-02 21:20:39 +00:00
|
|
|
|
foreach (var p in pouch)
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
p.GetPouchG1(ref Data);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
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.Items.Length)
|
|
|
|
|
p.Items[ofs++] = new InventoryItem { Count = 0, Index = 0 };
|
2017-06-18 01:37:19 +00:00
|
|
|
|
p.SetPouchG1(ref Data);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override int GetDaycareSlotOffset(int loc, int slot)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return Daycare;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override uint? GetDaycareEXP(int loc, int slot)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override bool? IsDaycareOccupied(int loc, int slot)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetDaycareEXP(int loc, int slot, uint EXP)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Storage
|
|
|
|
|
public override int PartyCount
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Data[PartyOffset]; protected set => Data[PartyOffset] = (byte)value;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override int GetBoxOffset(int box)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return Data.Length - SIZE_RESERVED + box * SIZE_BOX;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override int GetPartyOffset(int slot)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return Data.Length - SIZE_RESERVED + BoxCount * SIZE_BOX + slot * SIZE_STORED;
|
|
|
|
|
}
|
|
|
|
|
public override int CurrentBox
|
|
|
|
|
{
|
2017-05-13 03:32:36 +00:00
|
|
|
|
get => Data[CurrentBoxIndexOffset] & 0x7F; set => Data[CurrentBoxIndexOffset] = (byte)((Data[Japanese ? 0x2842 : 0x284C] & 0x80) | (value & 0x7F));
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override string GetBoxName(int box)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-08-01 06:03:51 +00:00
|
|
|
|
return StringConverter.GetString1(Data, BoxNamesOffset + box*9, 9, Japanese);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetBoxName(int box, string value)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
// Don't allow for custom box names
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override PKM GetPKM(byte[] data)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
if (data.Length == SIZE_STORED)
|
|
|
|
|
return new PokemonList2(data, PokemonList2.CapacityType.Single, Japanese)[0];
|
|
|
|
|
return new PK2(data);
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override byte[] DecryptPKM(byte[] data)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pokédex
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetDex(PKM pkm)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-03-15 05:41:15 +00:00
|
|
|
|
int species = pkm.Species;
|
2017-06-18 01:37:19 +00:00
|
|
|
|
if (!CanSetDex(species))
|
2017-03-15 05:41:15 +00:00
|
|
|
|
return;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
SetCaught(pkm.Species, true);
|
|
|
|
|
SetSeen(pkm.Species, true);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private bool CanSetDex(int species)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-03-15 05:41:15 +00:00
|
|
|
|
if (species <= 0)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
return false;
|
2017-03-15 05:41:15 +00:00
|
|
|
|
if (species > MaxSpeciesID)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
return false;
|
|
|
|
|
if (Version == GameVersion.Unknown)
|
|
|
|
|
return false;
|
2017-03-15 05:41:15 +00:00
|
|
|
|
return true;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetSeen(int species, bool seen)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-03-15 05:41:15 +00:00
|
|
|
|
int bit = species - 1;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
int ofs = bit >> 3;
|
|
|
|
|
byte bitval = (byte)(1 << (bit & 7));
|
2017-02-25 07:01:07 +00:00
|
|
|
|
|
2017-03-15 05:41:15 +00:00
|
|
|
|
if (seen)
|
|
|
|
|
Data[PokedexSeenOffset + ofs] |= bitval;
|
|
|
|
|
else
|
2017-02-25 07:01:07 +00:00
|
|
|
|
Data[PokedexSeenOffset + ofs] &= (byte)~bitval;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetCaught(int species, bool caught)
|
2016-09-02 21:20:39 +00:00
|
|
|
|
{
|
2017-03-15 05:41:15 +00:00
|
|
|
|
int bit = species - 1;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
int ofs = bit >> 3;
|
|
|
|
|
byte bitval = (byte)(1 << (bit & 7));
|
2017-02-25 07:01:07 +00:00
|
|
|
|
|
|
|
|
|
if (!caught)
|
|
|
|
|
{
|
|
|
|
|
// Clear the Captured Flag
|
|
|
|
|
Data[PokedexCaughtOffset + ofs] &= (byte)~bitval;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-02 21:20:39 +00:00
|
|
|
|
// Set the Captured Flag
|
2017-02-25 07:01:07 +00:00
|
|
|
|
Data[PokedexCaughtOffset + ofs] |= bitval;
|
2017-03-15 05:41:15 +00:00
|
|
|
|
if (species != 201)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Give all Unown caught to prevent a crash on pokedex view
|
|
|
|
|
for (int i = 1; i <= 26; i++)
|
|
|
|
|
Data[PokedexSeenOffset + 0x1F + i] = (byte) i;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override bool GetSeen(int species)
|
2017-03-15 05:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
int bit = species - 1;
|
|
|
|
|
int ofs = bit >> 3;
|
|
|
|
|
byte bitval = (byte)(1 << (bit & 7));
|
|
|
|
|
// Get the Seen Flag
|
|
|
|
|
return (Data[PokedexSeenOffset + ofs] & bitval) != 0;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override bool GetCaught(int species)
|
2017-03-15 05:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
int bit = species - 1;
|
|
|
|
|
int ofs = bit >> 3;
|
|
|
|
|
byte bitval = (byte)(1 << (bit & 7));
|
|
|
|
|
// Get the Caught Flag
|
|
|
|
|
return (Data[PokedexCaughtOffset + ofs] & bitval) != 0;
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
2017-04-09 21:06:50 +00:00
|
|
|
|
|
2017-08-01 06:03:51 +00:00
|
|
|
|
public override string GetString(int Offset, int Count) => StringConverter.GetString1(Data, Offset, Count, Japanese);
|
|
|
|
|
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0) => StringConverter.SetString1(value, maxLength, Japanese);
|
2016-09-02 21:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|