mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 20:43:07 +00:00
383d4b7700
be explicit that the string is empty rather than possibly missing disallow encrypted export for BK4 (they're not encrypted), removes type check simplify replaceall in showdownset (don't call ReplaceAll 4x, just get valid chars and rebuild) simplify get ribbon sprite name (precompute ToLower and appended values
613 lines
23 KiB
C#
613 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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|" : string.Empty) + "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
|
|
public readonly IReadOnlyList<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("-", string.Empty);
|
|
}
|
|
|
|
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 string.Empty;
|
|
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 => Array.Empty<bool>(); 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(byte[] data, 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);
|
|
}
|
|
}
|
|
}
|