Add preliminary Box R/S support

Some hacks were needed to get PC / box bin loading to work.

Wallpapers 17-20 are unavailable (not on bulbapedia)

http://bulbapedia.bulbagarden.net/wiki/Talk:Pok%C3%A9mon_Storage_System#Pok.C3.A9mon_Box_Ruby_.26_Sapphire_wallpapers_are_missing.21
This commit is contained in:
Kaphotics 2016-09-18 22:47:31 -07:00
parent 96188a3e8d
commit 7ebd469cc7
6 changed files with 351 additions and 33 deletions

View File

@ -601,14 +601,24 @@ private void openFile(byte[] input, string path, string ext)
{
MysteryGift tg; PKM temp; string c;
byte[] footer = new byte[0];
#region DeSmuME .dsv detect
if (input.Length > SaveUtil.SIZE_G4RAW)
byte[] header = new byte[0];
#region Header/Footer detect
if (input.Length > SaveUtil.SIZE_G4RAW) // DeSmuME
{
bool dsv = SaveUtil.FOOTER_DSV.SequenceEqual(input.Skip(input.Length - SaveUtil.FOOTER_DSV.Length));
if (dsv)
{
footer = input.Skip(SaveUtil.SIZE_G4RAW).ToArray();
input = input.Take(SaveUtil.SIZE_G4RAW).ToArray();
input = input.Take(footer.Length).ToArray();
}
}
if (input.Length == SaveUtil.SIZE_G3BOXGCI)
{
bool gci = SaveUtil.HEADER_GCI.SequenceEqual(input.Take(SaveUtil.HEADER_GCI.Length));
if (gci)
{
header = input.Take(SaveUtil.SIZE_G3BOXGCI - SaveUtil.SIZE_G3BOX).ToArray();
input = input.Skip(header.Length).ToArray();
}
}
#endregion
@ -640,8 +650,8 @@ private void openFile(byte[] input, string path, string ext)
}
#endregion
#region SAV/PKM
else if (SaveUtil.getSAVGeneration(input) > -1) // Supports Gen4/5/6
{ openSAV(input, path); SAV.Footer = footer; }
else if (SaveUtil.getSAVGeneration(input) != -1)
{ openSAV(input, path); SAV.Footer = footer; SAV.Header = header; }
else if ((temp = PKMConverter.getPKMfromBytes(input)) != null)
{
PKM pk = PKMConverter.convertToFormat(temp, SAV.Generation, out c);
@ -864,11 +874,30 @@ private void loadSAV(SaveFile sav, string path)
}
Menu_LoadBoxes.Enabled = Menu_DumpBoxes.Enabled = Menu_Report.Enabled = Menu_Modify.Enabled = B_SaveBoxBin.Enabled = SAV.HasBox;
int BoxTab = tabBoxMulti.TabPages.IndexOf(Tab_Box);
int PartyTab = tabBoxMulti.TabPages.IndexOf(Tab_PartyBattle);
if (!SAV.HasParty && tabBoxMulti.TabPages.Contains(Tab_PartyBattle))
tabBoxMulti.TabPages.Remove(Tab_PartyBattle);
else if (SAV.HasParty && !tabBoxMulti.TabPages.Contains(Tab_PartyBattle))
{
int index = BoxTab;
if (index < 0)
index = -1;
tabBoxMulti.TabPages.Insert(index + 1, Tab_PartyBattle);
WindowTranslationRequired = true;
}
if (!SAV.HasDaycare && tabBoxMulti.TabPages.Contains(Tab_Other))
tabBoxMulti.TabPages.Remove(Tab_Other);
else if (SAV.HasDaycare && !tabBoxMulti.TabPages.Contains(Tab_Other))
{
tabBoxMulti.TabPages.Insert(tabBoxMulti.TabPages.IndexOf(Tab_PartyBattle) + 1, Tab_Other);
int index = PartyTab;
if (index < 0)
index = BoxTab;
if (index < 0)
index = -1;
tabBoxMulti.TabPages.Insert(index + 1, Tab_Other);
WindowTranslationRequired = true;
}
@ -893,8 +922,12 @@ private void loadSAV(SaveFile sav, string path)
B_OpenEventFlags.Visible = SAV.HasEvents;
B_OpenLinkInfo.Visible = SAV.HasLink;
B_CGearSkin.Visible = SAV.Generation == 5;
B_OpenTrainerInfo.Visible = B_OpenItemPouch.Visible = SAV.HasParty; // Box RS
}
GB_SAVtools.Visible = FLP_SAVtools.Controls.Cast<Control>().Any(c => c.Visible);
// Generational Interface
byte[] extraBytes = new byte[1];
Tip1.RemoveAll(); Tip2.RemoveAll(); Tip3.RemoveAll(); // TSV/PSV

View File

@ -164,6 +164,7 @@
<Compile Include="Saves\Inventory.cs" />
<Compile Include="Saves\SAV2.cs" />
<Compile Include="Saves\SAV1.cs" />
<Compile Include="Saves\SAV3RSBox.cs" />
<Compile Include="Saves\SAV3.cs" />
<Compile Include="Saves\SAV4.cs" />
<Compile Include="Saves\SAV5.cs" />
@ -494,7 +495,7 @@
<None Include="Resources\text\gen3\text_ItemsG2_es.txt" />
<None Include="Resources\text\gen3\text_ItemsG3_es.txt" />
<None Include="Resources\text\gen3\text_rsefrlg_00000_es.txt" />
<None Include="Resources\text\gen3\text_gsc_00000_zh.txt" />
<None Include="Resources\text\gen3\text_gsc_00000_zh.txt" />
<None Include="Resources\text\gen3\text_ItemsG1_zh.txt" />
<None Include="Resources\text\gen3\text_ItemsG2_zh.txt" />
<None Include="Resources\text\gen3\text_ItemsG3_zh.txt" />

View File

@ -22,4 +22,60 @@ public BlockInfo(int offset, int length, int chkOffset, int chkMirror)
ChecksumMirror = chkMirror;
}
}
public class RSBOX_Block
{
private ushort CHK_0;
private ushort CHK_1;
public readonly uint BlockNumber;
public readonly uint SaveCount;
public readonly byte[] Data;
public readonly int Offset;
public RSBOX_Block(byte[] data, int offset)
{
Data = (byte[])data.Clone();
Offset = offset;
// Values stored in Big Endian format
CHK_0 = (ushort)((Data[0x0] << 8) | (Data[0x1] << 0));
CHK_1 = (ushort)((Data[0x2] << 8) | (Data[0x3] << 0));
BlockNumber = (uint)((Data[0x4] << 8) | (Data[0x5] << 8) | (Data[0x6] << 8) | (Data[0x7] << 0));
SaveCount = (uint)((Data[0x8] << 8) | (Data[0x9] << 8) | (Data[0xA] << 8) | (Data[0xB] << 0));
}
public bool ChecksumsValid
{
get
{
ushort[] chks = getCHK(Data);
return chks[0] == CHK_0 && chks[1] == CHK_1;
}
}
public void SetChecksums()
{
ushort[] chks = getCHK(Data);
CHK_0 = chks[0];
CHK_1 = chks[1];
Data[0] = (byte)(CHK_0 >> 8);
Data[1] = (byte)(CHK_0 & 0xFF);
Data[2] = (byte)(CHK_1 >> 8);
Data[3] = (byte)(CHK_1 & 0xFF);
}
private static ushort[] getCHK(byte[] data)
{
int chk = 0; // initial value
for (int j = 0x4; j < 0x1FFC; j += 2)
{
chk += data[j] << 8;
chk += data[j + 1];
}
ushort chk0 = (ushort)chk;
ushort chk1 = (ushort)(0xF004 - chk0);
return new[] { chk0, chk1 };
}
}
}

186
PKHeX/Saves/SAV3RSBox.cs Normal file
View File

@ -0,0 +1,186 @@
using System;
using System.Linq;
namespace PKHeX
{
public sealed class SAV3RSBox : SaveFile
{
public override string BAKName => $"{FileName} [{Version} #{SaveCount.ToString("0000")}].bak";
public override string Filter => "GameCube Save File|*.gci";
public override string Extension => ".gci";
public SAV3RSBox(byte[] data = null)
{
Data = data == null ? new byte[SaveUtil.SIZE_G3BOX] : (byte[])data.Clone();
BAK = (byte[])Data.Clone();
Exportable = !Data.SequenceEqual(new byte[Data.Length]);
if (SaveUtil.getIsG3BOXSAV(Data) != GameVersion.RSBOX)
return;
Blocks = new RSBOX_Block[2*BLOCK_COUNT];
for (int i = 0; i < Blocks.Length; i++)
{
int offset = BLOCK_SIZE + i* BLOCK_SIZE;
Blocks[i] = new RSBOX_Block(Data.Skip(offset).Take(BLOCK_SIZE).ToArray(), offset);
}
// Detect active save
int[] SaveCounts = Blocks.Select(block => (int)block.SaveCount).ToArray();
SaveCount = SaveCounts.Max();
int ActiveSAV = Array.IndexOf(SaveCounts, SaveCount) / BLOCK_COUNT;
Blocks = Blocks.Skip(ActiveSAV*BLOCK_COUNT).Take(BLOCK_COUNT).OrderBy(b => b.BlockNumber).ToArray();
// 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 block to the allocated location
foreach (RSBOX_Block b in Blocks)
Array.Copy(b.Data, 0xC, Data, Box + b.BlockNumber*(BLOCK_SIZE - 0x10), b.Data.Length - 0x10);
Personal = PersonalTable.RS;
HeldItems = Legal.HeldItems_RS;
if (!Exportable)
resetBoxes();
}
private readonly RSBOX_Block[] Blocks;
private readonly int SaveCount;
private const int BLOCK_COUNT = 23;
private const int BLOCK_SIZE = 0x2000;
private const int SIZE_RESERVED = BLOCK_COUNT * BLOCK_SIZE; // unpacked box data
public override byte[] Write(bool DSV)
{
// Copy Box data back to block
foreach (RSBOX_Block b in Blocks)
Array.Copy(Data, Box + b.BlockNumber * (BLOCK_SIZE - 0x10), b.Data, 0xC, b.Data.Length - 0x10);
setChecksums();
// Set Data Back
foreach (RSBOX_Block b in Blocks)
b.Data.CopyTo(Data, b.Offset);
return Data.Take(Data.Length - SIZE_RESERVED).ToArray();
}
// Configuration
public override SaveFile Clone() { return new SAV3(Write(DSV: false), Version); }
public override int SIZE_STORED => PKX.SIZE_3STORED + 4;
public override int SIZE_PARTY => PKX.SIZE_3PARTY; // unused
public override PKM BlankPKM => new PK3();
protected override Type PKMType => typeof(PK3);
public override int MaxMoveID => 354;
public override int MaxSpeciesID => 386;
public override int MaxAbilityID => 77;
public override int MaxItemID => 374;
public override int MaxBallID => 0xC;
public override int MaxGameID => 5;
public override int MaxEV => 252;
public override int Generation => 3;
protected override int GiftCountMax => 1;
public override int OTLength => 8;
public override int NickLength => 10;
public override int MaxMoney => 999999;
public override int BoxCount => 50;
public override bool HasParty => false;
// Checksums
protected override void setChecksums()
{
foreach (RSBOX_Block b in Blocks)
b.SetChecksums();
}
public override bool ChecksumsValid
{
get { return Blocks.All(t => t.ChecksumsValid); }
}
public override string ChecksumInfo
{
get
{
return string.Join(Environment.NewLine,
Blocks.Where(b => !b.ChecksumsValid).Select(b => $"Block {b.BlockNumber.ToString("00")} invalid"));
}
}
// Trainer Info
public override GameVersion Version { get { return GameVersion.RSBOX; } protected set { } }
// Storage
public override int getPartyOffset(int slot)
{
return -1;
}
public override int getBoxOffset(int box)
{
return Box + 8 + SIZE_STORED * box * 30;
}
public override int CurrentBox
{
get { return Data[Box + 4]*2; }
set { Data[Box + 4] = (byte)(value/2); }
}
public override int getBoxWallpaper(int box)
{
// Box Wallpaper is directly after the Box Names
int offset = Box + 0x1ED19 + box/2;
return Data[offset];
}
public override string getBoxName(int box)
{
// Tweaked for the 1-30/31-60 box showing
string lo = (30*(box%2) + 1).ToString("00");
string hi = (30*(box%2 + 1)).ToString("00");
string boxName = $"[{lo}-{hi}] ";
box = box / 2;
int offset = Box + 0x1EC38 + 9 * box;
if (Data[offset] == 0 || Data[offset] == 0xFF)
boxName += $"BOX {box + 1}";
boxName += PKX.getG3Str(Data.Skip(offset).Take(9).ToArray(), Japanese);
return boxName;
}
public override void setBoxName(int box, string value)
{
int offset = Box + 0x1EC38 + 9 * box;
if (value.Length > 8)
value = value.Substring(0, 8); // Hard cap
if (value == "BOX " + (box + 1))
new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }.CopyTo(Data, offset);
}
public override PKM getPKM(byte[] data)
{
return new PK3(data.Take(PKX.SIZE_3STORED).ToArray());
}
public override byte[] decryptPKM(byte[] data)
{
return PKX.decryptArray3(data.Take(PKX.SIZE_3STORED).ToArray());
}
protected override void setDex(PKM pkm) { }
public override void setStoredSlot(PKM pkm, int offset, bool? trade = null, bool? dex = null)
{
if (pkm == null) return;
if (pkm.GetType() != PKMType)
throw new InvalidCastException($"PKM Format needs to be {PKMType} when setting to a Gen{Generation} Save File.");
if (trade ?? SetUpdatePKM)
setPKM(pkm);
if (dex ?? SetUpdateDex)
setDex(pkm);
byte[] data = pkm.EncryptedBoxData;
setData(data, offset);
BitConverter.GetBytes((ushort)pkm.TID).CopyTo(Data, offset + data.Length + 0);
BitConverter.GetBytes((ushort)pkm.SID).CopyTo(Data, offset + data.Length + 2);
Edited = true;
}
}
}

View File

@ -19,6 +19,7 @@ public abstract class SaveFile
public abstract SaveFile Clone();
public abstract string Filter { get; }
public byte[] Footer { protected get; set; } = new byte[0]; // .dsv
public byte[] Header { protected get; set; } = new byte[0]; // .gci
public bool Japanese { protected get; set; }
public string PlayTimeString => $"{PlayedHours}ː{PlayedMinutes.ToString("00")}ː{PlayedSeconds.ToString("00")}"; // not :
@ -41,6 +42,8 @@ public virtual byte[] Write(bool DSV)
setChecksums();
if (Footer.Length > 0 && DSV)
return Data.Concat(Footer).ToArray();
if (Header.Length > 0)
return Header.Concat(Data).ToArray();
return Data;
}
public virtual string MiscSaveChecks() { return ""; }
@ -259,7 +262,7 @@ public ushort[] EventConsts
}
// Inventory
public abstract InventoryPouch[] Inventory { get; set; }
public virtual InventoryPouch[] Inventory { get; set; }
protected int OFS_PouchHeldItem { get; set; } = int.MinValue;
protected int OFS_PouchKeyItem { get; set; } = int.MinValue;
protected int OFS_PouchMedicine { get; set; } = int.MinValue;
@ -298,20 +301,20 @@ public virtual MysteryGiftAlbum GiftAlbum
public virtual int SubRegion { get { return -1; } set { } }
// Trainer Info
public abstract int Gender { get; set; }
public virtual int Gender { get; set; }
public virtual int Language { get { return -1; } set { } }
public virtual int Game { get { return -1; } set { } }
public abstract ushort TID { get; set; }
public abstract ushort SID { get; set; }
public abstract string OT { get; set; }
public abstract int PlayedHours { get; set; }
public abstract int PlayedMinutes { get; set; }
public abstract int PlayedSeconds { get; set; }
public virtual ushort TID { get; set; }
public virtual ushort SID { get; set; }
public virtual string OT { get; set; }
public virtual int PlayedHours { get; set; }
public virtual int PlayedMinutes { get; set; }
public virtual int PlayedSeconds { get; set; }
public virtual int SecondsToStart { get; set; }
public virtual int SecondsToFame { get; set; }
public abstract uint Money { get; set; }
public virtual uint Money { get; set; }
public abstract int BoxCount { get; }
public abstract int PartyCount { get; protected set; }
public virtual int PartyCount { get; protected set; }
public abstract int CurrentBox { get; set; }
public abstract string Extension { get; }
@ -325,19 +328,20 @@ public virtual MysteryGiftAlbum GiftAlbum
// Daycare
public int DaycareIndex = 0;
public abstract int getDaycareSlotOffset(int loc, int slot);
public abstract uint? getDaycareEXP(int loc, int slot);
public virtual int getDaycareSlotOffset(int loc, int slot) { return -1; }
public virtual uint? getDaycareEXP(int loc, int slot) { return null; }
public virtual ulong? getDaycareRNGSeed(int loc) { return null; }
public virtual bool? getDaycareHasEgg(int loc) { return null; }
public abstract bool? getDaycareOccupied(int loc, int slot);
public abstract void setDaycareEXP(int loc, int slot, uint EXP);
public virtual bool? getDaycareOccupied(int loc, int slot) { return null; }
public virtual void setDaycareEXP(int loc, int slot, uint EXP) { }
public virtual void setDaycareRNGSeed(int loc, ulong seed) { }
public virtual void setDaycareHasEgg(int loc, bool hasEgg) { }
public abstract void setDaycareOccupied(int loc, int slot, bool occupied);
public virtual void setDaycareOccupied(int loc, int slot, bool occupied) { }
// Storage
public virtual int BoxSlotCount => 30;
public PKM getPartySlot(int offset)
{
return getPKM(decryptPKM(getData(offset, SIZE_PARTY)));
@ -457,12 +461,14 @@ public bool setPCBin(byte[] data)
if (data.Length != getPCBin().Length)
return false;
int len = BlankPKM.EncryptedBoxData.Length;
// split up data to individual pkm
byte[][] pkdata = new byte[data.Length/SIZE_STORED][];
for (int i = 0; i < data.Length; i += SIZE_STORED)
byte[][] pkdata = new byte[data.Length/len][];
for (int i = 0; i < data.Length; i += len)
{
pkdata[i/SIZE_STORED] = new byte[SIZE_STORED];
Array.Copy(data, i, pkdata[i/SIZE_STORED], 0, SIZE_STORED);
pkdata[i/len] = new byte[len];
Array.Copy(data, i, pkdata[i/len], 0, len);
}
PKM[] pkms = BoxData;
@ -476,11 +482,14 @@ public bool setBoxBin(byte[] data, int box)
if (data.Length != getBoxBin(box).Length)
return false;
byte[][] pkdata = new byte[data.Length / SIZE_STORED][];
for (int i = 0; i < data.Length; i += SIZE_STORED)
int len = BlankPKM.EncryptedBoxData.Length;
// split up data to individual pkm
byte[][] pkdata = new byte[data.Length/len][];
for (int i = 0; i < data.Length; i += len)
{
pkdata[i/SIZE_STORED] = new byte[SIZE_STORED];
Array.Copy(data, i, pkdata[i/SIZE_STORED], 0, SIZE_STORED);
pkdata[i/len] = new byte[len];
Array.Copy(data, i, pkdata[i/len], 0, len);
}
PKM[] pkms = BoxData;

View File

@ -9,6 +9,7 @@ namespace PKHeX
public enum GameVersion
{
/* I don't want to assign Gen I/II... */
RSBOX = -5,
GS = -4,
C = -3,
Invalid = -2,
@ -46,6 +47,8 @@ public static class SaveUtil
internal const int SIZE_G5BW = 0x24000;
internal const int SIZE_G5B2W2 = 0x26000;
internal const int SIZE_G4RAW = 0x80000;
internal const int SIZE_G3BOX = 0x76000;
internal const int SIZE_G3BOXGCI = 0x76040; // +64 if has GCI data
internal const int SIZE_G3RAW = 0x20000;
internal const int SIZE_G3RAWHALF = 0x10000;
internal const int SIZE_G2RAW_U = 0x8000;
@ -57,6 +60,7 @@ public static class SaveUtil
internal const int SIZE_G1BAT = 0x802C;
internal static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|");
internal static readonly byte[] HEADER_GCI = {0x47, 0x50, 0x58}; // GPX*
/// <summary>Determines the generation of the given save data.</summary>
/// <param name="data">Save data of which to determine the generation</param>
@ -69,6 +73,8 @@ public static int getSAVGeneration(byte[] data)
return 2;
if (getIsG3SAV(data) != GameVersion.Invalid)
return 3;
if (getIsG3BOXSAV(data) != GameVersion.Invalid)
return (int)GameVersion.RSBOX;
if (getIsG4SAV(data) != GameVersion.Invalid)
return 4;
if (getIsG5SAV(data) != GameVersion.Invalid)
@ -183,7 +189,7 @@ public static GameVersion getIsG2SAVJ(byte[] data)
return GameVersion.C;
return GameVersion.Invalid;
}
/// <summary>Determines the type of 3th gen save</summary>
/// <summary>Determines the type of 3rd gen save</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
public static GameVersion getIsG3SAV(byte[] data)
@ -213,6 +219,31 @@ public static GameVersion getIsG3SAV(byte[] data)
default: return GameVersion.E;
}
}
/// <summary>Determines the type of 3rd gen Box RS</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
public static GameVersion getIsG3BOXSAV(byte[] data)
{
if (!new[] { SIZE_G3BOX, SIZE_G3BOXGCI }.Contains(data.Length))
return GameVersion.Invalid;
byte[] sav = data.Skip(data.Length - SIZE_G3BOX).Take(SIZE_G3BOX).ToArray();
// Verify first checksum
uint chk = 0; // initial value
for (int j = 0x4; j < 0x1FFC; j += 2)
{
chk += (ushort)(sav[0x2000 + j] << 8);
chk += sav[0x2000 + j + 1];
}
ushort chkA = (ushort)chk;
ushort chkB = (ushort)(0xF004 - chkA);
ushort CHK_A = (ushort)((sav[0x2000] << 8) | sav[0x2001]);
ushort CHK_B = (ushort)((sav[0x2002] << 8) | sav[0x2003]);
return CHK_A == chkA && CHK_B == chkB ? GameVersion.RSBOX : GameVersion.Invalid;
}
/// <summary>Determines the type of 4th gen save</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
@ -361,6 +392,8 @@ public static SaveFile getVariantSAV(byte[] data)
return new SAV2(data);
case 3:
return new SAV3(data);
case (int)GameVersion.RSBOX:
return new SAV3RSBox(data);
case 4:
return new SAV4(data);
case 5: