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
This commit is contained in:
Kurt 2018-01-13 16:32:57 -08:00
parent 029539003b
commit 6caefef181
3 changed files with 91 additions and 62 deletions

View File

@ -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;
}
}
}
}

View File

@ -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)

View File

@ -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)