Cleanup of data and assembly patches

Now encapsulates patched memory differently to Cheat Engine which
doesn't actually support auto-assembly like I assumed
This commit is contained in:
x1nixmzeng 2018-04-18 00:25:45 +01:00
parent 5012078a35
commit e0e2c434d2
7 changed files with 746 additions and 627 deletions

View File

@ -1,210 +0,0 @@
// Written by x1nixmzeng for the Cxbx-Reloaded project
//
using System;
using System.Collections.Generic;
using CxbxDebugger.CheatEngine;
namespace CxbxDebugger
{
class CheatEngineManager
{
public CheatTable Cheats;
public bool RefreshAll(DebuggerProcess Process)
{
bool Changed = false;
for (int i = 0; i < Cheats.CheatEntries.Count; ++i)
{
CheatEntry Entry = Cheats.CheatEntries[i];
//if (Entry.LastState.Activated)
{
string last = Entry.LastState.Value;
Entry.LastState.Value = Read(Process, i);
if(Entry.LastState.Value != last )
{
Changed = true;
}
Cheats.CheatEntries[i] = Entry;
}
}
return Changed;
}
public string Read(DebuggerProcess Process, int CheatIndex)
{
string printed_value = "??";
CheatEntry entry = Cheats.CheatEntries[CheatIndex];
int addr = 0;
if (!ReadHexInt(entry.Address, ref addr))
{
return printed_value;
}
switch (entry.VariableType)
{
case Variable.Byte:
{
var data = Process.ReadMemoryBlock(new IntPtr(addr), 1);
if (data != null)
{
if (entry.ShowAsHex)
{
printed_value = string.Format("0x{0:x}", data[0]);
}
else
{
printed_value = string.Format("{0}", data[0]);
}
}
break;
}
case Variable.Bytes2:
{
var data = Process.ReadMemoryBlock(new IntPtr(addr), 2);
if (data != null)
{
ushort val = BitConverter.ToUInt16(data, 0);
if (entry.ShowAsHex)
{
printed_value = string.Format("0x{0:x}", val);
}
else
{
printed_value = string.Format("{0}", val);
}
}
break;
}
case Variable.Bytes4:
{
var data = Process.ReadMemoryBlock(new IntPtr(addr), 4);
if (data != null)
{
uint val = BitConverter.ToUInt32(data, 0);
if (entry.ShowAsHex)
{
printed_value = string.Format("0x{0:x}", val);
}
else
{
printed_value = string.Format("{0}", val);
}
}
break;
}
case Variable.Float:
{
var data = Process.ReadMemoryBlock(new IntPtr(addr), 4);
if (data != null)
{
if (entry.ShowAsHex)
{
uint val = BitConverter.ToUInt32(data, 0);
printed_value = string.Format("{0:x8}", val);
}
else
{
float flt = BitConverter.ToSingle(data, 0);
printed_value = string.Format("{0}", flt);
}
}
break;
}
}
return printed_value;
}
public void Write(DebuggerProcess Process, int CheatIndex, string Value)
{
CheatEntry entry = Cheats.CheatEntries[CheatIndex];
int addr = 0;
if (!ReadHexInt(entry.Address, ref addr))
{
return;
}
switch (entry.VariableType)
{
case Variable.Byte:
{
uint byte_val = 0;
ReadAddress(Value, ref byte_val);
byte val = (byte)(byte_val & 0xFF);
Process.WriteMemory(new IntPtr(addr), val);
break;
}
case Variable.Float:
{
float flt_val = 0.0f;
ReadFloat(Value, ref flt_val);
Process.WriteMemory(new IntPtr(addr), flt_val);
break;
}
}
}
static private bool ReadHexInt(string HexSource, ref int Out)
{
if (int.TryParse(HexSource, System.Globalization.NumberStyles.HexNumber, null, out Out))
{
return true;
}
return false;
}
static private bool ReadAddress(string Source, ref uint Out)
{
try
{
if (Source.StartsWith("0x"))
{
Out = Convert.ToUInt32(Source.Substring(2), 16);
return true;
}
else
{
if (uint.TryParse(Source, out Out))
{
return true;
}
}
}
catch (Exception) { }
return false;
}
static private bool ReadFloat(string Source, ref float Out)
{
return float.TryParse(Source, out Out);
}
}
}

View File

@ -67,7 +67,6 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CheatEngineManager.cs" />
<Compile Include="DebuggerExtras\CheatTable.cs" />
<Compile Include="DebuggerExtras\CheatTableReader.cs" />
<Compile Include="DebuggerSymbols\Kernel\KernelSymbolProvider.cs" />
@ -93,6 +92,7 @@
<Compile Include="DebuggerSymbols\HLECache\HLECacheFile.cs" />
<Compile Include="DebuggerSymbols\HLECache\HLECacheProvider.cs" />
<Compile Include="DebuggerSymbols\HLECache\Utils\INIReader.cs" />
<Compile Include="PatchManager.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RicherTextBox.cs">

View File

@ -225,6 +225,18 @@ namespace CxbxDebugger
Data = new byte[] { (byte)GenericValue };
break;
case TypeCode.Int16:
Data = BitConverter.GetBytes((short)GenericValue);
break;
case TypeCode.UInt16:
Data = BitConverter.GetBytes((ushort)GenericValue);
break;
case TypeCode.Int32:
Data = BitConverter.GetBytes((int)GenericValue);
break;
case TypeCode.UInt32:
Data = BitConverter.GetBytes((uint)GenericValue);
break;
@ -242,5 +254,13 @@ namespace CxbxDebugger
WriteMemoryInternal(Address, Data);
}
}
public bool WriteMemoryBlock(IntPtr Address, byte[] Data)
{
if (Address == IntPtr.Zero)
return false;
return WriteMemoryInternal(Address, Data);
}
}
}

View File

@ -17,13 +17,11 @@ namespace CxbxDebugger
public enum Variable
{
Unsupported,
Binary,
Byte,
Bytes2,
Bytes4,
Float,
Double,
String,
}
public struct CheatEntry
@ -43,9 +41,9 @@ namespace CxbxDebugger
public uint Address;
public string ModuleName;
public uint ModuleNameOffset;
public byte[] Before;
public byte[] Actual;
public byte[] After;
public byte[] Before; // 5 bytes before 'Actual'
public byte[] Actual; // with a length of the opcode
public byte[] After; // 5 bytes after 'Actual'
};
public class CheatTable
@ -61,5 +59,29 @@ namespace CxbxDebugger
CodeEntires = new List<CodeEntry>();
}
};
class Helpers
{
public static int VariableSize(Variable Var)
{
switch (Var)
{
case Variable.Unsupported:
return 0;
case Variable.Byte:
return 1;
case Variable.Bytes2:
return 2;
case Variable.Bytes4:
return 4;
case Variable.Float:
return 4;
case Variable.Double:
return 8;
}
return 0;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using cs_x86;
using CxbxDebugger.CheatEngine;
namespace CxbxDebugger
{
@ -23,10 +22,11 @@ namespace CxbxDebugger
List<DebuggerThread> DebugThreads = new List<DebuggerThread>();
List<DebuggerModule> DebugModules = new List<DebuggerModule>();
DebuggerProcess MainProcess = null;
FileWatchManager fileWatchMan;
DebugOutputManager debugStrMan;
CheatEngineManager cheatMan;
PatchManager patchMan;
List<DebuggerMessages.FileOpened> FileHandles = new List<DebuggerMessages.FileOpened>();
@ -65,17 +65,17 @@ namespace CxbxDebugger
cbAction.SelectedIndex = 0;
foreach (string VariableEnum in Enum.GetNames(typeof(Variable)))
foreach (string VariableEnum in Enum.GetNames(typeof(PatchType)))
{
cbDataFormat.Items.Add(VariableEnum);
}
// Default to byte
cbDataFormat.SelectedIndex = 2;
cbDataFormat.SelectedIndex = 0;
InvokePatchTypeChange();
fileWatchMan = new FileWatchManager(clbBreakpoints);
debugStrMan = new DebugOutputManager(lbDebug);
cheatMan = new CheatEngineManager();
patchMan = new PatchManager();
}
private void OnDisassemblyNavigation(object sender, InlineLinkClickedEventArgs e)
@ -105,6 +105,7 @@ namespace CxbxDebugger
// Create debugger instance
DebuggerInst = new Debugger(CachedArgs);
DebuggerInst.RegisterEventInterfaces(DebugEvents);
DebuggerInst.RegisterEventInterfaces(patchMan);
// Setup new debugger thread
DebuggerWorkerThread = new Thread(x =>
@ -124,7 +125,7 @@ namespace CxbxDebugger
{
cbItems.Items.Clear();
uint AutoThreadId= DebugThreads[0].OwningProcess.MainThread.ThreadID;
uint AutoThreadId = DebugThreads[0].OwningProcess.MainThread.ThreadID;
if (FocusThread != null)
AutoThreadId = FocusThread.ThreadID;
@ -292,7 +293,8 @@ namespace CxbxDebugger
Text = string.Format("{0} - Cxbx-Reloaded Debugger", CachedTitle);
LoadCheatTable(string.Format("{0}.ct", CachedTitle));
// This is done too late - modules are already loaded
//LoadCheatTable(string.Format("{0}.ct", CachedTitle));
}));
}
@ -378,6 +380,7 @@ namespace CxbxDebugger
public void OnProcessCreate(DebuggerProcess Process)
{
frm.DebugModules.Add(Process);
frm.MainProcess = Process;
}
public void OnProcessExit(DebuggerProcess Process, uint ExitCode)
@ -609,6 +612,11 @@ namespace CxbxDebugger
static private bool ReadHexInt(string HexSource, ref int Out)
{
if (HexSource.StartsWith("0x"))
{
HexSource = HexSource.Substring(2);
}
if (int.TryParse(HexSource, System.Globalization.NumberStyles.HexNumber, null, out Out))
{
return true;
@ -1052,48 +1060,68 @@ namespace CxbxDebugger
HandleDisasmGo();
}
private static System.Drawing.Color MakeColor(uint RGB)
{
int r = (int)(RGB & 0xFF);
int g = (int)((RGB >> 8) & 0xFF);
int b = (int)((RGB >> 16) & 0xFF);
return System.Drawing.Color.FromArgb(r, g, b);
}
private void RefreshCheatTableDisplay()
private void RefreshPatches()
{
lvCEMemory.BeginUpdate();
lvCEMemory.Items.Clear();
foreach (var code in cheatMan.Cheats.CheatEntries)
foreach (Patch DataPatch in patchMan.Data)
{
var li = lvCEMemory.Items.Add("");
li.SubItems.Add(code.Description);
li.SubItems.Add(string.Format("{0:x8}", code.Address));
li.SubItems.Add(code.VariableType.ToString());
li.SubItems.Add(code.LastState.Value);
li.Checked = code.LastState.Activated;
li.ForeColor = MakeColor(code.Color);
var li = lvCEMemory.Items.Add(string.Format("{0} 0x{1:x}", DataPatch.Module, DataPatch.Offset));
li.SubItems.Add(DataPatch.Name);
li.SubItems.Add(string.Format("{0} byte(s)", DataPatch.Patched.Length));
if (MainProcess != null)
{
li.SubItems.Add(patchMan.Read(MainProcess, DataPatch));
}
else
{
li.SubItems.Add("??");
}
}
lvCEMemory.EndUpdate();
// Code entries are not supported at the moment
lvCEAssembly.BeginUpdate();
lvCEAssembly.Items.Clear();
foreach (var code in cheatMan.Cheats.CodeEntires)
foreach (Patch patch in patchMan.Assembly)
{
var li = lvCEAssembly.Items.Add(string.Format("{0} +{1:X}", code.ModuleName, code.ModuleNameOffset));
li.SubItems.Add(code.Description);
var li = lvCEAssembly.Items.Add(string.Format("{0} 0x{1:x}", patch.Module, patch.Offset));
li.SubItems.Add(patch.Name);
li.SubItems.Add(PrettyPrint(patch.Original));
li.SubItems.Add(PrettyPrint(patch.Patched));
}
lvCEAssembly.EndUpdate();
}
static private byte[] Fill(int Count, byte Val)
{
List<byte> ByteList = new List<byte>(Count);
for (int i = 0; i < Count; ++i) ByteList.Add(Val);
return ByteList.ToArray();
}
static private byte[] Nops(int Count)
{
return Fill(Count, 0x90);
}
static private string PrettyPrint(byte[] Bytes)
{
if (Bytes == null)
return "??";
string Str = "";
int Max = Math.Min(5, Bytes.Length);
for (int i = 0; i < Max; ++i)
{
Str += string.Format("{0:X2} ", Bytes[i]);
}
return Str;
}
private void LoadCheatTable(string filename)
{
string path = Directory.GetCurrentDirectory();
@ -1101,73 +1129,81 @@ namespace CxbxDebugger
if (File.Exists(filename))
{
DebugLog(string.Format("Attempting to load \"{0}\"", filename));
CheatTable ct_data = CheatTableReader.FromFile(filename);
CheatEngine.CheatTable ct_data = CheatEngine.CheatTableReader.FromFile(filename);
if (ct_data != null)
{
cheatMan.Cheats = ct_data;
foreach(CheatEngine.CheatEntry Entry in ct_data.CheatEntries)
{
int addr = 0;
if(ReadHexInt(Entry.Address, ref addr))
{
Patch DataPatch = new Patch();
DataPatch.DisplayAs = PatchType.Array;
DataPatch.Name = Entry.Description;
DataPatch.Module = "";
DataPatch.Offset = (uint)addr;
DataPatch.Original = null;
DataPatch.Patched = Nops(CheatEngine.Helpers.VariableSize(Entry.VariableType));
patchMan.Data.Add(DataPatch);
}
}
foreach(CheatEngine.CodeEntry Entry in ct_data.CodeEntires)
{
Patch DataPatch = new Patch();
DataPatch.DisplayAs = PatchType.Array;
DataPatch.Name = Entry.Description;
DataPatch.Module = Entry.ModuleName;
DataPatch.Offset = Entry.ModuleNameOffset;
DataPatch.Original = Entry.Actual;
DataPatch.Patched = Nops(Entry.Actual.Length);
patchMan.Assembly.Add(DataPatch);
}
DebugLog(string.Format("Loaded {0} auto-assembler entries", ct_data.CodeEntires.Count));
DebugLog(string.Format("Loaded {0} cheat entries", ct_data.CheatEntries.Count));
RefreshPatches();
}
}
}
private void button2_Click_1(object sender, EventArgs e)
{
if( diagBrowseCT.ShowDialog() == DialogResult.OK)
{
string filename = diagBrowseCT.FileNames[0];
LoadCheatTable(filename);
}
}
private void RefreshCEMemory()
{
if (DebugThreads.Count == 0)
return;
if (cheatMan.RefreshAll(DebugThreads[0].OwningProcess))
{
RefreshCheatTableDisplay();
}
}
private void button3_Click_1(object sender, EventArgs e)
{
if (DebugThreads.Count == 0)
return;
if (lvCEMemory.SelectedIndices.Count == 1)
{
int selected = lvCEMemory.SelectedIndices[0];
cheatMan.Write(DebugThreads[0].OwningProcess, selected, textBox1.Text);
}
}
private void button4_Click_1(object sender, EventArgs e)
{
RefreshCEMemory();
}
private void button5_Click(object sender, EventArgs e)
{
CheatEntry ce = new CheatEntry();
PatchType PatchType = (PatchType)cbDataFormat.SelectedIndex;
ce.ID = 0;
ce.Description = "Automatic";
ce.ShowAsHex = false;
ce.LastState.Activated = false;
ce.LastState.RealAddress = 0;
ce.LastState.Value = "";
ce.VariableType = (Variable)cbDataFormat.SelectedIndex;
if(txAddress.Text.StartsWith("0x"))
int PatchSize = PatchManager.PatchTypeLength(PatchType);
if(PatchSize == 0 )
{
ce.Address = txAddress.Text.Substring(2);
}
if (!ReadInt(textBox2, ref PatchSize))
return;
cheatMan.Cheats.CheatEntries.Add(ce);
if (PatchSize < 0)
return;
}
RefreshCEMemory();
int addr = 0;
if(ReadHexInt(txAddress.Text, ref addr))
{
Patch DataPatch = new Patch();
DataPatch.DisplayAs = PatchType;
DataPatch.Name = string.Format("Patched {0}", cbDataFormat.SelectedText);
DataPatch.Module = "";
DataPatch.Offset = (uint)addr;
// TODO: Read original memory at this location
DataPatch.Original = Nops(PatchSize);
DataPatch.Patched = Nops(PatchSize);
patchMan.Data.Add(DataPatch);
RefreshPatches();
}
}
private void button6_Click(object sender, EventArgs e)
@ -1179,5 +1215,48 @@ namespace CxbxDebugger
tabContainer.SelectedTab = tabMemory;
}
}
private void button2_Click_2(object sender, EventArgs e)
{
if (diagBrowseCT.ShowDialog() == DialogResult.OK)
{
string filename = diagBrowseCT.FileNames[0];
LoadCheatTable(filename);
}
}
private void button1_Click_1(object sender, EventArgs e)
{
RefreshPatches();
}
private void InvokePatchTypeChange()
{
PatchType Type = (PatchType)cbDataFormat.SelectedIndex;
textBox2.Text = PatchManager.PatchTypeLength(Type).ToString();
textBox2.Enabled = (Type == PatchType.Array);
}
private void cbDataFormat_SelectionChangeCommitted(object sender, EventArgs e)
{
InvokePatchTypeChange();
}
private void button3_Click_1(object sender, EventArgs e)
{
if (lvCEMemory.SelectedIndices.Count != 1)
return;
string Value = txNewValue.Text;
if(Value.Length != 0 )
{
Patch DataPatch = patchMan.Data[lvCEMemory.SelectedIndices[0]];
if( patchMan.Write(MainProcess, DataPatch, Value))
{
RefreshPatches();
}
}
}
}
}

View File

@ -0,0 +1,165 @@
// Written by x1nixmzeng for the Cxbx-Reloaded project
//
using System;
using System.Collections.Generic;
using System.IO;
namespace CxbxDebugger
{
enum PatchType
{
Byte,
Short,
UShort,
Int,
UInt,
Float,
Array
};
struct Patch
{
public PatchType DisplayAs { get; set; }
public string Name { get; set; }
public string Module { get; set; }
public uint Offset { get; set; }
public byte[] Original { get; set; }
public byte[] Patched { get; set; }
};
class PatchManager : IDebuggerProcessEvents, IDebuggerModuleEvents
{
public List<Patch> Data = new List<Patch>();
public List<Patch> Assembly = new List<Patch>();
public void OnProcessCreate(DebuggerProcess Process)
{
// TODO: apply pending patches
}
public void OnProcessExit(DebuggerProcess Process, uint ExitCode) { }
public void OnModuleLoaded(DebuggerModule Module)
{
// TODO: apply pending patches
}
public void OnModuleUnloaded(DebuggerModule Module) { }
static int PatchSize(Patch PatchItem)
{
if (PatchItem.Original != null)
return PatchItem.Original.Length;
if (PatchItem.Patched != null)
return PatchItem.Patched.Length;
return PatchTypeLength(PatchItem.DisplayAs);
}
private static string Format(PatchType Type, byte[] Data)
{
string Result = "";
switch (Type)
{
case PatchType.Byte:
{
Result = string.Format("{0}", Data[0]);
}
break;
case PatchType.Short:
{
var Value = BitConverter.ToInt16(Data, 0);
Result = string.Format("{0}", Value);
}
break;
case PatchType.UShort:
{
var Value = BitConverter.ToUInt16(Data, 0);
Result = string.Format("{0}", Value);
}
break;
case PatchType.Int:
{
var Value = BitConverter.ToInt32(Data, 0);
Result = string.Format("{0}", Value);
}
break;
case PatchType.UInt:
{
var Value = BitConverter.ToUInt32(Data, 0);
Result = string.Format("{0}", Value);
}
break;
case PatchType.Float:
{
var Value = BitConverter.ToSingle(Data, 0);
Result = string.Format("{0}", Value);
}
break;
case PatchType.Array:
{
foreach (byte B in Data)
{
Result += string.Format("{0:X2} ", B);
}
}
break;
}
return Result;
}
public string Read(DebuggerProcess OwningProcess, Patch PatchItem)
{
int Size = PatchSize(PatchItem);
if (Size == 0)
return "";
byte[] Current = OwningProcess.ReadMemoryBlock(new IntPtr(PatchItem.Offset), (uint)Size);
return Format(PatchItem.DisplayAs, Current);
}
public bool Write(DebuggerProcess OwningProcess, Patch PatchItem, string NewValue)
{
switch (PatchItem.DisplayAs)
{
case PatchType.Byte:
{
byte Value;
if( byte.TryParse(NewValue, out Value) )
{
OwningProcess.WriteMemory(new IntPtr(PatchItem.Offset), Value);
return true;
}
break;
}
}
return false;
}
static public int PatchTypeLength(PatchType Type)
{
switch (Type)
{
case PatchType.Byte:
return 1;
case PatchType.Short:
return 2;
case PatchType.UShort:
return 2;
case PatchType.Int:
return 4;
case PatchType.UInt:
return 4;
case PatchType.Float:
return 4;
}
return 0;
}
}
}