Hello, as the title says, I started to research the GameHelper from PoE2 thread for making it work with the new patch, now I'm thinking what if I make a discord and everyone that want to work on this open source will get to learn from each other how this works? What do you think about it? What I learn untill now
Pattern.cs
Code:
namespace GameOffsets
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
public struct Pattern
{
/// <summary>
/// User Friendly name for the pattern.
/// </summary>
public readonly string Name;
/// <summary>
/// Pattern bytes seperated by space or comma or both.
/// Each byte is considered as a HEX. Put ?? or ? in
/// case you don't care of that specific byte. e.g.
/// "0xD2 0xd2 d2, ??, f2". Put '^' in case you want the program
/// to calculate the BytesToSkip.
/// </summary>
public readonly byte[] Data;
/// <summary>
/// Return true if the byte needs to be checked, otherwise
/// returns false (for a wildcard entry).
/// </summary>
public readonly bool[] Mask;
/// <summary>
/// Number of bytes to skip in the offset returned from the pattern
/// in order to reach the static-address offset data. If the input HEX string
/// contains ^ in it, the BytesToSkip is calculated from the it.
/// e.g. "D2 F3 ^ 22 45 23" will put 2 in BytesToSkip.
/// </summary>
public readonly int BytesToSkip;
/// <summary>
/// Parses the Array of bytes in HEX format and converts it into
/// a byte array and a mask (in bool format) array.
/// </summary>
/// <param name="arrayOfHexBytes">Array of bytes in HEX format.</param>
/// <returns>byte array and a mask (bool) array for it.</returns>
private static (byte[], bool[]) ParseArrayOfHexBytes(List<string> arrayOfHexBytes)
{
List<bool> mask = new();
List<byte> data = new();
for (var i = 0; i < arrayOfHexBytes.Count; i++)
{
var hexByte = arrayOfHexBytes[i];
if (hexByte.StartsWith("?"))
{
data.Add(0x00);
mask.Add(false);
}
else
{
data.Add(byte.Parse(hexByte, NumberStyles.HexNumber));
mask.Add(true);
}
}
return (data.ToArray(), mask.ToArray());
}
/// <summary>
/// Create a new instance of the Pattern.
/// </summary>
/// <param name="name">user friendly name for the pattern</param>
/// <param name="arrayOfHexBytes">
/// Array of HEX Bytes with "^" in it to calculate the bytes to skip.
/// </param>
public Pattern(string name, string arrayOfHexBytes)
{
this.Name = name;
var arrayOfHexBytesList = arrayOfHexBytes.Split(
new[] { " ", "," }, StringSplitOptions.RemoveEmptyEntries).ToList();
this.BytesToSkip = arrayOfHexBytesList.FindIndex("^".Equals);
(this.Data, this.Mask) = ParseArrayOfHexBytes(
arrayOfHexBytesList.Where(hex => hex != "^").ToList());
}
/// <summary>
/// Create a new instance of the Pattern.
/// </summary>
/// <param name="name">user friendly name for the patter</param>
/// <param name="arrayOfHexBytes">Array of HEX Bytes</param>
/// <param name="bytesToSkip">
/// Number of bytes to skip to reach the static-address offset data.
/// </param>
public Pattern(string name, string arrayOfHexBytes, int bytesToSkip)
{
this.Name = name;
this.BytesToSkip = bytesToSkip;
(this.Data, this.Mask) = ParseArrayOfHexBytes(arrayOfHexBytes.Split(
new[] { " ", "," }, StringSplitOptions.RemoveEmptyEntries).ToList());
}
/// <summary>
/// Pretty prints the Pattern.
/// </summary>
/// <returns>Pattern in string format.</returns>
public override string ToString()
{
var data = $"Name: {this.Name} Pattern: ";
for (var i = 0; i < this.Data.Length; i++)
{
if (this.Mask[i])
{
data += $"0x{this.Data[i]:X} ";
}
else
{
data += "?? ";
}
}
data += $"BytesToSkip: {this.BytesToSkip}";
return data;
}
}
}
every function on the code above have comments on how it works.
let's take an example for AreaChangeCounter
Code:
// <HowToFindIt>
// This one is really simple/old school CE formula.
// The only purpose of this Counter is to figure out the files-loaded-in-current-area.
// 1: Open CheatEngine and Attach to POE Game
// 2: Search for 4 bytes, Search type should be "UnknownInitialValue"
// 3: Now Change the Area again and again and on every area change do a "Next Scan" with "Increased Value"
// 4: U will find 2 green addresses at the end of that search list.
// 5: Pick the smaller one and create pattern for it.
// 5.1: Normally pattern are created by "What accesses this address/value"
// 5.2: but in this case the pattern at "What writes to this address/value" is more unique.
//
// NOTE: Reason you picked the smaller one in step-5 is because
// the bigger one is some other number which increments by 3
// every time you move from Charactor Select screen to In Game screen.
// Feel free to test this, just to be sure that the Address you are picking
// is the correct one.
// </HowToFindIt>
new(
"AreaChangeCounter",
"FF ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? FF 05 ^ ?? ?? ?? ?? ?? 8D ?? ?? ?? 8B ??"
),
The Technical Execution (The "How" - Using Pattern.cs)
The generated pattern string is now fed into the Pattern.cs class.
Pattern: "FF ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? FF 05 ^ ?? ?? ?? ?? ?? 8D ?? ?? ?? 8B ??"
How Pattern.cs parses it:
1. Find the ^: The parser finds the ^ at position 11 in the split list. This sets BytesToSkip = 11.
2. Remove ^ and Parse:
Code:
The list becomes: ["FF", "??", "??", "??", "??", "??", "E8", "??", "??", "??", "??", "FF", "05", "??", "??", "??", "??", "??", "8D", "??", "??", "??", "8B", "??"]
Data array is created, with ?? becoming 0x00.
Mask array is created, with ?? becoming false.
The resulting Pattern struct is:
Name = "AreaChangeCounter"
BytesToSkip = 11
Data: [0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x8B, 0x00]
Mask: [true, false, false, false, false, false, true, false, false, false, false, true, true, false, false, false, false, false, true, false, false, false, true, false]
How a Scanner Uses This Pattern:
1. The scanner searches the game's memory for a byte sequence that matches this pattern (it only checks bytes where Mask is true).
2. Once found, it takes the address of the match (e.g., 0x7000).
3. It knows from BytesToSkip = 11 that it must read a 4-byte integer from matchAddress + 11 (i.e., 0x7000 + 11 = 0x700B).
4. It reads the relative offset from that location. Let's say it reads 0x11223344.
5. It calculates the final address of the counter:
Code:
IntPtr patternAddress = 0x7000; // Where the pattern was found
int relativeOffset = Memory.Read<int>(patternAddress + 11); // Read from 0x700B
int totalPatternLength = 24; // Length of the Pattern.Data array
IntPtr nextInstructionAddress = patternAddress + totalPatternLength; // 0x7000 + 24 = 0x7018
IntPtr finalCounterAddress = nextInstructionAddress + relativeOffset; // 0x7018 + 0x11223344 = 0x1129345C
6. finalCounterAddress (0x1129345C) is the current, runtime address of the AreaChangeCounter.
this is just one example on what I found, but if you are interesting on doing this and if are people that want to learn I'm thinking on make a discord, write a comment with your thoughts.