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
1538 lines
58 KiB
C#
1538 lines
58 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Generation 7 <see cref="SaveFile"/> object.
|
|
/// </summary>
|
|
public sealed class SAV7 : SaveFile, ITrainerStatRecord, ISecureValueStorage
|
|
{
|
|
// Save Data Attributes
|
|
protected override string BAKText => $"{OT} ({Version}) - {LastSavedTime}";
|
|
public override string Filter => "Main SAV|*.*";
|
|
public override string Extension => string.Empty;
|
|
|
|
public override string[] PKMExtensions => PKM.Extensions.Where(f =>
|
|
{
|
|
int gen = f.Last() - 0x30;
|
|
return gen <= 7;
|
|
}).ToArray();
|
|
|
|
public SAV7(byte[] data = null)
|
|
{
|
|
Data = data ?? new byte[SaveUtil.SIZE_G7SM];
|
|
BAK = (byte[])Data.Clone();
|
|
Exportable = !IsRangeEmpty(0, Data.Length);
|
|
|
|
// Load Info
|
|
Blocks = BlockInfo3DS.GetBlockInfoData(Data, out BlockInfoOffset, SaveUtil.CRC16);
|
|
if (Exportable)
|
|
CanReadChecksums();
|
|
GetSAVOffsets();
|
|
|
|
HeldItems = USUM ? Legal.HeldItems_USUM : Legal.HeldItems_SM;
|
|
Personal = USUM ? PersonalTable.USUM : PersonalTable.SM;
|
|
if (!Exportable)
|
|
ClearBoxes();
|
|
|
|
var demo = !USUM && Data.Skip(PCLayout).Take(0x4C4).All(z => z == 0); // up to Battle Box values
|
|
if (demo || !Exportable)
|
|
{
|
|
PokeDex = -1; // Disabled
|
|
TeamSlots = Array.Empty<int>();
|
|
}
|
|
else // Valid slot locking info present
|
|
{
|
|
LoadBattleTeams();
|
|
}
|
|
}
|
|
|
|
// Configuration
|
|
public override SaveFile Clone() { return new SAV7((byte[])Data.Clone()); }
|
|
|
|
public override int SIZE_STORED => PKX.SIZE_6STORED;
|
|
protected override int SIZE_PARTY => PKX.SIZE_6PARTY;
|
|
public override PKM BlankPKM => new PK7();
|
|
public override Type PKMType => typeof(PK7);
|
|
|
|
public override int BoxCount => 32;
|
|
public override int MaxEV => 252;
|
|
public override int Generation => 7;
|
|
protected override int GiftCountMax => 48;
|
|
protected override int GiftFlagMax => 0x100 * 8;
|
|
protected override int EventFlagMax => USUM ? 4928 : 3968;
|
|
protected override int EventConstMax => 1000;
|
|
public override int OTLength => 12;
|
|
public override int NickLength => 12;
|
|
|
|
public override int MaxMoveID => SM ? Legal.MaxMoveID_7 : Legal.MaxMoveID_7_USUM;
|
|
public override int MaxSpeciesID => SM ? Legal.MaxSpeciesID_7 : Legal.MaxSpeciesID_7_USUM;
|
|
public override int MaxItemID => SM ? Legal.MaxItemID_7 : Legal.MaxItemID_7_USUM;
|
|
public override int MaxAbilityID => SM ? Legal.MaxAbilityID_7 : Legal.MaxAbilityID_7_USUM;
|
|
public override int MaxBallID => Legal.MaxBallID_7; // 26
|
|
public override int MaxGameID => Legal.MaxGameID_7;
|
|
|
|
public int QRSaveData;
|
|
|
|
// Feature Overrides
|
|
|
|
// Blocks & Offsets
|
|
private readonly int BlockInfoOffset;
|
|
private readonly BlockInfo[] Blocks;
|
|
private bool IsMemeCryptoApplied = true;
|
|
private const int MemeCryptoBlock = 36;
|
|
public override bool ChecksumsValid => CanReadChecksums() && Blocks.GetChecksumsValid(Data);
|
|
public override string ChecksumInfo => CanReadChecksums() ? Blocks.GetChecksumInfo(Data) : string.Empty;
|
|
|
|
private bool CanReadChecksums()
|
|
{
|
|
if (Blocks.Length <= MemeCryptoBlock)
|
|
{ Debug.WriteLine($"Not enough blocks ({Blocks.Length}), aborting {nameof(CanReadChecksums)}"); return false; }
|
|
if (!IsMemeCryptoApplied)
|
|
return true;
|
|
// clear memecrypto sig
|
|
new byte[0x80].CopyTo(Data, Blocks[MemeCryptoBlock].Offset + 0x100);
|
|
IsMemeCryptoApplied = false;
|
|
return true;
|
|
}
|
|
|
|
protected override void SetChecksums()
|
|
{
|
|
if (!CanReadChecksums())
|
|
return;
|
|
Blocks.SetChecksums(Data);
|
|
SaveBattleTeams();
|
|
Data = SaveUtil.Resign7(Data);
|
|
IsMemeCryptoApplied = true;
|
|
}
|
|
|
|
public ulong TimeStampCurrent
|
|
{
|
|
get => BitConverter.ToUInt64(Data, BlockInfoOffset - 0x14);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, BlockInfoOffset - 0x14);
|
|
}
|
|
|
|
public ulong TimeStampPrevious
|
|
{
|
|
get => BitConverter.ToUInt64(Data, BlockInfoOffset - 0xC);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, BlockInfoOffset - 0xC);
|
|
}
|
|
|
|
private void GetSAVOffsets()
|
|
{
|
|
if (!Exportable) // Empty input
|
|
{
|
|
// Set bare minimum values for empty sav compatibility (based on S/M offsets)
|
|
Trainer1 = 0x00E00;
|
|
TrainerCard = 0x01200;
|
|
Party = 0x01400;
|
|
Box = 0x04E00;
|
|
PCLayout = 0x04800;
|
|
BattleBoxFlags = PCLayout + 0x4C4;
|
|
PCBackgrounds = PCLayout + 0x5C0;
|
|
LastViewedBox = PCLayout + 0x5E3;
|
|
PCFlags = PCLayout + 0x5E0;
|
|
return;
|
|
}
|
|
|
|
/* 00 */ Bag = Blocks[00].Offset; // 0x00000 // [DE0] MyItem
|
|
/* 01 */ Trainer1 = Blocks[01].Offset; // 0x00E00 // [07C] Situation
|
|
/* 02 */ // = Blocks[02].Offset; // 0x01000 // [014] RandomGroup
|
|
/* 03 */ TrainerCard = Blocks[03].Offset; // 0x01200 // [0C0] MyStatus
|
|
/* 04 */ Party = Blocks[04].Offset; // 0x01400 // [61C] PokePartySave
|
|
/* 05 */ EventConst = Blocks[05].Offset; // 0x01C00 // [E00] EventWork
|
|
/* 06 */ PokeDex = Blocks[06].Offset; // 0x02A00 // [F78] ZukanData
|
|
/* 07 */ GTS = Blocks[07].Offset; // 0x03A00 // [228] GtsData
|
|
/* 08 */ Fused = Blocks[08].Offset; // 0x03E00 // [104] UnionPokemon
|
|
/* 09 */ Misc = Blocks[09].Offset; // 0x04000 // [200] Misc
|
|
/* 10 */ Trainer2 = Blocks[10].Offset; // 0x04200 // [020] FieldMenu
|
|
/* 11 */ ConfigSave = Blocks[11].Offset; // 0x04400 // [004] ConfigSave
|
|
/* 12 */ AdventureInfo = Blocks[12].Offset; // 0x04600 // [058] GameTime
|
|
/* 13 */ PCLayout = Blocks[13].Offset; // 0x04800 // [5E6] BOX
|
|
/* 14 */ Box = Blocks[14].Offset; // 0x04E00 // [36600] BoxPokemon
|
|
/* 15 */ Resort = Blocks[15].Offset; // 0x3B400 // [572C] ResortSave
|
|
/* 16 */ PlayTime = Blocks[16].Offset; // 0x40C00 // [008] PlayTime
|
|
/* 17 */ Overworld = Blocks[17].Offset; // 0x40E00 // [1080] FieldMoveModelSave
|
|
/* 18 */ Fashion = Blocks[18].Offset; // 0x42000 // [1A08] Fashion
|
|
/* 19 */ // = Blocks[19].Offset; // 0x43C00 // [6408] JoinFestaPersonalSave
|
|
/* 20 */ // = Blocks[20].Offset; // 0x4A200 // [6408] JoinFestaPersonalSave
|
|
/* 21 */ JoinFestaData = Blocks[21].Offset; // 0x50800 // [3998] JoinFestaDataSave
|
|
/* 22 */ // = Blocks[22].Offset; // 0x54200 // [100] BerrySpot
|
|
/* 23 */ // = Blocks[23].Offset; // 0x54400 // [100] FishingSpot
|
|
/* 24 */ // = Blocks[24].Offset; // 0x54600 // [10528] LiveMatchData
|
|
/* 25 */ // = Blocks[25].Offset; // 0x64C00 // [204] BattleSpotData
|
|
/* 26 */ PokeFinderSave = Blocks[26].Offset; // 0x65000 // [B60] PokeFinderSave
|
|
/* 27 */ WondercardFlags= Blocks[27].Offset; // 0x65C00 // [3F50] MysteryGiftSave
|
|
/* 28 */ Record = Blocks[28].Offset; // 0x69C00 // [358] Record
|
|
/* 29 */ // = Blocks[29].Offset; // 0x6A000 // [728] ValidationSave
|
|
/* 30 */ // = Blocks[30].Offset; // 0x6A800 // [200] GameSyncSave
|
|
/* 31 */ // = Blocks[31].Offset; // 0x6AA00 // [718] PokeDiarySave
|
|
/* 32 */ BattleTree = Blocks[32].Offset; // 0x6B200 // [1FC] BattleInstSave
|
|
/* 33 */ Daycare = Blocks[33].Offset; // 0x6B400 // [200] Sodateya
|
|
/* 34 */ // = Blocks[34].Offset; // 0x6B600 // [120] WeatherSave
|
|
/* 35 */ QRSaveData = Blocks[35].Offset; // 0x6B800 // [1C8] QRReaderSaveData
|
|
/* 36 */ // = Blocks[36].Offset; // 0x6BA00 // [200] TurtleSalmonSave
|
|
|
|
EventFlag = EventConst + (EventConstMax * 2); // After Event Const (u16)*n
|
|
HoF = EventFlag + (EventFlagMax / 8); // After Event Flags (1b)*(1u8/8b)*n
|
|
|
|
OFS_PouchHeldItem = Bag + 0; // 430 (Case 0)
|
|
OFS_PouchKeyItem = Bag + 0x6B8; // 184 (Case 4)
|
|
OFS_PouchTMHM = Bag + 0x998; // 108 (Case 2)
|
|
OFS_PouchMedicine = Bag + 0xB48; // 64 (Case 1)
|
|
OFS_PouchBerry = Bag + 0xC48; // 72 (Case 3)
|
|
OFS_PouchZCrystals = Bag + 0xD68; // 30 (Case 5)
|
|
|
|
PokeDexLanguageFlags = PokeDex + 0x550;
|
|
WondercardData = WondercardFlags + 0x100;
|
|
|
|
BattleBoxFlags = PCLayout + 0x4C4;
|
|
PCBackgrounds = PCLayout + 0x5C0;
|
|
LastViewedBox = PCLayout + 0x5E3;
|
|
PCFlags = PCLayout + 0x5E0;
|
|
|
|
FashionLength = 0x1A08;
|
|
|
|
TeamCount = 6;
|
|
TeamSlots = new int[6*TeamCount];
|
|
if (USUM)
|
|
{
|
|
// slight differences
|
|
/* 37 */ // = Blocks[37].Offset; BattleFesSave
|
|
/* 38 */ // = Blocks[38].Offset; FinderStudioSave
|
|
|
|
OFS_PouchHeldItem = Bag + 0; // 427 (Case 0)
|
|
OFS_PouchKeyItem = OFS_PouchHeldItem + (4 * 427); // 198 (Case 4)
|
|
OFS_PouchTMHM = OFS_PouchKeyItem + (4 * 198); // 108 (Case 2)
|
|
OFS_PouchMedicine = OFS_PouchTMHM + (4 * 108); // 60 (Case 1)
|
|
OFS_PouchBerry = OFS_PouchMedicine + (4 * 60); // 67 (Case 3)
|
|
OFS_PouchZCrystals = OFS_PouchBerry + (4 * 67); // 35 (Case 5)
|
|
OFS_BattleItems = OFS_PouchZCrystals + (4 * 35); // 11 (Case 6)
|
|
}
|
|
}
|
|
|
|
// Private Only
|
|
private int Bag { get; set; } = int.MinValue;
|
|
private int AdventureInfo { get; set; } = int.MinValue;
|
|
private int Trainer2 { get; set; } = int.MinValue;
|
|
public int Misc { get; private set; } = int.MinValue;
|
|
private int LastViewedBox { get; set; } = int.MinValue;
|
|
private int WondercardFlags { get; set; } = int.MinValue;
|
|
private int PlayTime { get; set; } = int.MinValue;
|
|
private int Overworld { get; set; } = int.MinValue;
|
|
public int JoinFestaData { get; private set; } = int.MinValue;
|
|
private int PokeFinderSave { get; set; } = int.MinValue;
|
|
private int BattleTree { get; set; } = int.MinValue;
|
|
private int BattleBoxFlags { get; set; } = int.MinValue;
|
|
private int TeamCount { get; set; } = int.MinValue;
|
|
private int ConfigSave { get; set; } = int.MinValue;
|
|
|
|
// Accessible as SAV7
|
|
private int TrainerCard { get; set; } = 0x14000;
|
|
private int Resort { get; set; }
|
|
private int PCFlags { get; set; } = int.MinValue;
|
|
private int PCBackgrounds { get; set; } = int.MinValue;
|
|
public int PokeDexLanguageFlags { get; private set; } = int.MinValue;
|
|
public int Fashion { get; set; } = int.MinValue;
|
|
public int FashionLength { get; set; } = int.MinValue;
|
|
private int Record { get; set; } = int.MinValue;
|
|
|
|
public const int ResortCount = 93;
|
|
public int GetResortSlotOffset(int slot) => Resort + 0x16 + (slot * SIZE_STORED);
|
|
|
|
public PKM[] ResortPKM
|
|
{
|
|
get
|
|
{
|
|
PKM[] data = new PKM[ResortCount];
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
data[i] = GetPKM(GetData(GetResortSlotOffset(i), SIZE_STORED));
|
|
data[i].Identifier = $"Resort Slot {i}";
|
|
}
|
|
return data;
|
|
}
|
|
set
|
|
{
|
|
if (value?.Length != ResortCount)
|
|
throw new ArgumentException(nameof(ResortCount));
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
SetStoredSlot(value[i], GetResortSlotOffset(i));
|
|
}
|
|
}
|
|
|
|
public override GameVersion Version
|
|
{
|
|
get
|
|
{
|
|
switch (Game)
|
|
{
|
|
case 30: return GameVersion.SN;
|
|
case 31: return GameVersion.MN;
|
|
case 32: return GameVersion.US;
|
|
case 33: return GameVersion.UM;
|
|
}
|
|
return GameVersion.Invalid;
|
|
}
|
|
}
|
|
|
|
// Player Information
|
|
public override int TID
|
|
{
|
|
get => BitConverter.ToUInt16(Data, TrainerCard + 0);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, TrainerCard + 0);
|
|
}
|
|
|
|
public override int SID
|
|
{
|
|
get => BitConverter.ToUInt16(Data, TrainerCard + 2);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, TrainerCard + 2);
|
|
}
|
|
|
|
public override int Game
|
|
{
|
|
get => Data[TrainerCard + 4];
|
|
set => Data[TrainerCard + 4] = (byte)value;
|
|
}
|
|
|
|
public override int Gender
|
|
{
|
|
get => Data[TrainerCard + 5];
|
|
set => Data[TrainerCard + 5] = (byte)value;
|
|
}
|
|
|
|
public override int GameSyncIDSize => 16; // 64 bits
|
|
|
|
public override string GameSyncID
|
|
{
|
|
get
|
|
{
|
|
var data = Data.Skip(TrainerCard + 0x10).Take(GameSyncIDSize/2).Reverse().ToArray();
|
|
return BitConverter.ToString(data).Replace("-", string.Empty);
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
return;
|
|
if (value.Length > GameSyncIDSize)
|
|
return;
|
|
|
|
Enumerable.Range(0, value.Length)
|
|
.Where(x => x % 2 == 0)
|
|
.Reverse()
|
|
.Select(x => Convert.ToByte(value.Substring(x, 2), 16))
|
|
.ToArray().CopyTo(Data, TrainerCard + 0x10);
|
|
}
|
|
}
|
|
|
|
private const int NexUniqueIDSize = 32; // 128 bits
|
|
|
|
public string NexUniqueID
|
|
{
|
|
get
|
|
{
|
|
var data = Data.Skip(TrainerCard + 0x18).Take(NexUniqueIDSize/2).Reverse().ToArray();
|
|
return BitConverter.ToString(data).Replace("-", string.Empty);
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
return;
|
|
if (value.Length > NexUniqueIDSize)
|
|
return;
|
|
|
|
Enumerable.Range(0, value.Length)
|
|
.Where(x => x % 2 == 0)
|
|
.Reverse()
|
|
.Select(x => Convert.ToByte(value.Substring(x, 2), 16))
|
|
.ToArray().CopyTo(Data, TrainerCard + 0x18);
|
|
}
|
|
}
|
|
|
|
public byte[] FestaID // 12byte
|
|
{
|
|
get => Data.Skip(TrainerCard + 0x28).Take(4).Concat(Data.Skip(TrainerCard + 0x18).Take(8)).ToArray();
|
|
set
|
|
{
|
|
if ((value?.Length ?? 0) != 12) return;
|
|
Array.Copy(value, 0, Data, TrainerCard + 0x28, 4);
|
|
Array.Copy(value, 4, Data, TrainerCard + 0x18, 8);
|
|
}
|
|
}
|
|
|
|
public override int SubRegion
|
|
{
|
|
get => Data[TrainerCard + 0x2E];
|
|
set => Data[TrainerCard + 0x2E] = (byte)value;
|
|
}
|
|
|
|
public override int Country
|
|
{
|
|
get => Data[TrainerCard + 0x2F];
|
|
set => Data[TrainerCard + 0x2F] = (byte)value;
|
|
}
|
|
|
|
public override int ConsoleRegion
|
|
{
|
|
get => Data[TrainerCard + 0x34];
|
|
set => Data[TrainerCard + 0x34] = (byte)value;
|
|
}
|
|
|
|
public override int Language
|
|
{
|
|
get => Data[TrainerCard + 0x35];
|
|
set => Data[TrainerCard + 0x35] = (byte)value;
|
|
}
|
|
|
|
public override string OT
|
|
{
|
|
get => GetString(TrainerCard + 0x38, 0x1A);
|
|
set => SetString(value, OTLength).CopyTo(Data, TrainerCard + 0x38);
|
|
}
|
|
|
|
public int DressUpSkinColor
|
|
{
|
|
get => (Data[TrainerCard + 0x54] >> 2) & 7;
|
|
set => Data[TrainerCard + 0x54] = (byte)((Data[TrainerCard + 0x54] & ~(7 << 2)) | (value << 2));
|
|
}
|
|
|
|
public override int MultiplayerSpriteID
|
|
{
|
|
get => Data[TrainerCard + 0x58];
|
|
set => Data[TrainerCard + 0x58] = (byte)value;
|
|
}
|
|
|
|
public int BallThrowType
|
|
{
|
|
get => Data[TrainerCard + 0x7A];
|
|
set => Data[TrainerCard + 0x7A] = (byte)(value > 8 ? 0 : value);
|
|
}
|
|
|
|
public int M // "StartLocation"
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x00);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x00);
|
|
}
|
|
|
|
public float X
|
|
{
|
|
get => BitConverter.ToSingle(Data, Trainer1 + 0x08);
|
|
set
|
|
{
|
|
BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x08);
|
|
BitConverter.GetBytes(value).CopyTo(Data, Overworld + 0x08);
|
|
}
|
|
}
|
|
|
|
public float Z
|
|
{
|
|
get => BitConverter.ToSingle(Data, Trainer1 + 0x10);
|
|
set
|
|
{
|
|
BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x10);
|
|
BitConverter.GetBytes(value).CopyTo(Data, Overworld + 0x10);
|
|
}
|
|
}
|
|
|
|
public float Y
|
|
{
|
|
get => (int)BitConverter.ToSingle(Data, Trainer1 + 0x18);
|
|
set
|
|
{
|
|
BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x18);
|
|
BitConverter.GetBytes(value).CopyTo(Data, Overworld + 0x18);
|
|
}
|
|
}
|
|
|
|
public float R
|
|
{
|
|
get => (int)BitConverter.ToSingle(Data, Trainer1 + 0x20);
|
|
set
|
|
{
|
|
BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x20);
|
|
BitConverter.GetBytes(value).CopyTo(Data, Overworld + 0x20);
|
|
}
|
|
}
|
|
|
|
public int SpecialLocation
|
|
{
|
|
get => Data[Trainer1 + 0x24];
|
|
set => Data[Trainer1 + 0x24] = (byte)value;
|
|
}
|
|
|
|
public int WarpContinueRequest
|
|
{
|
|
get => Data[Trainer1 + 0x6E];
|
|
set => Data[Trainer1 + 0x6E] = (byte)value;
|
|
}
|
|
|
|
public int StepCountEgg
|
|
{
|
|
get => BitConverter.ToInt32(Data, Trainer1 + 0x70);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x70);
|
|
}
|
|
|
|
public int LastZoneID
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x74);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x74);
|
|
}
|
|
|
|
public int StepCountFriendship
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x76);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x76);
|
|
}
|
|
|
|
public int StepCountAffection // Kawaigari
|
|
{
|
|
get => BitConverter.ToUInt16(Data, Trainer1 + 0x78);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x78);
|
|
}
|
|
|
|
public int ConfigValue
|
|
{
|
|
get => BitConverter.ToInt32(Data, ConfigSave);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, ConfigSave);
|
|
}
|
|
|
|
public int TalkingSpeed
|
|
{
|
|
get => ConfigValue & 3;
|
|
set => ConfigValue = (ConfigValue & ~3) | (value & 3);
|
|
}
|
|
|
|
public int BattleAnimation
|
|
{
|
|
// Effects OFF = 1, Effects ON = 0
|
|
get => (ConfigValue >> 2) & 1;
|
|
set => ConfigValue = (ConfigValue & ~(1<<2)) | (value << 2);
|
|
}
|
|
|
|
public int BattleStyle
|
|
{
|
|
// SET = 1, SWITCH = 0
|
|
get => (ConfigValue >> 3) & 1;
|
|
set => ConfigValue = (ConfigValue & ~(1 << 3)) | (value << 3);
|
|
}
|
|
|
|
public int ButtonMode
|
|
{
|
|
get => (ConfigValue >> 13) & 3;
|
|
set => ConfigValue = (ConfigValue & ~(1 << 13)) | (value << 13);
|
|
}
|
|
|
|
public int BoxStatus
|
|
{
|
|
// MANUAL = 1, AUTOMATIC = 0
|
|
get => (ConfigValue >> 15) & 1;
|
|
set => ConfigValue = (ConfigValue & ~(1 << 15)) | (value << 15);
|
|
}
|
|
|
|
public override uint Money
|
|
{
|
|
get => BitConverter.ToUInt32(Data, Misc + 0x4);
|
|
set
|
|
{
|
|
if (value > 9999999) value = 9999999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, Misc + 0x4);
|
|
}
|
|
}
|
|
|
|
public uint Stamps
|
|
{
|
|
get => (BitConverter.ToUInt32(Data, Misc + 0x08) << 13) >> 17; // 15 stamps; discard top13, lowest4
|
|
set
|
|
{
|
|
uint flags = BitConverter.ToUInt32(Data, Misc + 0x08) & 0xFFF8000F;
|
|
flags |= (value & 0x7FFF) << 4;
|
|
BitConverter.GetBytes(flags).CopyTo(Data, Misc + 0x08);
|
|
}
|
|
}
|
|
|
|
public uint BP
|
|
{
|
|
get => BitConverter.ToUInt32(Data, Misc + 0x11C);
|
|
set
|
|
{
|
|
if (value > 9999) value = 9999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, Misc + 0x11C);
|
|
}
|
|
}
|
|
|
|
public int Vivillon
|
|
{
|
|
get => Data[Misc + 0x130] & 0x1F;
|
|
set => Data[Misc + 0x130] = (byte)((Data[Misc + 0x130] & ~0x1F) | (value & 0x1F));
|
|
}
|
|
|
|
public uint StarterEncryptionConstant
|
|
{
|
|
get => BitConverter.ToUInt32(Data, Misc + 0x148);
|
|
set => SetData(BitConverter.GetBytes(value), Misc + 0x148);
|
|
}
|
|
|
|
public int DaysFromRefreshed
|
|
{
|
|
get => Data[Misc + 0x123];
|
|
set => Data[Misc + 0x123] = (byte)value;
|
|
}
|
|
|
|
public uint UsedFestaCoins
|
|
{
|
|
get => BitConverter.ToUInt32(Data, Record + (38 << 2)); //Record[038]
|
|
set
|
|
{
|
|
if (value > 9999999) value = 9999999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, Record + (38 << 2));
|
|
}
|
|
}
|
|
|
|
public uint FestaCoins
|
|
{
|
|
get => BitConverter.ToUInt32(Data, JoinFestaData + 0x508);
|
|
set
|
|
{
|
|
if (value > 9999999) value = 9999999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x508);
|
|
|
|
TotalFestaCoins = UsedFestaCoins + value;
|
|
}
|
|
}
|
|
|
|
private uint TotalFestaCoins
|
|
{
|
|
get => BitConverter.ToUInt32(Data, JoinFestaData + 0x50C);
|
|
set
|
|
{
|
|
if (value > 9999999) value = 9999999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x50C);
|
|
}
|
|
}
|
|
|
|
public string FestivalPlazaName
|
|
{
|
|
get => Util.TrimFromZero(Encoding.Unicode.GetString(Data, JoinFestaData + 0x510, 0x2A));
|
|
set
|
|
{
|
|
const int max = 20;
|
|
if (value.Length > max) value = value.Substring(0, max);
|
|
Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, JoinFestaData + 0x510);
|
|
}
|
|
}
|
|
|
|
public ushort FestaRank { get => BitConverter.ToUInt16(Data, JoinFestaData + 0x53A); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x53A); }
|
|
public ushort GetFestaMessage(int index) => BitConverter.ToUInt16(Data, JoinFestaData + (index * 2));
|
|
public void SetFestaMessage(int index, ushort value) => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + (index * 2));
|
|
public bool GetFestaPhraseUnlocked(int index) => Data[JoinFestaData + 0x2A50 + index] != 0; //index: 0 to 105:commonPhrases, 106:Lv100!
|
|
|
|
public void SetFestaPhraseUnlocked(int index, bool value)
|
|
{
|
|
if (GetFestaPhraseUnlocked(index) != value) Data[JoinFestaData + 0x2A50 + index] = (byte)(value ? 1 : 0);
|
|
}
|
|
|
|
public byte GetFestPrizeReceived(int index) => Data[JoinFestaData + 0x53C + index];
|
|
public void SetFestaPrizeReceived(int index, byte value) => Data[JoinFestaData + 0x53C + index] = value;
|
|
private int FestaYear { get => BitConverter.ToInt32(Data, JoinFestaData + 0x2F0); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x2F0); }
|
|
private int FestaMonth { get => BitConverter.ToInt32(Data, JoinFestaData + 0x2F4); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x2F4); }
|
|
private int FestaDay { get => BitConverter.ToInt32(Data, JoinFestaData + 0x2F8); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x2F8); }
|
|
private int FestaHour { get => BitConverter.ToInt32(Data, JoinFestaData + 0x300); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x300); }
|
|
private int FestaMinute { get => BitConverter.ToInt32(Data, JoinFestaData + 0x304); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x304); }
|
|
private int FestaSecond { get => BitConverter.ToInt32(Data, JoinFestaData + 0x308); set => BitConverter.GetBytes(value).CopyTo(Data, JoinFestaData + 0x308); }
|
|
|
|
public DateTime? FestaDate
|
|
{
|
|
get => FestaYear >= 0 && FestaMonth > 0 && FestaDay > 0 && FestaHour >= 0 && FestaMinute >= 0 && FestaSecond >= 0 && Util.IsDateValid(FestaYear, FestaMonth, FestaDay)
|
|
? new DateTime(FestaYear, FestaMonth, FestaDay, FestaHour, FestaMinute, FestaSecond)
|
|
: (DateTime?)null;
|
|
set
|
|
{
|
|
if (value.HasValue)
|
|
{
|
|
DateTime dt = value.Value;
|
|
FestaYear = dt.Year;
|
|
FestaMonth = dt.Month;
|
|
FestaDay = dt.Day;
|
|
FestaHour = dt.Hour;
|
|
FestaMinute = dt.Minute;
|
|
FestaSecond = dt.Second;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class FashionItem
|
|
{
|
|
public bool IsOwned { get; set; }
|
|
public bool IsNew { get; set; }
|
|
}
|
|
|
|
public FashionItem[] Wardrobe
|
|
{
|
|
get
|
|
{
|
|
var data = GetData(Fashion, 0x5A8);
|
|
return data.Select(b => new FashionItem {IsOwned = (b & 1) != 0, IsNew = (b & 2) != 0}).ToArray();
|
|
}
|
|
set
|
|
{
|
|
if (value.Length != 0x5A8)
|
|
throw new ArgumentOutOfRangeException($"Unexpected size: 0x{value.Length:X}");
|
|
SetData(value.Select(t => (byte) ((t.IsOwned ? 1 : 0) | (t.IsNew ? 2 : 0))).ToArray(), Fashion);
|
|
}
|
|
}
|
|
|
|
public override int PlayedHours
|
|
{
|
|
get => BitConverter.ToUInt16(Data, PlayTime);
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, PlayTime);
|
|
}
|
|
|
|
public override int PlayedMinutes
|
|
{
|
|
get => Data[PlayTime + 2];
|
|
set => Data[PlayTime + 2] = (byte)value;
|
|
}
|
|
|
|
public override int PlayedSeconds
|
|
{
|
|
get => Data[PlayTime + 3];
|
|
set => Data[PlayTime + 3] = (byte)value;
|
|
}
|
|
|
|
private uint LastSaved { get => BitConverter.ToUInt32(Data, PlayTime + 0x4); set => BitConverter.GetBytes(value).CopyTo(Data, PlayTime + 0x4); }
|
|
private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; }
|
|
private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)value & 0xF) << 12; }
|
|
private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; }
|
|
private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; }
|
|
private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; }
|
|
private string LastSavedTime => $"{LastSavedYear:0000}{LastSavedMonth:00}{LastSavedDay:00}{LastSavedHour:00}{LastSavedMinute:00}";
|
|
|
|
public DateTime? LastSavedDate
|
|
{
|
|
get => !Util.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay)
|
|
? (DateTime?)null
|
|
: new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0);
|
|
set
|
|
{
|
|
// Only update the properties if a value is provided.
|
|
if (value.HasValue)
|
|
{
|
|
var dt = value.Value;
|
|
LastSavedYear = dt.Year;
|
|
LastSavedMonth = dt.Month;
|
|
LastSavedDay = dt.Day;
|
|
LastSavedHour = dt.Hour;
|
|
LastSavedMinute = dt.Minute;
|
|
}
|
|
else // Clear the date.
|
|
{
|
|
// If code tries to access MetDate again, null will be returned.
|
|
LastSavedYear = 0;
|
|
LastSavedMonth = 0;
|
|
LastSavedDay = 0;
|
|
LastSavedHour = 0;
|
|
LastSavedMinute = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int ResumeYear { get => BitConverter.ToInt32(Data, AdventureInfo + 0x4); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x4); }
|
|
public int ResumeMonth { get => Data[AdventureInfo + 0x8]; set => Data[AdventureInfo + 0x8] = (byte)value; }
|
|
public int ResumeDay { get => Data[AdventureInfo + 0x9]; set => Data[AdventureInfo + 0x9] = (byte)value; }
|
|
public int ResumeHour { get => Data[AdventureInfo + 0xB]; set => Data[AdventureInfo + 0xB] = (byte)value; }
|
|
public int ResumeMinute { get => Data[AdventureInfo + 0xC]; set => Data[AdventureInfo + 0xC] = (byte)value; }
|
|
public int ResumeSeconds { get => Data[AdventureInfo + 0xD]; set => Data[AdventureInfo + 0xD] = (byte)value; }
|
|
public override int SecondsToStart { get => BitConverter.ToInt32(Data, AdventureInfo + 0x28); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x28); }
|
|
public override int SecondsToFame { get => BitConverter.ToInt32(Data, AdventureInfo + 0x30); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x30); }
|
|
|
|
public ulong AlolaTime { get => BitConverter.ToUInt64(Data, AdventureInfo + 0x48); set => BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x48); }
|
|
|
|
// Stat Records
|
|
public int RecordCount => 200;
|
|
|
|
public int GetRecord(int recordID)
|
|
{
|
|
int ofs = Records.GetOffset(Record, recordID);
|
|
if (recordID < 100)
|
|
return BitConverter.ToInt32(Data, ofs);
|
|
if (recordID < 200)
|
|
return BitConverter.ToInt16(Data, ofs);
|
|
return 0;
|
|
}
|
|
|
|
public void SetRecord(int recordID, int value)
|
|
{
|
|
int ofs = Records.GetOffset(Record, recordID);
|
|
var maxes = USUM ? Records.MaxType_USUM: Records.MaxType_SM;
|
|
int max = Records.GetMax(recordID, maxes);
|
|
if (value > max)
|
|
value = max;
|
|
if (recordID < 100)
|
|
BitConverter.GetBytes(value).CopyTo(Data, ofs);
|
|
if (recordID < 200)
|
|
BitConverter.GetBytes((ushort)value).CopyTo(Data, ofs);
|
|
}
|
|
|
|
public int GetRecordMax(int recordID) => Records.GetMax(recordID, USUM ? Records.MaxType_USUM : Records.MaxType_SM);
|
|
public int GetRecordOffset(int recordID) => Records.GetOffset(Record, recordID);
|
|
public void AddRecord(int recordID) => SetRecord(recordID, GetRecord(recordID) + 1);
|
|
|
|
public ushort PokeFinderCameraVersion
|
|
{
|
|
get => BitConverter.ToUInt16(Data, PokeFinderSave + 0x00);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, PokeFinderSave + 0x00);
|
|
}
|
|
|
|
public bool PokeFinderGyroFlag
|
|
{
|
|
get => BitConverter.ToUInt16(Data, PokeFinderSave + 0x02) == 1;
|
|
set => BitConverter.GetBytes((ushort)(value ? 1 : 0)).CopyTo(Data, PokeFinderSave + 0x02);
|
|
}
|
|
|
|
public uint PokeFinderSnapCount
|
|
{
|
|
get => BitConverter.ToUInt32(Data, PokeFinderSave + 0x04);
|
|
set
|
|
{
|
|
if (value > 9999999) // Top bound is unchecked, check anyway
|
|
value = 9999999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, PokeFinderSave + 0x04);
|
|
}
|
|
}
|
|
|
|
public uint PokeFinderThumbsTotalValue
|
|
{
|
|
get => BitConverter.ToUInt32(Data, PokeFinderSave + 0x0C);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, PokeFinderSave + 0x0C);
|
|
}
|
|
|
|
public uint PokeFinderThumbsHighValue
|
|
{
|
|
get => BitConverter.ToUInt32(Data, PokeFinderSave + 0x10);
|
|
set
|
|
{
|
|
if (value > 9_999_999)
|
|
value = 9_999_999;
|
|
BitConverter.GetBytes(value).CopyTo(Data, PokeFinderSave + 0x10);
|
|
|
|
if (value > PokeFinderThumbsTotalValue)
|
|
PokeFinderThumbsTotalValue = value;
|
|
}
|
|
}
|
|
|
|
public ushort PokeFinderTutorialFlags
|
|
{
|
|
get => BitConverter.ToUInt16(Data, PokeFinderSave + 0x14);
|
|
set => BitConverter.GetBytes(value).CopyTo(Data, PokeFinderSave + 0x14);
|
|
}
|
|
|
|
// Inventory
|
|
public override InventoryPouch[] Inventory
|
|
{
|
|
get
|
|
{
|
|
var bag = GetPouches();
|
|
foreach (var p in bag)
|
|
p.GetPouch(Data);
|
|
return bag;
|
|
}
|
|
set
|
|
{
|
|
foreach (var p in value)
|
|
p.SetPouch(Data);
|
|
}
|
|
}
|
|
|
|
private InventoryPouch[] GetPouches()
|
|
{
|
|
if (SM)
|
|
{
|
|
return new InventoryPouch[]
|
|
{
|
|
new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, OFS_PouchMedicine),
|
|
new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, OFS_PouchHeldItem),
|
|
new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, OFS_PouchTMHM),
|
|
new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, OFS_PouchBerry),
|
|
new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_SM, 1, OFS_PouchKeyItem),
|
|
new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_SM, 1, OFS_PouchZCrystals),
|
|
};
|
|
}
|
|
return new InventoryPouch[] // USUM
|
|
{
|
|
new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, OFS_PouchMedicine),
|
|
new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, OFS_PouchHeldItem),
|
|
new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, OFS_PouchTMHM),
|
|
new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, OFS_PouchBerry),
|
|
new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_USUM, 1, OFS_PouchKeyItem),
|
|
new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_USUM, 1, OFS_PouchZCrystals),
|
|
new InventoryPouch7(InventoryType.BattleItems, Legal.Pouch_Roto_USUM, 999, OFS_BattleItems),
|
|
};
|
|
}
|
|
|
|
// Battle Tree
|
|
public int GetTreeStreak(int battletype, bool super, bool max)
|
|
{
|
|
if (battletype > 3)
|
|
throw new ArgumentException(nameof(battletype));
|
|
|
|
int offset = 8*battletype;
|
|
if (super)
|
|
offset += 2;
|
|
if (max)
|
|
offset += 4;
|
|
|
|
return BitConverter.ToUInt16(Data, BattleTree + offset);
|
|
}
|
|
|
|
public void SetTreeStreak(int value, int battletype, bool super, bool max)
|
|
{
|
|
if (battletype > 3)
|
|
throw new ArgumentException(nameof(battletype));
|
|
|
|
if (value > ushort.MaxValue)
|
|
value = ushort.MaxValue;
|
|
|
|
int offset = 8 * battletype;
|
|
if (super)
|
|
offset += 2;
|
|
if (max)
|
|
offset += 4;
|
|
|
|
BitConverter.GetBytes((ushort)value).CopyTo(Data, BattleTree + offset);
|
|
}
|
|
|
|
// Resort Save
|
|
public int GetPokebeanCount(int bean_id)
|
|
{
|
|
if (bean_id < 0 || bean_id > 14)
|
|
throw new ArgumentException("Invalid bean id!");
|
|
return Data[Resort + 0x564C + bean_id];
|
|
}
|
|
|
|
public void SetPokebeanCount(int bean_id, int count)
|
|
{
|
|
if (bean_id < 0 || bean_id > 14)
|
|
throw new ArgumentException("Invalid bean id!");
|
|
if (count < 0)
|
|
count = 0;
|
|
if (count > 255)
|
|
count = 255;
|
|
Data[Resort + 0x564C + bean_id] = (byte) count;
|
|
}
|
|
|
|
// Storage
|
|
public override int CurrentBox { get => Data[LastViewedBox]; set => Data[LastViewedBox] = (byte)value; }
|
|
|
|
public override int GetPartyOffset(int slot)
|
|
{
|
|
return Party + (SIZE_PARTY * slot);
|
|
}
|
|
|
|
public override int GetBoxOffset(int box)
|
|
{
|
|
return Box + (SIZE_STORED *box*30);
|
|
}
|
|
|
|
protected override int GetBoxWallpaperOffset(int box)
|
|
{
|
|
int ofs = PCBackgrounds > 0 && PCBackgrounds < Data.Length ? PCBackgrounds : -1;
|
|
if (ofs > -1)
|
|
return ofs + box;
|
|
return ofs;
|
|
}
|
|
|
|
public override void SetBoxWallpaper(int box, int value)
|
|
{
|
|
if (PCBackgrounds < 0)
|
|
return;
|
|
int ofs = PCBackgrounds > 0 && PCBackgrounds < Data.Length ? PCBackgrounds : 0;
|
|
Data[ofs + box] = (byte)value;
|
|
}
|
|
|
|
public override string GetBoxName(int box)
|
|
{
|
|
if (PCLayout < 0)
|
|
return $"B{box + 1}";
|
|
return Util.TrimFromZero(Encoding.Unicode.GetString(Data, PCLayout + (0x22 * box), 0x22));
|
|
}
|
|
|
|
public override void SetBoxName(int box, string value)
|
|
{
|
|
Encoding.Unicode.GetBytes(value.PadRight(0x11, '\0')).CopyTo(Data, PCLayout + (0x22 * box));
|
|
Edited = true;
|
|
}
|
|
|
|
public override PKM GetPKM(byte[] data)
|
|
{
|
|
return new PK7(data);
|
|
}
|
|
|
|
protected override void SetPKM(PKM pkm)
|
|
{
|
|
PK7 pk7 = (PK7)pkm;
|
|
// Apply to this Save File
|
|
int CT = pk7.CurrentHandler;
|
|
DateTime Date = DateTime.Now;
|
|
pk7.Trade(OT, TID, SID, Country, SubRegion, Gender, false, Date.Day, Date.Month, Date.Year);
|
|
if (CT != pk7.CurrentHandler) // Logic updated Friendship
|
|
{
|
|
// Copy over the Friendship Value only under certain circumstances
|
|
if (pk7.Moves.Contains(216)) // Return
|
|
pk7.CurrentFriendship = pk7.OppositeFriendship;
|
|
else if (pk7.Moves.Contains(218)) // Frustration
|
|
pkm.CurrentFriendship = pk7.OppositeFriendship;
|
|
}
|
|
pkm.RefreshChecksum();
|
|
if (Record > 0)
|
|
AddCountAcquired(pkm);
|
|
}
|
|
|
|
private void AddCountAcquired(PKM pkm)
|
|
{
|
|
AddRecord(pkm.WasEgg ? 008 : 006); // egg, capture
|
|
if (pkm.CurrentHandler == 1)
|
|
AddRecord(011); // trade
|
|
if (!pkm.WasEgg)
|
|
AddRecord(004); // wild encounters
|
|
}
|
|
|
|
protected override void SetDex(PKM pkm)
|
|
{
|
|
if (PokeDex < 0 || Version == GameVersion.Invalid) // sanity
|
|
return;
|
|
if (pkm.Species == 0 || pkm.Species > MaxSpeciesID) // out of range
|
|
return;
|
|
if (pkm.IsEgg) // do not add
|
|
return;
|
|
|
|
int bit = pkm.Species - 1;
|
|
int bd = bit >> 3; // div8
|
|
int bm = bit & 7; // mod8
|
|
int gender = pkm.Gender % 2; // genderless -> male
|
|
int shiny = pkm.IsShiny ? 1 : 0;
|
|
if (pkm.Species == 351) // castform
|
|
shiny = 0;
|
|
int shift = gender | (shiny << 1);
|
|
if (pkm.Species == 327) // Spinda
|
|
{
|
|
if ((Data[PokeDex + 0x84] & (1 << (shift + 4))) != 0) // Already 2
|
|
{
|
|
BitConverter.GetBytes(pkm.EncryptionConstant).CopyTo(Data, PokeDex + 0x8E8 + (shift * 4));
|
|
// Data[PokeDex + 0x84] |= (byte)(1 << (shift + 4)); // 2 -- pointless
|
|
Data[PokeDex + 0x84] |= (byte)(1 << shift); // 1
|
|
}
|
|
else if ((Data[PokeDex + 0x84] & (1 << shift)) == 0) // Not yet 1
|
|
{
|
|
Data[PokeDex + 0x84] |= (byte)(1 << shift); // 1
|
|
}
|
|
}
|
|
int ofs = PokeDex // Raw Offset
|
|
+ 0x08 // Magic + Flags
|
|
+ 0x80; // Misc Data (1024 bits)
|
|
// Set the Owned Flag
|
|
Data[ofs + bd] |= (byte)(1 << bm);
|
|
|
|
// Starting with Gen7, form bits are stored in the same region as the species flags.
|
|
|
|
int formstart = pkm.AltForm;
|
|
int formend = pkm.AltForm;
|
|
bool reset = SanitizeFormsToIterate(pkm.Species, out int fs, out int fe, formstart, USUM);
|
|
if (reset)
|
|
{
|
|
formstart = fs;
|
|
formend = fe;
|
|
}
|
|
|
|
for (int form = formstart; form <= formend; form++)
|
|
{
|
|
int bitIndex = bit;
|
|
if (form > 0) // Override the bit to overwrite
|
|
{
|
|
int fc = Personal[pkm.Species].FormeCount;
|
|
if (fc > 1) // actually has forms
|
|
{
|
|
int f = USUM
|
|
? SaveUtil.GetDexFormIndexUSUM(pkm.Species, fc, MaxSpeciesID - 1)
|
|
: SaveUtil.GetDexFormIndexSM(pkm.Species, fc, MaxSpeciesID - 1);
|
|
if (f >= 0) // bit index valid
|
|
bitIndex = f + form;
|
|
}
|
|
}
|
|
SetDexFlags(bitIndex, gender, shiny, pkm.Species - 1);
|
|
}
|
|
|
|
// Set the Language
|
|
int lang = pkm.Language;
|
|
const int langCount = 9;
|
|
if (lang <= 10 && lang != 6 && lang != 0) // valid language
|
|
{
|
|
if (lang >= 7)
|
|
lang--;
|
|
lang--; // 0-8 languages
|
|
if (lang < 0) lang = 1;
|
|
int lbit = (bit * langCount) + lang;
|
|
if (lbit >> 3 < 920) // Sanity check for max length of region
|
|
Data[PokeDexLanguageFlags + (lbit >> 3)] |= (byte)(1 << (lbit & 7));
|
|
}
|
|
}
|
|
|
|
protected override void SetPartyValues(PKM pkm, bool isParty)
|
|
{
|
|
base.SetPartyValues(pkm, isParty);
|
|
((PK7)pkm).FormDuration = GetFormDuration(pkm, isParty);
|
|
}
|
|
|
|
private static uint GetFormDuration(PKM pkm, bool isParty)
|
|
{
|
|
if (!isParty || pkm.AltForm == 0)
|
|
return 0;
|
|
switch (pkm.Species)
|
|
{
|
|
case 676: return 5; // Furfrou
|
|
case 720: return 3; // Hoopa
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
public static bool SanitizeFormsToIterate(int species, out int formStart, out int formEnd, int formIn, bool USUM)
|
|
{
|
|
// 004AA370 in Moon
|
|
// Simplified in terms of usage -- only overrides to give all the battle forms for a pkm
|
|
switch (species)
|
|
{
|
|
case 351: // Castform
|
|
formStart = 0;
|
|
formEnd = 3;
|
|
return true;
|
|
|
|
|
|
case 421: // Cherrim
|
|
case 555: // Darmanitan
|
|
case 648: // Meloetta
|
|
case 746: // Wishiwashi
|
|
case 778: // Mimikyu
|
|
// Alolans
|
|
case 020: // Raticate
|
|
case 105: // Marowak
|
|
formStart = 0;
|
|
formEnd = 1;
|
|
return true;
|
|
|
|
case 735: // Gumshoos
|
|
case 758: // Salazzle
|
|
case 754: // Lurantis
|
|
case 738: // Vikavolt
|
|
case 784: // Kommo-o
|
|
case 752: // Araquanid
|
|
case 777: // Togedemaru
|
|
case 743: // Ribombee
|
|
case 744: // Rockruff
|
|
break;
|
|
|
|
case 774 when formIn <= 6: // Minior
|
|
break; // don't give meteor forms except the first
|
|
|
|
case 718 when formIn > 1:
|
|
break;
|
|
default:
|
|
int count = USUM ? SaveUtil.GetDexFormCountUSUM(species) : SaveUtil.GetDexFormCountSM(species);
|
|
formStart = formEnd = 0;
|
|
return count < formIn;
|
|
}
|
|
formStart = 0;
|
|
formEnd = 0;
|
|
return true;
|
|
}
|
|
|
|
private void SetDexFlags(int index, int gender, int shiny, int baseSpecies)
|
|
{
|
|
const int brSize = 0x8C;
|
|
int shift = gender | (shiny << 1);
|
|
int ofs = PokeDex // Raw Offset
|
|
+ 0x08 // Magic + Flags
|
|
+ 0x80 // Misc Data (1024 bits)
|
|
+ 0x68; // Owned Flags
|
|
|
|
int bd = index >> 3; // div8
|
|
int bm = index & 7; // mod8
|
|
int bd1 = baseSpecies >> 3;
|
|
int bm1 = baseSpecies & 7;
|
|
// Set the [Species/Gender/Shiny] Seen Flag
|
|
int brSeen = shift * brSize;
|
|
Data[ofs + brSeen + bd] |= (byte)(1 << bm);
|
|
|
|
// Check Displayed Status for base form
|
|
bool Displayed = false;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int brDisplayed = (4 + i) * brSize;
|
|
Displayed |= (Data[ofs + brDisplayed + bd1] & (byte)(1 << bm1)) != 0;
|
|
}
|
|
|
|
// If form is not base form, check form too
|
|
if (!Displayed && baseSpecies != index)
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int brDisplayed = (4 + i) * brSize;
|
|
Displayed |= (Data[ofs + brDisplayed + bd] & (byte)(1 << bm)) != 0;
|
|
}
|
|
}
|
|
if (Displayed)
|
|
return;
|
|
|
|
// Set the Display flag if none are set
|
|
Data[ofs + ((4 + shift) * brSize) + bd] |= (byte)(1 << bm);
|
|
}
|
|
|
|
public bool NationalDex
|
|
{
|
|
get => (Data[PokeDex + 4] & 1) == 1;
|
|
set => Data[PokeDex + 4] = (byte)((Data[PokeDex + 4] & 0xFE) | (value ? 1 : 0));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the last viewed dex entry in the Pokedex (by National Dex ID), internally called DefaultMons
|
|
/// </summary>
|
|
public uint CurrentViewedDex => BitConverter.ToUInt32(Data, PokeDex + 4) >> 9 & 0x3FF;
|
|
|
|
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
|
|
+ 0x80; // Misc Data (1024 bits)
|
|
return (1 << bm & Data[ofs + bd]) != 0;
|
|
}
|
|
|
|
public override bool GetSeen(int species)
|
|
{
|
|
const int brSize = 0x8C;
|
|
|
|
int bit = species - 1;
|
|
int bd = bit >> 3; // div8
|
|
int bm = bit & 7; // mod8
|
|
byte mask = (byte)(1 << bm);
|
|
int ofs = PokeDex // Raw Offset
|
|
+ 0x08 // Magic + Flags
|
|
+ 0x80; // Misc Data (1024 bits)
|
|
|
|
for (int i = 1; i <= 4; i++) // check all 4 seen flags (gender/shiny)
|
|
{
|
|
if ((Data[ofs + bd + (i * brSize)] & mask) != 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override byte[] DecryptPKM(byte[] data)
|
|
{
|
|
return PKX.DecryptArray(data);
|
|
}
|
|
|
|
public override int PartyCount
|
|
{
|
|
get => Data[Party + (6 * SIZE_PARTY)];
|
|
protected set => Data[Party + (6 * SIZE_PARTY)] = (byte)value;
|
|
}
|
|
|
|
public override int BoxesUnlocked { get => Data[PCFlags + 1]; set => Data[PCFlags + 1] = (byte)value; }
|
|
|
|
private void LoadBattleTeams()
|
|
{
|
|
for (int i = 0; i < TeamCount*6; i++)
|
|
{
|
|
short val = BitConverter.ToInt16(Data, BattleBoxFlags + (i * 2));
|
|
if (val < 0)
|
|
{
|
|
TeamSlots[i] = -1;
|
|
continue;
|
|
}
|
|
|
|
int box = val >> 8;
|
|
int slot = val & 0xFF;
|
|
int index = (BoxSlotCount * box) + slot;
|
|
TeamSlots[i] = index & 0xFFFF;
|
|
}
|
|
}
|
|
|
|
private void SaveBattleTeams()
|
|
{
|
|
for (int i = 0; i < TeamCount * 6; i++)
|
|
{
|
|
int index = TeamSlots[i];
|
|
if (index < 0)
|
|
{
|
|
BitConverter.GetBytes((short)index).CopyTo(Data, BattleBoxFlags + (i * 2));
|
|
continue;
|
|
}
|
|
|
|
int box = index / BoxSlotCount;
|
|
int slot = index % BoxSlotCount;
|
|
int val = (box << 8) | slot;
|
|
BitConverter.GetBytes((short)val).CopyTo(Data, BattleBoxFlags + (i * 2));
|
|
}
|
|
}
|
|
|
|
private bool IsTeamLocked(int team) => Data[PCBackgrounds - TeamCount - team] == 1;
|
|
|
|
public override StorageSlotFlag GetSlotFlags(int index)
|
|
{
|
|
int team = Array.IndexOf(TeamSlots, index);
|
|
if (team < 0)
|
|
return StorageSlotFlag.None;
|
|
|
|
team /= 6;
|
|
var val = (StorageSlotFlag)((int)StorageSlotFlag.BattleTeam1 << team);
|
|
if (IsTeamLocked(team))
|
|
val |= StorageSlotFlag.Locked;
|
|
return val;
|
|
}
|
|
|
|
private int FusedCount => USUM ? 3 : 1;
|
|
|
|
public int GetFusedSlotOffset(int slot)
|
|
{
|
|
if (Fused < 0 || slot < 0 || slot >= FusedCount)
|
|
return -1;
|
|
return Fused + (SIZE_PARTY * slot); // 0x104*slot
|
|
}
|
|
|
|
public int GetSurfScore(int recordID)
|
|
{
|
|
if (recordID < 0 || recordID > 4)
|
|
recordID = 0;
|
|
return BitConverter.ToInt32(Data, Misc + 0x138 + (4 * recordID));
|
|
}
|
|
|
|
public void SetSurfScore(int recordID, int score)
|
|
{
|
|
if (recordID < 0 || recordID > 4)
|
|
recordID = 0;
|
|
SetData(BitConverter.GetBytes(score), Misc + 0x138 + (4 * recordID));
|
|
}
|
|
|
|
public string RotomOT
|
|
{
|
|
get => GetString(Trainer2 + 0x30, 0x1A);
|
|
set => SetString(value, OTLength).CopyTo(Data, Trainer2 + 0x30);
|
|
}
|
|
|
|
public override int DaycareSeedSize => 32; // 128 bits
|
|
|
|
public override int GetDaycareSlotOffset(int loc, int slot)
|
|
{
|
|
if (loc != 0)
|
|
return -1;
|
|
if (Daycare < 0)
|
|
return -1;
|
|
return Daycare + 1 + (slot * (SIZE_STORED + 1));
|
|
}
|
|
|
|
public override bool? IsDaycareOccupied(int loc, int slot)
|
|
{
|
|
if (loc != 0)
|
|
return null;
|
|
if (Daycare < 0)
|
|
return null;
|
|
|
|
return Data[Daycare + ((SIZE_STORED + 1) * slot)] != 0;
|
|
}
|
|
|
|
public override string GetDaycareRNGSeed(int loc)
|
|
{
|
|
if (loc != 0)
|
|
return null;
|
|
if (Daycare < 0)
|
|
return null;
|
|
|
|
var data = Data.Skip(Daycare + 0x1DC).Take(DaycareSeedSize / 2).Reverse().ToArray();
|
|
return BitConverter.ToString(data).Replace("-", string.Empty);
|
|
}
|
|
|
|
public override bool? IsDaycareHasEgg(int loc)
|
|
{
|
|
if (loc != 0)
|
|
return null;
|
|
if (Daycare < 0)
|
|
return null;
|
|
|
|
return Data[Daycare + 0x1D8] == 1;
|
|
}
|
|
|
|
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
|
|
{
|
|
if (loc != 0)
|
|
return;
|
|
if (Daycare < 0)
|
|
return;
|
|
|
|
Data[Daycare + ((SIZE_STORED + 1) * slot)] = (byte)(occupied ? 1 : 0);
|
|
}
|
|
|
|
public override void SetDaycareRNGSeed(int loc, string seed)
|
|
{
|
|
if (loc != 0)
|
|
return;
|
|
if (Daycare < 0)
|
|
return;
|
|
if (seed == null)
|
|
return;
|
|
if (seed.Length > DaycareSeedSize)
|
|
return;
|
|
|
|
Enumerable.Range(0, seed.Length)
|
|
.Where(x => x % 2 == 0)
|
|
.Reverse()
|
|
.Select(x => Convert.ToByte(seed.Substring(x, 2), 16))
|
|
.ToArray().CopyTo(Data, Daycare + 0x1DC);
|
|
}
|
|
|
|
public override void SetDaycareHasEgg(int loc, bool hasEgg)
|
|
{
|
|
if (loc != 0)
|
|
return;
|
|
if (Daycare < 0)
|
|
return;
|
|
|
|
Data[Daycare + 0x1D8] = (byte)(hasEgg ? 1 : 0);
|
|
}
|
|
|
|
// Mystery Gift
|
|
protected override bool[] MysteryGiftReceivedFlags
|
|
{
|
|
get
|
|
{
|
|
if (WondercardData < 0 || WondercardFlags < 0)
|
|
return Array.Empty<bool>();
|
|
|
|
bool[] result = new bool[(WondercardData-WondercardFlags)*8];
|
|
for (int i = 0; i < result.Length; i++)
|
|
result[i] = (Data[WondercardFlags + (i>>3)] >> (i&7) & 0x1) == 1;
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
if (WondercardData < 0 || WondercardFlags < 0)
|
|
return;
|
|
if (value == null || (WondercardData - WondercardFlags)*8 != value.Length)
|
|
return;
|
|
|
|
byte[] data = new byte[value.Length/8];
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
if (value[i])
|
|
data[i>>3] |= (byte)(1 << (i&7));
|
|
}
|
|
|
|
data.CopyTo(Data, WondercardFlags);
|
|
Edited = true;
|
|
}
|
|
}
|
|
|
|
protected override MysteryGift[] MysteryGiftCards
|
|
{
|
|
get
|
|
{
|
|
if (WondercardData < 0)
|
|
return Array.Empty<MysteryGift>();
|
|
MysteryGift[] cards = new MysteryGift[GiftCountMax];
|
|
for (int i = 0; i < cards.Length; i++)
|
|
cards[i] = GetWC7(i);
|
|
|
|
return cards;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
return;
|
|
if (value.Length > GiftCountMax)
|
|
Array.Resize(ref value, GiftCountMax);
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
SetWC7(value[i], i);
|
|
for (int i = value.Length; i < GiftCountMax; i++)
|
|
SetWC7(new WC7(), i);
|
|
}
|
|
}
|
|
|
|
private WC7 GetWC7(int index)
|
|
{
|
|
if (WondercardData < 0)
|
|
throw new ArgumentException(nameof(WondercardData));
|
|
if (index < 0 || index > GiftCountMax)
|
|
throw new ArgumentException(nameof(index));
|
|
|
|
return new WC7(GetData(WondercardData + (index * WC7.Size), WC7.Size));
|
|
}
|
|
|
|
private void SetWC7(MysteryGift wc7, int index)
|
|
{
|
|
if (WondercardData < 0)
|
|
return;
|
|
if (index < 0 || index > GiftCountMax)
|
|
return;
|
|
|
|
wc7.Data.CopyTo(Data, WondercardData + (index * WC7.Size));
|
|
|
|
Edited = true;
|
|
}
|
|
|
|
// Writeback Validity
|
|
public override string MiscSaveChecks()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
// FFFF checks
|
|
for (int i = 0; i < Data.Length / 0x200; i++)
|
|
{
|
|
if (Data.Skip(i * 0x200).Take(0x200).Any(z => z != 0xFF))
|
|
continue;
|
|
sb.Append("0x200 chunk @ 0x").AppendFormat("{0:X5}", i * 0x200).AppendLine(" is FF'd.");
|
|
sb.AppendLine("Cyber will screw up (as of August 31st 2014).");
|
|
sb.AppendLine();
|
|
|
|
// Check to see if it is in the Pokedex
|
|
if (i * 0x200 > PokeDex && i * 0x200 < PokeDex + 0x900)
|
|
{
|
|
sb.Append("Problem lies in the Pokedex. ");
|
|
if (i * 0x200 == PokeDex + 0x400)
|
|
sb.Append("Remove a language flag for a species < 585, ie Petilil");
|
|
}
|
|
break;
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
public override string MiscSaveInfo() => string.Join(Environment.NewLine, Blocks.Select(b => b.Summary));
|
|
|
|
public bool MegaUnlocked
|
|
{
|
|
get => (Data[TrainerCard + 0x78] & 0x01) != 0;
|
|
set => Data[TrainerCard + 0x78] = (byte)((Data[TrainerCard + 0x78] & 0xFE) | (value ? 1 : 0)); // in battle
|
|
// Data[0x1F22] = (byte)((Data[0x1F22] & 0xFE) | (value ? 1 : 0)); // event
|
|
}
|
|
|
|
public bool ZMoveUnlocked
|
|
{
|
|
get => (Data[TrainerCard + 0x78] & 2) != 0;
|
|
set => Data[TrainerCard + 0x78] = (byte)((Data[TrainerCard + 0x78] & ~2) | (value ? 2 : 0)); // in battle
|
|
}
|
|
|
|
public override string GetString(byte[] data, int offset, int length) => StringConverter.GetString7(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.SetString7(value, maxLength, Language, PadToSize, PadWith);
|
|
}
|
|
}
|
|
}
|