Files
EpinelPS/ServerSelector/SearchReplace.cs
2025-07-21 09:08:13 -04:00

164 lines
7.5 KiB
C#

// From: https://github.com/Ninka-Rex/CSharp-Search-and-Replace/blob/main/SearchReplace.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ServerSelector;
public static class PatchUtility
{
public static bool CanFindOffset(string filePath, string[] searchPatterns)
{
// Check if the file exists
if (!File.Exists(filePath)) { Console.WriteLine("[ERROR] File not found: " + filePath); return false; }
// Read the binary data from the file as an array of bytes
byte[] fileData = File.ReadAllBytes(filePath);
for (int k = 0; k < searchPatterns.Length; k++)
{
// Convert the hexadecimal strings to byte arrays using a helper method
byte[] searchBytes = HexStringToBytes(searchPatterns[k]);
// Find the index of the first occurrence of the search pattern in the file data using another helper method
List<int> results = FindPatternIndex(fileData, searchBytes);
if (results.Count <= 1) return false;
Console.WriteLine("offset: " + results[1].ToString("X"));
// If the index is -1, it means the pattern was not found, so we return false and log an error message
if (results[1] == -1)
{
Console.WriteLine("[ERROR] Search pattern not found: " + searchPatterns[k]);
return false;
}
}
return true;
}
// This method searches and replaces binary patterns in a given file
// It takes three parameters:
// - filePath: the path of the file to be patched
// - searchPatterns: an array of hexadecimal strings representing the patterns to be searched for
// - replacePatterns: an array of hexadecimal strings representing the patterns to be replaced with
// It returns true if the patching was successful, or false if there was an error or a pattern was not found
public static bool SearchAndReplace(string filePath, string[] searchPatterns, string[] replacePatterns)
{
try
{
// Check if the file exists
if (!File.Exists(filePath)) { Console.WriteLine("[ERROR] File not found: " + filePath); return false; }
// Read the binary data from the file as an array of bytes
byte[] fileData = File.ReadAllBytes(filePath);
// Backup the original file by copying it to a new file with a .bak extension
string backupFilePath = filePath + ".bak";
if (!File.Exists(backupFilePath))
{
File.Copy(filePath, backupFilePath);
}
// Loop through each pair of search and replace patterns and apply them to the file data
for (int k = 0; k < searchPatterns.Length; k++)
{
// Convert the hexadecimal strings to byte arrays using a helper method
byte[] searchBytes = HexStringToBytes(searchPatterns[k]);
byte[] replaceBytes = HexStringToBytes(replacePatterns[k]);
// Find the index of the first occurrence of the search pattern in the file data using another helper method
// Note: game versions 124 - 133 have 2 matches, 2nd one is the correct one
// game version 134 - ? has 1 match which is correct
int index = -1;
List<int> indexes = FindPatternIndex(fileData, searchBytes);
if (indexes.Count == 1)
index = indexes[0];
else index = indexes[1];
Console.WriteLine("Found offset: " + index.ToString("X"));
// If the index is -1, it means the pattern was not found, so we return false and log an error message
if (index == -1)
{
Console.WriteLine("[ERROR] Search pattern not found: " + searchPatterns[k]);
return false;
}
// Replace the pattern at the found index with the replace pattern, preserving original values when wildcards are encountered
// A wildcard is represented by either 00 or FF in the replace pattern, meaning that we keep the original value at that position
for (int i = 0; i < replaceBytes.Length; i++)
{
if (replaceBytes[i] != 0x00 && replaceBytes[i] != 0xFF)
{
fileData[index + i] = replaceBytes[i];
}
else if (replaceBytes[i] == 0x00)
{
fileData[index + i] = 0x00;
}
}
// Log a success message with the offset and file name where the patch was applied
string exeName = Path.GetFileName(filePath);
Console.WriteLine($"[Patch] Apply patch success at 0x{index:X} in {exeName}");
}
// Write the modified data back to the file, overwriting the original content
File.WriteAllBytes(filePath, fileData);
return true;
}
catch (Exception ex)
{
// If any exception occurs during the patching process, we return false and log an error message with the exception details
Console.WriteLine("[ERROR] An error occurred while writing the file: " + ex.Message);
return false;
}
}
// This helper method converts a hexadecimal string to a byte array
// It takes one parameter:
// - hex: a string of hexadecimal digits, optionally separated by spaces or question marks
// It returns a byte array corresponding to the hexadecimal values in the string
private static byte[] HexStringToBytes(string hex)
{
hex = hex.Replace(" ", "").Replace("??", "FF"); // Replace ?? with FF for wildcards
return [.. Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0) // Take every second character in the string
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))]; // Convert the result to an array of bytes
}
// This helper method finds the index of the first occurrence of a pattern in a data array
// It takes two parameters:
// - data: an array of bytes representing the data to be searched in
// - pattern: an array of bytes representing the pattern to be searched for
// It returns an integer representing the index where the pattern was found, or -1 if it was not found
private static List<int> FindPatternIndex(byte[] data, byte[] pattern)
{
List<int> points = [];
// Loop through each possible position in the data array where the pattern could start
for (int i = 0; i < data.Length - pattern.Length + 1; i++)
{
bool found = true; // Assume that the pattern is found until proven otherwise
// Loop through each byte in the pattern and compare it with the corresponding byte in the data array
for (int j = 0; j < pattern.Length; j++)
{
// If the pattern byte is not FF (wildcard) and it does not match the data byte, then the pattern is not found at this position
if (pattern[j] != 0xFF && data[i + j] != pattern[j])
{
found = false;
break;
}
}
// If the pattern was found at this position, return the index
if (found)
{
points.Add(i);
}
}
// If the pattern was not found in the entire data array, return -1
return points;
}
}