From 6caefef181bd2f96796ef3e5da3c9c97aa27bff0 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 13 Jan 2018 16:32:57 -0800 Subject: [PATCH] Add cgb->psk & misc clarifications Closes #1699 tested on black2 & white, skin isn't corrupt on game boot and appears correctly still don't like setpixel/System.Drawing reliance (maybe split logic to CGearSkin & CGearSkinVisualizer) for PKHeX.Core compat --- PKHeX.Core/Saves/SAV5.cs | 47 ++++++++++++++++ .../Save Editors/Gen5/CGearBackground.cs | 56 ++++++++++++------- .../Save Editors/Gen5/SAV_CGearSkin.cs | 50 +++-------------- 3 files changed, 91 insertions(+), 62 deletions(-) diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index 4196157b0..8172712e3 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -728,5 +728,52 @@ public override byte[] SetString(string value, int maxLength, int PadToSize = 0, PadToSize = maxLength + 1; return StringConverter.SetString5(value, maxLength, PadToSize, PadWith); } + + + // DLC + private int CGearSkinInfoOffset => CGearInfoOffset + (B2W2 ? 0x10 : 0) + 0x24; + private bool CGearSkinPresent + { + get => Data[CGearSkinInfoOffset + 2] == 1; + set => Data[CGearSkinInfoOffset + 2] = Data[Trainer1 + (B2W2 ? 0x6C : 0x54)] = (byte) (value ? 1 : 0); + } + public byte[] CGearSkinData + { + get + { + byte[] data = new byte[0x2600]; + if (CGearSkinPresent) + Array.Copy(Data, CGearDataOffset, data, 0, data.Length); + return data; + } + set + { + if (value == null) + return; // no clearing + byte[] dlcfooter = { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; + + byte[] bgdata = value; + SetData(bgdata, CGearDataOffset); + + ushort chk = SaveUtil.CRC16_CCITT(bgdata); + var chkbytes = BitConverter.GetBytes(chk); + int footer = CGearDataOffset + bgdata.Length; + + BitConverter.GetBytes((ushort)1).CopyTo(Data, footer); // block updated once + chkbytes.CopyTo(Data, footer + 2); // checksum + chkbytes.CopyTo(Data, footer + 0x100); // second checksum + dlcfooter.CopyTo(Data, footer + 0x102); + ushort skinchkval = SaveUtil.CRC16_CCITT(Data, footer + 0x100, 4); + BitConverter.GetBytes(skinchkval).CopyTo(Data, footer + 0x112); + + // Indicate in the save file that data is present + BitConverter.GetBytes((ushort)0xC21E).CopyTo(Data, 0x19438); + + chkbytes.CopyTo(Data, CGearSkinInfoOffset); + CGearSkinPresent = true; + + Edited = true; + } + } } } diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/CGearBackground.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/CGearBackground.cs index 4945d790e..ca608b345 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen5/CGearBackground.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/CGearBackground.cs @@ -30,12 +30,12 @@ public class CGearBackground * The tiles are chosen based on the 16bit index of the tile. * 0x300 * 2 = 0x600! * - * CGearBackgrounds tilemap (when stored) employs odd obfuscation. - * BW obfuscates by adding 0xA0A0, B2W2 adds 0xA000 + * CGearBackgrounds tilemap (when stored on BW) employs some obfuscation. + * BW obfuscates by adding 0xA0A0. * The obfuscated number is then tweaked by adding 15*(i/17) * To reverse, use a similar reverse calculation * PSK files are basically raw game rips (obfuscated) - * CGB files are un-obfuscated. + * CGB files are un-obfuscated / B2W2. * Due to BW and B2W2 using different obfuscation adds, PSK files are incompatible between the versions. */ @@ -46,7 +46,10 @@ public CGearBackground(byte[] data) // decode for easy handling if (!IsCGB(data)) - _psk = data = PSKtoCGB(data); + { + _psk = data; + data = PSKtoCGB(data); + } else _cgb = data; @@ -73,11 +76,11 @@ public CGearBackground(byte[] data) private byte[] _cgb; private byte[] _psk; + private byte[] GetCGB() => _cgb ?? Write(); + private byte[] GetPSK() => _psk ?? CGBtoPSK(Write()); + public byte[] GetSkin(bool B2W2) => B2W2 ? GetCGB() : GetPSK(); - public byte[] GetCGB() => _cgb ?? Write(); - public byte[] GetPSK(bool B2W2) => _psk ?? CGBtoPSK(Write(), B2W2); - - public byte[] Write() + private byte[] Write() { byte[] data = new byte[SIZE_CGB]; for (int i = 0; i < Tiles.Length; i++) @@ -91,7 +94,7 @@ public byte[] Write() return data; } - public static bool IsCGB(byte[] data) + private static bool IsCGB(byte[] data) { if (data.Length != SIZE_CGB) return false; @@ -102,27 +105,45 @@ public static bool IsCGB(byte[] data) return false; return true; } - public static byte[] CGBtoPSK(byte[] cgb, bool B2W2) + private static byte[] CGBtoPSK(byte[] cgb) { byte[] psk = (byte[])cgb.Clone(); - int shiftVal = B2W2 ? 0xA000 : 0xA0A0; for (int i = 0x2000; i < 0x2600; i += 2) { - int index = BitConverter.ToUInt16(cgb, i); - int val = IndexToVal(index, shiftVal); + var tileVal = BitConverter.ToUInt16(cgb, i); + int val = GetPSKValue(tileVal); + psk[i] = (byte)val; psk[i + 1] = (byte)(val >> 8); } return psk; } - public static byte[] PSKtoCGB(byte[] psk) + private static int GetPSKValue(ushort val) + { + int rot = val & 0xFF00; + int tile = val & 0x00FF; + if (tile == 0xFF) // invalid tile? + tile = 0; + + int result = tile + 15 * (tile / 17) + + 0xA0A0 + + rot; + return result; + } + private static byte[] PSKtoCGB(byte[] psk) { byte[] cgb = (byte[])psk.Clone(); for (int i = 0x2000; i < 0x2600; i += 2) { int val = BitConverter.ToUInt16(psk, i); int index = ValToIndex(val); - BitConverter.GetBytes((ushort)index).CopyTo(cgb, i); + + byte tile = (byte)index; + byte rot = (byte)(index >> 8); + if (tile == 0xFF) + tile = 0; + cgb[i] = tile; + cgb[i + 1] = rot; } return cgb; } @@ -329,11 +350,6 @@ internal byte[] Write() } } - private static int IndexToVal(int index, int shiftVal) - { - int val = index + shiftVal; - return val + 15*(index/17); - } private static int ValToIndex(int val) { if ((val & 0x3FF) < 0xA0 || (val & 0x3FF) > 0x280) diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs index 1d34e8e39..d845b9bc2 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs @@ -17,14 +17,10 @@ public SAV_CGearSkin(SaveFile sav) SAV = (SAV5)(Origin = sav).Clone(); InitializeComponent(); - bool cgearPresent = SAV.Data[SAV.CGearInfoOffset + 0x26] == 1; - byte[] data = new byte[CGearBackground.SIZE_CGB]; - if (cgearPresent) - Array.Copy(SAV.Data, SAV.CGearDataOffset, data, 0, CGearBackground.SIZE_CGB); + byte[] data = SAV.CGearSkinData; bg = new CGearBackground(data); PB_Background.Image = bg.GetImage(); - WinFormsUtil.Alert("Editor is incomplete.", "No guarantee of functionality."); } private CGearBackground bg; @@ -85,11 +81,6 @@ private void B_ImportCGB_Click(object sender, EventArgs e) } byte[] data = File.ReadAllBytes(path); - LoadBackground(data); - } - - private void LoadBackground(byte[] data) - { bg = new CGearBackground(data); PB_Background.Image = bg.GetImage(); } @@ -104,42 +95,17 @@ private void B_ExportCGB_Click(object sender, EventArgs e) if (sfd.ShowDialog() != DialogResult.OK) return; - byte[] data = bg.GetCGB(); + byte[] data = bg.GetSkin(true); File.WriteAllBytes(sfd.FileName, data); } private void B_Save_Click(object sender, EventArgs e) { - byte[] bgdata = bg.GetPSK(SAV.B2W2); - if (bgdata.SequenceEqual(new byte[CGearBackground.SIZE_CGB])) - return; - - byte[] dlcfooter = { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; - - bgdata.CopyTo(SAV.Data, SAV.CGearDataOffset); - ushort chk = SaveUtil.CRC16_CCITT(bgdata); - var chkbytes = BitConverter.GetBytes(chk); - int footer = SAV.CGearDataOffset + bgdata.Length; - - BitConverter.GetBytes((ushort)1).CopyTo(SAV.Data, footer); // block updated once - chkbytes.CopyTo(SAV.Data, footer + 2); // checksum - chkbytes.CopyTo(SAV.Data, footer + 0x100); // second checksum - - ushort skinchkval = SaveUtil.CRC16_CCITT(SAV.Data, footer + 0x100, 4); - dlcfooter.CopyTo(SAV.Data, footer + 0x104); - BitConverter.GetBytes(skinchkval).CopyTo(SAV.Data, footer + 0x112); - - // Indicate in the save file that data is present - BitConverter.GetBytes((ushort)0xC21E).CopyTo(SAV.Data, 0x19438); - - int info = SAV.CGearInfoOffset + 0x24; - if (SAV.B2W2) - info += 0x10; - chkbytes.CopyTo(SAV.Data, info); - SAV.Data[info + 2] = 1; // data present - int flag = SAV.CGearDataOffset + (SAV.B2W2 ? 0x6C : 0x54); - SAV.Data[flag] = 1; // data present - - Origin.SetData(SAV.Data, 0); + byte[] bgdata = bg.GetSkin(SAV.B2W2); + if (!bgdata.All(z => z == 0)) + { + SAV.CGearSkinData = bgdata; + Origin.SetData(SAV.Data, 0); + } Close(); } private void B_Cancel_Click(object sender, EventArgs e)