Last active
June 19, 2026 07:50
-
-
Save DartPower/70772a446598e73b969d60287599fc14 to your computer and use it in GitHub Desktop.
XBox One some Emu proto...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Program.cs | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.IO; | |
| using System.Reflection; | |
| using System.Reflection.Emit; | |
| using System.Runtime.ExceptionServices; | |
| using System.Runtime.InteropServices; | |
| using System.Security; | |
| using System.Text; | |
| using System.Threading; | |
| [assembly: AssemblyCompany("XBEmu180626_2")] | |
| [assembly: AssemblyConfiguration("Debug")] | |
| [assembly: AssemblyFileVersion("1.0.0.0")] | |
| [assembly: AssemblyInformationalVersion("1.0.0")] | |
| [assembly: AssemblyProduct("XBEmu180626_2")] | |
| [assembly: AssemblyTitle("XBEmu180626_2")] | |
| [assembly: AssemblyVersion("1.0.0.0")] | |
| namespace XBEmu180626_2 | |
| { | |
| public static partial class Program | |
| { | |
| public static void LogDirect(string msg) | |
| { | |
| try | |
| { | |
| byte[] bytes = Encoding.ASCII.GetBytes(msg + "\r\n"); | |
| IntPtr hFile = NativeMethods.CreateFile("C:\\xbemu_thread.log", NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OPEN_ALWAYS, 0, IntPtr.Zero); | |
| if (hFile != (IntPtr)(-1)) | |
| { | |
| NativeMethods.SetFilePointer(hFile, 0, IntPtr.Zero, 2); // FILE_END | |
| NativeMethods.WriteFile(hFile, bytes, (uint)bytes.Length, out _, IntPtr.Zero); | |
| NativeMethods.CloseHandle(hFile); | |
| } | |
| } | |
| catch { } | |
| } | |
| public static PeImageLoader CurrentLoader; | |
| public static IntPtr CurrentEntryPoint; | |
| public static GameThreadWrapperDelegate WrapperDelegate = new GameThreadWrapperDelegate(GameThreadWrapper); // ДОБАВЛЕНО | |
| static readonly string CheckpointFile = "xbemu_checkpoint.bin"; | |
| static readonly string LogFile = $"xbemu_log_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; | |
| public static bool IsHeadless = false; | |
| public static bool IsNoRender = false; | |
| public static bool IsDebug = false; | |
| public static string GamePath = ""; | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void GameThreadWrapperDelegate(IntPtr param); | |
| [Obsolete] | |
| static void Main(string[] args) | |
| { | |
| SetupTraceListener(); | |
| UpdateTitle("Initializing..."); | |
| AppDomain.CurrentDomain.UnhandledException += (s, e) => | |
| { | |
| var ex = (Exception)e.ExceptionObject; | |
| Trace.WriteLine($"[UNHANDLED] {ex.Message}\n{ex.StackTrace}"); | |
| }; | |
| try | |
| { | |
| Trace.WriteLine("[SYS] 1. Parsing arguments..."); | |
| ParseArgsRecursively(args); | |
| AutoLoadState(); | |
| Trace.WriteLine("[SYS] 2. Initializing COM (MTA)..."); | |
| NativeMethods.CoInitializeEx(IntPtr.Zero, NativeMethods.COINIT.MULTITHREADED); | |
| Trace.WriteLine("[SYS] 3. Initializing VEH..."); | |
| Diagnostics.InitVEH(); | |
| Trace.WriteLine("[SYS] 4. Initializing MemoryManager..."); | |
| MemoryManager.Init(); | |
| Trace.WriteLine("[SYS] 5. Initializing XboxExports..."); | |
| XboxExports.InitWinRTEvents(); | |
| Trace.WriteLine("[SYS] 6. Initializing StubManager..."); | |
| StubManager.Init(); | |
| Trace.WriteLine("[SYS] 7. Skipping RenderManager for debug..."); | |
| Trace.WriteLine("[SYS] 8. Setting up checkpoint timer..."); | |
| var checkpointTimer = new Timer(state => AutoSaveState(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); | |
| Trace.WriteLine("[SYS] 9. Checking game path..."); | |
| if (!string.IsNullOrEmpty(GamePath) && File.Exists(GamePath)) | |
| { | |
| UpdateTitle("Loading PE..."); | |
| Trace.WriteLine("[SYS] 10. Loading PE Image..."); | |
| PeImageLoader loader = new PeImageLoader(); | |
| IntPtr entryPoint = loader.Load(GamePath); | |
| if (entryPoint != IntPtr.Zero) | |
| { | |
| UpdateTitle("Running..."); | |
| Trace.WriteLine($"[SYS] 11. Entry Point found at: 0x{entryPoint.ToInt64():X}"); | |
| Trace.WriteLine("[SYS] 12. Creating game thread..."); | |
| CurrentLoader = loader; | |
| CurrentEntryPoint = entryPoint; | |
| // Увеличиваем стек до 32 МБ | |
| IntPtr hThread = NativeMethods.CreateThread(IntPtr.Zero, 32 * 1024 * 1024, Marshal.GetFunctionPointerForDelegate(WrapperDelegate), IntPtr.Zero, 0, out uint threadId); | |
| Trace.WriteLine($"[SYS] 13. Game thread created. ID: {threadId}"); | |
| if (hThread != IntPtr.Zero) | |
| { | |
| Trace.WriteLine("[SYS] 14. Entering main loop..."); | |
| while (NativeMethods.WaitForSingleObject(hThread, 100) == 258) | |
| { | |
| Thread.Sleep(10); | |
| } | |
| uint exitCode = 0; | |
| NativeMethods.GetExitCodeThread(hThread, out exitCode); | |
| Trace.WriteLine($"[SYS] 15. Game thread exited with code: 0x{exitCode:X}"); | |
| } | |
| } | |
| else | |
| { | |
| Trace.WriteLine("[ERROR] Failed to load PE or find entry point."); | |
| } | |
| } | |
| else | |
| { | |
| Trace.WriteLine("[ERROR] Game path invalid or not provided. Use -p <path>"); | |
| PrintHelp(); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Trace.WriteLine($"[FATAL] {ex.Message}\n{ex.StackTrace}"); | |
| } | |
| finally | |
| { | |
| AutoSaveState(); | |
| UpdateTitle("Shutting down..."); | |
| NativeMethods.CoUninitialize(); | |
| Console.ResetColor(); | |
| Trace.WriteLine("[SYS] Emulator stopped. Press any key to exit."); | |
| if (!IsHeadless) Console.ReadKey(); | |
| } | |
| } | |
| [HandleProcessCorruptedStateExceptions] | |
| [SecurityCritical] | |
| public static void GameThreadWrapper(IntPtr param) | |
| { | |
| LogDirect("[SYS] 12.5. Game thread started. Initializing TLS..."); | |
| try | |
| { | |
| if (CurrentLoader.TlsSize > 0) | |
| { | |
| IntPtr tlsBlock = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)CurrentLoader.TlsSize, 0x3000, 0x04); | |
| if (CurrentLoader.TlsStartAddress != IntPtr.Zero) | |
| { | |
| for (int i = 0; i < CurrentLoader.TlsSize; i++) | |
| Marshal.WriteByte(IntPtr.Add(tlsBlock, i), Marshal.ReadByte(IntPtr.Add(CurrentLoader.TlsStartAddress, i))); | |
| } | |
| NativeMethods.TlsSetValue((int)CurrentLoader.TlsIndex, tlsBlock); | |
| } | |
| foreach (var cb in CurrentLoader.TlsCallbacksList) | |
| { | |
| var cbDel = Marshal.GetDelegateForFunctionPointer<PeImageLoader.TlsCallbackDelegate>(cb); | |
| cbDel(IntPtr.Zero, 1, IntPtr.Zero); | |
| } | |
| LogDirect("[SYS] 12.7. Calling game entry point..."); | |
| var entry = Marshal.GetDelegateForFunctionPointer<GameThreadWrapperDelegate>(CurrentEntryPoint); | |
| entry(IntPtr.Zero); | |
| LogDirect("[SYS] 12.8. Game entry point returned."); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.ForegroundColor = ConsoleColor.Red; | |
| Console.Error.WriteLine($"\n[FATAL] Game thread crashed: {ex.GetType().Name}"); | |
| Console.Error.WriteLine($"[FATAL] Message: {ex.Message}"); | |
| Console.Error.WriteLine($"[FATAL] Stack: {ex.StackTrace}"); | |
| Console.ResetColor(); | |
| LogDirect($"[FATAL] Game thread crashed: {ex.GetType().Name}\nMessage: {ex.Message}\nStack: {ex.StackTrace}"); | |
| } | |
| } | |
| #region Arguments and TUI | |
| static void ParseArgsRecursively(string[] args, int index = 0) | |
| { | |
| if (index >= args.Length) return; | |
| string arg = args[index].ToLowerInvariant(); | |
| switch (arg) | |
| { | |
| case "-p": | |
| case "--path": | |
| if (index + 1 < args.Length) GamePath = args[index + 1]; | |
| ParseArgsRecursively(args, index + 2); | |
| return; | |
| case "--headless": | |
| IsHeadless = true; break; | |
| case "--no-render": | |
| IsNoRender = true; break; | |
| case "--debug": | |
| IsDebug = true; break; | |
| case "--help": | |
| PrintHelp(); Environment.Exit(0); break; | |
| } | |
| ParseArgsRecursively(args, index + 1); | |
| } | |
| static void PrintHelp() | |
| { | |
| Console.WriteLine("XBEmu180626_2 - Xbox One (Durango) High-Level Emulator"); | |
| Console.WriteLine("Usage: XBEmu180626_2 -p <path_to_game.exe> [options]"); | |
| Console.WriteLine("Options:"); | |
| Console.WriteLine(" -p, --path Path to Xbox One x64 PE executable."); | |
| Console.WriteLine(" --headless Run without interactive console UI (CI/CD)."); | |
| Console.WriteLine(" --no-render Disable D3D11 window rendering (headless graphics)."); | |
| Console.WriteLine(" --debug Enable verbose diagnostic logging and VEH."); | |
| } | |
| public static void DrawProgressBar(int progress, string label) | |
| { | |
| if (IsHeadless) return; | |
| Console.CursorLeft = 0; | |
| Console.Write("["); | |
| int pads = 30; | |
| int filled = (progress * pads) / 100; | |
| Console.Write(new string('#', filled) + new string('-', pads - filled)); | |
| Console.Write($"] {progress}% {label}"); | |
| } | |
| #endregion | |
| #region State Management | |
| static void AutoSaveState() | |
| { | |
| try | |
| { | |
| using (var fs = new FileStream(CheckpointFile, FileMode.Create)) | |
| using (var sw = new StreamWriter(fs)) | |
| { | |
| sw.WriteLine($"# XBEmu State Checkpoint - {DateTime.Now}"); | |
| sw.WriteLine($"GamePath={GamePath}"); | |
| } | |
| } | |
| catch { } | |
| } | |
| static void AutoLoadState() | |
| { | |
| if (!File.Exists(CheckpointFile)) return; | |
| try | |
| { | |
| foreach (var line in File.ReadAllLines(CheckpointFile)) | |
| { | |
| if (line.StartsWith("GamePath=") && string.IsNullOrEmpty(GamePath)) | |
| GamePath = line.Substring(9); | |
| } | |
| } | |
| catch { } | |
| } | |
| static void UpdateTitle(string status) => Console.Title = $"XBEmu180626_2 | {status} | {(IsDebug ? "Debug" : "Release")}"; | |
| static void SetupTraceListener() | |
| { | |
| Trace.Listeners.Clear(); | |
| Trace.Listeners.Add(new EmulatorTraceListener(LogFile)); | |
| Trace.AutoFlush = true; | |
| } | |
| #endregion | |
| } | |
| public class EmulatorTraceListener : TraceListener | |
| { | |
| private StreamWriter _logFile; | |
| private readonly object _lock = new object(); | |
| public EmulatorTraceListener(string logPath) : base() | |
| { | |
| _logFile = new StreamWriter(logPath, true) { AutoFlush = true }; | |
| } | |
| private void WriteColored(string message) | |
| { | |
| string fullMsg = $"[{DateTime.Now:HH:mm:ss.fff}] {message}"; | |
| ConsoleColor color = ConsoleColor.Gray; | |
| if (message.Contains("[ERROR]") || message.Contains("[FATAL]") || message.Contains("[VEH]")) color = ConsoleColor.Red; | |
| else if (message.Contains("[WARN]")) color = ConsoleColor.Yellow; | |
| else if (message.Contains("[GPU]")) color = ConsoleColor.Green; | |
| else if (message.Contains("[PE]")) color = ConsoleColor.Cyan; | |
| else if (message.Contains("[MEM]")) color = ConsoleColor.Magenta; | |
| else if (message.Contains("[HLE]")) color = ConsoleColor.Blue; | |
| lock (_lock) | |
| { | |
| try | |
| { | |
| var prevColor = Console.ForegroundColor; | |
| Console.ForegroundColor = color; | |
| Console.Write(fullMsg); | |
| Console.ForegroundColor = prevColor; | |
| _logFile.Write(fullMsg); | |
| _logFile.Flush(); | |
| } | |
| catch { } | |
| } | |
| } | |
| public override void Write(string message) => WriteColored(message); | |
| public override void WriteLine(string message) => WriteColored(message + Environment.NewLine); | |
| protected override void Dispose(bool disposing) | |
| { | |
| if (disposing) _logFile?.Dispose(); | |
| base.Dispose(disposing); | |
| } | |
| } | |
| public static class MemoryManager | |
| { | |
| public static IntPtr GuestBase = IntPtr.Zero; | |
| public static IntPtr EsramBase = IntPtr.Zero; | |
| public const ulong GUEST_MEMORY_SIZE = 6442450944; // 6 GB | |
| public const ulong ESRAM_SIZE = 33554432; // 32 MB | |
| public static void Init() | |
| { | |
| Trace.WriteLine("[MEM] Reserving 6GB Guest Memory (Lazy Commit)..."); | |
| ulong[] prefBases = { 0x14000000000, 0x100000000, 0x200000000, 0x300000000 }; | |
| foreach (var baseAddr in prefBases) | |
| { | |
| // 0x2000 = MEM_RESERVE. Мы только резервируем адресное пространство! | |
| GuestBase = NativeMethods.VirtualAlloc((IntPtr)baseAddr, (IntPtr)GUEST_MEMORY_SIZE, 0x2000, 0x40); | |
| if (GuestBase != IntPtr.Zero) break; | |
| } | |
| if (GuestBase == IntPtr.Zero) | |
| GuestBase = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)GUEST_MEMORY_SIZE, 0x2000, 0x40); | |
| if (GuestBase == IntPtr.Zero) | |
| throw new Exception("Failed to reserve 6GB Guest Memory."); | |
| Trace.WriteLine($"[MEM] Guest Base: 0x{GuestBase.ToInt64():X}"); | |
| EsramBase = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)ESRAM_SIZE, 0x2000, 0x40); | |
| Trace.WriteLine($"[MEM] ESRAM Base: 0x{EsramBase.ToInt64():X}"); | |
| } | |
| public static void MapSection(IntPtr dest, byte[] data) | |
| { | |
| // 0x1000 = MEM_COMMIT. Физически выделяем память только для нужных секций! | |
| NativeMethods.VirtualAlloc(dest, (IntPtr)data.Length, 0x1000, 0x40); | |
| Marshal.Copy(data, 0, dest, data.Length); | |
| } | |
| } | |
| public class PeImageLoader | |
| { | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void TlsCallbackDelegate(IntPtr hModule, uint reason, IntPtr pvReserved); | |
| public IntPtr Load(string path) | |
| { | |
| try | |
| { | |
| Trace.WriteLine($"[PE] Loading PE: {path}"); | |
| byte[] fileData = File.ReadAllBytes(path); | |
| Trace.WriteLine($"[PE] File read. Size: {fileData.Length} bytes."); | |
| int dosHeader = BitConverter.ToInt32(fileData, 0x3C); | |
| ushort machine = BitConverter.ToUInt16(fileData, dosHeader + 4); | |
| if (machine != 0x8664) throw new Exception("Not an x64 PE file."); | |
| ulong imageBase = BitConverter.ToUInt64(fileData, dosHeader + 24 + 24); | |
| uint sizeOfImage = BitConverter.ToUInt32(fileData, dosHeader + 24 + 56); | |
| uint entryPointRva = BitConverter.ToUInt32(fileData, dosHeader + 24 + 16); | |
| uint sizeOfHeaders = BitConverter.ToUInt32(fileData, dosHeader + 24 + 60); | |
| Trace.WriteLine($"[PE] ImageBase: 0x{imageBase:X}, SizeOfImage: 0x{sizeOfImage:X}, EntryPointRVA: 0x{entryPointRva:X}"); | |
| Trace.WriteLine("[PE] Attempting VirtualAlloc..."); | |
| // 0x2000 = MEM_RESERVE | |
| IntPtr imgBase = NativeMethods.VirtualAlloc((IntPtr)imageBase, (IntPtr)sizeOfImage, 0x2000, 0x40); | |
| if (imgBase == IntPtr.Zero) | |
| { | |
| Trace.WriteLine("[PE] Preferred base failed. Allocating dynamically..."); | |
| imgBase = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)sizeOfImage, 0x2000, 0x40); | |
| } | |
| if (imgBase == IntPtr.Zero) | |
| throw new Exception($"Failed to allocate memory for PE image. Win32 Error: {Marshal.GetLastWin32Error()}"); | |
| Trace.WriteLine($"[PE] Allocated Image Base: 0x{imgBase.ToInt64():X}"); | |
| ushort numSections = BitConverter.ToUInt16(fileData, dosHeader + 6); | |
| ushort sizeOfOptHeader = BitConverter.ToUInt16(fileData, dosHeader + 20); | |
| Trace.WriteLine("[PE] Mapping headers..."); | |
| byte[] headerData = new byte[sizeOfHeaders]; | |
| Array.Copy(fileData, 0, headerData, 0, sizeOfHeaders); | |
| MemoryManager.MapSection(imgBase, headerData); | |
| Trace.WriteLine("[PE] Mapping sections..."); | |
| int sectionOffset = dosHeader + 24 + sizeOfOptHeader; | |
| for (int i = 0; i < numSections; i++) | |
| { | |
| int secOff = sectionOffset + (i * 40); | |
| string name = Encoding.ASCII.GetString(fileData, secOff, 8).Trim('\0'); | |
| uint vAddr = BitConverter.ToUInt32(fileData, secOff + 12); | |
| uint vSize = BitConverter.ToUInt32(fileData, secOff + 8); | |
| uint rOff = BitConverter.ToUInt32(fileData, secOff + 20); | |
| uint rSize = BitConverter.ToUInt32(fileData, secOff + 16); | |
| if (vSize > 0) | |
| { | |
| // КРИТИЧЕСКИ ВАЖНО: Выделяем полный VirtualSize и заполняем нулями (для .bss) | |
| int copySize = (int)Math.Min(rSize, vSize); | |
| byte[] secData = new byte[vSize]; | |
| if (rSize > 0) Array.Copy(fileData, rOff, secData, 0, copySize); | |
| IntPtr dest = IntPtr.Add(imgBase, (int)vAddr); | |
| Trace.WriteLine($"[PE] Mapping {name} to 0x{dest.ToInt64():X} (VSize: 0x{vSize:X}, CSize: 0x{copySize:X})..."); | |
| MemoryManager.MapSection(dest, secData); | |
| } | |
| } | |
| if ((ulong)imgBase.ToInt64() != imageBase) | |
| { | |
| Trace.WriteLine("[PE] Applying relocations..."); | |
| ApplyRelocations(fileData, dosHeader, imgBase, (IntPtr)imageBase); | |
| } | |
| // CRITICAL FIX: Process Imports (IAT) | |
| ProcessImports(fileData, dosHeader, imgBase); | |
| int dataDirOffset = dosHeader + 24 + 112; | |
| uint exceptionRva = BitConverter.ToUInt32(fileData, dataDirOffset + (3 * 8)); | |
| uint exceptionSize = BitConverter.ToUInt32(fileData, dataDirOffset + (3 * 8) + 4); | |
| if (exceptionSize > 0 && exceptionRva > 0) | |
| { | |
| IntPtr pdataPtr = IntPtr.Add(imgBase, (int)exceptionRva); | |
| uint count = exceptionSize / 12; | |
| NativeMethods.RtlAddFunctionTable(pdataPtr, count, imgBase); | |
| Trace.WriteLine($"[PE] Registered {count} exception handlers."); | |
| } | |
| // Обработка TLS | |
| ProcessTlsCallbacks(fileData, dosHeader, imgBase); | |
| Trace.WriteLine("[PE] Scanning for INT3..."); | |
| PatchAntiDebug(imgBase, sizeOfImage); | |
| return IntPtr.Add(imgBase, (int)entryPointRva); | |
| } | |
| catch (Exception ex) | |
| { | |
| Trace.WriteLine($"[PE] FATAL Load Error: {ex.Message}\n{ex.StackTrace}"); | |
| return IntPtr.Zero; | |
| } | |
| } | |
| // CRITICAL FIX: Сохраняем TLS данные, чтобы настроить поток перед запуском | |
| public uint TlsIndex = 0; | |
| public IntPtr TlsIndexPtr = IntPtr.Zero; | |
| public IntPtr TlsStartAddress = IntPtr.Zero; | |
| public IntPtr TlsEndAddress = IntPtr.Zero; | |
| public uint TlsSize = 0; | |
| public List<IntPtr> TlsCallbacksList = new List<IntPtr>(); | |
| private void ProcessTlsCallbacks(byte[] fileData, int dosHeader, IntPtr imgBase) | |
| { | |
| try | |
| { | |
| int dataDirOffset = dosHeader + 24 + 112; | |
| uint tlsRva = BitConverter.ToUInt32(fileData, dataDirOffset + (9 * 8)); | |
| if (tlsRva != 0) | |
| { | |
| Trace.WriteLine($"[PE] Found TLS Directory at RVA: 0x{tlsRva:X}"); | |
| IntPtr tlsDirPtr = IntPtr.Add(imgBase, (int)tlsRva); | |
| TlsStartAddress = Marshal.ReadIntPtr(tlsDirPtr, 0); | |
| TlsEndAddress = Marshal.ReadIntPtr(tlsDirPtr, 8); | |
| TlsIndexPtr = Marshal.ReadIntPtr(tlsDirPtr, 16); | |
| IntPtr callbacksPtr = Marshal.ReadIntPtr(tlsDirPtr, 24); | |
| if (TlsIndexPtr != IntPtr.Zero) | |
| { | |
| TlsIndex = (uint)Marshal.ReadInt32(TlsIndexPtr); | |
| if (TlsIndex == 0) | |
| { | |
| TlsIndex = (uint)NativeMethods.TlsAlloc(); | |
| Marshal.WriteInt32(TlsIndexPtr, (int)TlsIndex); | |
| Trace.WriteLine($"[PE] Allocated TLS Slot: {TlsIndex}"); | |
| } | |
| } | |
| TlsSize = (uint)(TlsEndAddress.ToInt64() - TlsStartAddress.ToInt64()); | |
| TlsSize = (TlsSize + 15) & ~15u; // Выравнивание | |
| if (callbacksPtr != IntPtr.Zero) | |
| { | |
| int i = 0; | |
| while (true) | |
| { | |
| IntPtr callback = Marshal.ReadIntPtr(callbacksPtr, i * IntPtr.Size); | |
| if (callback == IntPtr.Zero) break; | |
| TlsCallbacksList.Add(callback); | |
| i++; | |
| } | |
| } | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Trace.WriteLine($"[PE] TLS Callback processing failed: {ex.Message}"); | |
| } | |
| } | |
| private void ProcessImports(byte[] fileData, int dosHeader, IntPtr imgBase) | |
| { | |
| Trace.WriteLine("[PE] Processing Imports (IAT)..."); | |
| int dataDirOffset = dosHeader + 24 + 112; | |
| uint importRva = BitConverter.ToUInt32(fileData, dataDirOffset + (1 * 8)); // Import Directory is index 1 | |
| if (importRva == 0) return; | |
| int importOffset = RvaToOffset(fileData, dosHeader, importRva); | |
| int descriptorSize = 20; | |
| int dllCount = 0; | |
| while (true) | |
| { | |
| int descOffset = importOffset + (dllCount * descriptorSize); | |
| if (descOffset + 20 > fileData.Length) break; | |
| uint nameRva = BitConverter.ToUInt32(fileData, descOffset + 12); | |
| if (nameRva == 0) break; // End of descriptors | |
| // Read DLL Name | |
| int nameOff = RvaToOffset(fileData, dosHeader, nameRva); | |
| string dllName = ""; | |
| while (fileData[nameOff] != 0) { dllName += (char)fileData[nameOff]; nameOff++; } | |
| uint originalFirstThunkRva = BitConverter.ToUInt32(fileData, descOffset); | |
| uint firstThunkRva = BitConverter.ToUInt32(fileData, descOffset + 16); | |
| // Use OriginalFirstThunk for reading names, FirstThunk for patching | |
| uint readThunkRva = originalFirstThunkRva != 0 ? originalFirstThunkRva : firstThunkRva; | |
| int readThunkOffset = RvaToOffset(fileData, dosHeader, readThunkRva); | |
| IntPtr iatPtr = IntPtr.Add(imgBase, (int)firstThunkRva); | |
| int thunkIndex = 0; | |
| int funcCount = 0; | |
| while (true) | |
| { | |
| if (readThunkOffset + (thunkIndex * 8) + 8 > fileData.Length) break; | |
| ulong thunk = (ulong)BitConverter.ToInt64(fileData, readThunkOffset + (thunkIndex * 8)); | |
| if (thunk == 0) break; | |
| string funcName = ""; | |
| if ((thunk & 0x8000000000000000UL) == 0) // Import by name | |
| { | |
| int nameRva2 = (int)thunk; | |
| int nameOffset2 = RvaToOffset(fileData, dosHeader, (uint)nameRva2) + 2; // Skip 2-byte Hint | |
| while (fileData[nameOffset2] != 0) { funcName += (char)fileData[nameOffset2]; nameOffset2++; } | |
| } | |
| else | |
| { | |
| funcName = $"Ordinal_{thunk & 0xFFFF}"; | |
| } | |
| IntPtr stubPtr = StubManager.GetStub(dllName, funcName); | |
| Marshal.WriteIntPtr(IntPtr.Add(iatPtr, thunkIndex * 8), stubPtr); | |
| thunkIndex++; | |
| funcCount++; | |
| } | |
| Trace.WriteLine($"[PE] Patched {funcCount} imports from {dllName}"); | |
| dllCount++; | |
| } | |
| Trace.WriteLine("[PE] IAT Patching complete."); | |
| } | |
| private int RvaToOffset(byte[] fileData, int dosHeader, uint rva) | |
| { | |
| ushort numSections = BitConverter.ToUInt16(fileData, dosHeader + 6); | |
| ushort sizeOfOptHeader = BitConverter.ToUInt16(fileData, dosHeader + 20); | |
| int sectionOffset = dosHeader + 24 + sizeOfOptHeader; | |
| for (int i = 0; i < numSections; i++) | |
| { | |
| int secOff = sectionOffset + (i * 40); | |
| uint vAddr = BitConverter.ToUInt32(fileData, secOff + 12); | |
| uint vSize = BitConverter.ToUInt32(fileData, secOff + 8); | |
| uint rOff = BitConverter.ToUInt32(fileData, secOff + 20); | |
| uint rSize = BitConverter.ToUInt32(fileData, secOff + 16); | |
| if (rva >= vAddr && rva < vAddr + Math.Max(vSize, rSize)) | |
| { | |
| return (int)(rva - vAddr + rOff); | |
| } | |
| } | |
| return (int)rva; // Fallback (usually headers) | |
| } | |
| private void ApplyRelocations(byte[] fileData, int dosHeader, IntPtr actualBase, IntPtr preferredBase) | |
| { | |
| int dataDirOffset = dosHeader + 24 + 112; | |
| uint relocRva = BitConverter.ToUInt32(fileData, dataDirOffset + (5 * 8)); | |
| if (relocRva == 0) return; | |
| long delta = actualBase.ToInt64() - preferredBase.ToInt64(); | |
| if (delta == 0) return; | |
| int relocOffset = RvaToOffset(fileData, dosHeader, relocRva); | |
| while (relocOffset < fileData.Length) | |
| { | |
| uint pageRva = BitConverter.ToUInt32(fileData, relocOffset); | |
| uint blockSize = BitConverter.ToUInt32(fileData, relocOffset + 4); | |
| if (blockSize == 0) break; | |
| int entryCount = (int)((blockSize - 8) / 2); | |
| for (int i = 0; i < entryCount; i++) | |
| { | |
| ushort entry = BitConverter.ToUInt16(fileData, relocOffset + 8 + (i * 2)); | |
| int type = entry >> 12; | |
| int offset = entry & 0xFFF; | |
| if (type == 10) | |
| { | |
| IntPtr patchAddr = IntPtr.Add(actualBase, (int)pageRva + offset); | |
| long val = Marshal.ReadInt64(patchAddr); | |
| Marshal.WriteInt64(patchAddr, val + delta); | |
| } | |
| } | |
| relocOffset += (int)blockSize; | |
| } | |
| } | |
| private unsafe void PatchAntiDebug(IntPtr imgBase, uint sizeOfImage) | |
| { | |
| // ПОЛНОСТЬЮ ОТКЛЮЧАЕМ. Слепой патчинг ломает инструкции игры. | |
| return; | |
| } | |
| } | |
| public static class Diagnostics | |
| { | |
| public static VectoredHandler VehHandlerDelegate = null!; | |
| private static GCHandle _vehHandle; | |
| public static void InitVEH() | |
| { | |
| VehHandlerDelegate = new VectoredHandler(VehHandler); | |
| _vehHandle = GCHandle.Alloc(VehHandlerDelegate); | |
| Trace.WriteLine("[DIAG] Vectored Exception Handler attached."); | |
| NativeMethods.AddVectoredExceptionHandler(1, VehHandlerDelegate); | |
| } | |
| [UnmanagedFunctionPointer(CallingConvention.StdCall)] | |
| public delegate int VectoredHandler(IntPtr exceptionInfo); | |
| [HandleProcessCorruptedStateExceptions] | |
| [SecurityCritical] | |
| public static int VehHandler(IntPtr exceptionInfo) | |
| { | |
| try | |
| { | |
| IntPtr exRecord = Marshal.ReadIntPtr(exceptionInfo); | |
| uint code = (uint)Marshal.ReadInt32(exRecord); | |
| IntPtr addr = Marshal.ReadIntPtr(exRecord, 0x10); // ExceptionAddress на x64 | |
| // ИСПРАВЛЕНО: Явное приведение типов для STATUS_BREAKPOINT (0x80000003) | |
| if (code == 0x80000003) | |
| { | |
| IntPtr ctxRecord = Marshal.ReadIntPtr(exceptionInfo, 8); // PCONTEXT | |
| IntPtr ripPtr = IntPtr.Add(ctxRecord, 0xF8); // Смещение RIP в CONTEXT для x64 | |
| long rip = Marshal.ReadInt64(ripPtr); | |
| Marshal.WriteInt64(ripPtr, rip + 1); // Сдвигаем указатель на 1 байт (размер int 3) | |
| // Читаем 16 байт вокруг точки останова для отладки | |
| byte[] bytes = new byte[16]; | |
| Marshal.Copy(addr, bytes, 0, 16); | |
| string hex = BitConverter.ToString(bytes); | |
| string msg = $"[VEH] Skipped BREAKPOINT at 0x{addr.ToInt64():X} -> 0x{rip + 1:X} | Bytes: {hex}\r\n"; | |
| byte[] msgBytes = Encoding.ASCII.GetBytes(msg); | |
| IntPtr hFile = NativeMethods.CreateFile("C:\\xbemu_veh.log", NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OPEN_ALWAYS, 0, IntPtr.Zero); | |
| if (hFile != (IntPtr)(-1)) | |
| { | |
| NativeMethods.SetFilePointer(hFile, 0, IntPtr.Zero, 2); | |
| NativeMethods.WriteFile(hFile, msgBytes, (uint)msgBytes.Length, out _, IntPtr.Zero); | |
| NativeMethods.CloseHandle(hFile); | |
| } | |
| Trace.WriteLine(msg); | |
| return -1; // EXCEPTION_CONTINUE_EXECUTION | |
| } | |
| string msg2 = $"[VEH] EXCEPTION 0x{code:X8} at 0x{addr.ToInt64():X}\r\n"; | |
| byte[] bytes2 = Encoding.ASCII.GetBytes(msg2); | |
| IntPtr hFile2 = NativeMethods.CreateFile("C:\\xbemu_veh.log", NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OPEN_ALWAYS, 0, IntPtr.Zero); | |
| if (hFile2 != (IntPtr)(-1)) | |
| { | |
| NativeMethods.SetFilePointer(hFile2, 0, IntPtr.Zero, 2); | |
| NativeMethods.WriteFile(hFile2, bytes2, (uint)bytes2.Length, out _, IntPtr.Zero); | |
| NativeMethods.CloseHandle(hFile2); | |
| } | |
| Trace.WriteLine(msg2); | |
| } | |
| catch { } | |
| return 0; // EXCEPTION_CONTINUE_SEARCH | |
| } | |
| } | |
| public static class StubManager | |
| { | |
| public delegate int RaiseFailFastExceptionDelegate(IntPtr ex, IntPtr ctx, uint flags); | |
| public delegate IntPtr SetUnhandledExceptionFilterDelegate(IntPtr filter); | |
| static int RaiseFailFastExceptionImpl(IntPtr ex, IntPtr ctx, uint flags) => throw new Exception("[HLE] RaiseFailFastException API called!"); | |
| static IntPtr SetUnhandledExceptionFilterImpl(IntPtr filter) => IntPtr.Zero; | |
| public delegate void TerminateProcessDelegate(IntPtr hProcess, uint exitCode); | |
| public delegate void ExitProcessDelegate(uint exitCode); | |
| static void TerminateProcessImpl(IntPtr hProcess, uint exitCode) => throw new Exception($"[HLE] TerminateProcess called! Code: 0x{exitCode:X}"); | |
| static void ExitProcessImpl(uint exitCode) => throw new Exception($"[HLE] ExitProcess called! Code: 0x{exitCode:X}"); | |
| public delegate int MapTitlePhysicalPagesDelegate(IntPtr process, IntPtr section, uint type, ref uint count, out IntPtr pages); | |
| public delegate IntPtr ApuCreateHeapDelegate(); | |
| public delegate IntPtr ApuAllocDelegate(IntPtr heap, uint size); | |
| public delegate IntPtr AcpHalCreateDelegate(); | |
| static int MapTitlePhysicalPagesImpl(IntPtr process, IntPtr section, uint type, ref uint count, out IntPtr pages) | |
| { | |
| Console.WriteLine($"[HLE] MapTitlePhysicalPages called. Allocating {count} pages."); | |
| pages = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)(count * 4096), 0x3000, 0x04); | |
| return 1; | |
| } | |
| static IntPtr ApuCreateHeapImpl() => new IntPtr(1); | |
| static IntPtr ApuAllocImpl(IntPtr heap, uint size) => NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)size, 0x3000, 0x04); | |
| static IntPtr AcpHalCreateImpl() => new IntPtr(1); | |
| static IntPtr AcpHalAllocateShapeContextsImpl() => new IntPtr(1); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr HeapAllocateDelegate(ulong size); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void HeapFreeDelegate(IntPtr ptr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int AllocateTitlePhysicalPagesDelegate(IntPtr process, uint type, ref uint count, out IntPtr pages); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void FailFastDelegate(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void TerminateDelegate(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void AmsgExitDelegate(int code); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void SetAppTypeDelegate(int type); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void LockDelegate(int locknum); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void TypeDtorDelegate(IntPtr thisPtr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void ThrowStdExceptionDelegate(IntPtr msg); | |
| static IntPtr HeapAllocateImpl(ulong size) => NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)size, 0x3000, 0x04); | |
| static void HeapFreeImpl(IntPtr ptr) { } | |
| static int AllocateTitlePhysicalPagesImpl(IntPtr process, uint type, ref uint count, out IntPtr pages) | |
| { | |
| Trace.WriteLine($"[HLE] AllocateTitlePhysicalPages called. Allocating {count} pages."); | |
| pages = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)(count * 4096), 0x3000, 0x04); | |
| return 1; // TRUE | |
| } | |
| static void FailFastImpl() => throw new Exception("[HLE] Platform::__abi_FailFast called!"); | |
| static void TerminateImpl() => throw new Exception("[HLE] std::terminate() called!"); | |
| static void AmsgExitImpl(int code) => throw new Exception($"[HLE] _amsg_exit({code}) called!"); | |
| static void SetAppTypeImpl(int type) { } | |
| static void LockImpl(int locknum) { } | |
| static void UnlockImpl(int locknum) { } | |
| static void TypeDtorImpl(IntPtr thisPtr) { } | |
| static void OrphanAllImpl(IntPtr thisPtr) { } | |
| static void ThrowStdExceptionImpl(IntPtr msg) => throw new Exception("[GAME] std::exception: " + Marshal.PtrToStringAnsi(msg)); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int PrintfDelegate(IntPtr format); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int FprintfDelegate(IntPtr file, IntPtr format); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr IobFuncDelegate(); | |
| static IntPtr _acmdlnPtr = IntPtr.Zero; | |
| static IntPtr _fmodePtr = IntPtr.Zero; | |
| static IntPtr _commodePtr = IntPtr.Zero; | |
| static IntPtr _iobPtr = IntPtr.Zero; | |
| static int PrintfImpl(IntPtr format) | |
| { | |
| try { Console.WriteLine("[GAME] " + Marshal.PtrToStringAnsi(format)); } catch { } | |
| return 0; | |
| } | |
| static int FprintfImpl(IntPtr file, IntPtr format) | |
| { | |
| try { Console.WriteLine("[GAME] " + Marshal.PtrToStringAnsi(format)); } catch { } | |
| return 0; | |
| } | |
| static IntPtr IobFuncImpl() | |
| { | |
| if (_iobPtr == IntPtr.Zero) _iobPtr = Marshal.AllocHGlobal(128); // Фейковый массив FILE* | |
| return _iobPtr; | |
| } | |
| // Правильное выделение памяти под данные CRT | |
| public static IntPtr GetDataStub(string name) | |
| { | |
| if (name == "_acmdln" || name == "_wcmdln") | |
| { | |
| if (_acmdlnPtr == IntPtr.Zero) | |
| { | |
| _acmdlnPtr = Marshal.AllocHGlobal(IntPtr.Size); | |
| Marshal.WriteIntPtr(_acmdlnPtr, GetCommandLineA()); | |
| } | |
| return _acmdlnPtr; | |
| } | |
| if (name == "_fmode") | |
| { | |
| if (_fmodePtr == IntPtr.Zero) { _fmodePtr = Marshal.AllocHGlobal(4); Marshal.WriteInt32(_fmodePtr, 0); } | |
| return _fmodePtr; | |
| } | |
| if (name == "_commode") | |
| { | |
| if (_commodePtr == IntPtr.Zero) { _commodePtr = Marshal.AllocHGlobal(4); Marshal.WriteInt32(_commodePtr, 0); } | |
| return _commodePtr; | |
| } | |
| // Безопасный фолбэк для неизвестных данных: выдаем указатель на 0 | |
| IntPtr genericPtr = Marshal.AllocHGlobal(8); | |
| Marshal.WriteInt64(genericPtr, 0); | |
| return genericPtr; | |
| } | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr CallocDelegate(IntPtr count, IntPtr size); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetActivationFactoryDelegate(IntPtr classId, out IntPtr factory); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetActivationFactoryByPCWSTRDelegate(IntPtr classId, IntPtr iid, out IntPtr factory); // Было ref Guid | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetIidsFnDelegate(int count, IntPtr sizes, IntPtr guids, out IntPtr iidArray); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetProxyImplDelegate(IntPtr pUnk, ref Guid iid, ref Guid destIid, out IntPtr proxy); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr GetObjectContextDelegate(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int ReCreateFromExceptionDelegate(IntPtr ex); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr GetIBoxArrayVtableDelegate(IntPtr a); | |
| static int GetActivationFactoryByPCWSTRImpl(IntPtr classId, ref Guid iid, out IntPtr factory) | |
| { | |
| Trace.WriteLine("[HLE] GetActivationFactoryByPCWSTR called. Returning fake COM object."); | |
| // ИСПРАВЛЕНО: Вызываем функцию, она сама заполнит 'factory' через out, а мы просто вернем 0 (S_OK) | |
| XboxExports.GetActivationFactoryImpl(classId, out factory); | |
| return 0; | |
| } | |
| static int GetIidsFnImpl(int count, IntPtr sizes, IntPtr guids, out IntPtr iidArray) | |
| { | |
| Trace.WriteLine("[HLE] GetIidsFn called. Returning empty array."); | |
| // ИСПРАВЛЕНО: Возвращаем E_NOTIMPL (0x80004001), чтобы C++/CX не пытался читать пустой массив | |
| iidArray = IntPtr.Zero; | |
| return unchecked((int)0x80004001); | |
| } | |
| static int GetProxyImplImpl(IntPtr pUnk, ref Guid iid, ref Guid destIid, out IntPtr proxy) | |
| { | |
| proxy = IntPtr.Zero; | |
| return 0; | |
| } | |
| static IntPtr GetObjectContextImpl() => IntPtr.Zero; | |
| static int ReCreateFromExceptionImpl(IntPtr ex) => 0; | |
| static IntPtr GetIBoxArrayVtableImpl(IntPtr a) => IntPtr.Zero; | |
| static IntPtr CallocImpl(IntPtr count, IntPtr size) | |
| { | |
| long total = count.ToInt64() * size.ToInt64(); | |
| if (total <= 0) return IntPtr.Zero; | |
| return NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)total, 0x3000, 0x04); | |
| } | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr GetCmdArgsDelegate(out int argc); | |
| static IntPtr _wargvArray = IntPtr.Zero; | |
| static IntPtr GetCmdArgsImpl(out int argc) | |
| { | |
| Trace.WriteLine("[HLE] Platform::GetCmdArguments called."); | |
| if (_wargvArray == IntPtr.Zero) | |
| { | |
| IntPtr wargv0 = Marshal.StringToHGlobalUni("Emu.exe"); | |
| _wargvArray = Marshal.AllocHGlobal(16); | |
| Marshal.WriteIntPtr(_wargvArray, wargv0); | |
| Marshal.WriteIntPtr(_wargvArray, 8, IntPtr.Zero); | |
| } | |
| argc = 1; | |
| return _wargvArray; | |
| } | |
| // CRITICAL FIX: Список для удержания делегатов в памяти, чтобы GC их не удалил | |
| private static List<Delegate> _aliveDelegates = new List<Delegate>(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr NoArgDelegate(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr Arg1Delegate(IntPtr a); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr Arg4Delegate(IntPtr a, IntPtr b, IntPtr c, IntPtr d); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void InitTermDelegate(IntPtr start, IntPtr end); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetMainArgsDelegate(ref int argc, ref IntPtr argv, ref IntPtr envp, int dowildcard, IntPtr startinfo); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int InitTermEFuncDelegate(); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int InitTermEDelegate(IntPtr start, IntPtr end); | |
| // Делегаты для CRT | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public unsafe delegate IntPtr MemCpyDelegate(IntPtr dest, IntPtr src, IntPtr count); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public unsafe delegate IntPtr MemSetDelegate(IntPtr dest, int val, IntPtr count); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr StrLenDelegate(IntPtr str); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr MallocDelegate(IntPtr size); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void FreeDelegate(IntPtr ptr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate IntPtr ReturnThisDelegate(IntPtr thisPtr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate void VoidDelegate(); | |
| static IntPtr ReturnThisImpl(IntPtr thisPtr) => thisPtr; // Конструктор возвращает указатель на созданный объект | |
| static void VoidImpl() { } // Деструкторы и бросалки исключений ничего не возвращают | |
| static IntPtr ReturnZero() => IntPtr.Zero; // Возвращаем S_OK (0) для неизвестных функций | |
| static IntPtr ReturnOne() => new IntPtr(1); | |
| static IntPtr ReturnMinusOne() => new IntPtr(-1); | |
| static IntPtr ReturnMinusTwo() => new IntPtr(-2); | |
| static IntPtr EncodePointerImpl(IntPtr ptr) => ptr; | |
| static IntPtr DecodePointerImpl(IntPtr ptr) => ptr; | |
| static IntPtr QueryPerformanceCounterImpl(IntPtr ptr) | |
| { | |
| try { Marshal.WriteInt64(ptr, Stopwatch.GetTimestamp()); } catch { } | |
| return IntPtr.Zero; | |
| } | |
| static IntPtr GetSystemTimeAsFileTimeImpl(IntPtr ptr) | |
| { | |
| try { Marshal.WriteInt64(ptr, DateTime.Now.ToFileTime()); } catch { } | |
| return IntPtr.Zero; | |
| } | |
| static IntPtr GetTickCount64Impl() => new IntPtr(Environment.TickCount); | |
| [Obsolete] | |
| static IntPtr GetCurrentThreadIdImpl() => new IntPtr(NativeMethods.GetCurrentThreadId()); | |
| static void InitTermImpl(IntPtr start, IntPtr end) | |
| { | |
| Trace.WriteLine("[HLE] _initterm called. Executing CRT initializers..."); | |
| while (start.ToInt64() < end.ToInt64()) | |
| { | |
| IntPtr funcPtr = Marshal.ReadIntPtr(start); | |
| if (funcPtr != IntPtr.Zero) | |
| { | |
| try | |
| { | |
| var func = Marshal.GetDelegateForFunctionPointer<NoArgDelegate>(funcPtr); | |
| func(); | |
| } | |
| catch (Exception ex) { Trace.WriteLine($"[HLE] _initterm func crashed: {ex.Message}"); } | |
| } | |
| start = IntPtr.Add(start, IntPtr.Size); | |
| } | |
| } | |
| static int InitTermEImpl(IntPtr start, IntPtr end) | |
| { | |
| Trace.WriteLine("[HLE] _initterm_e called. Executing CRT initializers..."); | |
| while (start.ToInt64() < end.ToInt64()) | |
| { | |
| IntPtr funcPtr = Marshal.ReadIntPtr(start); | |
| if (funcPtr != IntPtr.Zero) | |
| { | |
| try | |
| { | |
| var func = Marshal.GetDelegateForFunctionPointer<InitTermEFuncDelegate>(funcPtr); | |
| int ret = func(); | |
| if (ret != 0) | |
| { | |
| Trace.WriteLine($"[HLE] _initterm_e function returned {ret}, aborting initialization."); | |
| return ret; | |
| } | |
| } | |
| catch (Exception ex) { Trace.WriteLine($"[HLE] _initterm_e func crashed: {ex.Message}"); } | |
| } | |
| start = IntPtr.Add(start, IntPtr.Size); | |
| } | |
| return 0; | |
| } | |
| static int GetMainArgsImpl(ref int argc, ref IntPtr argv, ref IntPtr envp, int dowildcard, IntPtr startinfo) | |
| { | |
| Trace.WriteLine("[HLE] __getmainargs called."); | |
| argc = 1; | |
| IntPtr argvArray = Marshal.AllocHGlobal(16); | |
| Marshal.WriteIntPtr(argvArray, GetCommandLineA()); | |
| Marshal.WriteIntPtr(argvArray, 8, IntPtr.Zero); | |
| argv = argvArray; | |
| IntPtr envpArray = Marshal.AllocHGlobal(8); | |
| Marshal.WriteIntPtr(envpArray, IntPtr.Zero); | |
| envp = envpArray; | |
| return 0; | |
| } | |
| static IntPtr _cmdLineWPtr = IntPtr.Zero; | |
| static IntPtr _cmdLineAPtr = IntPtr.Zero; | |
| static IntPtr GetCommandLineW() | |
| { | |
| if (_cmdLineWPtr == IntPtr.Zero) | |
| { | |
| string cmd = "Emu.exe"; | |
| byte[] bytes = Encoding.Unicode.GetBytes(cmd + "\0"); | |
| _cmdLineWPtr = Marshal.AllocHGlobal(bytes.Length); | |
| Marshal.Copy(bytes, 0, _cmdLineWPtr, bytes.Length); | |
| } | |
| return _cmdLineWPtr; | |
| } | |
| static IntPtr GetCommandLineA() | |
| { | |
| if (_cmdLineAPtr == IntPtr.Zero) | |
| { | |
| string cmd = "Emu.exe"; | |
| byte[] bytes = Encoding.ASCII.GetBytes(cmd + "\0"); | |
| _cmdLineAPtr = Marshal.AllocHGlobal(bytes.Length); | |
| Marshal.Copy(bytes, 0, _cmdLineAPtr, bytes.Length); | |
| } | |
| return _cmdLineAPtr; | |
| } | |
| // Реальные реализации CRT | |
| static unsafe IntPtr MemCpyImpl(IntPtr dest, IntPtr src, IntPtr count) | |
| { | |
| if (count == IntPtr.Zero) return dest; | |
| Buffer.MemoryCopy((void*)src, (void*)dest, count.ToInt64(), count.ToInt64()); | |
| return dest; | |
| } | |
| static unsafe IntPtr MemSetImpl(IntPtr dest, int val, IntPtr count) | |
| { | |
| if (count == IntPtr.Zero) return dest; | |
| byte* d = (byte*)dest; | |
| byte v = (byte)val; | |
| for (long i = 0; i < count.ToInt64(); i++) d[i] = v; | |
| return dest; | |
| } | |
| static unsafe IntPtr StrLenImpl(IntPtr str) | |
| { | |
| byte* p = (byte*)str; | |
| long len = 0; | |
| while (*p != 0) { p++; len++; } | |
| return new IntPtr(len); | |
| } | |
| static IntPtr MallocImpl(IntPtr size) | |
| { | |
| return NativeMethods.VirtualAlloc(IntPtr.Zero, size, 0x3000, 0x04); // PAGE_READWRITE | |
| } | |
| static void FreeImpl(IntPtr ptr) { /* Игнорируем для утечек памяти в эмуляторе */ } | |
| public static Dictionary<string, IntPtr> KnownStubs = new Dictionary<string, IntPtr>(); | |
| public static IntPtr FallbackStub = IntPtr.Zero; | |
| [Obsolete] | |
| public static void Init() | |
| { | |
| Trace.WriteLine("[HLE] Initializing Stub Manager..."); | |
| FallbackStub = CreateJitStub("Unknown", new NoArgDelegate(ReturnZero)); | |
| KnownStubs["memcpy"] = CreateJitStub("memcpy", new MemCpyDelegate(MemCpyImpl)); | |
| KnownStubs["memset"] = CreateJitStub("memset", new MemSetDelegate(MemSetImpl)); | |
| KnownStubs["memmove"] = CreateJitStub("memmove", new MemCpyDelegate(MemCpyImpl)); | |
| KnownStubs["strlen"] = CreateJitStub("strlen", new StrLenDelegate(StrLenImpl)); | |
| KnownStubs["malloc"] = CreateJitStub("malloc", new MallocDelegate(MallocImpl)); | |
| KnownStubs["free"] = CreateJitStub("free", new FreeDelegate(FreeImpl)); | |
| // КРИТИЧЕСКИ ВАЖНО: C++ new/delete. Без них игра падает при первом же выделении памяти | |
| KnownStubs["??2@YAPEAX_K@Z"] = CreateJitStub("operator_new", new MallocDelegate(MallocImpl)); | |
| KnownStubs["??_U@YAPEAX_K@Z"] = CreateJitStub("operator_new[]", new MallocDelegate(MallocImpl)); | |
| KnownStubs["??3@YAXPEAX@Z"] = CreateJitStub("operator_delete", new FreeDelegate(FreeImpl)); | |
| KnownStubs["??_V@YAXPEAX@Z"] = CreateJitStub("operator_delete[]", new FreeDelegate(FreeImpl)); | |
| KnownStubs["_calloc_crt"] = CreateJitStub("_calloc_crt", new CallocDelegate(CallocImpl)); | |
| KnownStubs["RoInitialize"] = CreateJitStub("RoInitialize", new NoArgDelegate(ReturnZero)); | |
| KnownStubs["CoTaskMemAlloc"] = CreateJitStub("CoTaskMemAlloc", new MallocDelegate(MallocImpl)); | |
| KnownStubs["WindowsCreateString"] = CreateJitStub("WindowsCreateString", new NoArgDelegate(ReturnZero)); | |
| // ИСПРАВЛЕНО: Правильная сигнатура для GetActivationFactory | |
| KnownStubs["GetActivationFactory"] = CreateJitStub("GetActivationFactory", new GetActivationFactoryDelegate(XboxExports.GetActivationFactoryImpl)); | |
| KnownStubs["?GetActivationFactoryByPCWSTR@@YAJPEAXAEAVGuid@Platform@@PEAPEAX@Z"] = CreateJitStub("GetActivationFactoryByPCWSTR", new GetActivationFactoryByPCWSTRDelegate(XboxExports.GetActivationFactoryByPCWSTRImpl)); | |
| KnownStubs["GetCurrentProcess"] = CreateJitStub("GetCurrentProcess", new NoArgDelegate(ReturnMinusOne)); | |
| KnownStubs["GetCurrentThread"] = CreateJitStub("GetCurrentThread", new NoArgDelegate(ReturnMinusTwo)); | |
| KnownStubs["IsDebuggerPresent"] = CreateJitStub("IsDebuggerPresent", new NoArgDelegate(ReturnZero)); | |
| KnownStubs["GetLastError"] = CreateJitStub("GetLastError", new NoArgDelegate(ReturnZero)); | |
| KnownStubs["GetTickCount64"] = CreateJitStub("GetTickCount64", new NoArgDelegate(GetTickCount64Impl)); | |
| KnownStubs["GetCurrentThreadId"] = CreateJitStub("GetCurrentThreadId", new NoArgDelegate(GetCurrentThreadIdImpl)); | |
| KnownStubs["GetCommandLineW"] = CreateJitStub("GetCommandLineW", new NoArgDelegate(GetCommandLineW)); | |
| KnownStubs["GetCommandLineA"] = CreateJitStub("GetCommandLineA", new NoArgDelegate(GetCommandLineA)); | |
| KnownStubs["EncodePointer"] = CreateJitStub("EncodePointer", new Arg1Delegate(EncodePointerImpl)); | |
| KnownStubs["DecodePointer"] = CreateJitStub("DecodePointer", new Arg1Delegate(DecodePointerImpl)); | |
| KnownStubs["QueryPerformanceCounter"] = CreateJitStub("QueryPerformanceCounter", new Arg1Delegate(QueryPerformanceCounterImpl)); | |
| KnownStubs["GetSystemTimeAsFileTime"] = CreateJitStub("GetSystemTimeAsFileTime", new Arg1Delegate(GetSystemTimeAsFileTimeImpl)); | |
| // KnownStubs["VirtualProtect"] = CreateJitStub("VirtualProtect", new Arg4Delegate((a, b, c, d) => ReturnOne())); | |
| KnownStubs["_initterm"] = CreateJitStub("_initterm", new InitTermDelegate(InitTermImpl)); | |
| KnownStubs["_initterm_e"] = CreateJitStub("_initterm_e", new InitTermEDelegate(InitTermEImpl)); | |
| KnownStubs["__getmainargs"] = CreateJitStub("__getmainargs", new GetMainArgsDelegate(GetMainArgsImpl)); | |
| KnownStubs["?GetCmdArguments@Details@Platform@@YAPEAPEA_WPEAH@Z"] = CreateJitStub("GetCmdArguments", new GetCmdArgsDelegate(GetCmdArgsImpl)); | |
| // Регистрируем новые HLE-заглушки | |
| KnownStubs["?GetIidsFn@@YAJHPEAKPEBU__s_GUID@@PEAPEAVGuid@Platform@@@Z"] = CreateJitStub("GetIidsFn", new GetIidsFnDelegate(GetIidsFnImpl)); | |
| KnownStubs["?GetProxyImpl@Details@Platform@@YAJPEAUIUnknown@@AEBU_GUID@@0PEAPEAU3@@Z"] = CreateJitStub("GetProxyImpl", new GetProxyImplDelegate(GetProxyImplImpl)); | |
| KnownStubs["?GetObjectContext@Details@Platform@@YAPEAUIUnknown@@XZ"] = CreateJitStub("GetObjectContext", new GetObjectContextDelegate(GetObjectContextImpl)); | |
| KnownStubs["?ReCreateFromException@Details@Platform@@YAJPE$AAVException@2@@Z"] = CreateJitStub("ReCreateFromException", new ReCreateFromExceptionDelegate(ReCreateFromExceptionImpl)); | |
| KnownStubs["?GetIBoxArrayVtable@Details@Platform@@YAPEAXPEAX@Z"] = CreateJitStub("GetIBoxArrayVtable", new GetIBoxArrayVtableDelegate(GetIBoxArrayVtableImpl)); | |
| // Xbox One memory API | |
| KnownStubs["AllocateTitlePhysicalPages"] = CreateJitStub("AllocateTitlePhysicalPages", new AllocateTitlePhysicalPagesDelegate(AllocateTitlePhysicalPagesImpl)); | |
| KnownStubs["?Allocate@Heap@Details@Platform@@SAPEAX_K@Z"] = CreateJitStub("Heap_Allocate", new HeapAllocateDelegate(HeapAllocateImpl)); | |
| KnownStubs["?AllocateException@Heap@Details@Platform@@SAPEAX_K@Z"] = CreateJitStub("Heap_AllocateException", new HeapAllocateDelegate(HeapAllocateImpl)); | |
| KnownStubs["?Free@Heap@Details@Platform@@SAXPEAX@Z"] = CreateJitStub("Heap_Free", new HeapFreeDelegate(HeapFreeImpl)); | |
| KnownStubs["?FreeException@Heap@Details@Platform@@SAXPEAX@Z"] = CreateJitStub("Heap_FreeException", new HeapFreeDelegate(HeapFreeImpl)); | |
| // Перехват критических ошибок CRT | |
| KnownStubs["?__abi_FailFast@@YAXXZ"] = CreateJitStub("FailFast", new FailFastDelegate(FailFastImpl)); | |
| KnownStubs["?terminate@@YAXXZ"] = CreateJitStub("terminate", new TerminateDelegate(TerminateImpl)); | |
| KnownStubs["_amsg_exit"] = CreateJitStub("_amsg_exit", new AmsgExitDelegate(AmsgExitImpl)); | |
| KnownStubs["__set_app_type"] = CreateJitStub("__set_app_type", new SetAppTypeDelegate(SetAppTypeImpl)); | |
| KnownStubs["_lock"] = CreateJitStub("_lock", new LockDelegate(LockImpl)); | |
| KnownStubs["_unlock"] = CreateJitStub("_unlock", new LockDelegate(UnlockImpl)); | |
| KnownStubs["?_type_info_dtor_internal_method@type_info@@QEAAXXZ"] = CreateJitStub("type_info_dtor", new TypeDtorDelegate(TypeDtorImpl)); | |
| KnownStubs["?_Orphan_all@_Container_base0@std@@QEAAXXZ"] = CreateJitStub("Orphan_all", new TypeDtorDelegate(OrphanAllImpl)); | |
| KnownStubs["?_Xlength_error@std@@YAXPEBD@Z"] = CreateJitStub("Xlength_error", new ThrowStdExceptionDelegate(ThrowStdExceptionImpl)); | |
| KnownStubs["?_Xout_of_range@std@@YAXPEBD@Z"] = CreateJitStub("Xout_of_range", new ThrowStdExceptionDelegate(ThrowStdExceptionImpl)); | |
| KnownStubs["?_Xbad_alloc@std@@YAXXZ"] = CreateJitStub("Xbad_alloc", new FailFastDelegate(FailFastImpl)); | |
| // Xbox One memory & audio API | |
| KnownStubs["MapTitlePhysicalPages"] = CreateJitStub("MapTitlePhysicalPages", new MapTitlePhysicalPagesDelegate(MapTitlePhysicalPagesImpl)); | |
| KnownStubs["ApuCreateHeap"] = CreateJitStub("ApuCreateHeap", new ApuCreateHeapDelegate(ApuCreateHeapImpl)); | |
| KnownStubs["ApuAlloc"] = CreateJitStub("ApuAlloc", new ApuAllocDelegate(ApuAllocImpl)); | |
| KnownStubs["AcpHalAllocateShapeContexts"] = CreateJitStub("AcpHalAllocateShapeContexts", new ApuCreateHeapDelegate(AcpHalAllocateShapeContextsImpl)); | |
| KnownStubs["AcpHalCreate"] = CreateJitStub("AcpHalCreate", new AcpHalCreateDelegate(AcpHalCreateImpl)); | |
| // Перехват убийства процесса | |
| KnownStubs["TerminateProcess"] = CreateJitStub("TerminateProcess", new TerminateProcessDelegate(TerminateProcessImpl)); | |
| KnownStubs["ExitProcess"] = CreateJitStub("ExitProcess", new ExitProcessDelegate(ExitProcessImpl)); | |
| KnownStubs["RaiseFailFastException"] = CreateJitStub("RaiseFailFastException", new RaiseFailFastExceptionDelegate(RaiseFailFastExceptionImpl)); | |
| KnownStubs["SetUnhandledExceptionFilter"] = CreateJitStub("SetUnhandledExceptionFilter", new SetUnhandledExceptionFilterDelegate(SetUnhandledExceptionFilterImpl)); | |
| KnownStubs["printf"] = CreateJitStub("printf", new PrintfDelegate(PrintfImpl)); | |
| KnownStubs["fprintf"] = CreateJitStub("fprintf", new FprintfDelegate(FprintfImpl)); | |
| KnownStubs["__iob_func"] = CreateJitStub("__iob_func", new IobFuncDelegate(IobFuncImpl)); | |
| // Известные переменные CRT | |
| KnownStubs["_acmdln"] = GetDataStub("_acmdln"); | |
| KnownStubs["_wcmdln"] = GetDataStub("_wcmdln"); | |
| KnownStubs["_fmode"] = GetDataStub("_fmode"); | |
| KnownStubs["_commode"] = GetDataStub("_commode"); | |
| // ИСПРАВЛЕНО: Явно возвращаем S_OK для инициализации C++/CX | |
| KnownStubs["?InitializeData@Details@Platform@@YAJH@Z"] = CreateJitStub("InitializeData", new NoArgDelegate(ReturnZero)); | |
| KnownStubs["?UninitializeData@Details@Platform@@YAXH@Z"] = CreateJitStub("UninitializeData", new NoArgDelegate(ReturnZero)); | |
| } | |
| public static IntPtr GetStub(string dllName, string funcName) | |
| { | |
| if (KnownStubs.TryGetValue(funcName, out IntPtr stub)) | |
| return stub; | |
| IntPtr hModule = IntPtr.Zero; | |
| string normDll = dllName.ToLowerInvariant(); | |
| if (normDll == "kernelx.dll" || normDll == "kernelbase.dll") | |
| hModule = NativeMethods.GetModuleHandleA("kernel32.dll"); | |
| else if (normDll == "combase.dll") | |
| hModule = NativeMethods.LoadLibraryA("combase.dll"); | |
| else if (normDll == "msvcr110.dll" || normDll == "msvcp110.dll") | |
| hModule = NativeMethods.LoadLibraryA("ucrtbase.dll"); | |
| if (hModule != IntPtr.Zero) | |
| { | |
| IntPtr proc = NativeMethods.GetProcAddress(hModule, funcName); | |
| if (proc != IntPtr.Zero) | |
| { | |
| KnownStubs[funcName] = proc; | |
| return proc; | |
| } | |
| } | |
| // ИСПРАВЛЕНО: Перехватываем функции вывода игры | |
| if (funcName == "printf" || funcName == "wprintf") | |
| { | |
| var del = new PrintfDelegate(PrintfImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| return ptr; | |
| } | |
| if (funcName == "fprintf" || funcName == "fwprintf") | |
| { | |
| var del = new FprintfDelegate(FprintfImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| return ptr; | |
| } | |
| if (funcName == "__iob_func") | |
| { | |
| var del = new IobFuncDelegate(IobFuncImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| return ptr; | |
| } | |
| // ИСПРАВЛЕНО: Известные переменные CRT | |
| if (funcName == "_acmdln" || funcName == "_wcmdln" || funcName == "_fmode" || funcName == "_commode" || funcName == "_pctype") | |
| { | |
| IntPtr dataPtr = GetDataStub(funcName); | |
| KnownStubs[funcName] = dataPtr; | |
| return dataPtr; | |
| } | |
| // ИСПРАВЛЕНИЕ: Автоматическая заглушка для конструкторов C++ (??0). Возвращает thisPtr (RCX) | |
| if (funcName.StartsWith("??0")) | |
| { | |
| var del = new ReturnThisDelegate(ReturnThisImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| Trace.WriteLine($"[HLE] Auto-stubbed C++ constructor: {funcName}"); | |
| return ptr; | |
| } | |
| // ИСПРАВЛЕНИЕ: Автоматическая заглушка для деструкторов (ничего не делают) | |
| if (funcName.StartsWith("??1")) | |
| { | |
| var del = new VoidDelegate(VoidImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| Trace.WriteLine($"[HLE] Auto-stubbed C++ destructor: {funcName}"); | |
| return ptr; | |
| } | |
| // ИСПРАВЛЕНИЕ: Raisers должны бросать исключения, чтобы игра не шла дальше | |
| if (funcName.Contains("__abi_WinRTraise")) | |
| { | |
| var del = new VoidDelegate(ThrowWinRTExceptionImpl); | |
| _aliveDelegates.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| KnownStubs[funcName] = ptr; | |
| Trace.WriteLine($"[HLE] Auto-stubbed WinRT raiser: {funcName}"); | |
| return ptr; | |
| } | |
| Trace.WriteLine($"[HLE] WARN: Unimplemented import '{funcName}' from '{dllName}'. Using fallback."); | |
| return FallbackStub; | |
| } | |
| static void ThrowWinRTExceptionImpl() | |
| { | |
| // ИСПРАВЛЕНО: Бросаем C# исключение, чтобы безопасно раскрутить стек и завершить поток, | |
| // вместо того чтобы возвращаться в мусорный код (int 3). | |
| throw new Exception("[HLE] WinRT Exception raised by game!"); | |
| } | |
| public static IntPtr CreateJitStub(string name, Delegate impl) | |
| { | |
| _aliveDelegates.Add(impl); // CRITICAL FIX: Сохраняем ссылку | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(impl); | |
| Trace.WriteLine($"[HLE] Generated stub for {name} at 0x{ptr.ToInt64():X}"); | |
| return ptr; | |
| } | |
| } | |
| public static class RenderManager | |
| { | |
| static Thread renderThread = null!; | |
| static volatile bool isRunning = false; | |
| public static void InitWindow() | |
| { | |
| renderThread = new Thread(() => | |
| { | |
| isRunning = true; | |
| while (isRunning) Thread.Sleep(16); | |
| }); | |
| renderThread.IsBackground = true; | |
| renderThread.Start(); | |
| } | |
| public static void RenderFrame() | |
| { | |
| int tick = Environment.TickCount; | |
| if (Program.IsHeadless) return; | |
| Program.DrawProgressBar((int)(50 + 50 * Math.Sin(tick * 0.005)), "Pulsing Xbox Green Frame..."); | |
| } | |
| } | |
| public static class XboxExports | |
| { | |
| public static void InitWinRTEvents() | |
| { | |
| IntPtr hEvent = NativeMethods.CreateEvent(IntPtr.Zero, true, false, "Global\\RoInitializationCompletedEvent"); | |
| if (hEvent != IntPtr.Zero) NativeMethods.SetEvent(hEvent); | |
| } | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int AddRefDelegate(IntPtr thisPtr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int ReleaseDelegate(IntPtr thisPtr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetIidsDelegate(IntPtr thisPtr, out int count, out IntPtr iids); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetRuntimeClassNameDelegate(IntPtr thisPtr, out IntPtr className); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int GetTrustLevelDelegate(IntPtr thisPtr, out int trustLevel); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int ActivateInstanceDelegate(IntPtr thisPtr, out IntPtr instance); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int QueryInterfaceDelegate(IntPtr thisPtr, IntPtr iid, out IntPtr ppv); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int SafeComStubDelegate(IntPtr thisPtr, IntPtr arg1, IntPtr arg2, IntPtr arg3); | |
| static QueryInterfaceDelegate _qi = QueryInterfaceImpl; | |
| static AddRefDelegate _addRef = AddRefImpl; | |
| static ReleaseDelegate _release = ReleaseImpl; | |
| static GetIidsDelegate _getIids = GetIidsImpl; | |
| static GetRuntimeClassNameDelegate _getRuntimeClassName = GetRuntimeClassNameImpl; | |
| static GetTrustLevelDelegate _getTrustLevel = GetTrustLevelImpl; | |
| static ActivateInstanceDelegate _activateInstance = ActivateInstanceImpl; | |
| public static List<Delegate> _comSafeStubs = new List<Delegate>(); | |
| private static IntPtr _comVTable = IntPtr.Zero; | |
| private static bool IsWritable(IntPtr address) | |
| { | |
| NativeMethods.MEMORY_BASIC_INFORMATION mbi = new NativeMethods.MEMORY_BASIC_INFORMATION(); | |
| if (NativeMethods.VirtualQuery(address, ref mbi, (IntPtr)Marshal.SizeOf(mbi)) != IntPtr.Zero) | |
| { | |
| // PAGE_READWRITE = 0x04, PAGE_EXECUTE_READWRITE = 0x40, PAGE_WRITECOPY = 0x08 | |
| return (mbi.Protect & 0x04) != 0 || (mbi.Protect & 0x40) != 0 || (mbi.Protect & 0x08) != 0; | |
| } | |
| return false; | |
| } | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int VoidHresultDelegate(IntPtr thisPtr); | |
| // ИСПРАВЛЕНО: Правильные сигнатуры COM-методов (возвращают HRESULT, объекты через out) | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int CreateViewDelegate(IntPtr thisPtr, out IntPtr view); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int InitializeDelegate(IntPtr thisPtr, IntPtr applicationView); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int SetWindowDelegate(IntPtr thisPtr, IntPtr window); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int LoadDelegate(IntPtr thisPtr, IntPtr entryPoint); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int RunDelegate(IntPtr thisPtr); | |
| [UnmanagedFunctionPointer(CallingConvention.Winapi)] | |
| public delegate int UninitializeDelegate(IntPtr thisPtr); | |
| [HandleProcessCorruptedStateExceptions] | |
| [SecurityCritical] | |
| public static int CoreApplicationRunImpl(IntPtr viewSourcePtr) | |
| { | |
| try | |
| { | |
| if (viewSourcePtr == IntPtr.Zero) return 0; | |
| Console.WriteLine("[HLE] Calling IFrameworkViewSource::CreateView..."); | |
| IntPtr vtable = Marshal.ReadIntPtr(viewSourcePtr); | |
| // CreateView - это индекс 6 в VTable | |
| IntPtr createViewPtr = Marshal.ReadIntPtr(vtable, 6 * IntPtr.Size); | |
| var createViewDel = Marshal.GetDelegateForFunctionPointer<CreateViewDelegate>(createViewPtr); | |
| int hr = createViewDel(viewSourcePtr, out IntPtr viewPtr); | |
| Console.WriteLine($"[HLE] CreateView returned HR: 0x{hr:X}, View: 0x{viewPtr.ToInt64():X}"); | |
| if (hr == 0 && viewPtr != IntPtr.Zero) | |
| { | |
| IntPtr viewVtable = Marshal.ReadIntPtr(viewPtr); | |
| // 6: Initialize(CoreApplicationView*) | |
| Console.WriteLine("[HLE] Calling IFrameworkView::Initialize..."); | |
| IntPtr initializePtr = Marshal.ReadIntPtr(viewVtable, 6 * IntPtr.Size); | |
| var initDel = Marshal.GetDelegateForFunctionPointer<InitializeDelegate>(initializePtr); | |
| initDel(viewPtr, GenerateFakeComInstance(TYPE_CORE_APPLICATION_VIEW)); // ИСПРАВЛЕНО | |
| // 7: SetWindow(CoreWindow*) - передаем фейковый CoreWindow | |
| Console.WriteLine("[HLE] Calling IFrameworkView::SetWindow..."); | |
| IntPtr setWindowPtr = Marshal.ReadIntPtr(viewVtable, 7 * IntPtr.Size); | |
| var setWindowDel = Marshal.GetDelegateForFunctionPointer<SetWindowDelegate>(setWindowPtr); | |
| setWindowDel(viewPtr, GenerateFakeComInstance()); | |
| // 8: Load(HSTRING) | |
| Console.WriteLine("[HLE] Calling IFrameworkView::Load..."); | |
| IntPtr loadPtr = Marshal.ReadIntPtr(viewVtable, 8 * IntPtr.Size); | |
| var loadDel = Marshal.GetDelegateForFunctionPointer<LoadDelegate>(loadPtr); | |
| loadDel(viewPtr, IntPtr.Zero); | |
| // 9: Run() | |
| Console.WriteLine("[HLE] Calling IFrameworkView::Run..."); | |
| IntPtr runPtr = Marshal.ReadIntPtr(viewVtable, 9 * IntPtr.Size); | |
| var runDel = Marshal.GetDelegateForFunctionPointer<RunDelegate>(runPtr); | |
| runDel(viewPtr); | |
| // 10: Uninitialize() | |
| Console.WriteLine("[HLE] Calling IFrameworkView::Uninitialize..."); | |
| IntPtr uninitPtr = Marshal.ReadIntPtr(viewVtable, 10 * IntPtr.Size); | |
| var uninitDel = Marshal.GetDelegateForFunctionPointer<UninitializeDelegate>(uninitPtr); | |
| uninitDel(viewPtr); | |
| } | |
| else | |
| { | |
| Console.WriteLine("[HLE] CreateView failed or returned null!"); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"[HLE] CoreApplicationRunImpl crashed: {ex.Message}\n{ex.StackTrace}"); | |
| } | |
| return 0; | |
| } | |
| public static int GetActivationFactoryImpl(IntPtr classId, out IntPtr factory) | |
| { | |
| Console.WriteLine("[HLE] GetActivationFactory called. Returning fake COM object."); | |
| factory = GenerateFakeComInstance(); | |
| return 0; // S_OK | |
| } | |
| public static int GetActivationFactoryByPCWSTRImpl(IntPtr classId, IntPtr iid, out IntPtr factory) | |
| { | |
| string className = "[null]"; | |
| try { if (classId != IntPtr.Zero) className = Marshal.PtrToStringUni(classId); } catch { } | |
| Guid iidGuid = Guid.Empty; | |
| try { if (iid != IntPtr.Zero) iidGuid = GuidFromPtr(iid); } catch { } | |
| Console.WriteLine($"[HLE] GetActivationFactoryByPCWSTR called. Class: {className}, IID: {iidGuid}"); | |
| factory = GenerateFakeComInstance(); | |
| return 0; // S_OK | |
| } | |
| public static Guid GuidFromPtr(IntPtr ptr) | |
| { | |
| byte[] bytes = new byte[16]; | |
| Marshal.Copy(ptr, bytes, 0, 16); | |
| return new Guid(bytes); | |
| } | |
| [HandleProcessCorruptedStateExceptions] | |
| [SecurityCritical] | |
| static int QueryInterfaceImpl(IntPtr thisPtr, IntPtr iid, out IntPtr ppv) | |
| { | |
| Guid iidGuid = Guid.Empty; | |
| try { if (iid != IntPtr.Zero) iidGuid = GuidFromPtr(iid); } catch { } | |
| Console.WriteLine($"[HLE] COM QueryInterface called. IID: {iidGuid}"); | |
| ppv = thisPtr; // Возвращаем тот же фейковый объект | |
| return 0; // S_OK | |
| } | |
| static int AddRefImpl(IntPtr thisPtr) => 1; | |
| static int ReleaseImpl(IntPtr thisPtr) => 1; | |
| static int GetIidsImpl(IntPtr thisPtr, out int count, out IntPtr iids) | |
| { | |
| count = 0; | |
| iids = IntPtr.Zero; | |
| return unchecked((int)0x80004001); // E_NOTIMPL | |
| } | |
| static int GetRuntimeClassNameImpl(IntPtr thisPtr, out IntPtr className) | |
| { | |
| className = IntPtr.Zero; | |
| return unchecked((int)0x80004001); // E_NOTIMPL | |
| } | |
| static int GetTrustLevelImpl(IntPtr thisPtr, out int trustLevel) | |
| { | |
| trustLevel = 1; // BaseTrust | |
| return 0; // S_OK | |
| } | |
| static int ActivateInstanceImpl(IntPtr thisPtr, out IntPtr instance) | |
| { | |
| Console.WriteLine("[HLE] IActivationFactory::ActivateInstance called."); | |
| instance = GenerateFakeComInstance(); | |
| return 0; // S_OK | |
| } | |
| public const int TYPE_UNKNOWN = 0; | |
| public const int TYPE_CORE_APPLICATION_VIEW = 1; | |
| public const int TYPE_LAUNCH_ACTIVATED_EVENT_ARGS = 2; | |
| public const int TYPE_CORE_WINDOW = 3; | |
| private static IntPtr _comInstancePool = IntPtr.Zero; | |
| private static int _comInstancePoolOffset = 0; | |
| public static IntPtr GenerateFakeComInstance(int typeId = 0) | |
| { | |
| if (_comVTable == IntPtr.Zero) | |
| { | |
| _comVTable = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)1024, 0x3000, 0x40); | |
| if (_comVTable == IntPtr.Zero) return IntPtr.Zero; | |
| for (int i = 0; i < 128; i++) | |
| { | |
| var dm = new DynamicMethod($"ComStub_{i}", typeof(int), new[] { typeof(IntPtr), typeof(IntPtr), typeof(IntPtr), typeof(IntPtr) }, typeof(XboxExports).Module, true); | |
| var il = dm.GetILGenerator(); | |
| il.Emit(OpCodes.Ldc_I4, i); | |
| il.Emit(OpCodes.Ldarg_0); | |
| il.Emit(OpCodes.Ldarg_1); | |
| il.Emit(OpCodes.Ldarg_2); | |
| il.Emit(OpCodes.Ldarg_3); | |
| il.EmitCall(OpCodes.Call, typeof(XboxExports).GetMethod("SafeComStubImplWithIndex", BindingFlags.Static | BindingFlags.Public), null); | |
| il.Emit(OpCodes.Ret); | |
| var del = dm.CreateDelegate(typeof(SafeComStubDelegate)); | |
| _comSafeStubs.Add(del); | |
| IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); | |
| Marshal.WriteIntPtr(_comVTable, i * 8, ptr); | |
| } | |
| Marshal.WriteIntPtr(_comVTable, 0 * 8, Marshal.GetFunctionPointerForDelegate(_qi)); | |
| Marshal.WriteIntPtr(_comVTable, 1 * 8, Marshal.GetFunctionPointerForDelegate(_addRef)); | |
| Marshal.WriteIntPtr(_comVTable, 2 * 8, Marshal.GetFunctionPointerForDelegate(_release)); | |
| Marshal.WriteIntPtr(_comVTable, 3 * 8, Marshal.GetFunctionPointerForDelegate(_getIids)); | |
| Marshal.WriteIntPtr(_comVTable, 4 * 8, Marshal.GetFunctionPointerForDelegate(_getRuntimeClassName)); | |
| Marshal.WriteIntPtr(_comVTable, 5 * 8, Marshal.GetFunctionPointerForDelegate(_getTrustLevel)); | |
| } | |
| // ИСПРАВЛЕНО: Используем пул памяти вместо VirtualAlloc для каждого объекта | |
| if (_comInstancePool == IntPtr.Zero) | |
| { | |
| _comInstancePool = NativeMethods.VirtualAlloc(IntPtr.Zero, (IntPtr)(1024 * 1024), 0x3000, 0x04); // 1 MB pool | |
| if (_comInstancePool == IntPtr.Zero) | |
| { | |
| Console.WriteLine("[HLE] FATAL: Failed to allocate COM instance pool!"); | |
| return IntPtr.Zero; | |
| } | |
| } | |
| if (_comInstancePoolOffset >= 1024 * 1024 - 16) | |
| { | |
| Console.WriteLine("[HLE] FATAL: COM instance pool exhausted!"); | |
| return IntPtr.Zero; | |
| } | |
| IntPtr fakeInstance = IntPtr.Add(_comInstancePool, _comInstancePoolOffset); | |
| _comInstancePoolOffset += 16; // 8 bytes for VTable, 8 bytes for TypeID | |
| Marshal.WriteIntPtr(fakeInstance, _comVTable); | |
| Marshal.WriteInt32(fakeInstance, 8, typeId); | |
| return fakeInstance; | |
| } | |
| [HandleProcessCorruptedStateExceptions] | |
| [SecurityCritical] | |
| public static int SafeComStubImplWithIndex(int index, IntPtr thisPtr, IntPtr arg1, IntPtr arg2, IntPtr arg3) | |
| { | |
| int typeId = 0; | |
| try { typeId = Marshal.ReadInt32(thisPtr, 8); } catch { } | |
| Console.WriteLine($"[HLE] COM Method called. Type: {typeId}, VTable Index: {index}, Args: 0x{arg1.ToInt64():X}, 0x{arg2.ToInt64():X}, 0x{arg3.ToInt64():X}"); | |
| // ICoreApplication::Run(IFrameworkViewSource*) - индекс 13 | |
| if (index == 13 && typeId == TYPE_UNKNOWN) | |
| { | |
| Console.WriteLine("[HLE] ICoreApplication::Run called!"); | |
| return CoreApplicationRunImpl(arg1); | |
| } | |
| // CoreApplicationView (Type 1) | |
| if (typeId == TYPE_CORE_APPLICATION_VIEW) | |
| { | |
| if (index == 6) // get_CoreWindow | |
| { | |
| Console.WriteLine("[HLE] CoreApplicationView::get_CoreWindow"); | |
| Marshal.WriteIntPtr(arg1, GenerateFakeComInstance(TYPE_CORE_WINDOW)); | |
| return 0; | |
| } | |
| if (index == 7) // get_LaunchActivatedEventArgs | |
| { | |
| Console.WriteLine("[HLE] CoreApplicationView::get_LaunchActivatedEventArgs"); | |
| Marshal.WriteIntPtr(arg1, GenerateFakeComInstance(TYPE_LAUNCH_ACTIVATED_EVENT_ARGS)); | |
| return 0; | |
| } | |
| } | |
| // LaunchActivatedEventArgs (Type 2) | |
| if (typeId == TYPE_LAUNCH_ACTIVATED_EVENT_ARGS) | |
| { | |
| if (index == 6) // get_Kind (ActivationKind enum, 4 байта) | |
| { | |
| Console.WriteLine("[HLE] LaunchActivatedEventArgs::get_Kind -> Launch"); | |
| Marshal.WriteInt32(arg1, 0); // 0 = Launch | |
| return 0; | |
| } | |
| if (index == 7) // get_PreviousExecutionState (enum, 4 байта) | |
| { | |
| Console.WriteLine("[HLE] LaunchActivatedEventArgs::get_PreviousExecutionState -> NotRunning"); | |
| Marshal.WriteInt32(arg1, 1); // 1 = NotRunning | |
| return 0; | |
| } | |
| if (index == 8) // get_SplashScreen | |
| { | |
| Console.WriteLine("[HLE] LaunchActivatedEventArgs::get_SplashScreen"); | |
| Marshal.WriteIntPtr(arg1, GenerateFakeComInstance(TYPE_UNKNOWN)); | |
| return 0; | |
| } | |
| if (index == 9) // get_Arguments (HSTRING) | |
| { | |
| Console.WriteLine("[HLE] LaunchActivatedEventArgs::get_Arguments"); | |
| Marshal.WriteIntPtr(arg1, IntPtr.Zero); // No arguments | |
| return 0; | |
| } | |
| } | |
| // ИСПРАВЛЕНО: Для всех остальных методов возвращаем S_OK, но НИЧЕГО не пишем в arg1! | |
| // Это предотвратит затирание стека и мусорных указателей. | |
| return 0; // S_OK | |
| } | |
| } | |
| public static class NativeMethods | |
| { | |
| public const uint GENERIC_WRITE = 0x40000000; | |
| public const uint OPEN_ALWAYS = 4; | |
| [StructLayout(LayoutKind.Sequential)] | |
| public struct MEMORY_BASIC_INFORMATION | |
| { | |
| public IntPtr BaseAddress; | |
| public IntPtr AllocationBase; | |
| public uint AllocationProtect; | |
| public IntPtr RegionSize; | |
| public uint State; | |
| public uint Protect; | |
| public uint Type; | |
| } | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr VirtualQuery(IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, IntPtr dwLength); | |
| [DllImport("kernel32.dll")] | |
| public static extern uint GetCurrentThreadId(); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern int TlsAlloc(); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool TlsSetValue(int dwTlsIndex, IntPtr lpTlsValue); | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] | |
| public static extern IntPtr LoadLibraryA(string lpFileName); | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] | |
| public static extern IntPtr GetModuleHandleA(string lpModuleName); | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] | |
| public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool CloseHandle(IntPtr hObject); | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] | |
| public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern uint SetFilePointer(IntPtr hFile, int lDistanceToMove, IntPtr lpDistanceToMoveHigh, uint dwMoveMethod); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr GetStdHandle(int nStdHandle); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool SetEvent(IntPtr hEvent); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool RtlAddFunctionTable(IntPtr FunctionTable, uint EntryCount, IntPtr BaseAddress); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern IntPtr AddVectoredExceptionHandler(uint First, Diagnostics.VectoredHandler handler); | |
| [DllImport("ole32.dll")] | |
| public static extern int CoInitializeEx(IntPtr pvReserved, COINIT dwCoInit); | |
| [DllImport("ole32.dll")] | |
| public static extern void CoUninitialize(); | |
| [Flags] | |
| public enum COINIT : uint { APARTMENTTHREADED = 0x2, MULTITHREADED = 0x0 } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment