Rework mainline sav3 to not operate off chunks

Previous style was to resize the sav buffer and concat all the storage chunks at the end. Allocated a double save file.

Now, just allocate 3 buffers.
This commit is contained in:
Kurt 2021-03-07 23:22:07 -08:00
parent cb7ee8a64e
commit 49541e6073
9 changed files with 370 additions and 397 deletions

View file

@ -70,7 +70,7 @@ namespace PKHeX.Core
return None;
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GetBlockOffset(4) + 0xE18) {Type = StorageSlotType.Daycare }
new(sav.Large, 0, 0x3C98) {Type = StorageSlotType.Daycare }
};
}

View file

@ -19,7 +19,7 @@ namespace PKHeX.Core
Slot = slot;
Offset = offset;
PartyFormat = party;
Data = sav is SAV4 s ? s.General : sav.Data;
Data = sav is SAV4 s ? s.General : sav is SAV3 s3 ? s3.Large : sav.Data;
}
public SlotInfoMisc(byte[] data, int slot, int offset, bool party = false)

View file

@ -18,24 +18,44 @@ namespace PKHeX.Core
public bool Korean => false;
public bool IndeterminateGame => Version == GameVersion.Unknown;
/* SAV3 Structure:
* 0xE000 per save file
* 14 blocks @ 0x1000 each.
* Blocks do not use all 0x1000 bytes allocated.
* Via: http://bulbapedia.bulbagarden.net/wiki/Save_data_structure_in_Generation_III
*/
// Similar to future games, the Generation 3 Mainline save files are comprised of two separate objects:
// Object 1 - Small Block, containing misc configuration data & the Pokédex.
// Object 2 - Large Block, containing everything else that isn't PC Storage system data.
// Object 3 - Storage Block, containing all the data for the PC storage system.
// When the objects are serialized to the savedata, the game breaks up each object into chunks < 0x1000 bytes.
// Each serialized save occupies 14 chunks; there are a total of two serialized saves.
// After the serialized save data, there is "extra data", for stuff like Hall of Fame and battle videos.
private const int SIZE_BLOCK = 0x1000;
private const int BLOCK_COUNT = 14;
private const int SIZE_RESERVED = 0x10000; // unpacked box data will start after the save data
public const int SIZE_BLOCK_USED = 0xF80;
private const int COUNT_BOX = 14;
private const int COUNT_SLOTSPERBOX = 30;
// Use the largest of structure sizes, as zeroes being fed into checksum function don't change the value.
private const int SIZE_SMALL = 0xF2C; // maximum size for R/S/E/FR/LG structures
private const int SIZE_LARGE = (3 * 0xF80) + 0xF08; // maximum size for R/S/E/FR/LG structures
public readonly byte[] Small = new byte[SIZE_SMALL];
public readonly byte[] Large = new byte[SIZE_LARGE];
public readonly byte[] Storage = new byte[SIZE_PC];
protected override byte[] BoxBuffer => Storage;
protected override byte[] PartyBuffer => Large;
// 0x83D0
private const int SIZE_PC = sizeof(int) // Current Box
+ (COUNT_BOX * (COUNT_SLOTSPERBOX * PokeCrypto.SIZE_3STORED)) // Slots
+ (COUNT_BOX * (8 + 1)) // Box Names
+ (COUNT_BOX * 1); // Box Wallpapers
private static readonly ushort[] chunkLength =
{
0xf2c, // 0 | Small Block (Trainer Info)
0xf2c, // 0 | Small Block (Trainer Info) [0x890 RS, 0xf24 FR/LG]
0xf80, // 1 | Large Block Part 1
0xf80, // 2 | Large Block Part 2
0xf80, // 3 | Large Block Part 3
0xf08, // 4 | Large Block Part 4
0xf08, // 4 | Large Block Part 4 [0xc40 RS, 0xee8 FR/LG]
0xf80, // 5 | PC Block 0
0xf80, // 6 | PC Block 1
0xf80, // 7 | PC Block 2
@ -47,36 +67,11 @@ namespace PKHeX.Core
0x7d0 // D | PC Block 8
};
public static void GetLargeBlockOffset(int contiguousOffset, out int chunk, out int chunkOffset)
{
for (chunk = 1; chunk <= 4; chunk++)
{
int chunkSize = chunkLength[chunk];
if (chunkSize > contiguousOffset)
break;
contiguousOffset -= chunkSize;
}
chunkOffset = contiguousOffset;
}
public static int GetLargeBlockOffset(int chunk, int chunkOffset)
{
if (chunk == 1)
return chunkOffset;
for (int i = 1; i <= 4; i++)
{
if (chunk == i)
break;
chunkOffset += chunkLength[i];
}
return chunkOffset;
}
private PersonalTable _personal;
public override PersonalTable Personal => _personal;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public SAV3(GameVersion version = GameVersion.FRLG, bool japanese = false) : base(SaveUtil.SIZE_G3RAW)
public SAV3(GameVersion version = GameVersion.FRLG, bool japanese = false)
{
Version = version switch
{
@ -87,9 +82,7 @@ namespace PKHeX.Core
_personal = SaveUtil.GetG3Personal(Version);
Japanese = japanese;
LoadBlocks(out BlockOrder, out BlockOfs);
// spoof block offsets
BlockOfs = Enumerable.Range(0, BLOCK_COUNT).ToArray();
BlockOrder = Array.Empty<short>();
LegalKeyItems = Version switch
{
@ -97,7 +90,7 @@ namespace PKHeX.Core
GameVersion.E => Legal.Pouch_Key_E,
_ => Legal.Pouch_Key_FRLG
};
PokeDex = BlockOfs[0] + 0x18;
PokeDex = 0x18;
SeenFlagOffsets = Array.Empty<int>();
Initialize();
@ -106,14 +99,20 @@ namespace PKHeX.Core
public SAV3(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data)
{
LoadBlocks(out BlockOrder, out BlockOfs);
Version = versionOverride != GameVersion.Any ? versionOverride : GetVersion(Data, BlockOfs[0]);
LoadBlocks(out BlockOrder);
// Copy chunk to the allocated location
LoadBlocks(Small, 0, 1);
LoadBlocks(Large, 1, 5);
LoadBlocks(Storage, 5, BLOCK_COUNT);
Version = versionOverride != GameVersion.Any ? versionOverride : GetVersion(Small);
_personal = SaveUtil.GetG3Personal(Version);
// Japanese games are limited to 5 character OT names; any unused characters are 0xFF.
// 5 for JP, 7 for INT. There's always 1 terminator, thus we can check 0x6-0x7 being 0xFFFF = INT
// OT name is stored at the top of the first block.
Japanese = BitConverter.ToInt16(Data, BlockOfs[0] + 0x6) == 0;
Japanese = BitConverter.ToInt16(Small, 0x6) == 0;
LegalKeyItems = Version switch
{
@ -122,12 +121,12 @@ namespace PKHeX.Core
_ => Legal.Pouch_Key_FRLG
};
PokeDex = BlockOfs[0] + 0x18;
PokeDex = 0x18;
SeenFlagOffsets = Version switch
{
GameVersion.RS => new[] { PokeDex + 0x44, BlockOfs[1] + 0x938, BlockOfs[4] + 0xC0C },
GameVersion.E => new[] { PokeDex + 0x44, BlockOfs[1] + 0x988, BlockOfs[4] + 0xCA4 },
_ => new[] { PokeDex + 0x44, BlockOfs[1] + 0x5F8, BlockOfs[4] + 0xB98 }
GameVersion.RS => new[] { 0x938, 0x3A8C },
GameVersion.E => new[] { 0x988, 0x3B24 },
_ => new[] { 0x5F8, 0x3A18 }
};
Initialize();
@ -135,55 +134,44 @@ namespace PKHeX.Core
private void Initialize()
{
// Set up PC data buffer beyond end of save file.
Box = Data.Length;
Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space.
// Copy chunk to the allocated location
for (short i = 5; i < BLOCK_COUNT; i++)
{
int blockIndex = Array.IndexOf(BlockOrder, i);
if (blockIndex == -1) // block empty
continue;
Array.Copy(Data, (blockIndex * SIZE_BLOCK) + ABO, Data, Box + ((i - 5) * 0xF80), chunkLength[i]);
}
Box = 0;
switch (Version)
{
case GameVersion.RS:
OFS_PCItem = BlockOfs[1] + 0x0498;
OFS_PouchHeldItem = BlockOfs[1] + 0x0560;
OFS_PouchKeyItem = BlockOfs[1] + 0x05B0;
OFS_PouchBalls = BlockOfs[1] + 0x0600;
OFS_PouchTMHM = BlockOfs[1] + 0x0640;
OFS_PouchBerry = BlockOfs[1] + 0x0740;
EventFlag = BlockOfs[2] + 0x2A0;
EventConst = EventFlag + (EventFlagMax / 8);
OFS_Decorations = BlockOfs[3] + 0x7A0;
DaycareOffset = BlockOfs[4] + 0x11C;
OFS_PCItem = 0x0498;
OFS_PouchHeldItem = 0x0560;
OFS_PouchKeyItem = 0x05B0;
OFS_PouchBalls = 0x0600;
OFS_PouchTMHM = 0x0640;
OFS_PouchBerry = 0x0740;
EventFlag = 0x1220;
EventConst = 0x1340;
OFS_Decorations = 0x26A0;
DaycareOffset = 0x2F9C;
break;
case GameVersion.E:
OFS_PCItem = BlockOfs[1] + 0x0498;
OFS_PouchHeldItem = BlockOfs[1] + 0x0560;
OFS_PouchKeyItem = BlockOfs[1] + 0x05D8;
OFS_PouchBalls = BlockOfs[1] + 0x0650;
OFS_PouchTMHM = BlockOfs[1] + 0x0690;
OFS_PouchBerry = BlockOfs[1] + 0x0790;
EventFlag = BlockOfs[2] + 0x2F0;
EventConst = EventFlag + (EventFlagMax / 8);
OFS_Decorations = BlockOfs[3] + 0x834;
DaycareOffset = BlockOfs[4] + 0x1B0;
OFS_PCItem = 0x0498;
OFS_PouchHeldItem = 0x0560;
OFS_PouchKeyItem = 0x05D8;
OFS_PouchBalls = 0x0650;
OFS_PouchTMHM = 0x0690;
OFS_PouchBerry = 0x0790;
EventFlag = 0x1270;
EventConst = 0x139C;
OFS_Decorations = 0x2734;
DaycareOffset = 0x3030;
break;
case GameVersion.FRLG:
OFS_PCItem = BlockOfs[1] + 0x0298;
OFS_PouchHeldItem = BlockOfs[1] + 0x0310;
OFS_PouchKeyItem = BlockOfs[1] + 0x03B8;
OFS_PouchBalls = BlockOfs[1] + 0x0430;
OFS_PouchTMHM = BlockOfs[1] + 0x0464;
OFS_PouchBerry = BlockOfs[1] + 0x054C;
EventFlag = BlockOfs[1] + 0xEE0;
EventConst = BlockOfs[2] + 0x80;
DaycareOffset = BlockOfs[4] + 0x100;
OFS_PCItem = 0x0298;
OFS_PouchHeldItem = 0x0310;
OFS_PouchKeyItem = 0x03B8;
OFS_PouchBalls = 0x0430;
OFS_PouchTMHM = 0x0464;
OFS_PouchBerry = 0x054C;
EventFlag = 0xEE0;
EventConst = 0x1000;
DaycareOffset = 0x2F80;
break;
default:
throw new ArgumentException(nameof(Version));
@ -195,7 +183,37 @@ namespace PKHeX.Core
SeenFlagOffsets = SeenFlagOffsets.Where(z => z >= 0).ToArray();
}
private void LoadBlocks(out short[] blockOrder, out int[] blockOfs)
private void LoadBlocks(byte[] dest, short start, short end)
{
for (short i = start; i < end; i++)
{
int blockIndex = Array.IndexOf(BlockOrder, i);
if (blockIndex == -1) // block empty
continue;
var sOfs = (blockIndex * SIZE_BLOCK) + ABO;
var dOfs = (i - start) * SIZE_BLOCK_USED;
var count = chunkLength[i];
Buffer.BlockCopy(Data, sOfs, dest, dOfs, count);
}
}
private void SaveBlocks(byte[] dest, short start, short end)
{
for (short i = start; i < end; i++)
{
int blockIndex = Array.IndexOf(BlockOrder, i);
if (blockIndex == -1) // block empty
continue;
var sOfs = (blockIndex * SIZE_BLOCK) + ABO;
var dOfs = (i - start) * SIZE_BLOCK_USED;
var count = chunkLength[i];
Buffer.BlockCopy(dest, dOfs, Data, sOfs, count);
}
}
private void LoadBlocks(out short[] blockOrder)
{
var o1 = GetBlockOrder(0);
if (Data.Length > SaveUtil.SIZE_G3RAWHALF)
@ -209,13 +227,6 @@ namespace PKHeX.Core
ActiveSAV = 0;
blockOrder = o1;
}
blockOfs = new int[BLOCK_COUNT];
for (short i = 0; i < BLOCK_COUNT; i++)
{
int index = Array.IndexOf(blockOrder, i);
blockOfs[i] = index < 0 ? int.MinValue : (index * SIZE_BLOCK) + ABO;
}
}
private short[] GetBlockOrder(int ofs)
@ -239,9 +250,9 @@ namespace PKHeX.Core
return count1 > count2 ? 0 : 1;
}
public static GameVersion GetVersion(byte[] data, int block0Ofs)
public static GameVersion GetVersion(byte[] data, int offset = 0)
{
uint GameCode = BitConverter.ToUInt32(data, block0Ofs + 0xAC);
uint GameCode = BitConverter.ToUInt32(data, offset + 0xAC);
switch (GameCode)
{
case 1: return GameVersion.FRLG; // fixed value
@ -252,9 +263,9 @@ namespace PKHeX.Core
// 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00
// ^ byte pattern in Emerald saves, is all zero in Ruby/Sapphire as far as I can tell.
// Some saves have had data @ 0x550
if (BitConverter.ToUInt64(data, block0Ofs + 0xEE0) != 0)
if (BitConverter.ToUInt64(data, offset + 0xEE0) != 0)
return GameVersion.E;
if (BitConverter.ToUInt64(data, block0Ofs + 0xEE8) != 0)
if (BitConverter.ToUInt64(data, offset + 0xEE8) != 0)
return GameVersion.E;
return GameVersion.RS;
}
@ -263,25 +274,15 @@ namespace PKHeX.Core
protected override byte[] GetFinalData()
{
// Copy Box data back
for (short i = 5; i < BLOCK_COUNT; i++)
{
int blockIndex = Array.IndexOf(BlockOrder, i);
if (blockIndex == -1) // block empty
continue;
Array.Copy(Data, Box + ((i - 5) * 0xF80), Data, (blockIndex * SIZE_BLOCK) + ABO, chunkLength[i]);
}
SetChecksums();
var result = new byte[Data.Length - SIZE_RESERVED];
Buffer.BlockCopy(Data, 0, result, 0, result.Length);
return result;
SaveBlocks(Small, 0, 1);
SaveBlocks(Large, 1, 5);
SaveBlocks(Storage, 5, BLOCK_COUNT);
return base.GetFinalData();
}
private int ActiveSAV;
private int ABO => ActiveSAV*SIZE_BLOCK*0xE;
private readonly short[] BlockOrder;
private readonly int[] BlockOfs;
public int GetBlockOffset(int block) => BlockOfs[block];
// Configuration
protected override SaveFile CloneInternal() => new SAV3(Write(), Version);
@ -406,61 +407,61 @@ namespace PKHeX.Core
public uint SecurityKey => Version switch
{
GameVersion.E => BitConverter.ToUInt32(Data, BlockOfs[0] + 0xAC),
GameVersion.FRLG => BitConverter.ToUInt32(Data, BlockOfs[0] + 0xF20),
GameVersion.E => BitConverter.ToUInt32(Small, 0xAC),
GameVersion.FRLG => BitConverter.ToUInt32(Small, 0xF20),
_ => 0u
};
public override string OT
{
get => GetString(BlockOfs[0], 0x10);
get => GetString(Small, 0, 0x10);
set
{
int len = Japanese ? 5 : OTLength;
SetString(value, len, PadToSize: len, PadWith: 0xFF).CopyTo(Data, BlockOfs[0]);
SetString(value, len, PadToSize: len, PadWith: 0xFF).CopyTo(Small, 0);
}
}
public override int Gender
{
get => Data[BlockOfs[0] + 8];
set => Data[BlockOfs[0] + 8] = (byte)value;
get => Small[8];
set => Small[8] = (byte)value;
}
public override int TID
{
get => BitConverter.ToUInt16(Data, BlockOfs[0] + 0xA + 0);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, BlockOfs[0] + 0xA + 0);
get => BitConverter.ToUInt16(Small, 0xA);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xA);
}
public override int SID
{
get => BitConverter.ToUInt16(Data, BlockOfs[0] + 0xC);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, BlockOfs[0] + 0xC);
get => BitConverter.ToUInt16(Small, 0xC);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xC);
}
public override int PlayedHours
{
get => BitConverter.ToUInt16(Data, BlockOfs[0] + 0xE);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, BlockOfs[0] + 0xE);
get => BitConverter.ToUInt16(Small, 0xE);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xE);
}
public override int PlayedMinutes
{
get => Data[BlockOfs[0] + 0x10];
set => Data[BlockOfs[0] + 0x10] = (byte)value;
get => Small[0x10];
set => Small[0x10] = (byte)value;
}
public override int PlayedSeconds
{
get => Data[BlockOfs[0] + 0x11];
set => Data[BlockOfs[0] + 0x11] = (byte)value;
get => Small[0x11];
set => Small[0x11] = (byte)value;
}
public int PlayedFrames
{
get => Data[BlockOfs[0] + 0x12];
set => Data[BlockOfs[0] + 0x12] = (byte)value;
get => Small[0x12];
set => Small[0x12] = (byte)value;
}
public override bool GetEventFlag(int flagNumber)
@ -469,11 +470,6 @@ namespace PKHeX.Core
throw new ArgumentException($"Event Flag to get ({flagNumber}) is greater than max ({EventFlagMax}).");
var start = EventFlag;
if (Version == GameVersion.FRLG && flagNumber >= 0x500)
{
flagNumber -= 0x500;
start = BlockOfs[2];
}
return GetFlag(start + (flagNumber >> 3), flagNumber & 7);
}
@ -483,16 +479,31 @@ namespace PKHeX.Core
throw new ArgumentException($"Event Flag to set ({flagNumber}) is greater than max ({EventFlagMax}).");
var start = EventFlag;
if (Version == GameVersion.FRLG && flagNumber >= 0x500)
{
flagNumber -= 0x500;
start = BlockOfs[2];
}
SetFlag(start + (flagNumber >> 3), flagNumber & 7, value);
}
public ushort GetEventConst(int index) => BitConverter.ToUInt16(Data, EventConst + (index * 2));
public void SetEventConst(int index, ushort value) => BitConverter.GetBytes(value).CopyTo(Data, EventConst + (index * 2));
public override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex);
public override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value);
public ushort GetEventConst(int index) => BitConverter.ToUInt16(Large, EventConst + (index * 2));
public void SetEventConst(int index, ushort value) => BitConverter.GetBytes(value).CopyTo(Large, EventConst + (index * 2));
public override ushort[] GetEventConsts()
{
ushort[] Constants = new ushort[EventConstMax];
for (int i = 0; i < Constants.Length; i++)
Constants[i] = GetEventConst(i);
return Constants;
}
public override void SetEventConsts(ushort[] value)
{
if (value.Length != EventConstMax)
return;
for (int i = 0; i < value.Length; i++)
SetEventConst(i, value[i]);
}
public int Badges
{
@ -535,8 +546,8 @@ namespace PKHeX.Core
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: return BitConverter.ToUInt32(Data, BlockOfs[1] + 0x0490) ^ SecurityKey;
case GameVersion.FRLG: return BitConverter.ToUInt32(Data, BlockOfs[1] + 0x0290) ^ SecurityKey;
case GameVersion.E: return BitConverter.ToUInt32(Large, 0x0490) ^ SecurityKey;
case GameVersion.FRLG: return BitConverter.ToUInt32(Large, 0x0290) ^ SecurityKey;
default: return 0;
}
}
@ -545,8 +556,8 @@ namespace PKHeX.Core
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Data, BlockOfs[1] + 0x0490); break;
case GameVersion.FRLG: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Data, BlockOfs[1] + 0x0290); break;
case GameVersion.E: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Large, 0x0490); break;
case GameVersion.FRLG: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Large, 0x0290); break;
}
}
}
@ -558,8 +569,8 @@ namespace PKHeX.Core
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: return (ushort)(BitConverter.ToUInt16(Data, BlockOfs[1] + 0x0494) ^ SecurityKey);
case GameVersion.FRLG: return (ushort)(BitConverter.ToUInt16(Data, BlockOfs[1] + 0x0294) ^ SecurityKey);
case GameVersion.E: return (ushort)(BitConverter.ToUInt16(Large, 0x0494) ^ SecurityKey);
case GameVersion.FRLG: return (ushort)(BitConverter.ToUInt16(Large, 0x0294) ^ SecurityKey);
default: return 0;
}
}
@ -570,31 +581,31 @@ namespace PKHeX.Core
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Data, BlockOfs[1] + 0x0494); break;
case GameVersion.FRLG: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Data, BlockOfs[1] + 0x0294); break;
case GameVersion.E: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Large, 0x0494); break;
case GameVersion.FRLG: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Large, 0x0294); break;
}
}
}
public uint BP
{
get => BitConverter.ToUInt16(Data, BlockOfs[0] + 0xEB8);
get => BitConverter.ToUInt16(Small, 0xEB8);
set
{
if (value > 9999)
value = 9999;
BitConverter.GetBytes((ushort)value).CopyTo(Data, BlockOfs[0] + 0xEB8);
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEB8);
}
}
public uint BPEarned
{
get => BitConverter.ToUInt16(Data, BlockOfs[0] + 0xEBA);
get => BitConverter.ToUInt16(Small, 0xEBA);
set
{
if (value > 65535)
value = 65535;
BitConverter.GetBytes((ushort)value).CopyTo(Data, BlockOfs[0] + 0xEBA);
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEBA);
}
}
@ -604,13 +615,13 @@ namespace PKHeX.Core
{
if (Version != GameVersion.FRLG)
return 0;
return BitConverter.ToUInt32(Data, BlockOfs[0] + 0xAF8) ^ SecurityKey;
return BitConverter.ToUInt32(Small, 0xAF8) ^ SecurityKey;
}
set
{
if (Version != GameVersion.FRLG)
return;
SetData(BitConverter.GetBytes(value ^ SecurityKey), BlockOfs[0] + 0xAF8);
SetData(Small, BitConverter.GetBytes(value ^ SecurityKey), 0xAF8);
}
}
@ -642,16 +653,16 @@ namespace PKHeX.Core
if (p.Type != InventoryType.PCItems)
((InventoryPouch3)p).SecurityKey = SecurityKey;
}
return pouch.LoadAll(Data);
return pouch.LoadAll(Large);
}
set => value.SaveAll(Data);
set => value.SaveAll(Large);
}
private int DaycareSlotSize => RS ? SIZE_STORED : SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => E ? 8 : 4; // 32bit, 16bit
public override uint? GetDaycareEXP(int loc, int slot) => BitConverter.ToUInt32(Data, GetDaycareEXPOffset(slot));
public override void SetDaycareEXP(int loc, int slot, uint EXP) => BitConverter.GetBytes(EXP).CopyTo(Data, GetDaycareEXPOffset(slot));
public override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Data, GetDaycareSlotOffset(loc, slot));
public override uint? GetDaycareEXP(int loc, int slot) => BitConverter.ToUInt32(Large, GetDaycareEXPOffset(slot));
public override void SetDaycareEXP(int loc, int slot, uint EXP) => BitConverter.GetBytes(EXP).CopyTo(Large, GetDaycareEXPOffset(slot));
public override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large, GetDaycareSlotOffset(loc, slot));
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize);
@ -669,8 +680,8 @@ namespace PKHeX.Core
public override string GetDaycareRNGSeed(int loc)
{
if (Version == GameVersion.E)
return BitConverter.ToUInt32(Data, GetDaycareSlotOffset(0, 2)).ToString("X8"); // after the 2 slots, before the step counter
return BitConverter.ToUInt16(Data, GetDaycareEXPOffset(2)).ToString("X4"); // after the 2nd slot EXP, before the step counter
return BitConverter.ToUInt32(Large, GetDaycareSlotOffset(0, 2)).ToString("X8"); // after the 2 slots, before the step counter
return BitConverter.ToUInt16(Large, GetDaycareEXPOffset(2)).ToString("X4"); // after the 2nd slot EXP, before the step counter
}
public override void SetDaycareRNGSeed(int loc, string seed)
@ -678,12 +689,12 @@ namespace PKHeX.Core
if (Version == GameVersion.E) // egg pid
{
var val = Util.GetHexValue(seed);
BitConverter.GetBytes(val).CopyTo(Data, GetDaycareSlotOffset(0, 2));
BitConverter.GetBytes(val).CopyTo(Large, GetDaycareSlotOffset(0, 2));
}
// egg pid half
{
var val = (ushort)Util.GetHexValue(seed);
BitConverter.GetBytes(val).CopyTo(Data, GetDaycareEXPOffset(2));
BitConverter.GetBytes(val).CopyTo(Large, GetDaycareEXPOffset(2));
}
}
@ -695,14 +706,14 @@ namespace PKHeX.Core
int ofs = 0x34;
if (GameVersion.FRLG != Version)
ofs += 0x200;
return Data[BlockOfs[1] + ofs];
return Large[ofs];
}
protected set
{
int ofs = 0x34;
if (GameVersion.FRLG != Version)
ofs += 0x200;
Data[BlockOfs[1] + ofs] = (byte)value;
Large[ofs] = (byte)value;
}
}
@ -716,32 +727,50 @@ namespace PKHeX.Core
int ofs = 0x38;
if (GameVersion.FRLG != Version)
ofs += 0x200;
return BlockOfs[1] + ofs + (SIZE_PARTY * slot);
return ofs + (SIZE_PARTY * slot);
}
public override int CurrentBox
{
get => Data[Box];
set => Data[Box] = (byte)value;
get => Storage[0];
set => Storage[0] = (byte)value;
}
public override int GetBoxWallpaper(int box)
{
if (box > COUNT_BOX)
return box;
int offset = GetBoxWallpaperOffset(box);
return Storage[offset];
}
private const int COUNT_BOXNAME = 8 + 1;
public override void SetBoxWallpaper(int box, int value)
{
if (box > COUNT_BOX)
return;
int offset = GetBoxWallpaperOffset(box);
Storage[offset] = (byte)value;
}
protected override int GetBoxWallpaperOffset(int box)
{
int offset = GetBoxOffset(BoxCount);
offset += (BoxCount * 0x9) + box;
int offset = GetBoxOffset(COUNT_BOX);
offset += (COUNT_BOX * COUNT_BOXNAME) + box;
return offset;
}
public override string GetBoxName(int box)
{
int offset = GetBoxOffset(BoxCount);
return StringConverter3.GetString3(Data, offset + (box * 9), 9, Japanese);
int offset = GetBoxOffset(COUNT_BOX);
return StringConverter3.GetString3(Storage, offset + (box * COUNT_BOXNAME), COUNT_BOXNAME, Japanese);
}
public override void SetBoxName(int box, string value)
{
int offset = GetBoxOffset(BoxCount);
SetString(value, 8).CopyTo(Data, offset + (box * 9));
int offset = GetBoxOffset(COUNT_BOX);
SetString(value, COUNT_BOXNAME - 1).CopyTo(Storage, offset + (box * COUNT_BOXNAME));
}
protected override PKM GetPKM(byte[] data)
@ -754,7 +783,7 @@ namespace PKHeX.Core
return PokeCrypto.DecryptArray3(data);
}
// Pokédex
/// <summary> Mirrors of the Seen Flags (inside the Large block) </summary>
private int[] SeenFlagOffsets;
protected override void SetDex(PKM pkm)
@ -784,13 +813,11 @@ namespace PKHeX.Core
return false;
if (Version == GameVersion.Invalid)
return false;
if (BlockOfs.Any(z => z < 0))
return false;
return true;
}
public uint DexPIDUnown { get => BitConverter.ToUInt32(Data, PokeDex + 0x4); set => BitConverter.GetBytes(value).CopyTo(Data, PokeDex + 0x4); }
public uint DexPIDSpinda { get => BitConverter.ToUInt32(Data, PokeDex + 0x8); set => BitConverter.GetBytes(value).CopyTo(Data, PokeDex + 0x8); }
public uint DexPIDUnown { get => BitConverter.ToUInt32(Small, PokeDex + 0x4); set => BitConverter.GetBytes(value).CopyTo(Small, PokeDex + 0x4); }
public uint DexPIDSpinda { get => BitConverter.ToUInt32(Small, PokeDex + 0x8); set => BitConverter.GetBytes(value).CopyTo(Small, PokeDex + 0x8); }
public int DexUnownForm => PKX.GetUnownForm(DexPIDUnown);
public override bool GetCaught(int species)
@ -798,7 +825,7 @@ namespace PKHeX.Core
int bit = species - 1;
int ofs = bit >> 3;
int caughtOffset = PokeDex + 0x10;
return GetFlag(caughtOffset + ofs, bit & 7);
return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7);
}
public override void SetCaught(int species, bool caught)
@ -806,7 +833,7 @@ namespace PKHeX.Core
int bit = species - 1;
int ofs = bit >> 3;
int caughtOffset = PokeDex + 0x10;
SetFlag(caughtOffset + ofs, bit & 7, caught);
FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught);
}
public override bool GetSeen(int species)
@ -814,7 +841,7 @@ namespace PKHeX.Core
int bit = species - 1;
int ofs = bit >> 3;
int seenOffset = PokeDex + 0x44;
return GetFlag(seenOffset + ofs, bit & 7);
return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7);
}
public override void SetSeen(int species, bool seen)
@ -822,32 +849,34 @@ namespace PKHeX.Core
int bit = species - 1;
int ofs = bit >> 3;
int seenOffset = PokeDex + 0x44;
FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen);
foreach (int o in SeenFlagOffsets)
SetFlag(o + ofs, bit & 7, seen);
FlagUtil.SetFlag(Large, o + ofs, bit & 7, seen);
}
public byte PokedexSort
{
get => Data[PokeDex + 0x01];
set => Data[PokeDex + 0x01] = value;
get => Small[PokeDex + 0x01];
set => Small[PokeDex + 0x01] = value;
}
public byte PokedexMode
{
get => Data[PokeDex + 0x01];
set => Data[PokeDex + 0x01] = value;
get => Small[PokeDex + 0x01];
set => Small[PokeDex + 0x01] = value;
}
public byte PokedexNationalMagicRSE
{
get => Data[PokeDex + 0x02];
set => Data[PokeDex + 0x02] = value;
get => Small[PokeDex + 0x02];
set => Small[PokeDex + 0x02] = value;
}
public byte PokedexNationalMagicFRLG
{
get => Data[PokeDex + 0x03];
set => Data[PokeDex + 0x03] = value;
get => Small[PokeDex + 0x03];
set => Small[PokeDex + 0x03] = value;
}
private const int PokedexNationalUnlockRSE = 0xDA;
@ -859,8 +888,6 @@ namespace PKHeX.Core
{
get
{
if (BlockOfs.Any(z => z < 0))
return false;
return Version switch // only check natdex status in Block0
{
// enable nat dex option magic value
@ -871,9 +898,6 @@ namespace PKHeX.Core
}
set
{
if (BlockOfs.Any(z => z < 0))
return;
PokedexMode = value ? 1 : 0; // mode
switch (Version)
{
@ -912,9 +936,9 @@ namespace PKHeX.Core
// Offset and checksum code based from
// https://github.com/suloku/wc-tool by Suloku
private const int SIZE_EBERRY = 0x530;
private const int OFFSET_EBERRY = 0x2E0;
private const int OFFSET_EBERRY = 0x2E80 + 0x2E0;
private uint EBerryChecksum => BitConverter.ToUInt32(Data, BlockOfs[4] + OFFSET_EBERRY + SIZE_EBERRY - 4);
private uint EBerryChecksum => BitConverter.ToUInt32(Large, OFFSET_EBERRY + SIZE_EBERRY - 4);
private bool IsEBerryChecksumValid { get; set; }
public string EBerryName
@ -923,7 +947,7 @@ namespace PKHeX.Core
{
if (!GameVersion.RS.Contains(Version) || !IsEBerryChecksumValid)
return string.Empty;
return StringConverter3.GetString3(Data, BlockOfs[4] + OFFSET_EBERRY, 7, Japanese).Trim();
return StringConverter3.GetString3(Large, OFFSET_EBERRY, 7, Japanese).Trim();
}
}
@ -934,7 +958,7 @@ namespace PKHeX.Core
if (!GameVersion.RS.Contains(Version))
return;
byte[] data = GetData(BlockOfs[4] + OFFSET_EBERRY, SIZE_EBERRY - 4);
byte[] data = GetData(Large, OFFSET_EBERRY, SIZE_EBERRY - 4);
// 8 bytes are 0x00 for chk calculation
for (int i = 0; i < 8; i++)
@ -950,15 +974,13 @@ namespace PKHeX.Core
{
if (FRLG)
throw new ArgumentException(nameof(ClockInitial));
int block0 = GetBlockOffset(0);
return new RTC3(GetData(block0 + 0x98, RTC3.Size));
return new RTC3(GetData(Small, 0x98, RTC3.Size));
}
set
{
if (FRLG)
return;
int block0 = GetBlockOffset(0);
SetData(value.Data, block0 + 0x98);
SetData(Small, value.Data, 0x98);
}
}
@ -968,15 +990,13 @@ namespace PKHeX.Core
{
if (FRLG)
throw new ArgumentException(nameof(ClockElapsed));
int block0 = GetBlockOffset(0);
return new RTC3(GetData(block0 + 0xA0, RTC3.Size));
return new RTC3(GetData(Small, 0xA0, RTC3.Size));
}
set
{
if (FRLG)
return;
int block0 = GetBlockOffset(0);
SetData(value.Data, block0 + 0xA0);
SetData(Small, value.Data, 0xA0);
}
}
@ -987,9 +1007,9 @@ namespace PKHeX.Core
var ofs = PokeBlockOffset;
if (ofs < 0)
throw new ArgumentException($"Game does not support {nameof(PokeBlocks)}.");
return new PokeBlock3Case(Data, ofs);
return new PokeBlock3Case(Large, ofs);
}
set => SetData(value.Write(), PokeBlockOffset);
set => SetData(Large, value.Write(), PokeBlockOffset);
}
private int PokeBlockOffset
@ -997,52 +1017,38 @@ namespace PKHeX.Core
get
{
if (Version == GameVersion.E)
return BlockOfs[1] + 0x848;
return 0x848;
if (Version == GameVersion.RS)
return BlockOfs[1] + 0x7F8;
return 0x7F8;
return -1;
}
}
public int GetMailOffset(int index)
{
GetMailBlockOffset(Version, ref index, out int block, out int offset);
return (index * Mail3.SIZE) + GetBlockOffset(block) + offset;
var offset = Version switch
{
GameVersion.RS => 0x2B4C,
GameVersion.E => 0x2BE0,
_ => 0x2CD0, // FRLG
};
return (index * Mail3.SIZE) + offset;
}
private static void GetMailBlockOffset(GameVersion game, ref int index, out int block, out int offset)
private int ExternalEventFlags => Version switch
{
block = 3;
if (game == GameVersion.E)
{
offset = 0xCE0;
}
else if (GameVersion.RS.Contains(game))
{
offset = 0xC4C;
}
else // FRLG
{
if (index >= 12)
{
block = 4;
offset = 0;
index -= 12;
}
else
{
offset = 0xDD0;
}
}
}
GameVersion.RS => 0x312F,
GameVersion.E => 0x31C7,
_ => throw new IndexOutOfRangeException(),
};
public bool HasReceivedWishmkrJirachi
{
get => GameVersion.RS.Contains(Version) && GetFlag(BlockOfs[4] + 0x2B1, 0);
get => !GameVersion.FRLG.Contains(Version) && GetFlag(ExternalEventFlags + 2, 0);
set
{
if (GameVersion.RS.Contains(Version))
SetFlag(BlockOfs[4] + 0x2B1, 0, value);
if (!GameVersion.FRLG.Contains(Version))
SetFlag(ExternalEventFlags + 2, 0, value);
}
}
@ -1060,13 +1066,13 @@ namespace PKHeX.Core
{
if (Version == GameVersion.FRLG)
throw new Exception();
return Data.Slice(OFS_Decorations, DecorationInventory3.SIZE).ToStructure<DecorationInventory3>();
return Large.Slice(OFS_Decorations, DecorationInventory3.SIZE).ToStructure<DecorationInventory3>();
}
set
{
if (Version == GameVersion.FRLG)
throw new Exception();
SetData(value.ToBytes(), OFS_Decorations);
SetData(Large, value.ToBytes(), OFS_Decorations);
}
}
}

View file

@ -7,30 +7,25 @@ namespace PKHeX.Core
{
private readonly SAV3 SAV;
public uint GetRecord(int record) => BitConverter.ToUInt32(SAV.Data, GetRecordOffset(record)) ^ GetKey(SAV);
public void SetRecord(int record, uint value) => SAV.SetData(BitConverter.GetBytes(value ^ GetKey(SAV)), GetRecordOffset(record));
public uint GetRecord(int record) => BitConverter.ToUInt32(SAV.Large, GetRecordOffset(record)) ^ GetKey(SAV);
public void SetRecord(int record, uint value) => SAV.SetData(SAV.Large, BitConverter.GetBytes(value ^ GetKey(SAV)), GetRecordOffset(record));
private int GetRecordOffset(int record)
{
var baseOffset = GetOffset(SAV.Version);
var offset = baseOffset + (4 * record);
SAV3.GetLargeBlockOffset(offset, out var chunk, out var ofs);
return SAV.GetBlockOffset(chunk) + ofs;
return offset;
}
public Record3(SAV3 sav) => SAV = sav;
public static int GetOffset(GameVersion ver)
public static int GetOffset(GameVersion ver) => ver switch
{
return ver switch
{
GameVersion.RS => 0x1540,
GameVersion.FRLG => 0x1200,
GameVersion.E => 0x159C,
_ => throw new ArgumentException(nameof(ver))
};
}
GameVersion.RS => 0x1540,
GameVersion.E => 0x159C,
GameVersion.FRLG => 0x1200,
_ => throw new ArgumentException(nameof(ver))
};
public static uint GetKey(SAV3 sav)
{

View file

@ -4,62 +4,62 @@ namespace PKHeX.Core
{
public sealed class Roamer3 : IContestStats, IContestStatsMutable
{
private readonly SaveFile SAV;
private readonly int Offset;
public bool IsGlitched { get; }
private readonly byte[] Data;
private readonly SAV3 SAV;
public Roamer3(SAV3 sav)
{
SAV = sav;
Offset = sav.GetBlockOffset(4);
if (GameVersion.FRLG.Contains(SAV.Version))
Offset += 0x250;
else if (SAV.Version == GameVersion.E)
Offset += 0x35C;
else // RS
Offset += 0x2C4;
IsGlitched = SAV.Version != GameVersion.E;
Data = (SAV = sav).Large;
Offset = sav.Version switch
{
GameVersion.RS => 0x3144,
GameVersion.E => 0x31DC,
_ => 0x30D0, // FRLG
};
IsGlitched = sav.Version != GameVersion.E;
}
private uint IV32
{
get => BitConverter.ToUInt32(SAV.Data, Offset);
set => SAV.SetData(BitConverter.GetBytes(value), Offset);
get => BitConverter.ToUInt32(Data, Offset);
set => SAV.SetData(Data, BitConverter.GetBytes(value), Offset);
}
public uint PID
{
get => BitConverter.ToUInt32(SAV.Data, Offset + 4);
set => SAV.SetData(BitConverter.GetBytes(value), Offset + 4);
get => BitConverter.ToUInt32(Data, Offset + 4);
set => SAV.SetData(Data, BitConverter.GetBytes(value), Offset + 4);
}
public int Species
{
get => SpeciesConverter.GetG4Species(BitConverter.ToInt16(SAV.Data, Offset + 8));
set => SAV.SetData(BitConverter.GetBytes((ushort)SpeciesConverter.GetG3Species(value)), Offset + 8);
get => SpeciesConverter.GetG4Species(BitConverter.ToInt16(Data, Offset + 8));
set => SAV.SetData(Data, BitConverter.GetBytes((ushort)SpeciesConverter.GetG3Species(value)), Offset + 8);
}
public int HP_Current
{
get => BitConverter.ToInt16(SAV.Data, Offset + 10);
set => SAV.SetData(BitConverter.GetBytes((short)value), Offset + 10);
get => BitConverter.ToInt16(Data, Offset + 10);
set => SAV.SetData(Data, BitConverter.GetBytes((short)value), Offset + 10);
}
public int CurrentLevel
{
get => SAV.Data[Offset + 12];
set => SAV.Data[Offset + 12] = (byte)value;
get => Data[Offset + 12];
set => Data[Offset + 12] = (byte)value;
}
public int Status { get => SAV.Data[Offset + 0x0D]; set => SAV.Data[Offset + 0x0D] = (byte)value; }
public int Status { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
public byte CNT_Cool { get => SAV.Data[Offset + 0x0E]; set => SAV.Data[Offset + 0x0E] = value; }
public byte CNT_Beauty { get => SAV.Data[Offset + 0x0F]; set => SAV.Data[Offset + 0x0F] = value; }
public byte CNT_Cute { get => SAV.Data[Offset + 0x10]; set => SAV.Data[Offset + 0x10] = value; }
public byte CNT_Smart { get => SAV.Data[Offset + 0x11]; set => SAV.Data[Offset + 0x11] = value; }
public byte CNT_Tough { get => SAV.Data[Offset + 0x12]; set => SAV.Data[Offset + 0x12] = value; }
public byte CNT_Cool { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = value; }
public byte CNT_Beauty { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = value; }
public byte CNT_Cute { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; }
public byte CNT_Smart { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = value; }
public byte CNT_Tough { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = value; }
public byte CNT_Sheen { get => 0; set { } }
public bool Active { get => SAV.Data[Offset + 0x13] == 1; set => SAV.Data[Offset + 0x13] = value ? 1 : 0; }
public bool Active { get => Data[Offset + 0x13] == 1; set => Data[Offset + 0x13] = value ? 1 : 0; }
// Derived Properties
private int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); }

View file

@ -28,6 +28,7 @@ namespace PKHeX.Core
public override ushort GetMessage(int index1, int index2) => BitConverter.ToUInt16(Data, ((index1 * 3) + index2) * 2);
public override void SetMessage(int index1, int index2, ushort value) => BitConverter.GetBytes(value).CopyTo(Data, ((index1 * 3) + index2) * 2);
public override void CopyTo(SaveFile sav) => sav.SetData(((SAV3)sav).Large, DataOffset);
public override string AuthorName
{

View file

@ -43,7 +43,7 @@
this.CB_TCM1 = new System.Windows.Forms.ComboBox();
this.NUD_Coins = new System.Windows.Forms.NumericUpDown();
this.L_Coins = new System.Windows.Forms.Label();
this.TB_OTName = new System.Windows.Forms.TextBox();
this.TB_RivalName = new System.Windows.Forms.TextBox();
this.L_TrainerName = new System.Windows.Forms.Label();
this.NUD_BP = new System.Windows.Forms.NumericUpDown();
this.L_BP = new System.Windows.Forms.Label();
@ -177,7 +177,7 @@
this.TAB_Main.Controls.Add(this.GB_TCM);
this.TAB_Main.Controls.Add(this.NUD_Coins);
this.TAB_Main.Controls.Add(this.L_Coins);
this.TAB_Main.Controls.Add(this.TB_OTName);
this.TAB_Main.Controls.Add(this.TB_RivalName);
this.TAB_Main.Controls.Add(this.L_TrainerName);
this.TAB_Main.Controls.Add(this.NUD_BP);
this.TAB_Main.Controls.Add(this.L_BP);
@ -310,16 +310,16 @@
this.L_Coins.Text = "Coins:";
this.L_Coins.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// TB_OTName
// TB_RivalName
//
this.TB_OTName.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.TB_OTName.Location = new System.Drawing.Point(120, 93);
this.TB_OTName.MaxLength = 7;
this.TB_OTName.Name = "TB_OTName";
this.TB_OTName.Size = new System.Drawing.Size(63, 20);
this.TB_OTName.TabIndex = 31;
this.TB_OTName.Text = "WWWWWWW";
this.TB_OTName.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.TB_RivalName.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.TB_RivalName.Location = new System.Drawing.Point(120, 93);
this.TB_RivalName.MaxLength = 7;
this.TB_RivalName.Name = "TB_RivalName";
this.TB_RivalName.Size = new System.Drawing.Size(63, 20);
this.TB_RivalName.TabIndex = 31;
this.TB_RivalName.Text = "WWWWWWW";
this.TB_RivalName.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// L_TrainerName
//
@ -1112,7 +1112,7 @@
private System.Windows.Forms.Label L_B5Score;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox TB_OTName;
private System.Windows.Forms.TextBox TB_RivalName;
private System.Windows.Forms.Label L_TrainerName;
private System.Windows.Forms.NumericUpDown NUD_BP;
private System.Windows.Forms.Label L_BP;

View file

@ -37,31 +37,22 @@ namespace PKHeX.WinForms
if (SAV.FRLG)
{
TB_OTName.Text = SAV.GetString(SAV.GetBlockOffset(4) + 0xBCC, 8);
TB_RivalName.Text = SAV.GetString(SAV.Large, 0x3A4C, 8);
// Trainer Card Species
ComboBox[] cba = { CB_TCM1, CB_TCM2, CB_TCM3, CB_TCM4, CB_TCM5, CB_TCM6 };
int[] HoennListMixed = {
277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,
304,305,309,310,392,393,394,311,312,306,307,364,365,366,301,302,303,370,371,372,335,336,350,320,315,316,
322,355,382,383,384,356,357,337,338,353,354,386,387,363,367,368,330,331,313,314,
339,340,321,351,352,308,332,333,334,344,345,358,359,380,379,348,349,323,324,
326,327,318,319,388,389,390,391,328,329,385,317,377,378,361,362,369,411,376,360,
346,347,341,342,343,373,374,375,381,325,395,396,397,398,399,400,
401,402,403,407,408,404,405,406,409,410
};
var speciesList = GameInfo.SpeciesDataSource.Where(v => v.Value <= SAV.MaxSpeciesID).Select(v =>
new ComboItem (v.Text, v.Value < 252 ? v.Value : HoennListMixed[v.Value - 252])
).ToList();
int ofsTCM = SAV.GetBlockOffset(2) + 0x106;
var legal = GameInfo.SpeciesDataSource.Where(v => v.Value <= SAV.MaxSpeciesID);
var speciesList = legal.Select(v => new ComboItem(v.Text, SpeciesConverter.GetG3Species(v.Value))).ToList();
for (int i = 0; i < cba.Length; i++)
{
cba[i].Items.Clear();
cba[i].InitializeBinding();
cba[i].DataSource = new BindingSource(speciesList, null);
cba[i].SelectedValue = (int)BitConverter.ToUInt16(SAV.Data, ofsTCM + (i << 1));
cba[i].SelectedValue = SAV.GetEventConst(0x43 + i);
}
}
else
{ TB_OTName.Visible = L_TrainerName.Visible = GB_TCM.Visible = false; }
{ TB_RivalName.Visible = L_TrainerName.Visible = GB_TCM.Visible = false; }
NUD_Coins.Value = Math.Min(NUD_Coins.Maximum, SAV.Coin);
}
@ -76,11 +67,10 @@ namespace PKHeX.WinForms
SaveBattleFrontier();
if (SAV.FRLG)
{
SAV.SetData(SAV.SetString(TB_OTName.Text, TB_OTName.MaxLength), SAV.GetBlockOffset(4) + 0xBCC);
SAV.SetData(SAV.Large, SAV.SetString(TB_RivalName.Text, TB_RivalName.MaxLength), 0x3A4C);
ComboBox[] cba = { CB_TCM1, CB_TCM2, CB_TCM3, CB_TCM4, CB_TCM5, CB_TCM6 };
int ofsTCM = SAV.GetBlockOffset(2) + 0x106;
for (int i = 0; i < cba.Length; i++)
BitConverter.GetBytes((ushort)(int)cba[i].SelectedValue).CopyTo(SAV.Data, ofsTCM + (i << 1));
SAV.SetEventConst(0x43 + i, (ushort)(int)cba[i].SelectedValue);
}
if (!SAV.RS)
@ -102,40 +92,40 @@ namespace PKHeX.WinForms
switch (SAV.Version)
{
case GameVersion.E:
JUMPS_IN_ROW = SAV.GetBlockOffset(0) + 0x1fc;
JUMPS_SCORE = SAV.GetBlockOffset(0) + 0x208;
JUMPS_5_IN_ROW = SAV.GetBlockOffset(0) + 0x200;
JUMPS_IN_ROW = 0x1fc;
JUMPS_SCORE = 0x208;
JUMPS_5_IN_ROW = 0x200;
BERRIES_IN_ROW = SAV.GetBlockOffset(0) + 0x210;
BERRIES_SCORE = SAV.GetBlockOffset(0) + 0x20c;
BERRIES_5_IN_ROW = SAV.GetBlockOffset(0) + 0x212;
BERRIES_IN_ROW = 0x210;
BERRIES_SCORE = 0x20c;
BERRIES_5_IN_ROW = 0x212;
break;
case GameVersion.FRLG:
JUMPS_IN_ROW = SAV.GetBlockOffset(0) + 0xB00;
JUMPS_SCORE = SAV.GetBlockOffset(0) + 0xB0C;
JUMPS_5_IN_ROW = SAV.GetBlockOffset(0) + 0xB04;
JUMPS_IN_ROW = 0xB00;
JUMPS_SCORE = 0xB0C;
JUMPS_5_IN_ROW = 0xB04;
BERRIES_IN_ROW = SAV.GetBlockOffset(0) + 0xB14;
BERRIES_SCORE = SAV.GetBlockOffset(0) + 0xB10;
BERRIES_5_IN_ROW = SAV.GetBlockOffset(0) + 0xB16;
BERRIES_IN_ROW = 0xB14;
BERRIES_SCORE = 0xB10;
BERRIES_5_IN_ROW = 0xB16;
break;
}
TB_J1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, JUMPS_IN_ROW)).ToString();
TB_J2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, JUMPS_SCORE)).ToString();
TB_J3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, JUMPS_5_IN_ROW)).ToString();
TB_B1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, BERRIES_IN_ROW)).ToString();
TB_B2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, BERRIES_SCORE)).ToString();
TB_B3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Data, BERRIES_5_IN_ROW)).ToString();
TB_J1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_IN_ROW)).ToString();
TB_J2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_SCORE)).ToString();
TB_J3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_5_IN_ROW)).ToString();
TB_B1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_IN_ROW)).ToString();
TB_B2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_SCORE)).ToString();
TB_B3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_5_IN_ROW)).ToString();
}
private void SaveJoyful()
{
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J1.Text)).CopyTo(SAV.Data, JUMPS_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J2.Text)).CopyTo(SAV.Data, JUMPS_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J3.Text)).CopyTo(SAV.Data, JUMPS_5_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B1.Text)).CopyTo(SAV.Data, BERRIES_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B2.Text)).CopyTo(SAV.Data, BERRIES_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B3.Text)).CopyTo(SAV.Data, BERRIES_5_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J1.Text)).CopyTo(SAV.Small, JUMPS_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J2.Text)).CopyTo(SAV.Small, JUMPS_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J3.Text)).CopyTo(SAV.Small, JUMPS_5_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B1.Text)).CopyTo(SAV.Small, BERRIES_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B2.Text)).CopyTo(SAV.Small, BERRIES_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B3.Text)).CopyTo(SAV.Small, BERRIES_5_IN_ROW);
}
#endregion
@ -234,9 +224,6 @@ namespace PKHeX.WinForms
#endregion
#region BattleFrontier
private int[] Symbols = null!;
private int ofsSymbols;
private Color[] SymbolColorA = null!;
private Button[] SymbolButtonA = null!;
private bool editingcont;
private bool editingval;
@ -322,15 +309,16 @@ namespace PKHeX.WinForms
SetValToSav = Array.IndexOf(BFV[BFF[Facility][0]], SetValToSav);
if (SetValToSav < 0)
return;
if (val > 9999) val = 9999;
BitConverter.GetBytes(val).CopyTo(SAV.Data, SAV.GetBlockOffset(0) + BFF[Facility][2 + SetValToSav] + (4 * BattleType) + (2 * RBi));
if (val > 9999)
val = 9999;
BitConverter.GetBytes(val).CopyTo(SAV.Small, BFF[Facility][2 + SetValToSav] + (4 * BattleType) + (2 * RBi));
return;
}
if (SetValToSav == -1)
{
int p = BFF[Facility][2 + BFV[BFF[Facility][0]].Length + BattleType] + RBi;
int offset = SAV.GetBlockOffset(0) + 0xCDC;
BitConverter.GetBytes((BitConverter.ToUInt32(SAV.Data, offset) & (uint)~(1 << p)) | (uint)((CHK_Continue.Checked ? 1 : 0) << p)).CopyTo(SAV.Data, offset);
const int offset = 0xCDC;
BitConverter.GetBytes((BitConverter.ToUInt32(SAV.Small, offset) & (uint)~(1 << p)) | (uint)((CHK_Continue.Checked ? 1 : 0) << p)).CopyTo(SAV.Small, offset);
return;
}
if (!SetSavToVal)
@ -339,11 +327,12 @@ namespace PKHeX.WinForms
editingval = true;
for (int i = 0; i < BFV[BFF[Facility][0]].Length; i++)
{
int vali = BitConverter.ToUInt16(SAV.Data, SAV.GetBlockOffset(0) + BFF[Facility][2 + i] + (4 * BattleType) + (2 * RBi));
if (vali > 9999) vali = 9999;
int vali = BitConverter.ToUInt16(SAV.Small, BFF[Facility][2 + i] + (4 * BattleType) + (2 * RBi));
if (vali > 9999)
vali = 9999;
StatNUDA[BFV[BFF[Facility][0]][i]].Value = vali;
}
CHK_Continue.Checked = (BitConverter.ToUInt32(SAV.Data, SAV.GetBlockOffset(0) + 0xCDC) & 1 << (BFF[Facility][2 + BFV[BFF[Facility][0]].Length + BattleType] + RBi)) != 0;
CHK_Continue.Checked = (BitConverter.ToUInt32(SAV.Small, 0xCDC) & 1 << (BFF[Facility][2 + BFV[BFF[Facility][0]].Length + BattleType] + RBi)) != 0;
editingval = false;
}
@ -395,14 +384,8 @@ namespace PKHeX.WinForms
StatNUDA = new[] { NUD_Stat0, NUD_Stat1, NUD_Stat2, NUD_Stat3 };
StatLabelA = new[] { L_Stat0, L_Stat1, L_Stat2, L_Stat3 };
StatRBA = new[] { RB_Stats3_01, RB_Stats3_02 };
SymbolColorA = new[] { Color.Transparent, Color.Silver, Color.Silver, Color.Gold };
SymbolButtonA = new[] { BTN_SymbolA, BTN_SymbolT, BTN_SymbolS, BTN_SymbolG, BTN_SymbolK, BTN_SymbolL, BTN_SymbolB };
ofsSymbols = SAV.GetBlockOffset(2) + 0x408;
int iSymbols = BitConverter.ToInt32(SAV.Data, ofsSymbols) >> 4 & 0x7FFF;
CHK_ActivatePass.Checked = (iSymbols >> 14 & 1) != 0;
Symbols = new int[7];
for (int i = 0; i < 7; i++)
Symbols[i] = iSymbols >> i * 2 & 3;
CHK_ActivatePass.Checked = SAV.GetEventFlag(0x860 + 0x72);
SetFrontierSymbols();
CB_Stats1.Items.Clear();
@ -416,33 +399,39 @@ namespace PKHeX.WinForms
private void SetFrontierSymbols()
{
for (int i = 0; i < SymbolButtonA.Length; i++)
SymbolButtonA[i].BackColor = SymbolColorA[Symbols[i]];
{
var flagIndex = 0x860 + 0x64 + (i * 2);
var silver = SAV.GetEventFlag(flagIndex);
var gold = SAV.GetEventFlag(flagIndex + 1);
var value = silver ? gold ? Color.Gold : Color.Silver : Color.Transparent;
SymbolButtonA[i].BackColor = value;
}
}
private void SaveBattleFrontier()
{
uint iSymbols = 0;
for (int i = 0; i < 7; i++)
iSymbols |= (uint)((Symbols[i] & 3) << i * 2);
if (CHK_ActivatePass.Checked)
iSymbols |= 1 << 14;
{
var color = SymbolButtonA[i].BackColor;
bool silver = color != Color.Transparent;
bool gold = color == Color.Gold;
uint val = (uint)((BitConverter.ToUInt32(SAV.Data, ofsSymbols) & ~(0x7FFF << 4)) | (iSymbols & 0x7FFF) << 4);
BitConverter.GetBytes(val).CopyTo(SAV.Data, ofsSymbols);
var flagIndex = 0x860 + 0x64 + (i * 2);
SAV.SetEventFlag(flagIndex, silver);
SAV.SetEventFlag(flagIndex, gold);
}
SAV.SetEventFlag(0x860 + 0x72, CHK_ActivatePass.Checked);
}
private void BTN_Symbol_Click(object sender, EventArgs e)
{
int index = Array.IndexOf(SymbolButtonA, sender);
if (index < 0)
var match = Array.Find(SymbolButtonA, z => z == sender);
if (match == null)
return;
// 0 (none) | 1 (silver) | 2 (silver) | 3 (gold)
// bit rotation 00 -> 01 -> 11 -> 00
if (Symbols[index] == 1) Symbols[index] = 3;
else Symbols[index] = (Symbols[index] + 1) & 3;
SetFrontierSymbols();
var color = match.BackColor;
color = color == Color.Transparent ? Color.Silver : color == Color.Silver ? Color.Gold : Color.Transparent;
match.BackColor = color;
}
#endregion

View file

@ -70,7 +70,7 @@ namespace PKHeX.WinForms
for (int i = 0; i < m.Length; i++)
{
var ofs = sav3.GetMailOffset(i);
var data = sav.GetData(ofs, Mail3.SIZE);
var data = sav3.Large.Slice(ofs, Mail3.SIZE);
m[i] = new Mail3(data, ofs, sav3.Japanese);
}
@ -174,16 +174,6 @@ namespace PKHeX.WinForms
LB_PCBOX.SelectedIndex = 0;
}
private readonly int[] HoennListMixed = {
277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,
304,305,309,310,392,393,394,311,312,306,307,364,365,366,301,302,303,370,371,372,335,336,350,320,315,316,
322,355,382,383,384,356,357,337,338,353,354,386,387,363,367,368,330,331,313,314,
339,340,321,351,352,308,332,333,334,344,345,358,359,380,379,348,349,323,324,
326,327,318,319,388,389,390,391,328,329,385,317,377,378,361,362,369,411,376,360,
346,347,341,342,343,373,374,375,381,325,395,396,397,398,399,400,
401,402,403,407,408,404,405,406,409,410
};
private void LoadList()
{
if (entry < PartyBoxCount) MakePartyList();
@ -308,7 +298,7 @@ namespace PKHeX.WinForms
}
if (Gen == 3)
{
mail.AppearPKM = v < 252 ? v : HoennListMixed[v - 252];
mail.AppearPKM = SpeciesConverter.GetG3Species(v);
return;
}
@ -544,15 +534,7 @@ namespace PKHeX.WinForms
}
if (Gen == 3)
{
if (v < 252)
{
AppearPKMs[0].SelectedValue = v;
}
else
{
v = Array.IndexOf(HoennListMixed, v);
AppearPKMs[0].SelectedValue = v >= 0 ? (252 + v) : 0;
}
AppearPKMs[0].SelectedValue = SpeciesConverter.GetG4Species(v);
editing = false;
return;
}