mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-14 16:27:21 +00:00
Merge branch 'master' into kwsch
This commit is contained in:
commit
8655976d0f
16 changed files with 604 additions and 40 deletions
12
PKHeX.WinForms/MainWindow/Main.Designer.cs
generated
12
PKHeX.WinForms/MainWindow/Main.Designer.cs
generated
|
@ -1597,16 +1597,8 @@
|
||||||
//
|
//
|
||||||
this.NUD_Purification.Location = new System.Drawing.Point(110, 1);
|
this.NUD_Purification.Location = new System.Drawing.Point(110, 1);
|
||||||
this.NUD_Purification.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
|
this.NUD_Purification.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
|
||||||
this.NUD_Purification.Maximum = new decimal(new int[] {
|
this.NUD_Purification.Maximum = new decimal(int.MaxValue);
|
||||||
0,
|
this.NUD_Purification.Minimum = new decimal(0);
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0});
|
|
||||||
this.NUD_Purification.Minimum = new decimal(new int[] {
|
|
||||||
100,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
-2147483648});
|
|
||||||
this.NUD_Purification.Name = "NUD_Purification";
|
this.NUD_Purification.Name = "NUD_Purification";
|
||||||
this.NUD_Purification.Size = new System.Drawing.Size(51, 20);
|
this.NUD_Purification.Size = new System.Drawing.Size(51, 20);
|
||||||
this.NUD_Purification.TabIndex = 103;
|
this.NUD_Purification.TabIndex = 103;
|
||||||
|
|
|
@ -725,7 +725,7 @@ namespace PKHeX.WinForms
|
||||||
|
|
||||||
string ext = Path.GetExtension(path);
|
string ext = Path.GetExtension(path);
|
||||||
FileInfo fi = new FileInfo(path);
|
FileInfo fi = new FileInfo(path);
|
||||||
if (fi.Length > 0x10009C && fi.Length != 0x380000)
|
if (fi.Length > 0x10009C && fi.Length != 0x380000 && ! SAV3GCMemoryCard.IsMemoryCardSize(fi.Length))
|
||||||
WinFormsUtil.Error("Input file is too large." + Environment.NewLine + $"Size: {fi.Length} bytes", path);
|
WinFormsUtil.Error("Input file is too large." + Environment.NewLine + $"Size: {fi.Length} bytes", path);
|
||||||
else if (fi.Length < 32)
|
else if (fi.Length < 32)
|
||||||
WinFormsUtil.Error("Input file is too small." + Environment.NewLine + $"Size: {fi.Length} bytes", path);
|
WinFormsUtil.Error("Input file is too small." + Environment.NewLine + $"Size: {fi.Length} bytes", path);
|
||||||
|
@ -782,6 +782,20 @@ namespace PKHeX.WinForms
|
||||||
{
|
{
|
||||||
openSAV(sav, path);
|
openSAV(sav, path);
|
||||||
}
|
}
|
||||||
|
else if ((SAV3GCMemoryCard.IsMemoryCardSize(input)))
|
||||||
|
{
|
||||||
|
SAV3GCMemoryCard MC = CheckGCMemoryCard(input, path);
|
||||||
|
if (MC == null)
|
||||||
|
return;
|
||||||
|
if ((sav = SaveUtil.getVariantSAV(MC)) != null)
|
||||||
|
{
|
||||||
|
openSAV(sav, path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
WinFormsUtil.Error("Attempted to load an unsupported file type/size.",
|
||||||
|
$"File Loaded:{Environment.NewLine}{path}",
|
||||||
|
$"File Size:{Environment.NewLine}{input.Length} bytes (0x{input.Length:X4})");
|
||||||
|
}
|
||||||
else if ((temp = PKMConverter.getPKMfromBytes(input, prefer: ext.Length > 0 ? (ext.Last() - 0x30)&7 : SAV.Generation)) != null)
|
else if ((temp = PKMConverter.getPKMfromBytes(input, prefer: ext.Length > 0 ? (ext.Last() - 0x30)&7 : SAV.Generation)) != null)
|
||||||
{
|
{
|
||||||
PKM pk = PKMConverter.convertToFormat(temp, SAV.PKMType, out c);
|
PKM pk = PKMConverter.convertToFormat(temp, SAV.PKMType, out c);
|
||||||
|
@ -902,6 +916,83 @@ namespace PKHeX.WinForms
|
||||||
openSAV(s, s.FileName);
|
openSAV(s, s.FileName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
private GameVersion SelectMemoryCardSaveGame(SAV3GCMemoryCard MC)
|
||||||
|
{
|
||||||
|
//SaveGameCount
|
||||||
|
if (MC.SaveGameCount == 1)
|
||||||
|
return MC.SelectedGameVersion;
|
||||||
|
|
||||||
|
GameVersion[] Options = new GameVersion[2];
|
||||||
|
string[] Names = new string[2];
|
||||||
|
if (MC.SaveGameCount == 3)
|
||||||
|
{
|
||||||
|
var drGC3Select = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, $"Pokémon Colloseum, Pokémon XD and Pokémon RS Box Save Files detected. Select game to edit.",
|
||||||
|
"Yes: Pokémon Colloseum/XD" + Environment.NewLine + "No: Pokémon RS Box");
|
||||||
|
if (drGC3Select == DialogResult.Cancel)
|
||||||
|
return GameVersion.CXD;
|
||||||
|
if(drGC3Select == DialogResult.No)
|
||||||
|
return GameVersion.RSBOX;
|
||||||
|
Options = new[] { GameVersion.COLO, GameVersion.XD };
|
||||||
|
Names = new[] { "Pokémon Colloseum", "Pokémon XD" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 games only
|
||||||
|
if (MC.SaveGameCount == 2 && MC.HaveRSBoxSaveGame)
|
||||||
|
{
|
||||||
|
if( MC.HaveColloseumSaveGame)
|
||||||
|
{
|
||||||
|
Options[0] = GameVersion.COLO;
|
||||||
|
Names[0] = "Pokémon Colloseum";
|
||||||
|
}
|
||||||
|
else //XD
|
||||||
|
{
|
||||||
|
Options[0] = GameVersion.XD;
|
||||||
|
Names[0] = "Pokémon XD";
|
||||||
|
}
|
||||||
|
Options[1] = GameVersion.RSBOX;
|
||||||
|
Names[1] = "Pokémon RS Box";
|
||||||
|
}
|
||||||
|
else // RXBox discarted
|
||||||
|
{
|
||||||
|
Options = new[] { GameVersion.COLO, GameVersion.XD };
|
||||||
|
Names = new[] { "Pokémon Colloseum", "Pokémon XD" };
|
||||||
|
}
|
||||||
|
|
||||||
|
var drGCSelect = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, $"{Names[0]} and {Names[1]} Save Files detected. Select game to edit.",
|
||||||
|
$"Yes: {Names[0]}" + Environment.NewLine + $"No: {Names[1]}");
|
||||||
|
if (drGCSelect == DialogResult.Cancel)
|
||||||
|
return GameVersion.CXD;
|
||||||
|
if (drGCSelect == DialogResult.Yes)
|
||||||
|
return Options[0];
|
||||||
|
return Options[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private SAV3GCMemoryCard CheckGCMemoryCard(byte[] Data, string path)
|
||||||
|
{
|
||||||
|
SAV3GCMemoryCard MC = new SAV3GCMemoryCard();
|
||||||
|
GCMemoryCardState MCState = MC.LoadMemoryCardFile(Data);
|
||||||
|
switch(MCState)
|
||||||
|
{
|
||||||
|
case GCMemoryCardState.Invalid: { WinFormsUtil.Error("Invalid or corrupted GC Memory Card. Aborting.", path); return null; }
|
||||||
|
case GCMemoryCardState.NoPkmSaveGame: { WinFormsUtil.Error("GC Memory Card without any Pokémon save file. Aborting.", path); return null; }
|
||||||
|
case GCMemoryCardState.ColloseumSaveGameDuplicated: { WinFormsUtil.Error("GC Memory Card with multiple Pokémon Colloseum save files. Aborting.", path); return null; }
|
||||||
|
case GCMemoryCardState.XDSaveGameDuplicated: { WinFormsUtil.Error("GC Memory Card with multiple Pokémon XD save files. Aborting.", path); return null; }
|
||||||
|
case GCMemoryCardState.RSBoxSaveGameDuplicated: { WinFormsUtil.Error("GC Memory Card with multiple Pokémon RS Box save files. Aborting.", path); return null; }
|
||||||
|
case GCMemoryCardState.MultipleSaveGame:
|
||||||
|
{
|
||||||
|
GameVersion Game = SelectMemoryCardSaveGame(MC);
|
||||||
|
if (Game == GameVersion.CXD) //Cancel
|
||||||
|
return null;
|
||||||
|
MC.SelectSaveGame(Game);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GCMemoryCardState.ColloseumSaveGame: { MC.SelectSaveGame(GameVersion.COLO); break; }
|
||||||
|
case GCMemoryCardState.XDSaveGame: { MC.SelectSaveGame(GameVersion.XD); break; }
|
||||||
|
case GCMemoryCardState.RSBoxSaveGame: { MC.SelectSaveGame(GameVersion.RSBOX); break; }
|
||||||
|
}
|
||||||
|
return MC;
|
||||||
|
}
|
||||||
|
|
||||||
private void openSAV(SaveFile sav, string path)
|
private void openSAV(SaveFile sav, string path)
|
||||||
{
|
{
|
||||||
if (sav == null || sav.Version == GameVersion.Invalid)
|
if (sav == null || sav.Version == GameVersion.Invalid)
|
||||||
|
@ -2884,7 +2975,7 @@ namespace PKHeX.WinForms
|
||||||
if (!fieldsLoaded)
|
if (!fieldsLoaded)
|
||||||
return;
|
return;
|
||||||
fieldsLoaded = false;
|
fieldsLoaded = false;
|
||||||
CHK_Shadow.Checked = NUD_Purification.Value == 0;
|
CHK_Shadow.Checked = NUD_Purification.Value > 0;
|
||||||
fieldsLoaded = true;
|
fieldsLoaded = true;
|
||||||
}
|
}
|
||||||
private void updateShadowCHK(object sender, EventArgs e)
|
private void updateShadowCHK(object sender, EventArgs e)
|
||||||
|
@ -3319,9 +3410,10 @@ namespace PKHeX.WinForms
|
||||||
SAV.CurrentBox = CB_BoxSelect.SelectedIndex;
|
SAV.CurrentBox = CB_BoxSelect.SelectedIndex;
|
||||||
|
|
||||||
bool dsv = Path.GetExtension(main.FileName)?.ToLower() == ".dsv";
|
bool dsv = Path.GetExtension(main.FileName)?.ToLower() == ".dsv";
|
||||||
|
bool gci = Path.GetExtension(main.FileName)?.ToLower() == ".gci";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.WriteAllBytes(main.FileName, SAV.Write(dsv));
|
File.WriteAllBytes(main.FileName, SAV.Write(dsv, gci));
|
||||||
SAV.Edited = false;
|
SAV.Edited = false;
|
||||||
WinFormsUtil.Alert("SAV exported to:", main.FileName);
|
WinFormsUtil.Alert("SAV exported to:", main.FileName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,13 +102,11 @@ namespace PKHeX.WinForms
|
||||||
if (ck3.ShadowID > 0)
|
if (ck3.ShadowID > 0)
|
||||||
{
|
{
|
||||||
int puri = ck3.Purification;
|
int puri = ck3.Purification;
|
||||||
if (puri > NUD_Purification.Maximum)
|
if (puri < NUD_Purification.Minimum)
|
||||||
puri = 0;
|
|
||||||
else if (puri < NUD_Purification.Minimum)
|
|
||||||
puri = (int)NUD_Purification.Minimum;
|
puri = (int)NUD_Purification.Minimum;
|
||||||
|
|
||||||
NUD_Purification.Value = puri;
|
NUD_Purification.Value = puri;
|
||||||
CHK_Shadow.Checked = puri < 0;
|
CHK_Shadow.Checked = puri > 0;
|
||||||
|
|
||||||
NUD_ShadowID.Value = Math.Max(ck3.ShadowID, 0);
|
NUD_ShadowID.Value = Math.Max(ck3.ShadowID, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,13 +102,11 @@ namespace PKHeX.WinForms
|
||||||
if (xk3.ShadowID > 0)
|
if (xk3.ShadowID > 0)
|
||||||
{
|
{
|
||||||
int puri = xk3.Purification;
|
int puri = xk3.Purification;
|
||||||
if (puri > NUD_Purification.Maximum)
|
if (puri < NUD_Purification.Minimum)
|
||||||
puri = 0;
|
|
||||||
else if (puri < NUD_Purification.Minimum)
|
|
||||||
puri = (int)NUD_Purification.Minimum;
|
puri = (int)NUD_Purification.Minimum;
|
||||||
|
|
||||||
NUD_Purification.Value = puri;
|
NUD_Purification.Value = puri;
|
||||||
CHK_Shadow.Checked = puri < 0;
|
CHK_Shadow.Checked = puri > 0;
|
||||||
|
|
||||||
NUD_ShadowID.Value = Math.Max(xk3.ShadowID, 0);
|
NUD_ShadowID.Value = Math.Max(xk3.ShadowID, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,11 @@
|
||||||
|
|
||||||
// Extra Game Groupings (Generation)
|
// Extra Game Groupings (Generation)
|
||||||
Gen1, Gen2, Gen3, Gen4, Gen5, Gen6, Gen7,
|
Gen1, Gen2, Gen3, Gen4, Gen5, Gen6, Gen7,
|
||||||
GBCartEraOnly, // Stadium
|
GBCartEraOnly,
|
||||||
|
Stadium,
|
||||||
|
Stadium2,
|
||||||
|
EventsGen1,
|
||||||
|
EventsGen2,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Extension
|
public static class Extension
|
||||||
|
@ -54,24 +58,34 @@
|
||||||
case GameVersion.RBY:
|
case GameVersion.RBY:
|
||||||
return (g2 == GameVersion.RD || g2 == GameVersion.BU || g2 == GameVersion.YW || g2 == GameVersion.GN);
|
return (g2 == GameVersion.RD || g2 == GameVersion.BU || g2 == GameVersion.YW || g2 == GameVersion.GN);
|
||||||
case GameVersion.Gen1:
|
case GameVersion.Gen1:
|
||||||
return ( GameVersion.RBY.Contains(g2) || g2 == GameVersion.GBCartEraOnly);
|
return ( GameVersion.RBY.Contains(g2) || g2 == GameVersion.Stadium|| g2 == GameVersion.EventsGen1);
|
||||||
|
case GameVersion.Stadium:
|
||||||
|
case GameVersion.EventsGen1:
|
||||||
|
return GameVersion.RBY.Contains(g2);
|
||||||
|
|
||||||
case GameVersion.GS: return (g2 == GameVersion.GD || g2 == GameVersion.SV);
|
case GameVersion.GS: return (g2 == GameVersion.GD || g2 == GameVersion.SV);
|
||||||
case GameVersion.GSC:
|
case GameVersion.GSC:
|
||||||
return (GameVersion.GS.Contains(g2) || g2 == GameVersion.C);
|
return (GameVersion.GS.Contains(g2) || g2 == GameVersion.C);
|
||||||
case GameVersion.Gen2:
|
case GameVersion.Gen2:
|
||||||
return (GameVersion.GSC.Contains(g2) || g2 == GameVersion.GBCartEraOnly);
|
return (GameVersion.GSC.Contains(g2) || g2 == GameVersion.Stadium2 || g2 == GameVersion.EventsGen2);
|
||||||
|
case GameVersion.Stadium2:
|
||||||
|
case GameVersion.EventsGen2:
|
||||||
|
return GameVersion.GSC.Contains(g2);
|
||||||
|
case GameVersion.GBCartEraOnly:
|
||||||
|
return g2 == GameVersion.Stadium || g2 == GameVersion.Stadium2 || g2 == GameVersion.EventsGen1 || g2 == GameVersion.EventsGen2;
|
||||||
|
|
||||||
case GameVersion.RS: return (g2 == GameVersion.R || g2 == GameVersion.S);
|
case GameVersion.RS: return (g2 == GameVersion.R || g2 == GameVersion.S);
|
||||||
case GameVersion.FRLG: return (g2 == GameVersion.FR || g2 == GameVersion.LG);
|
case GameVersion.FRLG: return (g2 == GameVersion.FR || g2 == GameVersion.LG);
|
||||||
case GameVersion.CXD: return (g2 == GameVersion.COLO || g2 == GameVersion.XD);
|
case GameVersion.CXD: return (g2 == GameVersion.COLO || g2 == GameVersion.XD);
|
||||||
|
case GameVersion.RSBOX: return (GameVersion.RS.Contains(g2) || g2 == GameVersion.E || GameVersion.FRLG.Contains(g2));
|
||||||
case GameVersion.Gen3:
|
case GameVersion.Gen3:
|
||||||
return (GameVersion.RS.Contains(g2) || g2 == GameVersion.E || GameVersion.FRLG.Contains(g2) || GameVersion.CXD.Contains(g2) || g2 == GameVersion.RSBOX);
|
return (GameVersion.RS.Contains(g2) || g2 == GameVersion.E || GameVersion.FRLG.Contains(g2) || GameVersion.CXD.Contains(g2) || g2 == GameVersion.RSBOX);
|
||||||
|
|
||||||
case GameVersion.DP: return (g2 == GameVersion.D || g2 == GameVersion.P);
|
case GameVersion.DP: return (g2 == GameVersion.D || g2 == GameVersion.P);
|
||||||
case GameVersion.HGSS: return (g2 == GameVersion.HG || g2 == GameVersion.SS);
|
case GameVersion.HGSS: return (g2 == GameVersion.HG || g2 == GameVersion.SS);
|
||||||
|
case GameVersion.BATREV: return (GameVersion.DP.Contains(g2) || g2 == GameVersion.Pt || GameVersion.HGSS.Contains(g2));
|
||||||
case GameVersion.Gen4:
|
case GameVersion.Gen4:
|
||||||
return (GameVersion.DP.Contains(g2) || g2 == GameVersion.Pt || GameVersion.HGSS.Contains(g2));
|
return (GameVersion.DP.Contains(g2) || g2 == GameVersion.Pt || GameVersion.HGSS.Contains(g2) || g2 == GameVersion.BATREV);
|
||||||
|
|
||||||
case GameVersion.BW: return (g2 == GameVersion.B || g2 == GameVersion.W);
|
case GameVersion.BW: return (g2 == GameVersion.B || g2 == GameVersion.W);
|
||||||
case GameVersion.B2W2: return (g2 == GameVersion.B2 || g2 == GameVersion.W2);
|
case GameVersion.B2W2: return (g2 == GameVersion.B2 || g2 == GameVersion.W2);
|
||||||
|
|
|
@ -947,7 +947,7 @@ namespace PKHeX.Core
|
||||||
return new CheckResult(Severity.Invalid, V80, CheckIdentifier.Encounter);
|
return new CheckResult(Severity.Invalid, V80, CheckIdentifier.Encounter);
|
||||||
|
|
||||||
var s = EncounterMatch as EncounterStatic;
|
var s = EncounterMatch as EncounterStatic;
|
||||||
if (s != null && s.Version == GameVersion.GBCartEraOnly)
|
if (s != null && GameVersion.GBCartEraOnly.Contains(s.Version))
|
||||||
{
|
{
|
||||||
bool exceptions = false;
|
bool exceptions = false;
|
||||||
exceptions |= baseSpecies == 151 && pkm.TID == 22796;
|
exceptions |= baseSpecies == 151 && pkm.TID == 22796;
|
||||||
|
|
|
@ -942,7 +942,7 @@ namespace PKHeX.Core
|
||||||
// if (e.Gift && pkm.Ball != 4) // PokéBall
|
// if (e.Gift && pkm.Ball != 4) // PokéBall
|
||||||
// continue;
|
// continue;
|
||||||
|
|
||||||
if (!AllowGBCartEra && e.Version == GameVersion.GBCartEraOnly)
|
if (!AllowGBCartEra && GameVersion.GBCartEraOnly.Contains(e.Version))
|
||||||
continue; // disallow gb cart era encounters (as they aren't obtainable by Main/VC series)
|
continue; // disallow gb cart era encounters (as they aren't obtainable by Main/VC series)
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
|
|
|
@ -102,8 +102,8 @@ namespace PKHeX.Core
|
||||||
// new EncounterStatic { Species = 004, Level = 10, Version = GameVersion.YW }, // Charmander (Route 24)
|
// new EncounterStatic { Species = 004, Level = 10, Version = GameVersion.YW }, // Charmander (Route 24)
|
||||||
// new EncounterStatic { Species = 007, Level = 10, Version = GameVersion.YW }, // Squirtle (Vermillion City)
|
// new EncounterStatic { Species = 007, Level = 10, Version = GameVersion.YW }, // Squirtle (Vermillion City)
|
||||||
|
|
||||||
new EncounterStatic { Species = 054, Level = 15, Moves = new [] { 133, 10 }, Version = GameVersion.GBCartEraOnly }, // Stadium Psyduck (Amnesia)
|
new EncounterStatic { Species = 054, Level = 15, Moves = new [] { 133, 10 }, Version = GameVersion.Stadium }, // Stadium Psyduck (Amnesia)
|
||||||
new EncounterStatic { Species = 151, Level = 5, IVs = new [] {15,15,15,15,15,15}, Version = GameVersion.GBCartEraOnly }, // Event Mew
|
new EncounterStatic { Species = 151, Level = 5, IVs = new [] {15,15,15,15,15,15}, Version = GameVersion.EventsGen1 }, // Event Mew
|
||||||
};
|
};
|
||||||
internal static readonly EncounterTrade[] TradeGift_RBY =
|
internal static readonly EncounterTrade[] TradeGift_RBY =
|
||||||
{
|
{
|
||||||
|
|
|
@ -73,6 +73,9 @@ namespace PKHeX.Core
|
||||||
new EncounterStatic { Species = 100, Level = 23, Location = 036, Version = GameVersion.GSC }, // Voltorb @ Rocket Hideout (Mahogany Town)
|
new EncounterStatic { Species = 100, Level = 23, Location = 036, Version = GameVersion.GSC }, // Voltorb @ Rocket Hideout (Mahogany Town)
|
||||||
new EncounterStatic { Species = 101, Level = 23, Location = 036, Version = GameVersion.GSC }, // Electrode @ Rocket Hideout (Mahogany Town)
|
new EncounterStatic { Species = 101, Level = 23, Location = 036, Version = GameVersion.GSC }, // Electrode @ Rocket Hideout (Mahogany Town)
|
||||||
new EncounterStatic { Species = 143, Level = 50, Location = 061, Version = GameVersion.GSC }, // Snorlax @ Vermillion City
|
new EncounterStatic { Species = 143, Level = 50, Location = 061, Version = GameVersion.GSC }, // Snorlax @ Vermillion City
|
||||||
|
|
||||||
|
new EncounterStatic { Species = 083, Level = 05, Moves = new [] { 226, 14, 97, 37 }, Version = GameVersion.Stadium2 }, // Stadium 2 Baton Pass Farfetch'd
|
||||||
|
new EncounterStatic { Species = 207, Level = 05, Moves = new [] { 89, 68, 17 }, Version = GameVersion.Stadium2 }, // Stadium 2 Earthquake Gligar
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static readonly EncounterStatic[] Encounter_GS_Exclusive =
|
internal static readonly EncounterStatic[] Encounter_GS_Exclusive =
|
||||||
|
@ -102,7 +105,7 @@ namespace PKHeX.Core
|
||||||
|
|
||||||
new EncounterStatic { Species = 249, Level = 60, Location = 031, Version = GameVersion.C }, // Lugia @ Whirl Islands
|
new EncounterStatic { Species = 249, Level = 60, Location = 031, Version = GameVersion.C }, // Lugia @ Whirl Islands
|
||||||
new EncounterStatic { Species = 250, Level = 60, Location = 023, Version = GameVersion.C }, // Ho-Oh @ Tin Tower
|
new EncounterStatic { Species = 250, Level = 60, Location = 023, Version = GameVersion.C }, // Ho-Oh @ Tin Tower
|
||||||
new EncounterStatic { Species = 251, Level = 30, Location = 014, Version = GameVersion.GBCartEraOnly }, // Celebi @ Ilex Forest
|
new EncounterStatic { Species = 251, Level = 30, Location = 014, Version = GameVersion.EventsGen2 }, // Celebi @ Ilex Forest
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static readonly EncounterStatic[] Encounter_GS = Encounter_GSC_Common.Concat(Encounter_GS_Exclusive).ToArray();
|
internal static readonly EncounterStatic[] Encounter_GS = Encounter_GSC_Common.Concat(Encounter_GS_Exclusive).ToArray();
|
||||||
|
|
|
@ -211,6 +211,7 @@
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Saves\SAV3GCMemoryCard.cs" />
|
||||||
<Compile Include="Saves\SAV7.cs" />
|
<Compile Include="Saves\SAV7.cs" />
|
||||||
<Compile Include="Saves\Substructures\BattleVideo.cs" />
|
<Compile Include="Saves\Substructures\BattleVideo.cs" />
|
||||||
<Compile Include="Saves\Substructures\BlockInfo.cs" />
|
<Compile Include="Saves\Substructures\BlockInfo.cs" />
|
||||||
|
|
|
@ -7,8 +7,17 @@ namespace PKHeX.Core
|
||||||
public sealed class SAV3Colosseum : SaveFile, IDisposable
|
public sealed class SAV3Colosseum : SaveFile, IDisposable
|
||||||
{
|
{
|
||||||
public override string BAKName => $"{FileName} [{OT} ({Version}) - {PlayTimeString}].bak";
|
public override string BAKName => $"{FileName} [{OT} ({Version}) - {PlayTimeString}].bak";
|
||||||
public override string Filter => "GameCube Save File|*.gci|All Files|*.*";
|
public override string Filter
|
||||||
public override string Extension => ".gci";
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsMemoryCardSave)
|
||||||
|
return "Memory Card File|*.raw,*.bin|GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
return "GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Extension => IsMemoryCardSave ? ".raw" : ".gci";
|
||||||
|
|
||||||
// 3 Save files are stored
|
// 3 Save files are stored
|
||||||
// 0x0000-0x6000 contains memory card data
|
// 0x0000-0x6000 contains memory card data
|
||||||
|
@ -29,6 +38,13 @@ namespace PKHeX.Core
|
||||||
private readonly int Memo;
|
private readonly int Memo;
|
||||||
private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries, LegalCologne;
|
private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries, LegalCologne;
|
||||||
private readonly int OFS_PouchCologne;
|
private readonly int OFS_PouchCologne;
|
||||||
|
private SAV3GCMemoryCard MC;
|
||||||
|
public override bool IsMemoryCardSave => MC != null;
|
||||||
|
public SAV3Colosseum(byte[] data,SAV3GCMemoryCard MC)
|
||||||
|
: this(data)
|
||||||
|
{
|
||||||
|
this.MC = MC;
|
||||||
|
}
|
||||||
public SAV3Colosseum(byte[] data = null)
|
public SAV3Colosseum(byte[] data = null)
|
||||||
{
|
{
|
||||||
Data = data == null ? new byte[SaveUtil.SIZE_G3COLO] : (byte[])data.Clone();
|
Data = data == null ? new byte[SaveUtil.SIZE_G3COLO] : (byte[])data.Clone();
|
||||||
|
@ -100,6 +116,10 @@ namespace PKHeX.Core
|
||||||
|
|
||||||
private readonly byte[] OriginalData;
|
private readonly byte[] OriginalData;
|
||||||
public override byte[] Write(bool DSV)
|
public override byte[] Write(bool DSV)
|
||||||
|
{
|
||||||
|
return Write(DSV,false);
|
||||||
|
}
|
||||||
|
public override byte[] Write(bool DSV, bool GCI = false)
|
||||||
{
|
{
|
||||||
StrategyMemo.FinalData.CopyTo(Data, Memo);
|
StrategyMemo.FinalData.CopyTo(Data, Memo);
|
||||||
setChecksums();
|
setChecksums();
|
||||||
|
@ -111,6 +131,9 @@ namespace PKHeX.Core
|
||||||
// Put save slot back in original save data
|
// Put save slot back in original save data
|
||||||
byte[] newFile = (byte[])OriginalData.Clone();
|
byte[] newFile = (byte[])OriginalData.Clone();
|
||||||
Array.Copy(newSAV, 0, newFile, SLOT_START + SaveIndex*SLOT_SIZE, newSAV.Length);
|
Array.Copy(newSAV, 0, newFile, SLOT_START + SaveIndex*SLOT_SIZE, newSAV.Length);
|
||||||
|
//Return the complete memory card only if the save was loaded from a memory card and gci output was not selecte
|
||||||
|
if (IsMemoryCardSave && !GCI)
|
||||||
|
return MC.WriteSaveGameData(newFile.ToArray());
|
||||||
return Header.Concat(newFile).ToArray();
|
return Header.Concat(newFile).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
370
PKHeX/Saves/SAV3GCMemoryCard.cs
Normal file
370
PKHeX/Saves/SAV3GCMemoryCard.cs
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PKHeX.Core
|
||||||
|
{
|
||||||
|
/* GameCube memory card format data, checksum and code to extract files based on Dolphin code, adapted from C++ to C#
|
||||||
|
* https://github.com/dolphin-emu/dolphin/
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum GCMemoryCardState
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
NoPkmSaveGame,
|
||||||
|
ColloseumSaveGame,
|
||||||
|
XDSaveGame,
|
||||||
|
RSBoxSaveGame,
|
||||||
|
MultipleSaveGame,
|
||||||
|
ColloseumSaveGameDuplicated,
|
||||||
|
XDSaveGameDuplicated,
|
||||||
|
RSBoxSaveGameDuplicated,
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SAV3GCMemoryCard
|
||||||
|
{
|
||||||
|
const int BLOCK_SIZE = 0x2000;
|
||||||
|
const int MBIT_TO_BLOCKS = 0x10;
|
||||||
|
const int DENTRY_STRLEN = 0x20;
|
||||||
|
const int DENTRY_SIZE = 0x40;
|
||||||
|
int NumEntries_Directory { get { return BLOCK_SIZE / DENTRY_SIZE; } }
|
||||||
|
|
||||||
|
internal readonly string[] Colloseum_GameCode = new[]
|
||||||
|
{
|
||||||
|
"GC6J","GC6E","GC6P" // NTSC-J, NTSC-U, PAL
|
||||||
|
};
|
||||||
|
internal readonly string[] XD_GameCode = new[]
|
||||||
|
{
|
||||||
|
"GXXJ","GXXE","GXXP" // NTSC-J, NTSC-U, PAL
|
||||||
|
};
|
||||||
|
internal readonly string[] Box_GameCode = new[]
|
||||||
|
{
|
||||||
|
"GPXJ","GPXE","GPXP" // NTSC-J, NTSC-U, PAL
|
||||||
|
};
|
||||||
|
internal static readonly int[] validMCSizes = new[]
|
||||||
|
{
|
||||||
|
524288, // 512KB 59 Blocks Memory Card
|
||||||
|
1048576, // 1MB
|
||||||
|
2097152, // 2MB
|
||||||
|
4194304, // 4MB 251 Blocks Memory Card
|
||||||
|
8388608, // 8MB
|
||||||
|
16777216, // 16MB 1019 Blocks Default Dolphin Memory Card
|
||||||
|
33554432, // 64MB
|
||||||
|
67108864 // 128 MB
|
||||||
|
};
|
||||||
|
internal readonly byte[] RawEmpty_DEntry = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
// Control blocks
|
||||||
|
private const int Header_Block = 0;
|
||||||
|
private const int Directory_Block = 1;
|
||||||
|
private const int DirectoryBackup_Block = 2;
|
||||||
|
private const int BlockAlloc_Block = 3;
|
||||||
|
private const int BlockAllocBackup_Block = 4;
|
||||||
|
|
||||||
|
// BigEndian treatment
|
||||||
|
private ushort SwapEndian(ushort x)
|
||||||
|
{
|
||||||
|
return (ushort)((ushort)((x & 0xff) << 8) | ((x >> 8) & 0xff));
|
||||||
|
}
|
||||||
|
private ushort BigEndianToUint16(byte[] value, int startIndex)
|
||||||
|
{
|
||||||
|
ushort x = BitConverter.ToUInt16(value, startIndex);
|
||||||
|
if (!BitConverter.IsLittleEndian)
|
||||||
|
return x;
|
||||||
|
return SwapEndian(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calc_checksumsBE(int blockoffset, int offset, int length, ref ushort csum, ref ushort inv_csum)
|
||||||
|
{
|
||||||
|
csum = inv_csum = 0;
|
||||||
|
var ofs = blockoffset * BLOCK_SIZE + offset;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
csum += BigEndianToUint16(RawData, ofs + i * 2);
|
||||||
|
inv_csum += SwapEndian((ushort)(BitConverter.ToUInt16(RawData, ofs + i * 2) ^ 0xffff));
|
||||||
|
}
|
||||||
|
if (csum == 0xffff)
|
||||||
|
{
|
||||||
|
csum = 0;
|
||||||
|
}
|
||||||
|
if (inv_csum == 0xffff)
|
||||||
|
{
|
||||||
|
inv_csum = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint TestChecksums()
|
||||||
|
{
|
||||||
|
ushort csum = 0, csum_inv = 0;
|
||||||
|
|
||||||
|
uint results = 0;
|
||||||
|
|
||||||
|
calc_checksumsBE(Header_Block, 0, 0xFE, ref csum, ref csum_inv);
|
||||||
|
if ((Header_Checksum != csum) || (Header_Checksum_Inv != csum_inv))
|
||||||
|
results |= 1;
|
||||||
|
|
||||||
|
calc_checksumsBE(Directory_Block, 0, 0xFFE, ref csum, ref csum_inv);
|
||||||
|
if ((Directory_Checksum != csum) || (Directory_Checksum_Inv != csum_inv))
|
||||||
|
results |= 2;
|
||||||
|
|
||||||
|
calc_checksumsBE(DirectoryBackup_Block, 0, 0xFFE, ref csum, ref csum_inv);
|
||||||
|
if ((DirectoryBck_Checksum != csum) || (DirectoryBck_Checksum_Inv != csum_inv))
|
||||||
|
results |= 4;
|
||||||
|
|
||||||
|
calc_checksumsBE(BlockAlloc_Block, 4, 0xFFE, ref csum, ref csum_inv);
|
||||||
|
if ((BlockAlloc_Checksum != csum) || (BlockAlloc_Checksum_Inv != csum_inv))
|
||||||
|
results |= 8;
|
||||||
|
|
||||||
|
calc_checksumsBE(BlockAllocBackup_Block, 4, 0xFFE, ref csum, ref csum_inv);
|
||||||
|
if ((BlockAllocBck_Checksum != csum) || (BlockAllocBck_Checksum_Inv != csum_inv))
|
||||||
|
results |= 16;
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Header_Size { get { return BigEndianToUint16(RawData, Header_Block * BLOCK_SIZE + 0x0022); } }
|
||||||
|
ushort Header_Checksum { get { return BigEndianToUint16(RawData, Header_Block * BLOCK_SIZE + 0x01fc); } }
|
||||||
|
ushort Header_Checksum_Inv { get { return BigEndianToUint16(RawData, Header_Block * BLOCK_SIZE + 0x01fe); } }
|
||||||
|
|
||||||
|
//Encoding (Windows-1252 or Shift JIS)
|
||||||
|
int Header_Encoding { get { return BigEndianToUint16(RawData, Header_Block * BLOCK_SIZE + 0x0024); } }
|
||||||
|
bool Header_Japanese { get { return Header_Encoding == 1; } }
|
||||||
|
Encoding Header_EncodingType { get { return Header_Japanese ? Encoding.GetEncoding(1252) : Encoding.GetEncoding(932); } }
|
||||||
|
|
||||||
|
int Directory_UpdateCounter { get { return BigEndianToUint16(RawData, Directory_Block * BLOCK_SIZE + 0x1ffa); } }
|
||||||
|
int Directory_Checksum { get { return BigEndianToUint16(RawData, Directory_Block * BLOCK_SIZE + 0x1ffc); } }
|
||||||
|
int Directory_Checksum_Inv { get { return BigEndianToUint16(RawData, Directory_Block * BLOCK_SIZE + 0x1ffe); } }
|
||||||
|
|
||||||
|
int DirectoryBck_UpdateCounter { get { return BigEndianToUint16(RawData, DirectoryBackup_Block * BLOCK_SIZE + 0x1ffa); } }
|
||||||
|
int DirectoryBck_Checksum { get { return BigEndianToUint16(RawData, DirectoryBackup_Block * BLOCK_SIZE + 0x1ffc); } }
|
||||||
|
int DirectoryBck_Checksum_Inv { get { return BigEndianToUint16(RawData, DirectoryBackup_Block * BLOCK_SIZE + 0x1ffe); } }
|
||||||
|
|
||||||
|
int BlockAlloc_Checksum { get { return BigEndianToUint16(RawData, BlockAlloc_Block * BLOCK_SIZE + 0x0000); } }
|
||||||
|
int BlockAlloc_Checksum_Inv { get { return BigEndianToUint16(RawData, BlockAlloc_Block * BLOCK_SIZE + 0x0002); } }
|
||||||
|
|
||||||
|
int BlockAllocBck_Checksum { get { return BigEndianToUint16(RawData, BlockAllocBackup_Block * BLOCK_SIZE + 0x0000); } }
|
||||||
|
int BlockAllocBck_Checksum_Inv { get { return BigEndianToUint16(RawData, BlockAllocBackup_Block * BLOCK_SIZE + 0x0002); } }
|
||||||
|
|
||||||
|
byte[] RawData;
|
||||||
|
int DirectoryBlock_Used;
|
||||||
|
int NumBlocks => RawData.Length / BLOCK_SIZE - 5;
|
||||||
|
|
||||||
|
int Colloseum_Entry = -1;
|
||||||
|
int XD_Entry = -1;
|
||||||
|
int RSBox_Entry = -1;
|
||||||
|
int Selected_Entry = -1;
|
||||||
|
public int SaveGameCount = 0;
|
||||||
|
public bool HaveColloseumSaveGame => Colloseum_Entry > -1;
|
||||||
|
public bool HaveXDSaveGame => XD_Entry > -1;
|
||||||
|
public bool HaveRSBoxSaveGame => RSBox_Entry > -1;
|
||||||
|
|
||||||
|
private bool IsCorruptedMemoryCard()
|
||||||
|
{
|
||||||
|
uint csums = TestChecksums();
|
||||||
|
|
||||||
|
if ((csums & 0x1) == 1)
|
||||||
|
{
|
||||||
|
// Header checksum failed
|
||||||
|
// invalid files do not always get here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((csums & 0x2) == 1) // directory checksum error!
|
||||||
|
{
|
||||||
|
if ((csums & 0x4) == 1) // backup is also wrong!
|
||||||
|
{
|
||||||
|
// Directory checksum and directory backup checksum failed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// backup is correct, restore
|
||||||
|
Array.Copy(RawData, DirectoryBackup_Block * BLOCK_SIZE, RawData, Directory_Block * BLOCK_SIZE, BLOCK_SIZE);
|
||||||
|
Array.Copy(RawData, BlockAlloc_Block * BLOCK_SIZE, RawData, BlockAllocBackup_Block * BLOCK_SIZE, BLOCK_SIZE);
|
||||||
|
|
||||||
|
// update checksums
|
||||||
|
csums = TestChecksums();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((csums & 0x8) == 1) // BAT checksum error!
|
||||||
|
{
|
||||||
|
if ((csums & 0x10) == 1) // backup is also wrong!
|
||||||
|
{
|
||||||
|
// Block Allocation Table checksum failed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// backup is correct, restore
|
||||||
|
Array.Copy(RawData, DirectoryBackup_Block * BLOCK_SIZE, RawData, Directory_Block * BLOCK_SIZE, BLOCK_SIZE);
|
||||||
|
Array.Copy(RawData, BlockAlloc_Block * BLOCK_SIZE, RawData, BlockAllocBackup_Block * BLOCK_SIZE, BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsMemoryCardSize(long Size)
|
||||||
|
{
|
||||||
|
if (Size > int.MaxValue)
|
||||||
|
return false;
|
||||||
|
return validMCSizes.Contains(((int)Size));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsMemoryCardSize(byte[] Data)
|
||||||
|
{
|
||||||
|
return validMCSizes.Contains(Data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GCMemoryCardState LoadMemoryCardFile(byte[] Data)
|
||||||
|
{
|
||||||
|
RawData = Data;
|
||||||
|
if (!IsMemoryCardSize(RawData))
|
||||||
|
// Invalid size
|
||||||
|
return GCMemoryCardState.Invalid;
|
||||||
|
|
||||||
|
// Size in megabits, not megabytes
|
||||||
|
int m_sizeMb = ((RawData.Length / BLOCK_SIZE) / MBIT_TO_BLOCKS);
|
||||||
|
if (m_sizeMb != Header_Size)
|
||||||
|
//Memory card file size does not match the header size
|
||||||
|
return GCMemoryCardState.Invalid;
|
||||||
|
|
||||||
|
if (IsCorruptedMemoryCard())
|
||||||
|
return GCMemoryCardState.Invalid;
|
||||||
|
|
||||||
|
// Use the most recent directory block
|
||||||
|
if (DirectoryBck_UpdateCounter > Directory_UpdateCounter)
|
||||||
|
DirectoryBlock_Used = DirectoryBackup_Block;
|
||||||
|
else
|
||||||
|
DirectoryBlock_Used = Directory_Block;
|
||||||
|
|
||||||
|
string Empty_DEntry = Header_EncodingType.GetString(RawEmpty_DEntry, 0, 4);
|
||||||
|
// Search for pokemon savegames in the directory
|
||||||
|
for (int i = 0; i < NumEntries_Directory; i++)
|
||||||
|
{
|
||||||
|
string GameCode = Header_EncodingType.GetString(RawData, DirectoryBlock_Used * BLOCK_SIZE + i * DENTRY_SIZE, 4);
|
||||||
|
if (GameCode == Empty_DEntry)
|
||||||
|
continue;
|
||||||
|
int FirstBlock = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + i * DENTRY_SIZE + 0x36);
|
||||||
|
int BlockCount = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + i * DENTRY_SIZE + 0x38);
|
||||||
|
// Memory card directory contains info for deleted files with boundaries beyond memory card size, ignore
|
||||||
|
if (FirstBlock + BlockCount > NumBlocks)
|
||||||
|
continue;
|
||||||
|
if (Colloseum_GameCode.Contains(GameCode))
|
||||||
|
{
|
||||||
|
if (Colloseum_Entry > -1)
|
||||||
|
// Memory Card contains more than 1 Pokémon Colloseum save data.
|
||||||
|
// It is not possible with a real GC nor with Dolphin to have multiple savegames in the same MC
|
||||||
|
// If two are found assume corrupted memory card, it wont work with the games after all
|
||||||
|
return GCMemoryCardState.ColloseumSaveGameDuplicated;
|
||||||
|
|
||||||
|
Colloseum_Entry = i;
|
||||||
|
SaveGameCount++;
|
||||||
|
}
|
||||||
|
if (XD_GameCode.Contains(GameCode))
|
||||||
|
{
|
||||||
|
if (XD_Entry > -1)
|
||||||
|
// Memory Card contains more than 1 Pokémon XD save data.
|
||||||
|
return GCMemoryCardState.XDSaveGameDuplicated;
|
||||||
|
XD_Entry = i;
|
||||||
|
SaveGameCount++;
|
||||||
|
}
|
||||||
|
if (Box_GameCode.Contains(GameCode))
|
||||||
|
{
|
||||||
|
if (RSBox_Entry > -1)
|
||||||
|
// Memory Card contains more than 1 Pokémon RS Box save data.
|
||||||
|
return GCMemoryCardState.RSBoxSaveGameDuplicated;
|
||||||
|
RSBox_Entry = i;
|
||||||
|
SaveGameCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SaveGameCount == 0)
|
||||||
|
// There is no savedata from a Pokémon GameCube game.
|
||||||
|
return GCMemoryCardState.NoPkmSaveGame;
|
||||||
|
|
||||||
|
if (SaveGameCount > 1)
|
||||||
|
return GCMemoryCardState.MultipleSaveGame;
|
||||||
|
|
||||||
|
if (Colloseum_Entry > -1)
|
||||||
|
{
|
||||||
|
Selected_Entry = Colloseum_Entry;
|
||||||
|
return GCMemoryCardState.ColloseumSaveGame;
|
||||||
|
}
|
||||||
|
if(XD_Entry > -1)
|
||||||
|
{
|
||||||
|
Selected_Entry = XD_Entry;
|
||||||
|
return GCMemoryCardState.XDSaveGame;
|
||||||
|
}
|
||||||
|
Selected_Entry = RSBox_Entry;
|
||||||
|
return GCMemoryCardState.RSBoxSaveGame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameVersion SelectedGameVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(Colloseum_Entry > -1 && Selected_Entry == Colloseum_Entry)
|
||||||
|
return GameVersion.COLO;
|
||||||
|
if (XD_Entry > -1 && Selected_Entry == XD_Entry)
|
||||||
|
return GameVersion.XD;
|
||||||
|
if (RSBox_Entry > -1 && Selected_Entry == RSBox_Entry)
|
||||||
|
return GameVersion.RSBOX;
|
||||||
|
return GameVersion.CXD; //Default for no game selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectSaveGame(GameVersion Game)
|
||||||
|
{
|
||||||
|
switch(Game)
|
||||||
|
{
|
||||||
|
case GameVersion.COLO: if (Colloseum_Entry > -1) Selected_Entry = Colloseum_Entry; break;
|
||||||
|
case GameVersion.XD: if (XD_Entry > -1) Selected_Entry = XD_Entry; break;
|
||||||
|
case GameVersion.RSBOX: if (RSBox_Entry > -1) Selected_Entry = RSBox_Entry; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string getGCISaveGameName()
|
||||||
|
{
|
||||||
|
string GameCode = Header_EncodingType.GetString(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE, 4);
|
||||||
|
string Makercode = Header_EncodingType.GetString(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x04, 2);
|
||||||
|
string FileName = Header_EncodingType.GetString(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x08, DENTRY_STRLEN);
|
||||||
|
|
||||||
|
return Makercode + "-" + GameCode + "-" + FileName.Replace("\0", "") + ".gci";
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReadSaveGameData()
|
||||||
|
{
|
||||||
|
if (Selected_Entry == -1)
|
||||||
|
// Not selected any entry
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int FirstBlock = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x36);
|
||||||
|
int BlockCount = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x38);
|
||||||
|
|
||||||
|
byte[] SaveData = new byte[BlockCount * BLOCK_SIZE];
|
||||||
|
Array.Copy(RawData, FirstBlock * BLOCK_SIZE, SaveData, 0, BlockCount * BLOCK_SIZE);
|
||||||
|
|
||||||
|
return SaveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] WriteSaveGameData(byte[] SaveData)
|
||||||
|
{
|
||||||
|
if (Selected_Entry == -1)
|
||||||
|
// Not selected any entry
|
||||||
|
return RawData;
|
||||||
|
|
||||||
|
int FirstBlock = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x36);
|
||||||
|
int BlockCount = BigEndianToUint16(RawData, DirectoryBlock_Used * BLOCK_SIZE + Selected_Entry * DENTRY_SIZE + 0x38);
|
||||||
|
|
||||||
|
if (SaveData.Length != BlockCount * BLOCK_SIZE)
|
||||||
|
// Invalid File Size
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Array.Copy(SaveData, 0, RawData, FirstBlock * BLOCK_SIZE, BlockCount * BLOCK_SIZE);
|
||||||
|
return RawData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,23 @@ namespace PKHeX.Core
|
||||||
public sealed class SAV3RSBox : SaveFile
|
public sealed class SAV3RSBox : SaveFile
|
||||||
{
|
{
|
||||||
public override string BAKName => $"{FileName} [{Version} #{SaveCount:0000}].bak";
|
public override string BAKName => $"{FileName} [{Version} #{SaveCount:0000}].bak";
|
||||||
public override string Filter => "GameCube Save File|*.gci|All Files|*.*";
|
public override string Filter
|
||||||
public override string Extension => ".gci";
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsMemoryCardSave)
|
||||||
|
return "Memory Card File|*.raw,*.bin|GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
return "GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override string Extension => IsMemoryCardSave ? ".raw" : ".gci";
|
||||||
|
private SAV3GCMemoryCard MC;
|
||||||
|
public override bool IsMemoryCardSave => MC != null;
|
||||||
|
public SAV3RSBox(byte[] data, SAV3GCMemoryCard MC)
|
||||||
|
: this(data)
|
||||||
|
{
|
||||||
|
this.MC = MC;
|
||||||
|
}
|
||||||
public SAV3RSBox(byte[] data = null)
|
public SAV3RSBox(byte[] data = null)
|
||||||
{
|
{
|
||||||
Data = data == null ? new byte[SaveUtil.SIZE_G3BOX] : (byte[])data.Clone();
|
Data = data == null ? new byte[SaveUtil.SIZE_G3BOX] : (byte[])data.Clone();
|
||||||
|
@ -52,6 +66,10 @@ namespace PKHeX.Core
|
||||||
private const int BLOCK_SIZE = 0x2000;
|
private const int BLOCK_SIZE = 0x2000;
|
||||||
private const int SIZE_RESERVED = BLOCK_COUNT * BLOCK_SIZE; // unpacked box data
|
private const int SIZE_RESERVED = BLOCK_COUNT * BLOCK_SIZE; // unpacked box data
|
||||||
public override byte[] Write(bool DSV)
|
public override byte[] Write(bool DSV)
|
||||||
|
{
|
||||||
|
return Write(DSV, false);
|
||||||
|
}
|
||||||
|
public override byte[] Write(bool DSV, bool GCI = false)
|
||||||
{
|
{
|
||||||
// Copy Box data back to block
|
// Copy Box data back to block
|
||||||
foreach (RSBOX_Block b in Blocks)
|
foreach (RSBOX_Block b in Blocks)
|
||||||
|
@ -63,6 +81,9 @@ namespace PKHeX.Core
|
||||||
foreach (RSBOX_Block b in Blocks)
|
foreach (RSBOX_Block b in Blocks)
|
||||||
b.Data.CopyTo(Data, b.Offset);
|
b.Data.CopyTo(Data, b.Offset);
|
||||||
byte[] newFile = getData(0, Data.Length - SIZE_RESERVED);
|
byte[] newFile = getData(0, Data.Length - SIZE_RESERVED);
|
||||||
|
//Return the complete memory card only if the save was loaded from a memory card and gci output was not selecte
|
||||||
|
if (IsMemoryCardSave && !GCI)
|
||||||
|
return MC.WriteSaveGameData(newFile.ToArray());
|
||||||
return Header.Concat(newFile).ToArray();
|
return Header.Concat(newFile).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,16 @@ namespace PKHeX.Core
|
||||||
public sealed class SAV3XD : SaveFile
|
public sealed class SAV3XD : SaveFile
|
||||||
{
|
{
|
||||||
public override string BAKName => $"{FileName} [{OT} ({Version}) #{SaveCount:0000}].bak";
|
public override string BAKName => $"{FileName} [{OT} ({Version}) #{SaveCount:0000}].bak";
|
||||||
public override string Filter => "GameCube Save File|*.gci|All Files|*.*";
|
public override string Filter
|
||||||
public override string Extension => ".gci";
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsMemoryCardSave)
|
||||||
|
return "Memory Card File|*.raw|GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
return "GameCube Save File|*.gci|All Files|*.*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override string Extension => IsMemoryCardSave ? ".raw" : ".gci";
|
||||||
|
|
||||||
private const int SLOT_SIZE = 0x28000;
|
private const int SLOT_SIZE = 0x28000;
|
||||||
private const int SLOT_START = 0x6000;
|
private const int SLOT_START = 0x6000;
|
||||||
|
@ -22,6 +30,13 @@ namespace PKHeX.Core
|
||||||
private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries, LegalCologne, LegalDisc;
|
private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries, LegalCologne, LegalDisc;
|
||||||
private readonly int OFS_PouchCologne, OFS_PouchDisc;
|
private readonly int OFS_PouchCologne, OFS_PouchDisc;
|
||||||
private readonly int[] subOffsets = new int[16];
|
private readonly int[] subOffsets = new int[16];
|
||||||
|
private SAV3GCMemoryCard MC;
|
||||||
|
public override bool IsMemoryCardSave => MC != null;
|
||||||
|
public SAV3XD(byte[] data, SAV3GCMemoryCard MC)
|
||||||
|
: this(data)
|
||||||
|
{
|
||||||
|
this.MC = MC;
|
||||||
|
}
|
||||||
public SAV3XD(byte[] data = null)
|
public SAV3XD(byte[] data = null)
|
||||||
{
|
{
|
||||||
Data = data == null ? new byte[SaveUtil.SIZE_G3XD] : (byte[])data.Clone();
|
Data = data == null ? new byte[SaveUtil.SIZE_G3XD] : (byte[])data.Clone();
|
||||||
|
@ -109,6 +124,10 @@ namespace PKHeX.Core
|
||||||
|
|
||||||
private readonly byte[] OriginalData;
|
private readonly byte[] OriginalData;
|
||||||
public override byte[] Write(bool DSV)
|
public override byte[] Write(bool DSV)
|
||||||
|
{
|
||||||
|
return Write(DSV, false);
|
||||||
|
}
|
||||||
|
public override byte[] Write(bool DSV, bool GCI = false)
|
||||||
{
|
{
|
||||||
// Set Memo Back
|
// Set Memo Back
|
||||||
StrategyMemo.FinalData.CopyTo(Data, Memo);
|
StrategyMemo.FinalData.CopyTo(Data, Memo);
|
||||||
|
@ -124,6 +143,9 @@ namespace PKHeX.Core
|
||||||
// Put save slot back in original save data
|
// Put save slot back in original save data
|
||||||
byte[] newFile = (byte[])OriginalData.Clone();
|
byte[] newFile = (byte[])OriginalData.Clone();
|
||||||
Array.Copy(newSAV, 0, newFile, SLOT_START + SaveIndex * SLOT_SIZE, newSAV.Length);
|
Array.Copy(newSAV, 0, newFile, SLOT_START + SaveIndex * SLOT_SIZE, newSAV.Length);
|
||||||
|
//Return the complete memory card only if the save was loaded from a memory card and gci output was not selected
|
||||||
|
if (IsMemoryCardSave && !GCI)
|
||||||
|
return MC.WriteSaveGameData(newFile.ToArray());
|
||||||
return Header.Concat(newFile).ToArray();
|
return Header.Concat(newFile).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace PKHeX.Core
|
||||||
int gen = f.Last() - 0x30;
|
int gen = f.Last() - 0x30;
|
||||||
return 3 <= gen && gen <= Generation;
|
return 3 <= gen && gen <= Generation;
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
public virtual bool IsMemoryCardSave => false;
|
||||||
// General PKM Properties
|
// General PKM Properties
|
||||||
public abstract Type PKMType { get; }
|
public abstract Type PKMType { get; }
|
||||||
public abstract PKM getPKM(byte[] data);
|
public abstract PKM getPKM(byte[] data);
|
||||||
|
@ -44,6 +44,10 @@ namespace PKHeX.Core
|
||||||
public ushort[] HeldItems { get; protected set; }
|
public ushort[] HeldItems { get; protected set; }
|
||||||
|
|
||||||
// General SAV Properties
|
// General SAV Properties
|
||||||
|
public virtual byte[] Write(bool DSV, bool GCI = false)
|
||||||
|
{
|
||||||
|
return Write(DSV);
|
||||||
|
}
|
||||||
public virtual byte[] Write(bool DSV)
|
public virtual byte[] Write(bool DSV)
|
||||||
{
|
{
|
||||||
setChecksums();
|
setChecksums();
|
||||||
|
|
|
@ -274,12 +274,16 @@ namespace PKHeX.Core
|
||||||
// Check the intro bytes for each save slot
|
// Check the intro bytes for each save slot
|
||||||
byte[] slotintroXD = { 0x01, 0x01, 0x01, 0x00 };
|
byte[] slotintroXD = { 0x01, 0x01, 0x01, 0x00 };
|
||||||
int offset = data.Length - SIZE_G3XD;
|
int offset = data.Length - SIZE_G3XD;
|
||||||
|
// For XD savegames inside a memory card only the firs sequence is equal to slotintroXD
|
||||||
|
bool valid = false;
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var ident = data.Skip(0x6000 + offset + 0x28000 * i).Take(4);
|
var ident = data.Skip(0x6000 + offset + 0x28000 * i).Take(4);
|
||||||
if (!ident.SequenceEqual(slotintroXD))
|
if (ident.SequenceEqual(slotintroXD))
|
||||||
return GameVersion.Invalid;
|
valid = true;
|
||||||
}
|
}
|
||||||
|
if(!valid)
|
||||||
|
return GameVersion.Invalid;
|
||||||
return GameVersion.XD;
|
return GameVersion.XD;
|
||||||
}
|
}
|
||||||
/// <summary>Determines the type of 4th gen save</summary>
|
/// <summary>Determines the type of 4th gen save</summary>
|
||||||
|
@ -418,6 +422,28 @@ namespace PKHeX.Core
|
||||||
sav.Footer = footer;
|
sav.Footer = footer;
|
||||||
return sav;
|
return sav;
|
||||||
}
|
}
|
||||||
|
public static SaveFile getVariantSAV(SAV3GCMemoryCard MC)
|
||||||
|
{
|
||||||
|
// Pre-check for header/footer signatures
|
||||||
|
SaveFile sav;
|
||||||
|
byte[] header = new byte[0], footer = new byte[0];
|
||||||
|
byte[] data = MC.ReadSaveGameData();
|
||||||
|
CheckHeaderFooter(ref data, ref header, ref footer);
|
||||||
|
|
||||||
|
switch (MC.SelectedGameVersion)
|
||||||
|
{
|
||||||
|
// Side Games
|
||||||
|
case GameVersion.COLO: sav = new SAV3Colosseum(data,MC); break;
|
||||||
|
case GameVersion.XD: sav = new SAV3XD(data, MC); break;
|
||||||
|
case GameVersion.RSBOX: sav = new SAV3RSBox(data, MC); break;
|
||||||
|
|
||||||
|
// No pattern matched
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
sav.Header = header;
|
||||||
|
sav.Footer = footer;
|
||||||
|
return sav;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of a SaveFile with a blank base.
|
/// Creates an instance of a SaveFile with a blank base.
|
||||||
|
|
Loading…
Reference in a new issue