mirror of
https://github.com/kwsch/PKHeX
synced 2025-02-16 21:38:40 +00:00
Add conversion for Gen 4 saves between KO and JP/INTL formats (#4057)
* Refactor Gen 4 extra blocks * Replace FetchHallBlock with extra block getter * Add UI to convert save to/from Korean * Do not modify uninitialized General/Storage blocks * Detect invalid extra blocks
This commit is contained in:
parent
1f6d2de891
commit
894ea1d628
28 changed files with 290 additions and 31 deletions
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = Alle 3 Pokémon für jeden Freund entsperren?
|
|||
MsgSaveGen2RTCResetPassword = RTC Reset Passwort: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = Möchtest du die Uhr zurücksetzen?
|
||||
MsgSaveJPEGExportFail = Der Spielstand enthält keine Grafik Daten!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = Spielstand wurde bearbeitet. Integrität kann nicht geprüft werden.
|
||||
MsgSaveChecksumValid = Alle Prüfsummen sind gültig.
|
||||
MsgSaveChecksumFailExport = Prüfsummen in die Zwischenablage kopieren?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = Unlock all 3 slots for each friend?
|
|||
MsgSaveGen2RTCResetPassword = RTC Reset Password: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = Would you like to reset the RTC?
|
||||
MsgSaveJPEGExportFail = No picture data found in the save file!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = Save has been edited. Cannot integrity check.
|
||||
MsgSaveChecksumValid = Checksums are valid.
|
||||
MsgSaveChecksumFailExport = Export Checksum Info to Clipboard?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = ¿Desbloquear los 3 espacios para cada amigo?
|
|||
MsgSaveGen2RTCResetPassword = Contraseña de reseteo de RTC: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = ¿Le gustaría reiniciar el RTC?
|
||||
MsgSaveJPEGExportFail = ¡No se han encontrado datos de imagen en el archivo de guardado!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = El archivo de guardado ha sido editado. No se puede verificar la integridad.
|
||||
MsgSaveChecksumValid = Suma de verificación válido.
|
||||
MsgSaveChecksumFailExport = ¿Exportar información de la suma de verificación al portapapeles?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = Débloquer les 3 emplacements pour chaque ami
|
|||
MsgSaveGen2RTCResetPassword = Mot de passe de réinitialisation RTC: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = Souhaitez-vous réinitialiser le RTC?
|
||||
MsgSaveJPEGExportFail = Aucune donnée d'image trouvée dans le fichier de sauvegarde!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = La sauvegarde a été éditée. Impossible de vérifier l'intégrité.
|
||||
MsgSaveChecksumValid = Sommes de contrôle valides.
|
||||
MsgSaveChecksumFailExport = Copier les informations de somme de contrôle ?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = Sbloccare tutti i 3 slot per ogni amico?
|
|||
MsgSaveGen2RTCResetPassword = RTC Reset Password: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = Vuoi resettare l'orologio RTC?
|
||||
MsgSaveJPEGExportFail = Nessuna immagine è stata trovata in questo salvataggio!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = Il salvataggio è stato modificato. Impossibile eseguire il controllo di integrità.
|
||||
MsgSaveChecksumValid = I Checksum sono validi.
|
||||
MsgSaveChecksumFailExport = Esportare le info sui Checksum negli appunti?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = Unlock all 3 slots for each friend?
|
|||
MsgSaveGen2RTCResetPassword = RTC Reset Password: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = Would you like to reset the RTC?
|
||||
MsgSaveJPEGExportFail = No picture data found in the save file!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = Save has been edited. Cannot integrity check.
|
||||
MsgSaveChecksumValid = Checksums are valid.
|
||||
MsgSaveChecksumFailExport = Export Checksum Info to Clipboard?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = 각 친구마다 3개 슬롯을 모두 해금
|
|||
MsgSaveGen2RTCResetPassword = RTC 초기화 비밀번호: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = RTC를 초기화하시겠습니까?
|
||||
MsgSaveJPEGExportFail = 세이브 파일에서 사진 데이터를 찾을 수 없습니다!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = 세이브가 수정되었습니다. 무결성 검사를 할 수 없습니다.
|
||||
MsgSaveChecksumValid = 체크섬 검증에 성공했습니다.
|
||||
MsgSaveChecksumFailExport = 체크섬 정보를 클립보드로 복사하시겠습니까?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = 解锁所有朋友狩猎的三个槽位?
|
|||
MsgSaveGen2RTCResetPassword = 时钟重设密码: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = 你想重置RTC吗?
|
||||
MsgSaveJPEGExportFail = 无法在存档文件中找到图片数据!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = 存档已被编辑。无法进行完整性检查。
|
||||
MsgSaveChecksumValid = 检验值正确。
|
||||
MsgSaveChecksumFailExport = 导出校验值信息到剪贴板?
|
||||
|
|
|
@ -144,6 +144,8 @@ MsgSaveGen6FriendSafariCheatDesc = 解鎖所有朋友狩獵之三個格子?
|
|||
MsgSaveGen2RTCResetPassword = 時鐘重設密碼: {0:00000}
|
||||
MsgSaveGen2RTCResetBitflag = 你想重置RTC嗎?
|
||||
MsgSaveJPEGExportFail = 無法在儲存資料檔案中找到圖片資料!
|
||||
MsgSaveGen4ConvertKorean = Would you like to convert this Japanese/International save file to be playable with Korean games?
|
||||
MsgSaveGen4ConvertInternational = Would you like to convert this Korean save file to be playable with Japanese/International games?
|
||||
MsgSaveChecksumFailEdited = 儲存資料經已被編輯。無法進行完整性檢查。
|
||||
MsgSaveChecksumValid = 檢驗值正確。
|
||||
MsgSaveChecksumFailExport = 是否匯出校驗值信息至剪貼簿?
|
||||
|
|
81
PKHeX.Core/Saves/Blocks/BlockInfo4.cs
Normal file
81
PKHeX.Core/Saves/Blocks/BlockInfo4.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Gen4 Extra Block Info
|
||||
/// </summary>
|
||||
public class BlockInfo4 : BlockInfo
|
||||
{
|
||||
private const int SIZE_FOOTER = 0x10;
|
||||
private readonly int FooterOffset;
|
||||
|
||||
public BlockInfo4(uint id, int offset, int length)
|
||||
{
|
||||
ID = id;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
FooterOffset = offset + length - SIZE_FOOTER;
|
||||
}
|
||||
|
||||
public uint GetKey(ReadOnlySpan<byte> data) => ReadUInt32LittleEndian(data[Offset..]);
|
||||
public uint GetMagic(ReadOnlySpan<byte> data) => ReadUInt32LittleEndian(data[FooterOffset..]);
|
||||
public uint GetRevision(ReadOnlySpan<byte> data) => ReadUInt32LittleEndian(data[(FooterOffset + 0x4)..]);
|
||||
public int GetSize(ReadOnlySpan<byte> data) => ReadInt32LittleEndian(data[(FooterOffset + 0x8)..]);
|
||||
public ushort GetID(ReadOnlySpan<byte> data) => ReadUInt16LittleEndian(data[(FooterOffset + 0xC)..]);
|
||||
private ushort GetChecksum(ReadOnlySpan<byte> data) => Checksums.CRC16_CCITT(data.Slice(Offset, Length - 2));
|
||||
|
||||
private bool IsInitialized(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return (ID == 0 && GetRevision(data) != 0xFFFFFFFF) || (ID != 0 && GetKey(data) != 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
public bool SizeValid(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return GetSize(data) == Length;
|
||||
}
|
||||
|
||||
protected override bool ChecksumValid(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!IsInitialized(data))
|
||||
return true;
|
||||
|
||||
ushort chk = GetChecksum(data);
|
||||
if (chk != ReadUInt16LittleEndian(data[(FooterOffset + 14)..]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsValid(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return IsInitialized(data) && SizeValid(data) && ChecksumValid(data);
|
||||
}
|
||||
|
||||
protected override void SetChecksum(Span<byte> data)
|
||||
{
|
||||
if (!IsInitialized(data))
|
||||
return;
|
||||
ushort chk = GetChecksum(data);
|
||||
WriteUInt16LittleEndian(data[(FooterOffset + 14)..], chk);
|
||||
}
|
||||
|
||||
protected void SetMagic(Span<byte> data, uint magic)
|
||||
{
|
||||
if (!IsInitialized(data))
|
||||
return;
|
||||
WriteUInt32LittleEndian(data[FooterOffset..], magic);
|
||||
}
|
||||
|
||||
public static void SetMagics(IEnumerable<BlockInfo4> blocks, Span<byte> data, uint magic)
|
||||
{
|
||||
foreach (var b in blocks)
|
||||
b.SetMagic(data, magic);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static void SetMagics(this IEnumerable<BlockInfo4> blocks, Span<byte> data, uint magic) => BlockInfo4.SetMagics(blocks, data, magic);
|
||||
}
|
|
@ -27,6 +27,12 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
protected sealed override Span<byte> BoxBuffer => Storage;
|
||||
protected sealed override Span<byte> PartyBuffer => General;
|
||||
|
||||
private readonly Memory<byte> BackupStorageBuffer;
|
||||
private readonly Memory<byte> BackupGeneralBuffer;
|
||||
private Span<byte> BackupStorage => BackupStorageBuffer.Span;
|
||||
private Span<byte> BackupGeneral => BackupGeneralBuffer.Span;
|
||||
protected abstract IReadOnlyList<BlockInfo4> ExtraBlocks { get; }
|
||||
|
||||
public abstract Zukan4 Dex { get; }
|
||||
|
||||
protected abstract int EventFlag { get; }
|
||||
|
@ -38,6 +44,8 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
{
|
||||
GeneralBuffer = new byte[gSize];
|
||||
StorageBuffer = new byte[sSize];
|
||||
BackupGeneralBuffer = new byte[gSize];
|
||||
BackupStorageBuffer = new byte[sSize];
|
||||
ClearBoxes();
|
||||
}
|
||||
|
||||
|
@ -50,6 +58,11 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
var sbo = (StorageBlockPosition == 0 ? 0 : PartitionSize) + sStart;
|
||||
GeneralBuffer = Data.AsMemory(gbo, gSize);
|
||||
StorageBuffer = Data.AsMemory(sbo, sSize);
|
||||
|
||||
var gboBackup = (GeneralBlockPosition != 0 ? 0 : PartitionSize);
|
||||
var sboBackup = (StorageBlockPosition != 0 ? 0 : PartitionSize) + sStart;
|
||||
BackupGeneralBuffer = Data.AsMemory(gboBackup, gSize);
|
||||
BackupStorageBuffer = Data.AsMemory(sboBackup, sSize);
|
||||
}
|
||||
|
||||
// Configuration
|
||||
|
@ -101,10 +114,29 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
private static ushort GetBlockChecksumSaved(ReadOnlySpan<byte> data) => ReadUInt16LittleEndian(data[^2..]);
|
||||
private bool GetBlockChecksumValid(ReadOnlySpan<byte> data) => CalcBlockChecksum(data) == GetBlockChecksumSaved(data);
|
||||
|
||||
|
||||
protected void SetMagics(uint magic)
|
||||
{
|
||||
WriteUInt32LittleEndian(General[^8..^4], magic);
|
||||
WriteUInt32LittleEndian(Storage[^8..^4], magic);
|
||||
if (ReadUInt32LittleEndian(BackupGeneral[^8..^4]) != 0xFFFFFFFF)
|
||||
WriteUInt32LittleEndian(BackupGeneral[^8..^4], magic);
|
||||
if (ReadUInt32LittleEndian(BackupStorage[^8..^4]) != 0xFFFFFFFF)
|
||||
WriteUInt32LittleEndian(BackupStorage[^8..^4], magic);
|
||||
ExtraBlocks.SetMagics(Data.AsSpan(), magic);
|
||||
ExtraBlocks.SetMagics(Data.AsSpan(PartitionSize..), magic);
|
||||
}
|
||||
|
||||
protected sealed override void SetChecksums()
|
||||
{
|
||||
WriteUInt16LittleEndian(General[^2..], CalcBlockChecksum(General));
|
||||
WriteUInt16LittleEndian(Storage[^2..], CalcBlockChecksum(Storage));
|
||||
if (ReadUInt32LittleEndian(BackupGeneral[^8..^4]) != 0xFFFFFFFF)
|
||||
WriteUInt16LittleEndian(BackupGeneral[^2..], CalcBlockChecksum(BackupGeneral));
|
||||
if (ReadUInt32LittleEndian(BackupStorage[^8..^4]) != 0xFFFFFFFF)
|
||||
WriteUInt16LittleEndian(BackupStorage[^2..], CalcBlockChecksum(BackupStorage));
|
||||
ExtraBlocks.SetChecksums(Data.AsSpan());
|
||||
ExtraBlocks.SetChecksums(Data.AsSpan(PartitionSize..));
|
||||
}
|
||||
|
||||
public sealed override bool ChecksumsValid
|
||||
|
@ -115,6 +147,10 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
return false;
|
||||
if (!GetBlockChecksumValid(Storage))
|
||||
return false;
|
||||
if (!ExtraBlocks.GetChecksumsValid(Data.AsSpan()))
|
||||
return false;
|
||||
if (!ExtraBlocks.GetChecksumsValid(Data.AsSpan(PartitionSize..)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -129,6 +165,10 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
list.Add("Small block checksum is invalid");
|
||||
if (!GetBlockChecksumValid(Storage))
|
||||
list.Add("Large block checksum is invalid");
|
||||
if (!ExtraBlocks.GetChecksumsValid(Data.AsSpan()))
|
||||
list.Add(ExtraBlocks.GetChecksumInfo(Data.AsSpan()));
|
||||
if (!ExtraBlocks.GetChecksumsValid(Data.AsSpan(PartitionSize..)))
|
||||
list.Add(ExtraBlocks.GetChecksumInfo(Data.AsSpan(PartitionSize..)));
|
||||
|
||||
return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid.";
|
||||
}
|
||||
|
@ -140,10 +180,36 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
return SAV4BlockDetection.CompareFooters(data, offset, offset + PartitionSize);
|
||||
}
|
||||
|
||||
private int GetActiveExtraBlock(BlockInfo4 block)
|
||||
{
|
||||
int index = (int)block.ID;
|
||||
|
||||
// Hall of Fame
|
||||
if (index == 0)
|
||||
return SAV4BlockDetection.CompareExtra(Data, Data.AsSpan(PartitionSize), block);
|
||||
|
||||
// Battle Hall/Battle Videos
|
||||
var KeyOffset = Extra;
|
||||
var KeyBackupOffset = Extra + 0x4 * (ExtraBlocks.Count - 1);
|
||||
var PreferOffset = Extra + 2 * 0x4 * (ExtraBlocks.Count - 1);
|
||||
var key = ReadUInt32LittleEndian(General[(KeyOffset + 0x4 * (index - 1))..]);
|
||||
var keyBackup = ReadUInt32LittleEndian(General[(KeyBackupOffset + 0x4 * (index - 1))..]);
|
||||
var prefer = General[(PreferOffset + (index - 1))];
|
||||
return SAV4BlockDetection.CompareExtra(Data, Data.AsSpan(PartitionSize), block, key, keyBackup, prefer);
|
||||
}
|
||||
|
||||
public Hall4? GetHall()
|
||||
{
|
||||
var block = ExtraBlocks[1];
|
||||
var active = GetActiveExtraBlock(block);
|
||||
return active == -1 ? null : new Hall4(Data, (active == 0 ? 0 : PartitionSize) + block.Offset);
|
||||
}
|
||||
|
||||
protected int WondercardFlags = int.MinValue;
|
||||
protected int AdventureInfo = int.MinValue;
|
||||
protected int Seal = int.MinValue;
|
||||
public int Geonet = int.MinValue;
|
||||
protected int Extra = int.MinValue;
|
||||
protected int Trainer1;
|
||||
public int GTS { get; protected set; } = int.MinValue;
|
||||
|
||||
|
@ -268,6 +334,10 @@ public abstract class SAV4 : SaveFile, IEventFlag37
|
|||
set { if (value < 0) return; General[Geonet + 2] = (byte)value; }
|
||||
}
|
||||
|
||||
public const uint MAGIC_JAPAN_INTL = 0x20060623;
|
||||
public const uint MAGIC_KOREAN = 0x20070903;
|
||||
public uint Magic { get => ReadUInt32LittleEndian(General[^8..^4]); set => SetMagics(value); }
|
||||
|
||||
protected sealed override PK4 GetPKM(byte[] data) => new(data);
|
||||
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
|
||||
|
||||
|
|
|
@ -30,6 +30,9 @@ public sealed class SAV4DP : SAV4Sinnoh
|
|||
|
||||
private const int GeneralSize = 0xC100;
|
||||
private const int StorageSize = 0x121E0; // Start 0xC100, +4 starts box data
|
||||
protected override BlockInfo4[] ExtraBlocks => new[] {
|
||||
new BlockInfo4(0, 0x20000, 0x2AC0), // Hall of Fame
|
||||
};
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
|
|
|
@ -32,6 +32,14 @@ public sealed class SAV4HGSS : SAV4
|
|||
private const int StorageSize = 0x12310; // Start 0xF700, +0 starts box data
|
||||
private const int GeneralGap = 0xD8;
|
||||
protected override int FooterSize => 0x10;
|
||||
protected override BlockInfo4[] ExtraBlocks => new[] {
|
||||
new BlockInfo4(0, 0x23000, 0x2AC0), // Hall of Fame
|
||||
new BlockInfo4(1, 0x26000, 0x0BB0), // Battle Hall
|
||||
new BlockInfo4(2, 0x27000, 0x1D60), // Battle Video (My Video)
|
||||
new BlockInfo4(3, 0x29000, 0x1D60), // Battle Video (Other Videos 1)
|
||||
new BlockInfo4(4, 0x2B000, 0x1D60), // Battle Video (Other Videos 2)
|
||||
new BlockInfo4(5, 0x2D000, 0x1D60), // Battle Video (Other Videos 3)
|
||||
};
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
|
@ -48,6 +56,7 @@ public sealed class SAV4HGSS : SAV4
|
|||
Trainer1 = 0x64;
|
||||
Party = 0x98;
|
||||
PokeDex = 0x12B8;
|
||||
Extra = 0x230C;
|
||||
Geonet = 0x8D44;
|
||||
WondercardFlags = 0x9D3C;
|
||||
WondercardData = 0x9E3C;
|
||||
|
|
|
@ -29,6 +29,14 @@ public sealed class SAV4Pt : SAV4Sinnoh
|
|||
|
||||
private const int GeneralSize = 0xCF2C;
|
||||
private const int StorageSize = 0x121E4; // Start 0xCF2C, +4 starts box data
|
||||
protected override BlockInfo4[] ExtraBlocks => new[] {
|
||||
new BlockInfo4(0, 0x20000, 0x2AC0), // Hall of Fame
|
||||
new BlockInfo4(1, 0x23000, 0x0BB0), // Battle Hall
|
||||
new BlockInfo4(2, 0x24000, 0x1D60), // Battle Video (My Video)
|
||||
new BlockInfo4(3, 0x26000, 0x1D60), // Battle Video (Other Videos 1)
|
||||
new BlockInfo4(4, 0x28000, 0x1D60), // Battle Video (Other Videos 2)
|
||||
new BlockInfo4(5, 0x2A000, 0x1D60), // Battle Video (Other Videos 3)
|
||||
};
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
|
@ -45,6 +53,7 @@ public sealed class SAV4Pt : SAV4Sinnoh
|
|||
Trainer1 = 0x68;
|
||||
Party = 0xA0;
|
||||
PokeDex = 0x1328;
|
||||
Extra = 0x2820;
|
||||
Geonet = 0xA4C4;
|
||||
WondercardFlags = 0xB4C0;
|
||||
WondercardData = 0xB5C0;
|
||||
|
|
|
@ -48,4 +48,58 @@ public static class SAV4BlockDetection
|
|||
|
||||
return Same;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compares two extra blocks to determine which is newest.
|
||||
/// </summary>
|
||||
/// <returns>0=Primary, 1=Secondary, -1=Uninitialized.</returns>
|
||||
public static int CompareExtra(ReadOnlySpan<byte> data1, ReadOnlySpan<byte> data2, BlockInfo4 block)
|
||||
{
|
||||
// The Hall of Fame block uses a counter in the footer to determine which copy is used.
|
||||
// Entering the Hall of Fame overwrites both copies with the new data.
|
||||
var rev1 = block.GetRevision(data1);
|
||||
var rev2 = block.GetRevision(data2);
|
||||
var valid1 = rev1 != 0xFFFFFFFF && block.SizeValid(data1);
|
||||
var valid2 = rev2 != 0xFFFFFFFF && block.SizeValid(data2);
|
||||
if (valid1 && (rev1 >= rev2 || !valid2))
|
||||
return First;
|
||||
if (valid2 && (rev2 > rev1 || !valid1))
|
||||
return Second;
|
||||
return -1; // Uninitialized
|
||||
}
|
||||
|
||||
public static int CompareExtra(ReadOnlySpan<byte> data1, ReadOnlySpan<byte> data2, BlockInfo4 block, uint key, uint keyBackup, byte prefer)
|
||||
{
|
||||
// The Battle Hall/Battle Videos use a key in the General block to check if the block is valid.
|
||||
// If the key is 0xFFFFFFFF, the block is uninitialized or deleted.
|
||||
// Which partition is checked first is determined by a byte in the General block.
|
||||
var key1 = block.GetKey(data1);
|
||||
var key2 = block.GetKey(data2);
|
||||
var valid1 = key1 != 0xFFFFFFFF && block.SizeValid(data1);
|
||||
var valid2 = key2 != 0xFFFFFFFF && block.SizeValid(data2);
|
||||
if (prefer == First)
|
||||
{
|
||||
if (valid1 && key == key1)
|
||||
return First;
|
||||
if (valid2 && key == key2)
|
||||
return Second;
|
||||
if (valid1 && keyBackup == key1)
|
||||
return First;
|
||||
if (valid2 && keyBackup == key2)
|
||||
return Second;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (valid2 && key == key2)
|
||||
return Second;
|
||||
if (valid1 && key == key1)
|
||||
return First;
|
||||
if (valid2 && keyBackup == key2)
|
||||
return Second;
|
||||
if (valid1 && keyBackup == key1)
|
||||
return First;
|
||||
}
|
||||
return -1; // Uninitialized
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,6 +190,8 @@ public static class MessageStrings
|
|||
public static string MsgSaveGen2RTCResetPassword { get; set; } = "RTC Reset Password: {0:00000}";
|
||||
public static string MsgSaveGen2RTCResetBitflag { get; set; } = "Would you like to reset the RTC?";
|
||||
public static string MsgSaveJPEGExportFail { get; set; } = "No picture data found in the save file!";
|
||||
public static string MsgSaveGen4ConvertKorean { get; set; } = "Would you like to convert this Japanese/International save file to be playable with Korean games?";
|
||||
public static string MsgSaveGen4ConvertInternational { get; set; } = "Would you like to convert this Korean save file to be playable with Japanese/International games?";
|
||||
|
||||
public static string MsgSaveChecksumFailEdited { get; set; } = "Save has been edited. Cannot integrity check.";
|
||||
public static string MsgSaveChecksumValid { get; set; } = "Checksums are valid.";
|
||||
|
|
|
@ -106,6 +106,7 @@ namespace PKHeX.WinForms.Controls
|
|||
TB_Secure1 = new System.Windows.Forms.TextBox();
|
||||
L_GameSync = new System.Windows.Forms.Label();
|
||||
TB_GameSync = new System.Windows.Forms.TextBox();
|
||||
B_ConvertKorean = new System.Windows.Forms.Button();
|
||||
tabBoxMulti.SuspendLayout();
|
||||
Tab_Box.SuspendLayout();
|
||||
Tab_PartyBattle.SuspendLayout();
|
||||
|
@ -835,6 +836,7 @@ namespace PKHeX.WinForms.Controls
|
|||
FLP_SAVToolsMisc.Controls.Add(B_VerifySaveEntities);
|
||||
FLP_SAVToolsMisc.Controls.Add(Menu_ExportBAK);
|
||||
FLP_SAVToolsMisc.Controls.Add(B_JPEG);
|
||||
FLP_SAVToolsMisc.Controls.Add(B_ConvertKorean);
|
||||
FLP_SAVToolsMisc.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
FLP_SAVToolsMisc.Location = new System.Drawing.Point(0, 0);
|
||||
FLP_SAVToolsMisc.Margin = new System.Windows.Forms.Padding(0);
|
||||
|
@ -848,7 +850,7 @@ namespace PKHeX.WinForms.Controls
|
|||
B_SaveBoxBin.Margin = new System.Windows.Forms.Padding(0);
|
||||
B_SaveBoxBin.Name = "B_SaveBoxBin";
|
||||
B_SaveBoxBin.Size = new System.Drawing.Size(88, 48);
|
||||
B_SaveBoxBin.TabIndex = 8;
|
||||
B_SaveBoxBin.TabIndex = 1;
|
||||
B_SaveBoxBin.Text = "Save Box Data++";
|
||||
B_SaveBoxBin.UseVisualStyleBackColor = true;
|
||||
B_SaveBoxBin.Click += B_SaveBoxBin_Click;
|
||||
|
@ -870,7 +872,7 @@ namespace PKHeX.WinForms.Controls
|
|||
B_VerifySaveEntities.Margin = new System.Windows.Forms.Padding(0);
|
||||
B_VerifySaveEntities.Name = "B_VerifySaveEntities";
|
||||
B_VerifySaveEntities.Size = new System.Drawing.Size(88, 48);
|
||||
B_VerifySaveEntities.TabIndex = 104;
|
||||
B_VerifySaveEntities.TabIndex = 3;
|
||||
B_VerifySaveEntities.Text = "Verify All PKMs";
|
||||
B_VerifySaveEntities.UseVisualStyleBackColor = true;
|
||||
B_VerifySaveEntities.Click += ClickVerifyStoredEntities;
|
||||
|
@ -881,7 +883,7 @@ namespace PKHeX.WinForms.Controls
|
|||
Menu_ExportBAK.Margin = new System.Windows.Forms.Padding(0);
|
||||
Menu_ExportBAK.Name = "Menu_ExportBAK";
|
||||
Menu_ExportBAK.Size = new System.Drawing.Size(88, 48);
|
||||
Menu_ExportBAK.TabIndex = 103;
|
||||
Menu_ExportBAK.TabIndex = 4;
|
||||
Menu_ExportBAK.Text = "Export Backup";
|
||||
Menu_ExportBAK.UseVisualStyleBackColor = true;
|
||||
Menu_ExportBAK.Click += Menu_ExportBAK_Click;
|
||||
|
@ -892,7 +894,7 @@ namespace PKHeX.WinForms.Controls
|
|||
B_JPEG.Margin = new System.Windows.Forms.Padding(0);
|
||||
B_JPEG.Name = "B_JPEG";
|
||||
B_JPEG.Size = new System.Drawing.Size(88, 48);
|
||||
B_JPEG.TabIndex = 12;
|
||||
B_JPEG.TabIndex = 5;
|
||||
B_JPEG.Text = "Save PGL .JPEG";
|
||||
B_JPEG.UseVisualStyleBackColor = true;
|
||||
B_JPEG.Click += B_JPEG_Click;
|
||||
|
@ -982,6 +984,17 @@ namespace PKHeX.WinForms.Controls
|
|||
TB_GameSync.TabIndex = 10;
|
||||
TB_GameSync.Validated += UpdateStringSeed;
|
||||
//
|
||||
// B_ConvertKorean
|
||||
//
|
||||
B_ConvertKorean.Location = new System.Drawing.Point(0, 48);
|
||||
B_ConvertKorean.Margin = new System.Windows.Forms.Padding(0);
|
||||
B_ConvertKorean.Name = "B_ConvertKorean";
|
||||
B_ConvertKorean.Size = new System.Drawing.Size(88, 48);
|
||||
B_ConvertKorean.TabIndex = 6;
|
||||
B_ConvertKorean.Text = "Korean Save Conversion";
|
||||
B_ConvertKorean.UseVisualStyleBackColor = true;
|
||||
B_ConvertKorean.Click += B_ConvertKorean_Click;
|
||||
//
|
||||
// SAVEditor
|
||||
//
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
|
||||
|
@ -1081,5 +1094,6 @@ namespace PKHeX.WinForms.Controls
|
|||
private System.Windows.Forms.Button B_Poffins;
|
||||
private System.Windows.Forms.Button B_VerifySaveEntities;
|
||||
private System.Windows.Forms.Button B_RaidsSevenStar;
|
||||
private System.Windows.Forms.Button B_ConvertKorean;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -739,6 +739,19 @@ public partial class SAVEditor : UserControl, ISlotViewer<PictureBox>, ISaveFile
|
|||
File.WriteAllBytes(sfd.FileName, jpeg);
|
||||
}
|
||||
|
||||
private void B_ConvertKorean_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (SAV.Generation != 4)
|
||||
return;
|
||||
var s4 = (SAV4)SAV;
|
||||
var isKorean = s4.Magic == SAV4.MAGIC_KOREAN;
|
||||
var msg = isKorean ? MsgSaveGen4ConvertInternational : MsgSaveGen4ConvertKorean;
|
||||
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg))
|
||||
return;
|
||||
s4.Magic = isKorean ? SAV4.MAGIC_JAPAN_INTL : SAV4.MAGIC_KOREAN;
|
||||
SAV.State.Edited = true;
|
||||
}
|
||||
|
||||
private void ClickVerifyCHK(object sender, EventArgs e)
|
||||
{
|
||||
if (SAV.State.Edited)
|
||||
|
@ -1113,11 +1126,13 @@ public partial class SAVEditor : UserControl, ISlotViewer<PictureBox>, ISaveFile
|
|||
{
|
||||
FLP_SAVtools.Visible = false;
|
||||
B_JPEG.Visible = false;
|
||||
B_ConvertKorean.Visible = false;
|
||||
SL_Extra.HideAllSlots();
|
||||
return;
|
||||
}
|
||||
|
||||
GB_Daycare.Visible = sav.HasDaycare;
|
||||
B_ConvertKorean.Visible = sav is SAV4;
|
||||
B_OpenPokeblocks.Visible = sav is SAV6AO;
|
||||
B_OpenSecretBase.Visible = sav is SAV6AO;
|
||||
B_OpenPokepuffs.Visible = sav is ISaveBlock6Main;
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C-Gear Skin
|
|||
Main.B_Clear=Löschen
|
||||
Main.B_FestivalPlaza=Festival-Plaza
|
||||
Main.B_JPEG=Speichere PGL .JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=Briefbox
|
||||
Main.B_MoveShop=Attacken Tutor
|
||||
Main.B_OpenApricorn=Aprikokos
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C-Gear Skin
|
|||
Main.B_Clear=Clear
|
||||
Main.B_FestivalPlaza=Festival Plaza
|
||||
Main.B_JPEG=Save PGL .JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=Mail Box
|
||||
Main.B_MoveShop=Move Shop
|
||||
Main.B_OpenApricorn=Apricorns
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C-Gear
|
|||
Main.B_Clear=Limpiar
|
||||
Main.B_FestivalPlaza=Festi Plaza
|
||||
Main.B_JPEG=Guardar PGL .JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=Correo
|
||||
Main.B_MoveShop=Tienda Movs.
|
||||
Main.B_OpenApricorn=Bonguri
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=Fonds C-Gear
|
|||
Main.B_Clear=Effacer
|
||||
Main.B_FestivalPlaza=Place Festival
|
||||
Main.B_JPEG=Sauver image PGL
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=Boîte aux lettres
|
||||
Main.B_MoveShop=Move Shop
|
||||
Main.B_OpenApricorn=Noigrumes
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C-Gear Skin
|
|||
Main.B_Clear=Pulisci
|
||||
Main.B_FestivalPlaza=Festiplaza
|
||||
Main.B_JPEG=Salva PGL .JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=Messaggi
|
||||
Main.B_MoveShop=Negozio Mosse
|
||||
Main.B_OpenApricorn=Ghicocche
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=Cギア スキン
|
|||
Main.B_Clear=Clear
|
||||
Main.B_FestivalPlaza=フェスサークル
|
||||
Main.B_JPEG=PGL 画像保存
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=メールボックス
|
||||
Main.B_MoveShop=Move Shop
|
||||
Main.B_OpenApricorn=ぼんぐりのみ
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C기어 스킨
|
|||
Main.B_Clear=지우기
|
||||
Main.B_FestivalPlaza=페스서클
|
||||
Main.B_JPEG=PGL .JPEG 저장
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=메일박스
|
||||
Main.B_MoveShop=Move Shop
|
||||
Main.B_OpenApricorn=규토리
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C装置皮肤
|
|||
Main.B_Clear=清理
|
||||
Main.B_FestivalPlaza=圆庆广场
|
||||
Main.B_JPEG=保存PGL.JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=邮箱
|
||||
Main.B_MoveShop=招式商店
|
||||
Main.B_OpenApricorn=球果
|
||||
|
|
|
@ -177,6 +177,7 @@ Main.B_CGearSkin=C裝置皮膚
|
|||
Main.B_Clear=清理
|
||||
Main.B_FestivalPlaza=圓慶廣場
|
||||
Main.B_JPEG=儲存PGL.JPEG
|
||||
Main.B_ConvertKorean=Korean Save Conversion
|
||||
Main.B_MailBox=郵箱
|
||||
Main.B_MoveShop=招式商店
|
||||
Main.B_OpenApricorn=果球
|
||||
|
|
|
@ -46,7 +46,7 @@ public partial class SAV_Misc4 : Form
|
|||
new[] { 2, 0, 0x696C, 0x10, 0x7F00 },
|
||||
new[] { 0, 0, 0x699C, 0x04, 0x7F04 },
|
||||
};
|
||||
Hall = FetchHallBlock(SAV, 0x2820);
|
||||
Hall = SAV.GetHall();
|
||||
break;
|
||||
case GameVersion.HG or GameVersion.SS or GameVersion.HGSS:
|
||||
ofsFlag = 0x10C4;
|
||||
|
@ -63,7 +63,7 @@ public partial class SAV_Misc4 : Form
|
|||
new[] { 2, 0, 0x52F0, 0x10, 0x6884 },
|
||||
new[] { 0, 0, 0x5320, 0x04, 0x6888 },
|
||||
};
|
||||
Hall = FetchHallBlock(SAV, 0x230C);
|
||||
Hall = SAV.GetHall();
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
@ -514,31 +514,6 @@ public partial class SAV_Misc4 : Form
|
|||
CB_Stats1.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private static Hall4? FetchHallBlock(SAV4 sav, int magicKeyOffset)
|
||||
{
|
||||
for (int i = 0; i < 2; i++, magicKeyOffset += 0x14)
|
||||
{
|
||||
var h = ReadInt32LittleEndian(sav.General[magicKeyOffset..]);
|
||||
if (h == -1)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < 0x20; j++)
|
||||
{
|
||||
for (int k = 0, a = (j + 0x20) << 12; k < 2; k++, a += 0x40000)
|
||||
{
|
||||
var span = sav.Data.AsSpan(a);
|
||||
if (h != ReadInt32LittleEndian(span))
|
||||
continue;
|
||||
if (ReadInt16LittleEndian(span[0xBA8..]) != 0xBA0)
|
||||
continue;
|
||||
return new Hall4(sav.Data, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SaveBattleFrontier()
|
||||
{
|
||||
if (ofsPrints > 0)
|
||||
|
|
Loading…
Add table
Reference in a new issue