mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-24 13:03:06 +00:00
8e62803897
Use one enum to denote Version across all code, also identifies if data is invalid Move some SAV code to virtual members in abstract class (BAK/Exportable/Footer) Few minor bugfixes here and there (hiding Characteristic label, loading pk6 to sav3, sav3 version detection via gamecode)
847 lines
35 KiB
C#
847 lines
35 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace PKHeX
|
|
{
|
|
public sealed class SAV6 : SaveFile
|
|
{
|
|
// Save Data Attributes
|
|
public override string BAKName => $"{FileName} [{OT} ({Version}) - {LastSavedTime}].bak";
|
|
public override string Filter => "Main SAV|*.*";
|
|
public override string Extension => "";
|
|
public SAV6(byte[] data = null)
|
|
{
|
|
Data = data == null ? new byte[SaveUtil.SIZE_G6ORAS] : (byte[])data.Clone();
|
|
BAK = (byte[])Data.Clone();
|
|
Exportable = !Data.SequenceEqual(new byte[Data.Length]);
|
|
|
|
// Load Info
|
|
getBlockInfo();
|
|
getSAVOffsets();
|
|
|
|
HeldItems = ORAS ? Legal.HeldItem_AO : Legal.HeldItem_XY;
|
|
Personal = ORAS ? Legal.PersonalAO : Legal.PersonalXY;
|
|
if (!Exportable)
|
|
resetBoxes();
|
|
}
|
|
|
|
// Configuration
|
|
public override SaveFile Clone() { return new SAV6(Data); }
|
|
|
|
public override int SIZE_STORED => PKX.SIZE_6STORED;
|
|
public override int SIZE_PARTY => PKX.SIZE_6PARTY;
|
|
public override PKM BlankPKM => new PK6();
|
|
protected override Type PKMType => typeof(PK6);
|
|
|
|
public override int BoxCount => 31;
|
|
public override int MaxEV => 252;
|
|
public override int Generation => 6;
|
|
protected override int GiftCountMax => 24;
|
|
protected override int GiftFlagMax => 0x100 * 8;
|
|
protected override int EventFlagMax => 8 * 0x180;
|
|
protected override int EventConstMax => (EventFlag - EventConst) / 2;
|
|
public override int OTLength => 12;
|
|
public override int NickLength => 12;
|
|
|
|
public override int MaxMoveID => XY ? 617 : 621;
|
|
public override int MaxSpeciesID => 721;
|
|
public override int MaxItemID => XY ? 717 : 775;
|
|
public override int MaxAbilityID => XY ? 188 : 191;
|
|
public override int MaxBallID => 0x19;
|
|
public override int MaxGameID => 27; // OR
|
|
|
|
// Feature Overrides
|
|
public override bool HasGeolocation => true;
|
|
|
|
// Blocks & Offsets
|
|
private int BlockInfoOffset;
|
|
private BlockInfo[] Blocks;
|
|
private void getBlockInfo()
|
|
{
|
|
BlockInfoOffset = Data.Length - 0x200 + 0x10;
|
|
if (BitConverter.ToUInt32(Data, BlockInfoOffset) != SaveUtil.BEEF)
|
|
BlockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
|
|
int count = (Data.Length - BlockInfoOffset - 0x8) / 8;
|
|
BlockInfoOffset += 4;
|
|
|
|
Blocks = new BlockInfo[count];
|
|
int CurrentPosition = 0;
|
|
for (int i = 0; i < Blocks.Length; i++)
|
|
{
|
|
Blocks[i] = new BlockInfo
|
|
{
|
|
Offset = CurrentPosition,
|
|
Length = BitConverter.ToInt32(Data, BlockInfoOffset + 0 + 8 * i),
|
|
ID = BitConverter.ToUInt16(Data, BlockInfoOffset + 4 + 8 * i),
|
|
Checksum = BitConverter.ToUInt16(Data, BlockInfoOffset + 6 + 8 * i)
|
|
};
|
|
|
|
// Expand out to nearest 0x200
|
|
CurrentPosition += Blocks[i].Length % 0x200 == 0 ? Blocks[i].Length : 0x200 - Blocks[i].Length % 0x200 + Blocks[i].Length;
|
|
|
|
if ((Blocks[i].ID != 0) || i == 0) continue;
|
|
count = i;
|
|
break;
|
|
}
|
|
// Fix Final Array Lengths
|
|
Array.Resize(ref Blocks, count);
|
|
}
|
|
protected override void setChecksums()
|
|
{
|
|
// Check for invalid block lengths
|
|
if (Blocks.Length < 3) // arbitrary...
|
|
{
|
|
Console.WriteLine("Not enough blocks ({0}), aborting setChecksums", Blocks.Length);
|
|
return;
|
|
}
|
|
// Apply checksums
|
|
for (int i = 0; i < Blocks.Length; i++)
|
|
{
|
|
byte[] array = Data.Skip(Blocks[i].Offset).Take(Blocks[i].Length).ToArray();
|
|
BitConverter.GetBytes(SaveUtil.ccitt16(array)).CopyTo(Data, BlockInfoOffset + 6 + i * 8);
|
|
}
|
|
}
|
|
public override bool ChecksumsValid => SaveUtil.verifyG6SAV(Data);
|
|
public override string ChecksumInfo => SaveUtil.verifyG6CHK(Data);
|
|
public ulong Secure1
|
|
{
|
|
get { return BitConverter.ToUInt64(Data, BlockInfoOffset - 0x14); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, BlockInfoOffset - 0x14); }
|
|
}
|
|
public ulong Secure2
|
|
{
|
|
get { return BitConverter.ToUInt64(Data, BlockInfoOffset - 0xC); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, BlockInfoOffset - 0xC); }
|
|
}
|
|
|
|
private void getSAVOffsets()
|
|
{
|
|
if (ORASDEMO)
|
|
{
|
|
/* 00: */ Item = 0x00000;
|
|
/* 01: */ ItemInfo = 0x00C00; // Select Bound Items
|
|
/* 02: */ AdventureInfo = 0x00E00;
|
|
/* 03: */ Trainer1 = 0x01000;
|
|
/* 04: */ // = 0x01200; [00004] // ???
|
|
/* 05: */ PlayTime = 0x01400;
|
|
/* 06: */ // = 0x01600; [00024] // FFFFFFFF
|
|
/* 07: */ // = 0x01800; [02100] // Overworld Data
|
|
/* 08: */ Trainer2 = 0x03A00;
|
|
/* 09: */ TrainerCard = 0x03C00;
|
|
/* 10: */ Party = 0x03E00;
|
|
/* 11: */ EventConst = 0x04600; EventFlag = EventConst + 0x2FC;
|
|
/* 12: */ // = 0x04C00; [00004] // 87B1A23F const
|
|
/* 13: */ // = 0x04E00; [00048] // Repel Info, (Swarm?) and other overworld info
|
|
/* 14: */ SUBE = 0x05000;
|
|
/* 15: */ PSSStats = 0x05400;
|
|
|
|
OFS_PouchHeldItem = Item + 0;
|
|
OFS_PouchKeyItem = Item + 0x640;
|
|
OFS_PouchTMHM = Item + 0x7C0;
|
|
OFS_PouchMedicine = Item + 0x970;
|
|
OFS_PouchBerry = Item + 0xA70;
|
|
}
|
|
else if (XY)
|
|
{
|
|
Puff = 0x00000;
|
|
Item = 0x00400;
|
|
ItemInfo = 0x1000;
|
|
AdventureInfo = 0x01200;
|
|
Trainer1 = 0x1400;
|
|
PlayTime = 0x1800;
|
|
Accessories = 0x1A00;
|
|
Trainer2 = 0x4200;
|
|
PCLayout = 0x4400;
|
|
BattleBox = 0x04A00;
|
|
PSS = 0x05000;
|
|
TrainerCard = 0x14000;
|
|
Party = 0x14200;
|
|
EventConst = 0x14A00;
|
|
PSSStats = 0x1E400;
|
|
PokeDex = 0x15000;
|
|
Fused = 0x16000;
|
|
OPower = 0x16A00;
|
|
GTS = 0x17800;
|
|
HoF = 0x19400;
|
|
MaisonStats = 0x1B1C0;
|
|
Daycare = 0x1B200;
|
|
BerryField = 0x1B800;
|
|
WondercardFlags = 0x1BC00;
|
|
SUBE = 0x1D890;
|
|
SuperTrain = 0x1F200;
|
|
Box = 0x22600;
|
|
JPEG = 0x57200;
|
|
|
|
PCBackgrounds = PCLayout + 0x41E;
|
|
PCFlags = PCLayout + 0x43D;
|
|
LastViewedBox = PCLayout + 0x43F;
|
|
EventFlag = EventConst + 0x2FC;
|
|
PokeDexLanguageFlags = PokeDex + 0x3C8;
|
|
Spinda = PokeDex + 0x648;
|
|
WondercardData = WondercardFlags + 0x100;
|
|
|
|
OFS_PouchHeldItem = Item + 0;
|
|
OFS_PouchKeyItem = Item + 0x640;
|
|
OFS_PouchTMHM = Item + 0x7C0;
|
|
OFS_PouchMedicine = Item + 0x968;
|
|
OFS_PouchBerry = Item + 0xA68;
|
|
}
|
|
else if (ORAS)
|
|
{
|
|
Puff = 0x00000;
|
|
Item = 0x00400;
|
|
ItemInfo = 0x1000;
|
|
AdventureInfo = 0x01200;
|
|
Trainer1 = 0x01400;
|
|
PlayTime = 0x1800;
|
|
Accessories = 0x1A00;
|
|
Trainer2 = 0x04200;
|
|
PCLayout = 0x04400;
|
|
BattleBox = 0x04A00;
|
|
PSS = 0x05000;
|
|
TrainerCard = 0x14000;
|
|
Party = 0x14200;
|
|
EventConst = 0x14A00;
|
|
PokeDex = 0x15000;
|
|
Fused = 0x16A00;
|
|
OPower = 0x17400;
|
|
GTS = 0x18200;
|
|
HoF = 0x19E00;
|
|
MaisonStats = 0x1BBC0;
|
|
Daycare = 0x1BC00;
|
|
BerryField = 0x1C400;
|
|
WondercardFlags = 0x1CC00;
|
|
SUBE = 0x1D890;
|
|
PSSStats = 0x1F400;
|
|
SuperTrain = 0x20200;
|
|
Contest = 0x23600;
|
|
SecretBase = 0x23A00;
|
|
EonTicket = 0x319B8;
|
|
Box = 0x33000;
|
|
JPEG = 0x67C00;
|
|
|
|
PCBackgrounds = PCLayout + 0x41E;
|
|
PCFlags = PCLayout + 0x43D;
|
|
LastViewedBox = PCLayout + 0x43F;
|
|
EventFlag = EventConst + 0x2FC;
|
|
PokeDexLanguageFlags = PokeDex + 0x400;
|
|
Spinda = PokeDex + 0x680;
|
|
EncounterCount = PokeDex + 0x686;
|
|
WondercardData = WondercardFlags + 0x100;
|
|
Daycare2 = Daycare + 0x1F0;
|
|
|
|
OFS_PouchHeldItem = Item + 0;
|
|
OFS_PouchKeyItem = Item + 0x640;
|
|
OFS_PouchTMHM = Item + 0x7C0;
|
|
OFS_PouchMedicine = Item + 0x970;
|
|
OFS_PouchBerry = Item + 0xA70;
|
|
}
|
|
else // Empty input
|
|
{
|
|
Party = 0x0;
|
|
Box = Party + SIZE_PARTY * 6 + 0x1000;
|
|
}
|
|
}
|
|
|
|
// Private Only
|
|
private int Item { get; set; } = int.MinValue;
|
|
private int AdventureInfo { get; set; } = int.MinValue;
|
|
private int Trainer2 { get; 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 JPEG { get; set; } = int.MinValue;
|
|
private int ItemInfo { get; set; } = int.MinValue;
|
|
private int Daycare2 { get; set; } = int.MinValue;
|
|
|
|
// Accessible as SAV6
|
|
public int TrainerCard { get; private set; } = 0x14000;
|
|
public int PCFlags { get; private set; } = int.MinValue;
|
|
public int PSSStats { get; private set; } = int.MinValue;
|
|
public int MaisonStats { get; private set; } = int.MinValue;
|
|
public int EonTicket { get; private set; } = int.MinValue;
|
|
public int PCBackgrounds { get; private set; } = int.MinValue;
|
|
public int Contest { get; private set; } = int.MinValue;
|
|
public int Accessories { get; private set; } = int.MinValue;
|
|
public int PokeDexLanguageFlags { get; private set; } = int.MinValue;
|
|
public int Spinda { get; private set; } = int.MinValue;
|
|
public int EncounterCount { get; private set; } = int.MinValue;
|
|
|
|
public override GameVersion Version
|
|
{
|
|
get
|
|
{
|
|
switch (Game)
|
|
{
|
|
case 24: return GameVersion.X;
|
|
case 25: return GameVersion.Y;
|
|
case 26: return GameVersion.AS;
|
|
case 27: return GameVersion.OR;
|
|
}
|
|
return GameVersion.Unknown;
|
|
}
|
|
}
|
|
|
|
// Player Information
|
|
public override ushort TID
|
|
{
|
|
get { return BitConverter.ToUInt16(Data, TrainerCard + 0); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, TrainerCard + 0); }
|
|
}
|
|
public override ushort SID
|
|
{
|
|
get { return BitConverter.ToUInt16(Data, TrainerCard + 2); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, TrainerCard + 2); }
|
|
}
|
|
public override int Game
|
|
{
|
|
get { return Data[TrainerCard + 4]; }
|
|
set { Data[TrainerCard + 4] = (byte)value; }
|
|
}
|
|
public override int Gender
|
|
{
|
|
get { return Data[TrainerCard + 5]; }
|
|
set { Data[TrainerCard + 5] = (byte)value; }
|
|
}
|
|
public int Sprite
|
|
{
|
|
get { return Data[TrainerCard + 7]; }
|
|
set { Data[TrainerCard + 7] = (byte)value; }
|
|
}
|
|
public ulong GameSyncID
|
|
{
|
|
get { return BitConverter.ToUInt64(Data, TrainerCard + 8); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, TrainerCard + 8); }
|
|
}
|
|
public override int SubRegion
|
|
{
|
|
get { return Data[TrainerCard + 0x26]; }
|
|
set { Data[TrainerCard + 0x26] = (byte)value; }
|
|
}
|
|
public override int Country
|
|
{
|
|
get { return Data[TrainerCard + 0x27]; }
|
|
set { Data[TrainerCard + 0x27] = (byte)value; }
|
|
}
|
|
public override int ConsoleRegion
|
|
{
|
|
get { return Data[TrainerCard + 0x2C]; }
|
|
set { Data[TrainerCard + 0x2C] = (byte)value; }
|
|
}
|
|
public override int Language
|
|
{
|
|
get { return Data[TrainerCard + 0x2D]; }
|
|
set { Data[TrainerCard + 0x2D] = (byte)value; }
|
|
}
|
|
public override string OT
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x48, 0x1A)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(13, '\0')).CopyTo(Data, TrainerCard + 0x48); }
|
|
}
|
|
public string Saying1
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x7C + 0x22 * 0, 0x22)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, TrainerCard + 0x7C + 0x22 * 0); }
|
|
}
|
|
public string Saying2
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x7C + 0x22 * 1, 0x22)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, TrainerCard + 0x7C + 0x22 * 1); }
|
|
}
|
|
public string Saying3
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x7C + 0x22 * 2, 0x22)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, TrainerCard + 0x7C + 0x22 * 2); }
|
|
}
|
|
public string Saying4
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x7C + 0x22 * 3, 0x22)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, TrainerCard + 0x7C + 0x22 * 3); }
|
|
}
|
|
public string Saying5
|
|
{
|
|
get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x7C + 0x22 * 4, 0x22)); }
|
|
set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, TrainerCard + 0x7C + 0x22 * 4); }
|
|
}
|
|
|
|
public int M
|
|
{
|
|
get { return BitConverter.ToUInt16(Data, Trainer1 + 0x02); }
|
|
set { BitConverter.GetBytes((ushort)value).CopyTo(Data, Trainer1 + 0x02); }
|
|
}
|
|
public float X
|
|
{
|
|
get { return BitConverter.ToSingle(Data, Trainer1 + 0x10) / 18; }
|
|
set { BitConverter.GetBytes(value * 18).CopyTo(Data, Trainer1 + 0x10); }
|
|
}
|
|
public float Z
|
|
{
|
|
get { return BitConverter.ToSingle(Data, Trainer1 + 0x14); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, Trainer1 + 0x14); }
|
|
}
|
|
public float Y
|
|
{
|
|
get { return BitConverter.ToSingle(Data, Trainer1 + 0x18) / 18; }
|
|
set { BitConverter.GetBytes(value * 18).CopyTo(Data, Trainer1 + 0x18); }
|
|
}
|
|
public int Style
|
|
{
|
|
get { return Data[Trainer1 + 0x14D]; }
|
|
set { Data[Trainer1 + 0x14D] = (byte)value; }
|
|
}
|
|
public override uint Money
|
|
{
|
|
get { return BitConverter.ToUInt32(Data, Trainer2 + 0x8); }
|
|
set { BitConverter.GetBytes(value).CopyTo(Data, Trainer2 + 0x8); }
|
|
}
|
|
public int Badges
|
|
{
|
|
get { return Data[Trainer2 + 0xC]; }
|
|
set { Data[Trainer2 + 0xC] = (byte)value; }
|
|
}
|
|
public int BP
|
|
{
|
|
get
|
|
{
|
|
int offset = Trainer2 + 0x3C;
|
|
if (ORAS) offset -= 0xC; // 0x30
|
|
return BitConverter.ToUInt16(Data, offset);
|
|
}
|
|
set
|
|
{
|
|
int offset = Trainer2 + 0x3C;
|
|
if (ORAS) offset -= 0xC; // 0x30
|
|
BitConverter.GetBytes((ushort)value).CopyTo(Data, offset);
|
|
}
|
|
}
|
|
public int Vivillon
|
|
{
|
|
get
|
|
{
|
|
int offset = Trainer2 + 0x50;
|
|
if (ORAS) offset -= 0xC; // 0x44
|
|
return Data[offset];
|
|
}
|
|
set
|
|
{
|
|
int offset = Trainer2 + 0x50;
|
|
if (ORAS) offset -= 0xC; // 0x44
|
|
Data[offset] = (byte)value;
|
|
}
|
|
}
|
|
|
|
public override int PlayedHours
|
|
{
|
|
get { return BitConverter.ToUInt16(Data, PlayTime); }
|
|
set { BitConverter.GetBytes((ushort)value).CopyTo(Data, PlayTime); }
|
|
}
|
|
public override int PlayedMinutes
|
|
{
|
|
get { return Data[PlayTime + 2]; }
|
|
set { Data[PlayTime + 2] = (byte)value; }
|
|
}
|
|
public override int PlayedSeconds
|
|
{
|
|
get { return Data[PlayTime + 3]; }
|
|
set { Data[PlayTime + 3] = (byte)value; }
|
|
}
|
|
public uint LastSaved { get { return BitConverter.ToUInt32(Data, PlayTime + 0x4); } set { BitConverter.GetBytes(value).CopyTo(Data, PlayTime + 0x4); } }
|
|
public int LastSavedYear { get { return (int)(LastSaved & 0xFFF); } set { LastSaved = LastSaved & 0xFFFFF000 | (uint)value; } }
|
|
public int LastSavedMonth { get { return (int)(LastSaved >> 12 & 0xF); } set { LastSaved = LastSaved & 0xFFFF0FFF | ((uint)value & 0xF) << 12; } }
|
|
public int LastSavedDay { get { return (int)(LastSaved >> 16 & 0x1F); } set { LastSaved = LastSaved & 0xFFE0FFFF | ((uint)value & 0x1F) << 16; } }
|
|
public int LastSavedHour { get { return (int)(LastSaved >> 21 & 0x1F); } set { LastSaved = LastSaved & 0xFC1FFFFF | ((uint)value & 0x1F) << 21; } }
|
|
public int LastSavedMinute { get { return (int)(LastSaved >> 26 & 0x3F); } set { LastSaved = LastSaved & 0x03FFFFFF | ((uint)value & 0x3F) << 26; } }
|
|
public string LastSavedTime => $"{LastSavedYear.ToString("0000")}{LastSavedMonth.ToString("00")}{LastSavedDay.ToString("00")}{LastSavedHour.ToString("00")}{LastSavedMinute.ToString("00")}";
|
|
|
|
public int ResumeYear { get { return BitConverter.ToInt32(Data, AdventureInfo + 0x4); } set { BitConverter.GetBytes(value).CopyTo(Data,AdventureInfo + 0x4); } }
|
|
public int ResumeMonth { get { return Data[AdventureInfo + 0x8]; } set { Data[AdventureInfo + 0x8] = (byte)value; } }
|
|
public int ResumeDay { get { return Data[AdventureInfo + 0x9]; } set { Data[AdventureInfo + 0x9] = (byte)value; } }
|
|
public int ResumeHour { get { return Data[AdventureInfo + 0xB]; } set { Data[AdventureInfo + 0xB] = (byte)value; } }
|
|
public int ResumeMinute { get { return Data[AdventureInfo + 0xC]; } set { Data[AdventureInfo + 0xC] = (byte)value; } }
|
|
public int ResumeSeconds { get { return Data[AdventureInfo + 0xD]; } set { Data[AdventureInfo + 0xD] = (byte)value; } }
|
|
public override int SecondsToStart { get { return BitConverter.ToInt32(Data, AdventureInfo + 0x18); } set { BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x18); } }
|
|
public override int SecondsToFame { get { return BitConverter.ToInt32(Data, AdventureInfo + 0x20); } set { BitConverter.GetBytes(value).CopyTo(Data, AdventureInfo + 0x20); } }
|
|
|
|
public uint getPSSStat(int index) { return BitConverter.ToUInt32(Data, PSSStats + 4*index); }
|
|
public void setPSSStat(int index, uint value) { BitConverter.GetBytes(value).CopyTo(Data, PSSStats + 4*index); }
|
|
public ushort getMaisonStat(int index) { return BitConverter.ToUInt16(Data, MaisonStats + 2 * index); }
|
|
public void setMaisonStat(int index, ushort value) { BitConverter.GetBytes(value).CopyTo(Data, MaisonStats + 2*index); }
|
|
public uint getEncounterCount(int index) { return BitConverter.ToUInt16(Data, EncounterCount + 2*index); }
|
|
public void setEncounterCount(int index, ushort value) { BitConverter.GetBytes(value).CopyTo(Data, EncounterCount + 2*index); }
|
|
|
|
// Daycare
|
|
public override int DaycareSeedSize => 16;
|
|
public override int getDaycareSlotOffset(int loc, int slot)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs < 0)
|
|
return -1;
|
|
return ofs + 8 + slot*(SIZE_STORED + 8);
|
|
}
|
|
public override uint? getDaycareEXP(int loc, int slot)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
return BitConverter.ToUInt32(Data, ofs + (SIZE_STORED + 8)*slot + 4);
|
|
return null;
|
|
}
|
|
public override bool? getDaycareOccupied(int loc, int slot)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
return Data[ofs + (SIZE_STORED + 8) * slot] == 1;
|
|
return null;
|
|
}
|
|
public override ulong? getDaycareRNGSeed(int loc)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
return BitConverter.ToUInt64(Data, ofs + 0x1E8);
|
|
return null;
|
|
}
|
|
public override bool? getDaycareHasEgg(int loc)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
return Data[ofs + 0x1E0] == 1;
|
|
return null;
|
|
}
|
|
public override void setDaycareEXP(int loc, int slot, uint EXP)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
BitConverter.GetBytes(EXP).CopyTo(Data, ofs + (SIZE_STORED + 8)*slot + 4);
|
|
}
|
|
public override void setDaycareOccupied(int loc, int slot, bool occupied)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
Data[ofs + (SIZE_STORED + 8)*slot] = (byte) (occupied ? 1 : 0);
|
|
}
|
|
public override void setDaycareRNGSeed(int loc, ulong seed)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
BitConverter.GetBytes(seed).CopyTo(Data, ofs + 0x1E8);
|
|
}
|
|
public override void setDaycareHasEgg(int loc, bool hasEgg)
|
|
{
|
|
int ofs = loc == 0 ? Daycare : Daycare2;
|
|
if (ofs > -1)
|
|
Data[ofs + 0x1E0] = (byte)(hasEgg ? 1 : 0);
|
|
}
|
|
|
|
public byte[] Puffs { get { return Data.Skip(Puff).Take(100).ToArray(); } set { value.CopyTo(Data, Puff); } }
|
|
public int PuffCount { get { return BitConverter.ToInt32(Data, Puff + 100); } set { BitConverter.GetBytes(value).CopyTo(Data, Puff + 100); } }
|
|
|
|
public int[] SelectItems
|
|
{
|
|
// UP,RIGHT,DOWN,LEFT
|
|
get
|
|
{
|
|
int[] list = new int[4];
|
|
for (int i = 0; i < list.Length; i++)
|
|
list[i] = BitConverter.ToUInt16(Data, ItemInfo + 10 + 2 * i);
|
|
return list;
|
|
}
|
|
set
|
|
{
|
|
if (value == null || value.Length > 4)
|
|
return;
|
|
for (int i = 0; i < value.Length; i++)
|
|
BitConverter.GetBytes((ushort)value[i]).CopyTo(Data, ItemInfo + 10 + 2 * i);
|
|
}
|
|
}
|
|
public int[] RecentItems
|
|
{
|
|
// Items recently interacted with (Give, Use)
|
|
get
|
|
{
|
|
int[] list = new int[12];
|
|
for (int i = 0; i < list.Length; i++)
|
|
list[i] = BitConverter.ToUInt16(Data, ItemInfo + 20 + 2 * i);
|
|
return list;
|
|
}
|
|
set
|
|
{
|
|
if (value == null || value.Length > 12)
|
|
return;
|
|
for (int i = 0; i < value.Length; i++)
|
|
BitConverter.GetBytes((ushort)value[i]).CopyTo(Data, ItemInfo + 20 + 2 * i);
|
|
}
|
|
}
|
|
|
|
public override string JPEGTitle => JPEG < 0 ? null : Util.TrimFromZero(Encoding.Unicode.GetString(Data, JPEG, 0x1A));
|
|
public override byte[] JPEGData => JPEG < 0 || Data[JPEG + 0x54] != 0xFF ? null : Data.Skip(JPEG + 0x54).Take(0xE004).ToArray();
|
|
|
|
// Inventory
|
|
public override InventoryPouch[] Inventory
|
|
{
|
|
get
|
|
{
|
|
ushort[] legalItems = ORAS ? Legal.Pouch_Items_AO : Legal.Pouch_Items_XY;
|
|
ushort[] legalKey = ORAS ? Legal.Pouch_Key_AO : Legal.Pouch_Key_XY;
|
|
ushort[] legalTMHM = ORAS ? Legal.Pouch_TMHM_AO : Legal.Pouch_TMHM_XY;
|
|
ushort[] legalMedicine = ORAS ? Legal.Pouch_Medicine_AO : Legal.Pouch_Medicine_XY;
|
|
InventoryPouch[] pouch =
|
|
{
|
|
new InventoryPouch(InventoryType.Items, legalItems, 995, OFS_PouchHeldItem),
|
|
new InventoryPouch(InventoryType.KeyItems, legalKey, 1, OFS_PouchKeyItem),
|
|
new InventoryPouch(InventoryType.TMHMs, legalTMHM, 1, OFS_PouchTMHM),
|
|
new InventoryPouch(InventoryType.Medicine, legalMedicine, 995, OFS_PouchMedicine),
|
|
new InventoryPouch(InventoryType.Berries, Legal.Pouch_Berry_XY, 995, OFS_PouchBerry),
|
|
};
|
|
foreach (var p in pouch)
|
|
p.getPouch(ref Data);
|
|
return pouch;
|
|
}
|
|
set
|
|
{
|
|
foreach (var p in value)
|
|
p.setPouch(ref Data);
|
|
}
|
|
}
|
|
|
|
// Storage
|
|
public override int CurrentBox { get { return 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;
|
|
}
|
|
public override int getBoxWallpaper(int box)
|
|
{
|
|
return 1 + Data[PCBackgrounds + box];
|
|
}
|
|
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 val)
|
|
{
|
|
Encoding.Unicode.GetBytes(val.PadRight(0x11, '\0')).CopyTo(Data, PCLayout + 0x22*box);
|
|
Edited = true;
|
|
}
|
|
public override PKM getPKM(byte[] data)
|
|
{
|
|
return new PK6(data);
|
|
}
|
|
protected override void setPKM(PKM pkm)
|
|
{
|
|
PK6 pk6 = pkm as PK6;
|
|
// Apply to this Save File
|
|
int CT = pk6.CurrentHandler;
|
|
DateTime Date = DateTime.Now;
|
|
pk6.Trade(OT, TID, SID, Country, SubRegion, Gender, false, Date.Day, Date.Month, Date.Year);
|
|
if (CT != pk6.CurrentHandler) // Logic updated Friendship
|
|
{
|
|
// Copy over the Friendship Value only under certain circumstances
|
|
if (pk6.Moves.Contains(216)) // Return
|
|
pk6.CurrentFriendship = pk6.OppositeFriendship;
|
|
else if (pk6.Moves.Contains(218)) // Frustration
|
|
pkm.CurrentFriendship = pk6.OppositeFriendship;
|
|
else if (pk6.CurrentHandler == 1) // OT->HT, needs new Friendship/Affection
|
|
pk6.TradeFriendshipAffection(OT);
|
|
}
|
|
pkm.RefreshChecksum();
|
|
}
|
|
protected override void setDex(PKM pkm)
|
|
{
|
|
if (PokeDex < 0)
|
|
return;
|
|
if (pkm.Species == 0)
|
|
return;
|
|
if (pkm.Species > MaxSpeciesID)
|
|
return;
|
|
if (Version == GameVersion.Unknown)
|
|
return;
|
|
|
|
int bit = pkm.Species - 1;
|
|
int lang = pkm.Language - 1; if (lang > 5) lang--; // 0-6 language vals
|
|
int origin = pkm.Version;
|
|
int gender = pkm.Gender % 2; // genderless -> male
|
|
int shiny = pkm.IsShiny ? 1 : 0;
|
|
int shiftoff = shiny * 0x60 * 2 + gender * 0x60 + 0x60;
|
|
|
|
// Set the [Species/Gender/Shiny] Owned Flag
|
|
Data[PokeDex + shiftoff + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
|
|
|
|
// Owned quality flag
|
|
if (origin < 0x18 && bit < 649 && !ORAS) // Species: 1-649 for X/Y, and not for ORAS; Set the Foreign Owned Flag
|
|
Data[PokeDex + 0x64C + bit / 8] |= (byte)(1 << (bit % 8));
|
|
else if (origin >= 0x18 || ORAS) // Set Native Owned Flag (should always happen)
|
|
Data[PokeDex + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
|
|
|
|
// Set the Display flag if none are set
|
|
bool Displayed = false;
|
|
Displayed |= (Data[PokeDex + 0x60 * 5 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0;
|
|
Displayed |= (Data[PokeDex + 0x60 * 6 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0;
|
|
Displayed |= (Data[PokeDex + 0x60 * 7 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0;
|
|
Displayed |= (Data[PokeDex + 0x60 * 8 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0;
|
|
if (!Displayed) // offset is already biased by 0x60, reuse shiftoff but for the display flags.
|
|
Data[PokeDex + shiftoff + 0x60 * 4 + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
|
|
|
|
// Set the Language
|
|
if (lang < 0) lang = 1;
|
|
Data[PokeDexLanguageFlags + (bit * 7 + lang) / 8] |= (byte)(1 << ((bit * 7 + lang) % 8));
|
|
|
|
// Set Form flags
|
|
int fc = PKX.Personal[pkm.Species].FormeCount;
|
|
int f = ORAS ? SaveUtil.getDexFormIndexORAS(pkm.Species, fc) : SaveUtil.getDexFormIndexXY(pkm.Species, fc);
|
|
if (f >= 0)
|
|
{
|
|
int FormLen = ORAS ? 0x26 : 0x18;
|
|
int FormDex = PokeDex + 0x368;
|
|
bit = f + pkm.AltForm;
|
|
// Set Seen Flag
|
|
Data[FormDex + FormLen * shiny + bit / 8] |= (byte)(1 << (bit % 8));
|
|
|
|
// Set Displayed Flag if necessary, check all flags
|
|
bool FormDisplayed = false;
|
|
for (int i = 0; i < fc; i++)
|
|
{
|
|
bit = f + i;
|
|
FormDisplayed |= (Data[FormDex + FormLen * 2 + bit / 8] & (byte)(1 << (bit % 8))) != 0; // Nonshiny
|
|
FormDisplayed |= (Data[FormDex + FormLen * 3 + bit / 8] & (byte)(1 << (bit % 8))) != 0; // Shiny
|
|
}
|
|
if (!FormDisplayed)
|
|
{
|
|
bit = f + pkm.AltForm;
|
|
Data[FormDex + FormLen * (2 + shiny) + bit / 8] |= (byte)(1 << (bit % 8));
|
|
}
|
|
}
|
|
|
|
// Set DexNav count (only if not encountered previously)
|
|
if (ORAS && getEncounterCount(pkm.Species - 1) == 0)
|
|
setEncounterCount(pkm.Species - 1, 1);
|
|
}
|
|
public override byte[] decryptPKM(byte[] data)
|
|
{
|
|
return PKX.decryptArray(data);
|
|
}
|
|
public override int PartyCount
|
|
{
|
|
get { return Data[Party + 6 * SIZE_PARTY]; }
|
|
protected set { Data[Party + 6 * SIZE_PARTY] = (byte)value; }
|
|
}
|
|
public override bool BattleBoxLocked
|
|
{
|
|
get { return Data[BattleBox + 6 * SIZE_STORED] != 0; }
|
|
set { Data[BattleBox + 6 * SIZE_STORED] = (byte)(value ? 1 : 0); }
|
|
}
|
|
|
|
// Mystery Gift
|
|
protected override bool[] MysteryGiftReceivedFlags
|
|
{
|
|
get
|
|
{
|
|
if (WondercardData < 0 || WondercardFlags < 0)
|
|
return null;
|
|
|
|
bool[] r = new bool[(WondercardData-WondercardFlags)*8];
|
|
for (int i = 0; i < r.Length; i++)
|
|
r[i] = (Data[WondercardFlags + (i>>3)] >> (i&7) & 0x1) == 1;
|
|
return r;
|
|
}
|
|
set
|
|
{
|
|
if (WondercardData < 0 || WondercardFlags < 0)
|
|
return;
|
|
if ((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 null;
|
|
MysteryGift[] cards = new MysteryGift[GiftCountMax];
|
|
for (int i = 0; i < cards.Length; i++)
|
|
cards[i] = getWC6(i);
|
|
|
|
return cards;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
return;
|
|
if (value.Length > 24)
|
|
Array.Resize(ref value, 24);
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
setWC6(value[i], i);
|
|
for (int i = value.Length; i < GiftCountMax; i++)
|
|
setWC6(new WC6(), i);
|
|
}
|
|
}
|
|
private WC6 getWC6(int index)
|
|
{
|
|
if (WondercardData < 0)
|
|
return null;
|
|
if (index < 0 || index > GiftCountMax)
|
|
return null;
|
|
|
|
return new WC6(Data.Skip(WondercardData + index * WC6.Size).Take(WC6.Size).ToArray());
|
|
}
|
|
private void setWC6(MysteryGift wc6, int index)
|
|
{
|
|
if (WondercardData < 0)
|
|
return;
|
|
if (index < 0 || index > GiftCountMax)
|
|
return;
|
|
|
|
wc6.Data.CopyTo(Data, WondercardData + index * WC6.Size);
|
|
|
|
for (int i = 0; i < GiftCountMax; i++)
|
|
if (BitConverter.ToUInt16(Data, WondercardData + i * WC6.Size) == 0)
|
|
for (int j = i + 1; j < GiftCountMax - i; j++) // Shift everything down
|
|
Array.Copy(Data, WondercardData + j * WC6.Size, Data, WondercardData + (j - 1) * WC6.Size, WC6.Size);
|
|
|
|
Edited = true;
|
|
}
|
|
|
|
// Writeback Validity
|
|
public override string MiscSaveChecks()
|
|
{
|
|
string r = "";
|
|
byte[] FFFF = Enumerable.Repeat((byte)0xFF, 0x200).ToArray();
|
|
for (int i = 0; i < Data.Length / 0x200; i++)
|
|
{
|
|
if (!FFFF.SequenceEqual(Data.Skip(i * 0x200).Take(0x200))) continue;
|
|
r = $"0x200 chunk @ 0x{(i*0x200).ToString("X5")} is FF'd."
|
|
+ Environment.NewLine + "Cyber will screw up (as of August 31st 2014)." + Environment.NewLine + Environment.NewLine;
|
|
|
|
// Check to see if it is in the Pokedex
|
|
if (i * 0x200 > PokeDex && i * 0x200 < PokeDex + 0x900)
|
|
{
|
|
r += "Problem lies in the Pokedex. ";
|
|
if (i * 0x200 == PokeDex + 0x400)
|
|
r += "Remove a language flag for a species < 585, ie Petilil";
|
|
}
|
|
break;
|
|
}
|
|
return r;
|
|
}
|
|
public override string MiscSaveInfo()
|
|
{
|
|
return Blocks.Aggregate("", (current, b) => current +
|
|
$"{b.ID.ToString("00")}: {b.Offset.ToString("X5")}-{(b.Offset + b.Length).ToString("X5")}, {b.Length.ToString("X5")}{Environment.NewLine}");
|
|
}
|
|
}
|
|
}
|