@@ -82,6 +82,181 @@ constexpr DWORD Milliseconds(std::chrono::milliseconds duration)
8282
8383class CClientBase ;
8484
85+ // Record crash registers in scenario that a failure to create .dmp is likely
86+ namespace EmergencyCrashLogging
87+ {
88+ // Result codes for SEH-protected read operations
89+ enum class ReadResult : int
90+ {
91+ Success = 1 ,
92+ NullPointer = 0 ,
93+ AccessFault = -1
94+ };
95+
96+ static void DoAddReportLog (int id, const char * msg)
97+ {
98+ SharedUtil::AddReportLog (id, msg);
99+ }
100+
101+ static bool TryAddReportLog (int id, const char * msg)
102+ {
103+ __try
104+ {
105+ DoAddReportLog (id, msg);
106+ return true ;
107+ }
108+ __except (EXCEPTION_EXECUTE_HANDLER)
109+ {
110+ return false ;
111+ }
112+ }
113+
114+ static void SafeEmergencyLog (const char * msg)
115+ {
116+ // Always output to debugger first (guaranteed crash-safe)
117+ OutputDebugStringA (msg);
118+ OutputDebugStringA (" \n " );
119+
120+ TryAddReportLog (9800 , msg);
121+ }
122+
123+ // SEH-isolated reader for exception record
124+ #pragma warning(push)
125+ #pragma warning(disable: 4702)
126+ static ReadResult TryReadExceptionRecord (const _EXCEPTION_POINTERS* pException,
127+ char * buf, size_t bufSize)
128+ {
129+ ReadResult result = ReadResult::AccessFault;
130+ __try
131+ {
132+ if (pException->ExceptionRecord != nullptr )
133+ {
134+ sprintf_s (buf, bufSize, " Code=0x%08lX Addr=0x%08lX ThreadId=%lu" ,
135+ static_cast <unsigned long >(pException->ExceptionRecord ->ExceptionCode ),
136+ static_cast <unsigned long >(reinterpret_cast <uintptr_t >(
137+ pException->ExceptionRecord ->ExceptionAddress )),
138+ static_cast <unsigned long >(GetCurrentThreadId ()));
139+ result = ReadResult::Success;
140+ }
141+ else
142+ {
143+ result = ReadResult::NullPointer;
144+ }
145+ }
146+ __except (EXCEPTION_EXECUTE_HANDLER)
147+ {
148+ result = ReadResult::AccessFault;
149+ }
150+ return result;
151+ }
152+
153+ // SEH-isolated reader for context record
154+ static ReadResult TryReadContextRecord (const _EXCEPTION_POINTERS* pException,
155+ char * buf1, size_t buf1Size,
156+ char * buf2, size_t buf2Size)
157+ {
158+ ReadResult result = ReadResult::AccessFault;
159+ __try
160+ {
161+ if (pException->ContextRecord != nullptr )
162+ {
163+ const CONTEXT* ctx = pException->ContextRecord ;
164+
165+ sprintf_s (buf1, buf1Size, " EAX=0x%08lX EBX=0x%08lX ECX=0x%08lX EDX=0x%08lX" ,
166+ ctx->Eax , ctx->Ebx , ctx->Ecx , ctx->Edx );
167+
168+ sprintf_s (buf2, buf2Size, " ESI=0x%08lX EDI=0x%08lX EBP=0x%08lX ESP=0x%08lX EIP=0x%08lX EFL=0x%08lX" ,
169+ ctx->Esi , ctx->Edi , ctx->Ebp , ctx->Esp , ctx->Eip , ctx->EFlags );
170+
171+ result = ReadResult::Success;
172+ }
173+ else
174+ {
175+ result = ReadResult::NullPointer;
176+ }
177+ }
178+ __except (EXCEPTION_EXECUTE_HANDLER)
179+ {
180+ result = ReadResult::AccessFault;
181+ }
182+ return result;
183+ }
184+ #pragma warning(pop)
185+
186+ } // namespace EmergencyCrashLogging
187+
188+ // Call SEH-isolated readers
189+ static void LogEmergencyExceptionRecord (const _EXCEPTION_POINTERS* pException)
190+ {
191+ using namespace EmergencyCrashLogging ;
192+
193+ std::array<char , 128 > buf{};
194+
195+ const auto result = TryReadExceptionRecord (pException, buf.data (), buf.size ());
196+
197+ switch (result)
198+ {
199+ case ReadResult::Success:
200+ SafeEmergencyLog (buf.data ());
201+ break ;
202+ case ReadResult::NullPointer:
203+ SafeEmergencyLog (" [!] ExceptionRecord is NULL" );
204+ break ;
205+ case ReadResult::AccessFault:
206+ SafeEmergencyLog (" [!] Fault reading ExceptionRecord" );
207+ break ;
208+ default :
209+ SafeEmergencyLog (" [!] Unknown result reading ExceptionRecord" );
210+ break ;
211+ }
212+ }
213+
214+ static void LogEmergencyContextRecord (const _EXCEPTION_POINTERS* pException)
215+ {
216+ using namespace EmergencyCrashLogging ;
217+
218+ std::array<char , 128 > buf1{};
219+ std::array<char , 128 > buf2{};
220+
221+ const auto result = TryReadContextRecord (pException,
222+ buf1.data (), buf1.size (),
223+ buf2.data (), buf2.size ());
224+
225+ switch (result)
226+ {
227+ case ReadResult::Success:
228+ SafeEmergencyLog (buf1.data ());
229+ SafeEmergencyLog (buf2.data ());
230+ break ;
231+ case ReadResult::NullPointer:
232+ SafeEmergencyLog (" [!] ContextRecord is NULL" );
233+ break ;
234+ case ReadResult::AccessFault:
235+ SafeEmergencyLog (" [!] Fault reading context" );
236+ break ;
237+ default :
238+ SafeEmergencyLog (" [!] Unknown result reading ContextRecord" );
239+ break ;
240+ }
241+ }
242+
243+ static void LogEmergencyCrashContext (const _EXCEPTION_POINTERS* pException)
244+ {
245+ using namespace EmergencyCrashLogging ;
246+
247+ SafeEmergencyLog (" === EMERGENCY CRASH CONTEXT ===" );
248+
249+ if (pException == nullptr )
250+ {
251+ SafeEmergencyLog (" [!] pException is NULL" );
252+ return ;
253+ }
254+
255+ LogEmergencyExceptionRecord (pException);
256+
257+ LogEmergencyContextRecord (pException);
258+ }
259+
85260static bool SafeReadGameByte (uintptr_t address, unsigned char & outValue)
86261{
87262 __try
@@ -1267,6 +1442,11 @@ long WINAPI CCrashDumpWriter::HandleExceptionGlobal(_EXCEPTION_POINTERS* pExcept
12671442 // This is critical for diagnosing exceptions that may fault during handling
12681443 OutputDebugStringSafe (" CCrashDumpWriter::HandleExceptionGlobal - EMERGENCY ENTRY MARKER\n " );
12691444
1445+ // Log exception code and registers immediately - ensures crash context is captured
1446+ // even if subsequent processing fails and no dump is generated (stale artifact scenario)
1447+ // Uses SafeEmergencyLog internally which is SEH-protected
1448+ LogEmergencyCrashContext (pException);
1449+
12701450 SAFE_DEBUG_OUTPUT (" ========================================\n " );
12711451 SAFE_DEBUG_OUTPUT (" CCrashDumpWriter::HandleExceptionGlobal - ENTRY\n " );
12721452 SAFE_DEBUG_OUTPUT (" ========================================\n " );
0 commit comments