mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-27 14:30:56 +00:00
b280ffcfef
should be pretty much it
612 lines
23 KiB
C#
612 lines
23 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Generation 5 <see cref="SaveFile"/> object.
|
|
/// </summary>
|
|
public sealed class SAV5 : SaveFile
|
|
{
|
|
// Save Data Attributes
|
|
protected override string BAKText => $"{OT} ({(GameVersion)Game}) - {PlayTimeString}";
|
|
public override string Filter => (Footer.Length > 0 ? "DeSmuME DSV|*.dsv|" : "") + "SAV File|*.sav|All Files|*.*";
|
|
public override string Extension => ".sav";
|
|
|
|
public SAV5(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
|
|
{
|
|
Data = data ?? new byte[SaveUtil.SIZE_G5RAW];
|
|
BAK = (byte[])Data.Clone();
|
|
Exportable = !IsRangeEmpty(0, Data.Length);
|
|
|
|
// Get Version
|
|
if (data == null)
|
|
Version = GameVersion.B2W2;
|
|
else if (versionOverride != GameVersion.Any)
|
|
Version = versionOverride;
|
|
else Version = SaveUtil.GetIsG5SAV(Data);
|
|
if (Version == GameVersion.Invalid)
|
|
return;
|
|
|
|
// First blocks are always the same position/size
|
|
PCLayout = 0x0;
|
|
Box = 0x400;
|
|
Party = 0x18E00;
|
|
Trainer1 = 0x19400;
|
|
WondercardData = 0x1C800;
|
|
AdventureInfo = 0x1D900;
|
|
|
|
// Different Offsets for later blocks
|
|
switch (Version)
|
|
{
|
|
case GameVersion.BW:
|
|
BattleBox = 0x20A00;
|
|
Trainer2 = 0x21200;
|
|
EventConst = 0x20100;
|
|
EventFlag = EventConst + 0x27C;
|
|
Daycare = 0x20E00;
|
|
PokeDex = 0x21600;
|
|
PokeDexLanguageFlags = PokeDex + 0x320;
|
|
BattleSubway = 0x21D00;
|
|
CGearInfoOffset = 0x1C000;
|
|
CGearDataOffset = 0x52000;
|
|
EntreeForestOffset = 0x22C00;
|
|
|
|
// Inventory offsets are the same for each game.
|
|
OFS_PouchHeldItem = 0x18400; // 0x188D7
|
|
OFS_PouchKeyItem = 0x188D8; // 0x18A23
|
|
OFS_PouchTMHM = 0x18A24; // 0x18BD7
|
|
OFS_PouchMedicine = 0x18BD8; // 0x18C97
|
|
OFS_PouchBerry = 0x18C98; // 0x18DBF
|
|
LegalItems = Legal.Pouch_Items_BW;
|
|
LegalKeyItems = Legal.Pouch_Key_BW;
|
|
LegalTMHMs = Legal.Pouch_TMHM_BW;
|
|
LegalMedicine = Legal.Pouch_Medicine_BW;
|
|
LegalBerries = Legal.Pouch_Berries_BW;
|
|
|
|
Personal = PersonalTable.BW;
|
|
break;
|
|
case GameVersion.B2W2: // B2W2
|
|
BattleBox = 0x20900;
|
|
Trainer2 = 0x21100;
|
|
EventConst = 0x1FF00;
|
|
EventFlag = EventConst + 0x35E;
|
|
Daycare = 0x20D00;
|
|
PokeDex = 0x21400;
|
|
PokeDexLanguageFlags = PokeDex + 0x328; // forme flags size is + 8 from bw with new formes (therians)
|
|
BattleSubway = 0x21B00;
|
|
CGearInfoOffset = 0x1C000;
|
|
CGearDataOffset = 0x52800;
|
|
EntreeForestOffset = 0x22A00;
|
|
|
|
// Inventory offsets are the same for each game.
|
|
OFS_PouchHeldItem = 0x18400; // 0x188D7
|
|
OFS_PouchKeyItem = 0x188D8; // 0x18A23
|
|
OFS_PouchTMHM = 0x18A24; // 0x18BD7
|
|
OFS_PouchMedicine = 0x18BD8; // 0x18C97
|
|
OFS_PouchBerry = 0x18C98; // 0x18DBF
|
|
LegalItems = Legal.Pouch_Items_BW;
|
|
LegalKeyItems = Legal.Pouch_Key_B2W2;
|
|
LegalTMHMs = Legal.Pouch_TMHM_BW;
|
|
LegalMedicine = Legal.Pouch_Medicine_BW;
|
|
LegalBerries = Legal.Pouch_Berries_BW;
|
|
|
|
Personal = PersonalTable.B2W2;
|
|
break;
|
|
}
|
|
HeldItems = Legal.HeldItems_BW;
|
|
Blocks = Version == GameVersion.BW ? BlockInfoNDS.BlocksBW : BlockInfoNDS.BlocksB2W2;
|
|
|
|
if (!Exportable)
|
|
ClearBoxes();
|
|
}
|
|
|
|
// Configuration
|
|
public override SaveFile Clone() { return new SAV5((byte[])Data.Clone(), Version); }
|
|
|
|
public override int SIZE_STORED => PKX.SIZE_5STORED;
|
|
protected override int SIZE_PARTY => PKX.SIZE_5PARTY;
|
|
public override PKM BlankPKM => new PK5();
|
|
public override Type PKMType => typeof(PK5);
|
|
|
|
public override int BoxCount => 24;
|
|
public override int MaxEV => 255;
|
|
public override int Generation => 5;
|
|
public override int OTLength => 7;
|
|
public override int NickLength => 10;
|
|
protected override int EventConstMax => (Version == GameVersion.BW ? 0x27C : 0x35E) >> 1;
|
|
protected override int EventFlagMax => (Version == GameVersion.BW ? 0x16C : 0x17F) << 3;
|
|
protected override int GiftCountMax => 12;
|
|
|
|
public override int MaxMoveID => Legal.MaxMoveID_5;
|
|
public override int MaxSpeciesID => Legal.MaxSpeciesID_5;
|
|
public override int MaxItemID => Version == GameVersion.BW ? Legal.MaxItemID_5_BW : Legal.MaxItemID_5_B2W2;
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_5;
|
|
public override int MaxBallID => Legal.MaxBallID_5;
|
|
public override int MaxGameID => Legal.MaxGameID_5; // B2
|
|
|
|
// Blocks & Offsets
|
|
private readonly BlockInfoNDS[] Blocks;
|
|
protected override void SetChecksums() => Blocks.SetChecksums(Data);
|
|
public override bool ChecksumsValid => Blocks.GetChecksumsValid(Data);
|
|
public override string ChecksumInfo => Blocks.GetChecksumInfo(Data);
|
|
|
|
private const int wcSeed = 0x1D290;
|
|
|
|
public readonly int CGearInfoOffset, CGearDataOffset;
|
|
private readonly int EntreeForestOffset;
|
|
private readonly int Trainer2, AdventureInfo, BattleSubway;
|
|
public readonly int PokeDexLanguageFlags;
|
|
|
|
// Daycare
|
|
public override int DaycareSeedSize => 16;
|
|
|
|
public override int GetDaycareSlotOffset(int loc, int slot)
|
|
{
|
|
return Daycare + 4 + (0xE4 * slot);
|
|
}
|
|
|
|
public override string GetDaycareRNGSeed(int loc)
|
|
{
|
|
if (Version != GameVersion.B2W2)
|
|
return null;
|
|
var data = Data.Skip(Daycare + 0x1CC).Take(DaycareSeedSize/2).Reverse().ToArray();
|
|
return BitConverter.ToString(data).Replace("-", "");
|
|
}
|
|
|
|
public override uint? GetDaycareEXP(int loc, int slot)
|
|
{
|
|
return BitConverter.ToUInt32(Data, Daycare + 4 + 0xDC + (slot * 0xE4));
|
|
}
|
|
|
|
public override bool? IsDaycareOccupied(int loc, int slot)
|
|
{
|
|
return BitConverter.ToUInt32(Data, Daycare + (0xE4 * slot)) == 1;
|
|
}
|
|
|
|
public override void SetDaycareEXP(int loc, int slot, uint EXP)
|
|
{
|
|
BitConverter.GetBytes(EXP).CopyTo(Data, Daycare + 4 + 0xDC + (slot * 0xE4));
|
|
}
|
|
|
|
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
|
|
{
|
|
BitConverter.GetBytes((uint)(occupied ? 1 : 0)).CopyTo(Data, Daycare + 0x1CC);
|
|
}
|
|
|
|
public override void SetDaycareRNGSeed(int loc, string seed)
|
|
{
|
|
if (Version != GameVersion.B2W2)
|
|
return;
|
|
Enumerable.Range(0, seed.Length)
|
|
.Where(x => x % 2 == 0)
|
|
.Select(x => Convert.ToByte(seed.Substring(x, 2), 16))
|
|
.Reverse().ToArray().CopyTo(Data, Daycare + 0x1CC);
|
|
}
|
|
|
|
// Inventory
|
|
private readonly ushort[] LegalItems, LegalKeyItems, LegalTMHMs, LegalMedicine, LegalBerries;
|
|
|
|
public override InventoryPouch[] Inventory
|
|
{
|
|
get
|
|
{
|
|
InventoryPouch[] pouch =
|
|
{
|
|
new InventoryPouch4(InventoryType.Items, LegalItems, 999, OFS_PouchHeldItem),
|
|
new InventoryPouch4(InventoryType.KeyItems, LegalKeyItems, 1, OFS_PouchKeyItem),
|
|
new InventoryPouch4(InventoryType.TMHMs, LegalTMHMs, 1, OFS_PouchTMHM),
|
|
new InventoryPouch4(InventoryType.Medicine, LegalMedicine, 999, OFS_PouchMedicine),
|
|
new InventoryPouch4(InventoryType.Berries, LegalBerries, 999, OFS_PouchBerry),
|
|
};
|
|
foreach (var p in pouch)
|
|
p.GetPouch(Data);
|
|
return pouch;
|
|
}
|
|
set
|
|
{
|
|
foreach (var p in value)
|
|
p.SetPouch(Data);
|
|
}
|
|
}
|
|
|
|
// Storage
|
|
public override int PartyCount
|
|
{
|
|
get => Data[Party + 4];
|
|
protected set => Data[Party + 4] = (byte)value;
|
|
}
|
|
|
|
public override int GetBoxOffset(int box)
|
|
{
|
|
return Box + (SIZE_STORED * box * 30) + (box * 0x10);
|
|
}
|
|
|
|
public override int GetPartyOffset(int slot)
|
|
{
|
|
return Party + 8 + (SIZE_PARTY * slot);
|
|
}
|
|
|
|
public override string GetBoxName(int box)
|
|
{
|
|
if (box >= BoxCount)
|
|
return "";
|
|
return StringConverter.TrimFromFFFF(Encoding.Unicode.GetString(Data, PCLayout + (0x28 * box) + 4, 0x28));
|
|
}
|
|
|
|
public override void SetBoxName(int box, string value)
|
|
{
|
|
if (value.Length > 38)
|
|
return;
|
|
value += '\uFFFF';
|
|
Encoding.Unicode.GetBytes(value.PadRight(0x14, '\0')).CopyTo(Data, PCLayout + (0x28 * box) + 4);
|
|
Edited = true;
|
|
}
|
|
|
|
protected override int GetBoxWallpaperOffset(int box)
|
|
{
|
|
return PCLayout + 0x3C4 + box;
|
|
}
|
|
|
|
public override int CurrentBox
|
|
{
|
|
get => Data[PCLayout];
|
|
set => Data[PCLayout] = (byte)value;
|
|
}
|
|
|
|
public override bool BattleBoxLocked
|
|
{
|
|
get => BattleBox >= 0 && Data[BattleBox + 0x358] != 0; // wifi/live
|
|
set { }
|
|
}
|
|
|
|
public override PKM GetPKM(byte[] data)
|
|
{
|
|
return new PK5(data);
|
|
}
|
|
|
|
public override byte[] DecryptPKM(byte[] data)
|
|
{
|
|
return PKX.DecryptArray45(data);
|
|
}
|
|
|
|
protected override void SetPKM(PKM pkm)
|
|
{
|
|
var pk5 = (PK5)pkm;
|
|
// Apply to this Save File
|
|
DateTime Date = DateTime.Now;
|
|
if (pk5.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year))
|
|
pkm.RefreshChecksum();
|
|
}
|
|
|
|
// Mystery Gift
|
|
public override MysteryGiftAlbum GiftAlbum
|
|
{
|
|
get
|
|
{
|
|
uint seed = BitConverter.ToUInt32(Data, wcSeed);
|
|
MysteryGiftAlbum Info = new MysteryGiftAlbum { Seed = seed };
|
|
byte[] wcData = GetData(WondercardData, 0xA90); // Encrypted, Decrypt
|
|
for (int i = 0; i < wcData.Length; i += 2)
|
|
BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(wcData, i) ^ PKX.LCRNG(ref seed) >> 16)).CopyTo(wcData, i);
|
|
|
|
Info.Flags = new bool[GiftFlagMax];
|
|
Info.Gifts = new MysteryGift[GiftCountMax];
|
|
// 0x100 Bytes for Used Flags
|
|
for (int i = 0; i < GiftFlagMax; i++)
|
|
Info.Flags[i] = (wcData[i/8] >> i%8 & 0x1) == 1;
|
|
// 12 PGFs
|
|
for (int i = 0; i < Info.Gifts.Length; i++)
|
|
Info.Gifts[i] = new PGF(wcData.Skip(0x100 + (i *PGF.Size)).Take(PGF.Size).ToArray());
|
|
|
|
return Info;
|
|
}
|
|
set
|
|
{
|
|
byte[] wcData = new byte[0xA90];
|
|
|
|
// Toss back into byte[]
|
|
for (int i = 0; i < value.Flags.Length; i++)
|
|
{
|
|
if (value.Flags[i])
|
|
wcData[i/8] |= (byte)(1 << (i & 7));
|
|
}
|
|
|
|
for (int i = 0; i < value.Gifts.Length; i++)
|
|
value.Gifts[i].Data.CopyTo(wcData, 0x100 + (i *PGF.Size));
|
|
|
|
// Decrypted, Encrypt
|
|
uint seed = value.Seed;
|
|
for (int i = 0; i < wcData.Length; i += 2)
|
|
BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(wcData, i) ^ PKX.LCRNG(ref seed) >> 16)).CopyTo(wcData, i);
|
|
|
|
// Write Back
|
|
wcData.CopyTo(Data, WondercardData);
|
|
BitConverter.GetBytes(value.Seed).CopyTo(Data, wcSeed);
|
|
}
|
|
}
|
|
|
|
protected override bool[] MysteryGiftReceivedFlags { get => null; set { } }
|
|
protected override MysteryGift[] MysteryGiftCards { get => Array.Empty<MysteryGift>(); set { } }
|
|
|
|
// Trainer Info
|
|
public override string OT
|
|
{
|
|
get => GetString(Trainer1 + 0x4, 16);
|
|
set => SetString(value, OTLength).CopyTo(Data, Trainer1 + 0x4);
|
|
}
|
|
|
|
public override int TID
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x14 + 0);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x14 + 0);
|
|
}
|
|
|
|
public override int SID
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x14 + 2);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x14 + 2);
|
|
}
|
|
|
|
public override uint Money
|
|
{
|
|
get => BitConverter.ToUInt32(Data, Trainer2);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, Trainer2);
|
|
}
|
|
|
|
public override int Gender
|
|
{
|
|
get => Data[Trainer1 + 0x21];
|
|
set => Data[Trainer1 + 0x21] = (byte)value;
|
|
}
|
|
|
|
public override int Language
|
|
{
|
|
get => Data[Trainer1 + 0x1E];
|
|
set => Data[Trainer1 + 0x1E] = (byte)value;
|
|
}
|
|
|
|
public override int Game
|
|
{
|
|
get => Data[Trainer1 + 0x1F];
|
|
set => Data[Trainer1 + 0x1F] = (byte)value;
|
|
}
|
|
|
|
public int Badges
|
|
{
|
|
get => Data[Trainer2 + 0x4];
|
|
set => Data[Trainer2 + 0x4] = (byte)value;
|
|
}
|
|
|
|
public int M
|
|
{
|
|
get => BitConverter.ToInt32(Data, Trainer1 + 0x180);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x180);
|
|
}
|
|
|
|
public int X
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x186);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x186);
|
|
}
|
|
|
|
public int Z
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x18A);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x18A);
|
|
}
|
|
|
|
public int Y
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x18E);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x18E);
|
|
}
|
|
|
|
public override int PlayedHours
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x24);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x24);
|
|
}
|
|
|
|
public override int PlayedMinutes
|
|
{
|
|
get => Data[Trainer1 + 0x24 + 2];
|
|
set => Data[Trainer1 + 0x24 + 2] = (byte)value;
|
|
}
|
|
|
|
public override int PlayedSeconds
|
|
{
|
|
get => Data[Trainer1 + 0x24 + 3];
|
|
set => Data[Trainer1 + 0x24 + 3] = (byte)value;
|
|
}
|
|
|
|
public override int SecondsToStart { get => BitConverter.ToInt32(Data, AdventureInfo + 0x34); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x34); }
|
|
public override int SecondsToFame { get => BitConverter.ToInt32(Data, AdventureInfo + 0x3C); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x3C); }
|
|
|
|
public int BP
|
|
{
|
|
get => BitConverter.ToUInt16(Data, BattleSubway);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, BattleSubway);
|
|
}
|
|
|
|
public ushort GetPWTRecord(int id) => GetPWTRecord((PWTRecordID) id);
|
|
|
|
public ushort GetPWTRecord(PWTRecordID id)
|
|
{
|
|
if (id < PWTRecordID.Normal || id > PWTRecordID.MixMaster)
|
|
throw new ArgumentException(nameof(id));
|
|
int ofs = 0x2375C + ((int)id * 2);
|
|
return BitConverter.ToUInt16(Data, ofs);
|
|
}
|
|
|
|
public void SetPWTRecord(int id, ushort value) => SetPWTRecord((PWTRecordID) id, value);
|
|
|
|
public void SetPWTRecord(PWTRecordID id, ushort value)
|
|
{
|
|
if (id < PWTRecordID.Normal || id > PWTRecordID.MixMaster)
|
|
throw new ArgumentException(nameof(id));
|
|
int ofs = 0x2375C + ((int)id * 2);
|
|
SetData(BitConverter.GetBytes(value), ofs);
|
|
}
|
|
|
|
protected override void SetDex(PKM pkm)
|
|
{
|
|
if (pkm.Species == 0)
|
|
return;
|
|
if (pkm.Species > MaxSpeciesID)
|
|
return;
|
|
if (Version == GameVersion.Invalid)
|
|
return;
|
|
if (PokeDex < 0)
|
|
return;
|
|
|
|
const int brSize = 0x54;
|
|
int bit = pkm.Species - 1;
|
|
int gender = pkm.Gender % 2; // genderless -> male
|
|
int shiny = pkm.IsShiny ? 1 : 0;
|
|
int shift = (shiny * 2) + gender + 1;
|
|
int shiftoff = (shiny * brSize * 2) + (gender * brSize) + brSize;
|
|
int ofs = PokeDex + 0x8 + (bit >> 3);
|
|
|
|
// Set the Species Owned Flag
|
|
Data[ofs + (brSize * 0)] |= (byte)(1 << (bit % 8));
|
|
|
|
// Set the [Species/Gender/Shiny] Seen Flag
|
|
Data[PokeDex + 0x8 + shiftoff + (bit / 8)] |= (byte)(1 << (bit&7));
|
|
|
|
// Set the Display flag if none are set
|
|
bool Displayed = false;
|
|
Displayed |= (Data[ofs + (brSize * 5)] & (byte)(1 << (bit&7))) != 0;
|
|
Displayed |= (Data[ofs + (brSize * 6)] & (byte)(1 << (bit&7))) != 0;
|
|
Displayed |= (Data[ofs + (brSize * 7)] & (byte)(1 << (bit&7))) != 0;
|
|
Displayed |= (Data[ofs + (brSize * 8)] & (byte)(1 << (bit&7))) != 0;
|
|
if (!Displayed) // offset is already biased by brSize, reuse shiftoff but for the display flags.
|
|
Data[ofs + (brSize *(shift + 4))] |= (byte)(1 << (bit&7));
|
|
|
|
// Set the Language
|
|
if (bit < 493) // shifted by 1, Gen5 species do not have international language bits
|
|
{
|
|
int lang = pkm.Language - 1; if (lang > 5) lang--; // 0-6 language vals
|
|
if (lang < 0) lang = 1;
|
|
Data[PokeDexLanguageFlags + (((bit * 7) + lang)>>3)] |= (byte)(1 << (((bit * 7) + lang) & 7));
|
|
}
|
|
|
|
// Formes
|
|
int fc = Personal[pkm.Species].FormeCount;
|
|
int f = B2W2 ? SaveUtil.GetDexFormIndexB2W2(pkm.Species, fc) : SaveUtil.GetDexFormIndexBW(pkm.Species, fc);
|
|
if (f < 0) return;
|
|
|
|
int FormLen = B2W2 ? 0xB : 0x9;
|
|
int FormDex = PokeDex + 0x8 + (brSize * 9);
|
|
bit = f + pkm.AltForm;
|
|
|
|
// Set Form Seen Flag
|
|
Data[FormDex + (FormLen * shiny) + (bit>>3)] |= (byte)(1 << (bit&7));
|
|
|
|
// Set Displayed Flag if necessary, check all flags
|
|
for (int i = 0; i < fc; i++)
|
|
{
|
|
bit = f + i;
|
|
if ((Data[FormDex + (FormLen * 2) + (bit>>3)] & (byte)(1 << (bit&7))) != 0) // Nonshiny
|
|
return; // already set
|
|
if ((Data[FormDex + (FormLen * 3) + (bit>>3)] & (byte)(1 << (bit&7))) != 0) // Shiny
|
|
return; // already set
|
|
}
|
|
bit = f + pkm.AltForm;
|
|
Data[FormDex + (FormLen * (2 + shiny)) + (bit>>3)] |= (byte)(1 << (bit&7));
|
|
}
|
|
|
|
public override bool GetCaught(int species)
|
|
{
|
|
int bit = species - 1;
|
|
int bd = bit >> 3; // div8
|
|
int bm = bit & 7; // mod8
|
|
int ofs = PokeDex // Raw Offset
|
|
+ 0x08; // Magic + Flags
|
|
return (1 << bm & Data[ofs + bd]) != 0;
|
|
}
|
|
|
|
public override bool GetSeen(int species)
|
|
{
|
|
const int brSize = 0x54;
|
|
|
|
int bit = species - 1;
|
|
int bd = bit >> 3; // div8
|
|
int bm = bit & 7; // mod8
|
|
int ofs = PokeDex // Raw Offset
|
|
+ 0x08; // Magic + Flags
|
|
|
|
for (int i = 1; i <= 4; i++)
|
|
{
|
|
if ((1 << bm & Data[ofs + bd + (i * brSize)]) != 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override string GetString(int Offset, int Length) => StringConverter.GetString5(Data, Offset, Length);
|
|
|
|
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
|
|
{
|
|
if (PadToSize == 0)
|
|
PadToSize = maxLength + 1;
|
|
return StringConverter.SetString5(value, maxLength, PadToSize, PadWith);
|
|
}
|
|
|
|
// DLC
|
|
private int CGearSkinInfoOffset => CGearInfoOffset + (B2W2 ? 0x10 : 0) + 0x24;
|
|
|
|
private bool CGearSkinPresent
|
|
{
|
|
get => Data[CGearSkinInfoOffset + 2] == 1;
|
|
set => Data[CGearSkinInfoOffset + 2] = Data[Trainer1 + (B2W2 ? 0x6C : 0x54)] = (byte) (value ? 1 : 0);
|
|
}
|
|
|
|
public byte[] CGearSkinData
|
|
{
|
|
get
|
|
{
|
|
byte[] data = new byte[0x2600];
|
|
if (CGearSkinPresent)
|
|
Array.Copy(Data, CGearDataOffset, data, 0, data.Length);
|
|
return data;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
return; // no clearing
|
|
byte[] dlcfooter = { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 };
|
|
|
|
byte[] bgdata = value;
|
|
SetData(bgdata, CGearDataOffset);
|
|
|
|
ushort chk = SaveUtil.CRC16_CCITT(bgdata);
|
|
var chkbytes = BitConverter.GetBytes(chk);
|
|
int footer = CGearDataOffset + bgdata.Length;
|
|
|
|
BitConverter.GetBytes((ushort)1).CopyTo(Data, footer); // block updated once
|
|
chkbytes.CopyTo(Data, footer + 2); // checksum
|
|
chkbytes.CopyTo(Data, footer + 0x100); // second checksum
|
|
dlcfooter.CopyTo(Data, footer + 0x102);
|
|
ushort skinchkval = SaveUtil.CRC16_CCITT(Data, footer + 0x100, 4);
|
|
BitConverter.GetBytes(skinchkval).CopyTo(Data, footer + 0x112);
|
|
|
|
// Indicate in the save file that data is present
|
|
BitConverter.GetBytes((ushort)0xC21E).CopyTo(Data, 0x19438);
|
|
|
|
chkbytes.CopyTo(Data, CGearSkinInfoOffset);
|
|
CGearSkinPresent = true;
|
|
|
|
Edited = true;
|
|
}
|
|
}
|
|
|
|
public EntreeForest EntreeData
|
|
{
|
|
get => new EntreeForest(GetData(EntreeForestOffset, 0x850));
|
|
set => SetData(value.Write(), EntreeForestOffset);
|
|
}
|
|
}
|
|
}
|