pk3DS/pk3DS.WinForms/Subforms/ShinyRate.cs
2024-06-02 18:56:55 -05:00

170 lines
5.6 KiB
C#

using pk3DS.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
namespace pk3DS.WinForms;
public partial class ShinyRate : Form
{
public ShinyRate()
{
InitializeComponent();
if (Main.ExeFSPath == null) { WinFormsUtil.Alert("No exeFS code to load."); Close(); }
string[] files = Directory.GetFiles(Main.ExeFSPath);
if (!File.Exists(files[0]) || !Path.GetFileNameWithoutExtension(files[0]).Contains("code")) { WinFormsUtil.Alert("No .code.bin detected."); Close(); }
codebin = files[0];
exefsData = File.ReadAllBytes(codebin);
if (exefsData.Length % 0x200 != 0) { WinFormsUtil.Alert(".code.bin not decompressed. Aborting."); Close(); }
// Load instruction set
byte[] raw = Core.Properties.Resources.asm_mov;
for (int i = 0; i < raw.Length; i += 4)
{
byte[] data = new byte[2];
Array.Copy(raw, i + 2, data, 0, 2);
InstructionList.Add(new Instruction(BitConverter.ToUInt16(raw, i), data));
}
// Fetch Offset
byte[] pattern = [0x01, 0x50, 0x85, 0xE2, 0x05, 0x00, 0x50, 0xE1, 0xDE, 0xFF, 0xFF, 0xCA];
offset = Util.IndexOfBytes(exefsData, pattern, 0, 0) - 4;
if (offset < 0)
{
WinFormsUtil.Alert("Unable to find PID Generation routine.", "Closing.");
Close();
}
if (exefsData[offset] != 0x23) // already patched
{
uint val = BitConverter.ToUInt16(exefsData, offset);
var instruction = InstructionList.Find(z => z.ArgVal == val);
if (instruction == null)
{
WinFormsUtil.Alert(".code.bin was modified externally.", "Existing value not loaded.");
}
else
{
WinFormsUtil.Alert(".code.bin was already patched for shiny rate.", "Loaded existing value.");
NUD_Rerolls.Value = instruction.Value;
}
modified = true;
}
ChangeRerolls(null, null);
CheckAlwaysShiny();
}
private readonly List<Instruction> InstructionList = [];
private readonly bool modified;
private readonly string codebin;
private readonly int offset;
private readonly byte[] exefsData;
private class Instruction
{
public readonly int Value;
private readonly byte[] Argument;
public readonly ushort ArgVal;
public Instruction(int val, byte[] arg)
{
Value = val;
Argument = arg;
ArgVal = BitConverter.ToUInt16(Argument, 0);
}
public byte[] Bytes
{
get
{
var bytes = new byte[] { 0, 0, 0xA0, 0xE3 };
Argument.CopyTo(bytes, 0);
return bytes;
}
}
}
private int alwaysIndex;
private void CheckAlwaysShiny()
{
byte[] pattern = [0x00, 0x20, 0x22, 0xE0, 0x02, 0x30, 0x21, 0xE2, 0x03, 0x20, 0x92, 0xE1, 0x1C, 0x00, 0x00];
int index = alwaysIndex = Util.IndexOfBytes(exefsData, pattern, 0, 0) + pattern.Length;
if (index < 0)
{
CHK_EverythingShiny.Enabled = CHK_EverythingShiny.Visible = false;
return;
}
bool original = exefsData[index] == 0x0A;
bool always = exefsData[index] == 0xEA;
if (!original && !always) // oh no
{
CHK_EverythingShiny.Enabled = CHK_EverythingShiny.Visible = false;
return;
}
CHK_EverythingShiny.Checked = always;
}
private void B_Cancel_Click(object sender, EventArgs e) => Close();
private void B_Save_Click(object sender, EventArgs e)
{
WriteCodePatch();
if (CHK_EverythingShiny.Enabled)
exefsData[alwaysIndex] = CHK_EverythingShiny.Checked ? (byte)0xEA : (byte)0x0A;
File.WriteAllBytes(codebin, exefsData);
Close();
}
private void ChangeRerolls(object sender, EventArgs e)
{
int count = (int)NUD_Rerolls.Value;
const int bc = 4096;
var pct = 1 - Math.Pow((float)(bc - 1) / bc, count);
L_Overall.Text = $"~{pct:P}";
}
private void WriteCodePatch()
{
// Overwrite the "load input argument value for reroll count" so that it loads a constant value.
// 23 00 D4 E5 is then replaced with the instruction MOV R0, $value
// $value is the amount of PID rerolls to iterate for.
int rerolls = (int)NUD_Rerolls.Value;
if (rerolls > ushort.MaxValue)
rerolls = ushort.MaxValue;
// lazy precomputed table for MOV0 up to 9000, lol
var instruction = InstructionList.Find(z => z.Value >= rerolls) ?? InstructionList[^1];
byte[] data = instruction.Bytes;
data.CopyTo(exefsData, offset);
if (instruction.Value != rerolls)
WinFormsUtil.Alert("Specified reroll count increased to the next highest supported value.", $"{rerolls} -> {instruction.Value}");
}
private void B_RestoreOriginal_Click(object sender, EventArgs e)
{
if (modified)
{
new byte[] { 0x23, 0x00, 0xD4, 0xE5 }.CopyTo(exefsData, offset);
File.WriteAllBytes(codebin, exefsData);
}
Close();
}
private void ChangePercent(object sender, EventArgs e)
{
var pct = NUD_Rate.Value;
const int bc = 4096;
var inv = (int)Math.Log(1 - ((float)pct / 100), (float)(bc - 1) / bc);
if (pct == 0)
pct = 0.00001m; // arbitrary nonzero
L_RerollCount.Text = $"Count: {inv:0} = 1:{(int)(1 / (pct / 100))}";
}
}