mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-24 12:17:07 -05:00
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:
parent
96188a3e8d
commit
7ebd469cc7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
186
PKHeX/Saves/SAV3RSBox.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user