From 953c3847b1429eb591f8c495713df2cb0518e3a0 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:49:41 +0200 Subject: [PATCH] Add files via upload --- Patches/MemoryLinux.cs | 221 +++++++++++++++++++++++++++++++++++++++ Patches/MemoryWindows.cs | 34 ++++++ Patches/Patch.cs | 104 ++++++++++++++++++ WeaponPaints.cs | 9 +- 4 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 Patches/MemoryLinux.cs create mode 100644 Patches/MemoryWindows.cs create mode 100644 Patches/Patch.cs diff --git a/Patches/MemoryLinux.cs b/Patches/MemoryLinux.cs new file mode 100644 index 00000000..1886fa7b --- /dev/null +++ b/Patches/MemoryLinux.cs @@ -0,0 +1,221 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace WeaponPaints; + +public static class MemoryLinux +{ + // Based on https://github.com/Source2ZE/CS2Fixes/blob/main/src/utils/plat_unix.cpp + static int ParseProt(string s) + { + int prot = 0; + + foreach (var c in s) + { + switch (c) + { + case '-': + break; + case 'r': + prot |= NativeMethods.PROT_READ; + break; + case 'w': + prot |= NativeMethods.PROT_WRITE; + break; + case 'x': + prot |= NativeMethods.PROT_EXEC; + break; + case 's': + break; + case 'p': + break; + default: + break; + } + } + + return prot; + } + + static int GetProt(IntPtr pAddr, uint nSize) + { + using (var f = File.OpenRead("/proc/self/maps")) + using (var reader = new StreamReader(f)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (line == null) + continue; + + var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 5) + continue; + + var range = parts[0]; + var prot = parts[1]; + + var startEnd = range.Split('-'); + if (startEnd.Length != 2) + continue; + + var start = Convert.ToUInt64(startEnd[0], 16); + var end = Convert.ToUInt64(startEnd[1], 16); + + if (start < (ulong)pAddr && end > (ulong)pAddr + nSize) + { + return ParseProt(prot); + } + } + } + + return 0; + } + + public static void PatchBytesAtAddress(IntPtr pPatchAddress, byte[] pPatch, int iPatchSize) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; + + var oldProt = GetProt(pPatchAddress, (uint)iPatchSize); + + var pageSize = (ulong)NativeMethods.sysconf(NativeMethods._SC_PAGESIZE); + var alignAddr = (IntPtr)((long)pPatchAddress & ~(long)(pageSize - 1)); + + var end = (IntPtr)((long)pPatchAddress + iPatchSize); + var alignSize = (ulong)((long)end - (long)alignAddr); + + var result = NativeMethods.mprotect(alignAddr, alignSize, NativeMethods.PROT_READ | NativeMethods.PROT_WRITE); + + Marshal.Copy(pPatch, 0, pPatchAddress, iPatchSize); + + result = NativeMethods.mprotect(alignAddr, alignSize, oldProt); + } + + private static byte[]? ReadProcessMemory(int pid, long address, int size) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return null; + + byte[] buffer = new byte[size]; + + NativeMethods.Iovec local = new NativeMethods.Iovec + { + iov_base = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0), + iov_len = new IntPtr(size) + }; + + NativeMethods.Iovec remote = new NativeMethods.Iovec + { + iov_base = new IntPtr(address), + iov_len = new IntPtr(size) + }; + + long bytesRead = NativeMethods.process_vm_readv(pid, new NativeMethods.Iovec[] { local }, 1, new NativeMethods.Iovec[] { remote }, 1, 0); + if (bytesRead == -1) + { + throw new Exception($"process_vm_readv failed with error {Marshal.GetLastPInvokeError()}"); + } + + return buffer; + } + + public static byte[]? ReadMemory(IntPtr address, int size) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return null; + + return ReadProcessMemory(Process.GetCurrentProcess().Id, (long)address, size); + } + + #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + #pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. + static class NativeMethods + { + public const int O_RDONLY = 0; + public const int PROT_READ = 0x1; + public const int PROT_WRITE = 0x2; + public const int PROT_EXEC = 0x4; + public const int MAP_PRIVATE = 0x2; + public const int PT_LOAD = 1; + public const int PF_X = 0x1; + public const int _SC_PAGESIZE = 30; + public const int RTLD_DI_LINKMAP = 2; + + [DllImport("libc")] + public static extern int dlinfo(IntPtr handle, int request, out link_map lmap); + + [DllImport("libc")] + public static extern int dlclose(IntPtr handle); + + [DllImport("libc")] + public static extern int open(string pathname, int flags); + + [DllImport("libc")] + public static extern int fstat(int fd, out stat buf); + + [DllImport("libc")] + public static extern IntPtr mmap(IntPtr addr, ulong length, int prot, int flags, int fd, ulong offset); + + [DllImport("libc")] + public static extern int munmap(IntPtr addr, ulong length); + + [DllImport("libc")] + public static extern int mprotect(IntPtr addr, ulong len, int prot); + + [DllImport("libc")] + public static extern long sysconf(int name); + + [DllImport("libc")] + public static extern long process_vm_readv(int pid, Iovec[] local_iov, ulong liovcnt, Iovec[] remote_iov, ulong riovcnt, ulong flags); + + [StructLayout(LayoutKind.Sequential)] + public struct Iovec + { + public IntPtr iov_base; + public IntPtr iov_len; + } + + [StructLayout(LayoutKind.Sequential)] + public struct link_map + { + public IntPtr l_addr; + public IntPtr l_name; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ElfW + { + public struct Ehdr + { + public byte e_shnum; + public uint e_shoff; + public ushort e_phnum; + public uint e_phoff; + } + + public struct Phdr + { + public int p_type; + public int p_flags; + + public ulong p_vaddr; + public ulong p_filesz; + } + + public struct Shdr + { + public uint sh_name; + public uint sh_offset; + public uint sh_size; + public ulong sh_addr; + } + } + + [StructLayout(LayoutKind.Sequential)] + + public struct stat + { + public ulong st_size; + } + } + #pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. + #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value +} \ No newline at end of file diff --git a/Patches/MemoryWindows.cs b/Patches/MemoryWindows.cs new file mode 100644 index 00000000..de1c526a --- /dev/null +++ b/Patches/MemoryWindows.cs @@ -0,0 +1,34 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace WeaponPaints; + +public static class MemoryWindows +{ + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten); + + public static void PatchBytesAtAddress(IntPtr pPatchAddress, byte[] pPatch, int iPatchSize) + { + if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + IntPtr bytesWritten; + WriteProcessMemory(Process.GetCurrentProcess().Handle, pPatchAddress, pPatch, (uint)iPatchSize, out bytesWritten); + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead); + + public static byte[]? ReadMemory(IntPtr address, int size) + { + if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return null; + + byte[] buffer = new byte[size]; + int bytesRead; + ReadProcessMemory(Process.GetCurrentProcess().Handle, address, buffer, size, out bytesRead); + return buffer; + } +} \ No newline at end of file diff --git a/Patches/Patch.cs b/Patches/Patch.cs new file mode 100644 index 00000000..5119f544 --- /dev/null +++ b/Patches/Patch.cs @@ -0,0 +1,104 @@ +using System.Runtime.InteropServices; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; + +namespace WeaponPaints; + +// Thanks cssharp-fixes +public static class Patch +{ + private static IntPtr GetAddress(string modulePath, string signature) + { + // Returns address if found, otherwise a C++ nullptr which is a IntPtr.Zero in C# + var address = NativeAPI.FindSignature(modulePath, signature); + + return address; + } + + public static void PerformPatch(string signature, string patch) + { + IntPtr address = GetAddress(Addresses.ServerPath, signature); + if(address == IntPtr.Zero) + { + return; + } + + WriteBytesToAddress(address, HexToByte(patch)); + } + + private static void WriteBytesToAddress(IntPtr address, List bytes) + { + int patchSize = bytes.Count; + if(patchSize == 0) throw new ArgumentException("Patch bytes list cannot be empty."); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + MemoryLinux.PatchBytesAtAddress(address, bytes.ToArray(), patchSize); + } + else + { + MemoryWindows.PatchBytesAtAddress(address, bytes.ToArray(), patchSize); + } + } + + private static List HexToByte(string src) + { + if (string.IsNullOrEmpty(src)) + { + return new List(); + } + + byte HexCharToByte(char c) + { + if (c is >= '0' and <= '9') return (byte)(c - '0'); + if (c is >= 'A' and <= 'F') return (byte)(c - 'A' + 10); + if (c is >= 'a' and <= 'f') return (byte)(c - 'a' + 10); + return 0xFF; // Invalid hex character + } + + List result = new List(); + bool isCodeStyle = src[0] == '\\'; + string pattern = isCodeStyle ? "\\x" : " "; + string wildcard = isCodeStyle ? "2A" : "?"; + int pos = 0; + + while (pos < src.Length) + { + int found = src.IndexOf(pattern, pos); + if (found == -1) + { + found = src.Length; + } + + string str = src.Substring(pos, found - pos); + pos = found + pattern.Length; + + if (string.IsNullOrEmpty(str)) continue; + + string byteStr = str; + + if (byteStr.Substring(0, wildcard.Length) == wildcard) + { + result.Add(0xFF); // Representing wildcard as 0xFF + continue; + } + + if (byteStr.Length < 2) + { + return new List(); // Invalid byte length + } + + byte high = HexCharToByte(byteStr[0]); + byte low = HexCharToByte(byteStr[1]); + + if (high == 0xFF || low == 0xFF) + { + return new List(); // Invalid hex character + } + + result.Add((byte)((high << 4) | low)); + } + + return result; + } +} \ No newline at end of file diff --git a/WeaponPaints.cs b/WeaponPaints.cs index 9fb4f62b..b10eac6e 100644 --- a/WeaponPaints.cs +++ b/WeaponPaints.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; @@ -16,10 +17,16 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig "Nereziel & daffyy"; public override string ModuleDescription => "Skin, gloves, agents and knife selector, standalone and web-based"; public override string ModuleName => "WeaponPaints"; - public override string ModuleVersion => "3.1c"; + public override string ModuleVersion => "3.1d"; public override void Load(bool hotReload) { + // Hardcoded hotfix needs to be changed later + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + Patch.PerformPatch("0F 85 ? ? ? ? 31 C0 B9 ? ? ? ? BA ? ? ? ? 66 0F EF C0 31 F6 31 FF 48 C7 45 ? ? ? ? ? 48 C7 45 ? ? ? ? ? 48 C7 45 ? ? ? ? ? 48 C7 45 ? ? ? ? ? 0F 29 45 ? 48 C7 45 ? ? ? ? ? C7 45 ? ? ? ? ? 66 89 45 ? E8 ? ? ? ? 41 89 C5 85 C0 0F 8E", "90 90 90 90 90 90"); + else + Patch.PerformPatch("74 ? 48 8D 0D ? ? ? ? FF 15 ? ? ? ? EB ? BA", "EB"); + Instance = this; if (hotReload)