Port latest features from test client to extension
This commit is contained in:
		
							parent
							
								
									c88ae7d972
								
							
						
					
					
						commit
						2706b7f628
					
				| @ -17,12 +17,16 @@ def BuildEverything(): | ||||
| 	compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD'], 'public', 'extensions')) | ||||
| 	compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD'], 'public', 'sourcepawn')) | ||||
| 
 | ||||
| 	compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src')) | ||||
| 
 | ||||
| 	if AMBuild.target['platform'] in ['linux']: | ||||
| 		compiler['POSTLINKFLAGS'].append('-lm') | ||||
| 		compiler['POSTLINKFLAGS'].append('-lstdc++') | ||||
| 		compiler['POSTLINKFLAGS'].append('-pthread') | ||||
| 
 | ||||
| 		compiler['CDEFINES'].append('HAVE_CONFIG_H') | ||||
| 		compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'breakpad', 'build', 'src')) | ||||
| 
 | ||||
| 	compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src')) | ||||
| 
 | ||||
| 	name = 'accelerator.ext' | ||||
| 	extension = AMBuild.AddJob(name) | ||||
| 	binary = Cpp.LibraryBuilder(name, AMBuild, extension, compiler) | ||||
| @ -35,23 +39,50 @@ def BuildEverything(): | ||||
| 	binary.AddSourceFiles(AMBuild.cache['SOURCEMOD'], ['public/smsdk_ext.cpp']) | ||||
| 
 | ||||
|         if AMBuild.target['platform'] in ['linux']: | ||||
| 		link = os.path.join(AMBuild.outputFolder, extension.workFolder, 'libbreakpad_client.a') | ||||
| 		target = os.path.join(AMBuild.sourceFolder, 'breakpad', 'build', 'src', 'client', 'linux', 'libbreakpad_client.a') | ||||
|             binary.AddSourceFiles(os.path.join('breakpad', 'src', 'src', 'common'), [ | ||||
|                 'dwarf_cfi_to_module.cc', | ||||
|                 'dwarf_cu_to_module.cc', | ||||
|                 'dwarf_line_to_module.cc', | ||||
|                 'language.cc', | ||||
|                 'module.cc', | ||||
|                 'path_helper.cc', | ||||
|                 'stabs_reader.cc', | ||||
|                 'stabs_to_module.cc', | ||||
|                 'dwarf/bytereader.cc', | ||||
|                 'dwarf/dwarf2diehandler.cc', | ||||
|                 'dwarf/dwarf2reader.cc', | ||||
|                 'dwarf/elf_reader.cc', | ||||
|                 'linux/crc32.cc', | ||||
|                 'linux/dump_symbols.cc', | ||||
|                 'linux/elf_symbols_to_module.cc', | ||||
|             ]) | ||||
| 
 | ||||
| 	if AMBuild.target['platform'] in ['linux']: | ||||
| 		libs = [ | ||||
| 			('libbreakpad_client.a', os.path.join('breakpad', 'build', 'src', 'client', 'linux', 'libbreakpad_client.a')), | ||||
| 			('libbreakpad.a', os.path.join('breakpad', 'build', 'src', 'libbreakpad.a')), | ||||
| 			('libdisasm.a', os.path.join('breakpad', 'build', 'src', 'third_party', 'libdisasm', 'libdisasm.a')), | ||||
| 		] | ||||
| 
 | ||||
| 		for lib, target in libs: | ||||
| 			link = os.path.join(AMBuild.outputFolder, extension.workFolder, lib) | ||||
| 			target = os.path.join(AMBuild.sourceFolder, target) | ||||
| 			try: | ||||
| 				os.lstat(link) | ||||
| 			except: | ||||
| 				extension.AddCommand(SymlinkCommand(link, target)) | ||||
| 		binary.AddObjectFiles(['libbreakpad_client.a']) | ||||
| 			binary.AddObjectFiles([lib]) | ||||
| 
 | ||||
| 	elif AMBuild.target['platform'] in ['windows']: | ||||
| 		libs = ['exception_handler', 'common'] | ||||
| 		for lib in libs: | ||||
| 			path = os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'client', 'windows', 'handler', 'Release', 'lib', lib + '.lib') | ||||
| 			if os.path.isfile(path): | ||||
| 				binary.RelinkIfNewer(path) | ||||
| 			binary['POSTLINKFLAGS'].extend([path]) | ||||
| 		libs = [ | ||||
| 		    os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'client', 'windows', 'handler', 'Release', 'lib', 'common.lib'), | ||||
| 		    os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'client', 'windows', 'handler', 'Release', 'lib', 'exception_handler.lib'), | ||||
| 		    os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'client', 'windows', 'crash_generation', 'Release', 'lib', 'crash_generation_client.lib'), | ||||
| 		    os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'processor', 'Release', 'lib', 'libdisasm.lib'), | ||||
| 		    os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'processor', 'Release', 'lib', 'processor.lib'), | ||||
| 		] | ||||
| 
 | ||||
| 		path = os.path.join(AMBuild.sourceFolder, 'breakpad', 'src', 'src', 'client', 'windows', 'crash_generation', 'Release', 'lib', 'crash_generation_client.lib') | ||||
| 		for path in libs: | ||||
| 			if os.path.isfile(path): | ||||
| 				binary.RelinkIfNewer(path) | ||||
| 			binary['POSTLINKFLAGS'].extend([path]) | ||||
|  | ||||
| @ -26,17 +26,52 @@ | ||||
| #include "client/linux/handler/exception_handler.h" | ||||
| #include "common/linux/linux_libc_support.h" | ||||
| #include "third_party/lss/linux_syscall_support.h" | ||||
| #include "common/linux/dump_symbols.h" | ||||
| #include "common/path_helper.h" | ||||
| 
 | ||||
| #include <signal.h> | ||||
| #include <dirent.h> | ||||
| #include <unistd.h> | ||||
| #include <paths.h> | ||||
| 
 | ||||
| class StderrInhibitor | ||||
| { | ||||
| 	FILE *saved_stderr = nullptr; | ||||
| 
 | ||||
| public: | ||||
| 	StderrInhibitor() { | ||||
| 		saved_stderr = fdopen(dup(fileno(stderr)), "w"); | ||||
| 		if (freopen(_PATH_DEVNULL, "w", stderr)) { | ||||
| 			// If it fails, not a lot we can (or should) do.
 | ||||
| 			// Add this brace section to silence gcc warnings.
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	~StderrInhibitor() { | ||||
| 		fflush(stderr); | ||||
| 		dup2(fileno(saved_stderr), fileno(stderr)); | ||||
| 		fclose(saved_stderr); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| #elif defined _WINDOWS | ||||
| #define _STDINT // ~.~
 | ||||
| #include "client/windows/handler/exception_handler.h" | ||||
| 
 | ||||
| #else | ||||
| #error Bad platform. | ||||
| #endif | ||||
| 
 | ||||
| #include <google_breakpad/processor/minidump.h> | ||||
| #include <google_breakpad/processor/minidump_processor.h> | ||||
| #include <google_breakpad/processor/process_state.h> | ||||
| #include <google_breakpad/processor/call_stack.h> | ||||
| #include <google_breakpad/processor/stack_frame.h> | ||||
| #include <processor/pathname_stripper.h> | ||||
| 
 | ||||
| #include <sstream> | ||||
| #include <streambuf> | ||||
| 
 | ||||
| Accelerator g_accelerator; | ||||
| SMEXT_LINK(&g_accelerator); | ||||
| 
 | ||||
| @ -219,6 +254,7 @@ void OnGameFrame(bool simulating) | ||||
| 		sigaction(kExceptionSignals[i], &act, NULL); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #elif defined _WINDOWS | ||||
| void *vectoredHandler = NULL; | ||||
| 
 | ||||
| @ -307,12 +343,283 @@ static bool dumpCallback(const wchar_t* dump_path, | ||||
| 
 | ||||
| 	return succeeded; | ||||
| } | ||||
| 
 | ||||
| #else | ||||
| #error Bad platform. | ||||
| #endif | ||||
| 
 | ||||
| bool UploadAndDeleteCrashDump(const char *path, char *response, int maxlen) | ||||
| class ClogInhibitor | ||||
| { | ||||
| 	std::streambuf *saved_clog = nullptr; | ||||
| 
 | ||||
| public: | ||||
| 	ClogInhibitor() { | ||||
| 		saved_clog = std::clog.rdbuf(); | ||||
| 		std::clog.rdbuf(nullptr); | ||||
| 	} | ||||
| 
 | ||||
| 	~ClogInhibitor() { | ||||
| 		std::clog.rdbuf(saved_clog); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| class UploadThread: public IThread | ||||
| { | ||||
| 	void RunThread(IThreadHandle *pHandle) { | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread started."); | ||||
| 
 | ||||
| 		FILE *log = fopen(logPath, "a"); | ||||
| 		if (!log) { | ||||
| 			g_pSM->LogError(myself, "Failed to open Accelerator log file: %s", logPath); | ||||
| 		} | ||||
| 
 | ||||
| 		IDirectory *dumps = libsys->OpenDirectory(dumpStoragePath); | ||||
| 
 | ||||
| 		int count = 0; | ||||
| 		int failed = 0; | ||||
| 		char path[512]; | ||||
| 		char response[512]; | ||||
| 
 | ||||
| 		while (dumps->MoreFiles()) { | ||||
| 			if (!dumps->IsEntryFile()) { | ||||
| 				dumps->NextEntry(); | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			const char *name = dumps->GetEntryName(); | ||||
| 
 | ||||
| 			int namelen = strlen(name); | ||||
| 			if (namelen < 4 || strcmp(&name[namelen-4], ".dmp") != 0) { | ||||
| 				dumps->NextEntry(); | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			g_pSM->Format(path, sizeof(path), "%s/%s", dumpStoragePath, name); | ||||
| 
 | ||||
| 			// TODO: Check the return value.
 | ||||
| 			bool wantsUpload = PresubmitCrashDump(path); | ||||
| 
 | ||||
| 			bool uploaded = UploadAndDeleteCrashDump(path, response, sizeof(response)); | ||||
| 
 | ||||
| 			if (uploaded) { | ||||
| 				count++; | ||||
| 				g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response); | ||||
| 				if (log) fprintf(log, "Uploaded crash dump: %s\n", response); | ||||
| 			} else { | ||||
| 				failed++; | ||||
| 				g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response); | ||||
|                                 if (log) fprintf(log, "Failed to upload crash dump: %s\n", response); | ||||
| 			} | ||||
| 
 | ||||
| 			dumps->NextEntry(); | ||||
| 		} | ||||
| 
 | ||||
| 		libsys->CloseDirectory(dumps); | ||||
| 
 | ||||
| 		if (log) { | ||||
| 			fclose(log); | ||||
| 		} | ||||
| 
 | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread finished. (%d uploaded, %d failed)", count, failed); | ||||
| 	} | ||||
| 
 | ||||
| 	void OnTerminate(IThreadHandle *pHandle, bool cancel) { | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread terminated. (canceled = %s)", (cancel ? "true" : "false")); | ||||
| 	} | ||||
| 
 | ||||
| 	bool PresubmitCrashDump(const char *path) { | ||||
| 		google_breakpad::ProcessState processState; | ||||
| 		google_breakpad::ProcessResult processResult; | ||||
| 		google_breakpad::MinidumpProcessor minidumpProcessor(nullptr, nullptr); | ||||
| 
 | ||||
| 		{ | ||||
| 			ClogInhibitor clogInhibitor; | ||||
| 			processResult = minidumpProcessor.Process(path, &processState); | ||||
| 		} | ||||
| 
 | ||||
| 		if (processResult != google_breakpad::PROCESS_OK) { | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		int requestingThread = processState.requesting_thread(); | ||||
| 		if (requestingThread == -1) { | ||||
| 			requestingThread = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		const google_breakpad::CallStack *stack = processState.threads()->at(requestingThread); | ||||
| 		if (!stack) { | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		int frameCount = stack->frames()->size(); | ||||
| 		/*if (frameCount > 10) {
 | ||||
| 			frameCount = 10; | ||||
| 		}*/ | ||||
| 
 | ||||
| 		std::ostringstream summaryStream; | ||||
| 		summaryStream << 1 << "|" << processState.crashed() << "|" << processState.crash_reason() << "|" << std::hex << processState.crash_address() << std::dec << "|" << requestingThread; | ||||
| 
 | ||||
| 		std::map<const google_breakpad::CodeModule *, unsigned int> moduleMap; | ||||
| 
 | ||||
| 		unsigned int moduleCount = processState.modules()->module_count(); | ||||
| 		for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { | ||||
| 			auto module = processState.modules()->GetModuleAtIndex(moduleIndex); | ||||
| 			moduleMap[module] = moduleIndex; | ||||
| 
 | ||||
| 			auto debugFile = google_breakpad::PathnameStripper::File(module->debug_file()); | ||||
| 			auto debugIdentifier = module->debug_identifier(); | ||||
| 
 | ||||
| 			summaryStream << "|M|" << debugFile << "|" << debugIdentifier; | ||||
| 		} | ||||
| 
 | ||||
| 		for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { | ||||
| 			auto frame = stack->frames()->at(frameIndex); | ||||
| 
 | ||||
| 			int moduleIndex = -1; | ||||
| 			auto moduleOffset = frame->ReturnAddress(); | ||||
| 			if (frame->module) { | ||||
| 				moduleIndex = moduleMap[frame->module]; | ||||
| 				moduleOffset -= frame->module->base_address(); | ||||
| 			} | ||||
| 
 | ||||
| 			summaryStream << "|F|" << moduleIndex << "|" << std::hex << moduleOffset << std::dec; | ||||
| 		} | ||||
| 
 | ||||
| 		auto summaryLine = summaryStream.str(); | ||||
| 		// printf("%s\n", summaryLine.c_str());
 | ||||
| 
 | ||||
| 		IWebForm *form = webternet->CreateForm(); | ||||
| 
 | ||||
| 		const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount"); | ||||
| 		if (minidumpAccount) form->AddString("UserID", minidumpAccount); | ||||
| 
 | ||||
| 		form->AddString("CrashSignature", summaryLine.c_str()); | ||||
| 
 | ||||
| 		MemoryDownloader data; | ||||
| 		IWebTransfer *xfer = webternet->CreateSession(); | ||||
| 		xfer->SetFailOnHTTPError(true); | ||||
| 
 | ||||
| 		const char *minidumpUrl = g_pSM->GetCoreConfigValue("MinidumpUrl"); | ||||
| 		if (!minidumpUrl) minidumpUrl = "http://crash.limetech.org/submit"; | ||||
| 
 | ||||
| 		bool uploaded = xfer->PostAndDownload(minidumpUrl, form, &data, NULL); | ||||
| 
 | ||||
| 		if (!uploaded) { | ||||
| 			printf(">>> Presubmit failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode()); | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		int responseSize = data.GetSize(); | ||||
| 		char *response = new char[responseSize + 1]; | ||||
| 		strncpy(response, data.GetBuffer(), responseSize + 1); | ||||
| 		response[responseSize] = '\0'; | ||||
| 		printf(">>> Presubmit complete: %s\n", response); | ||||
| 
 | ||||
| 		if (responseSize < 2) { | ||||
| 			printf(">>> Response too short\n"); | ||||
| 			delete[] response; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		if (response[0] == 'E') { | ||||
| 			printf(">>> Presubmit error: %s\n", &response[2]); | ||||
| 			delete[] response; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		bool submitCrash = (response[0] == 'Y'); | ||||
| 
 | ||||
| 		if (response[1] != '|') { | ||||
| 			printf(">>> Response delimiter missing\n"); | ||||
| 			delete[] response; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		unsigned int responseCount = responseSize - 2; | ||||
| 		if (responseCount != moduleCount) { | ||||
| 			printf(">>> Response module list doesn't match sent list (%d != %d)\n", responseCount, moduleCount); | ||||
| 			delete[] response; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| #if defined _LINUX | ||||
| 		for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) { | ||||
| 			bool submitModule = (response[2 + moduleIndex] == 'Y'); | ||||
| 			if (!submitModule) { | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			auto module = processState.modules()->GetModuleAtIndex(moduleIndex); | ||||
| 
 | ||||
| 			auto debugFile = module->debug_file(); | ||||
| 			if (debugFile[0] != '/') { | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			printf(">>> Submitting %s\n", debugFile.c_str()); | ||||
| 
 | ||||
| 			auto debugFileDir = google_breakpad::DirName(debugFile); | ||||
| 			std::vector<string> debug_dirs{ | ||||
| 				debugFileDir, | ||||
| 			}; | ||||
| 
 | ||||
| 			std::ostringstream outputStream; | ||||
| 			google_breakpad::DumpOptions options(ALL_SYMBOL_DATA, true); | ||||
| 
 | ||||
| 			{ | ||||
| 				StderrInhibitor stdrrInhibitor; | ||||
| 
 | ||||
| 				if (!WriteSymbolFile(debugFile, debug_dirs, options, outputStream)) { | ||||
| 					outputStream.str(""); | ||||
| 					outputStream.clear(); | ||||
| 
 | ||||
| 					// Try again without debug dirs.
 | ||||
| 					if (!WriteSymbolFile(debugFile, {}, options, outputStream)) { | ||||
| 						// TODO: Something.
 | ||||
| 						continue; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			auto output = outputStream.str(); | ||||
| 			// output = output.substr(0, output.find("\n"));
 | ||||
| 			// printf(">>> %s\n", output.c_str());
 | ||||
| 
 | ||||
| 			IWebForm *symbolForm = webternet->CreateForm(); | ||||
| 			symbolForm->AddString("symbol_file", output.c_str()); | ||||
| 
 | ||||
| 			MemoryDownloader symbolData; | ||||
| 			IWebTransfer *symbolXfer = webternet->CreateSession(); | ||||
| 			xfer->SetFailOnHTTPError(true); | ||||
| 
 | ||||
| 			const char *symbolUrl = g_pSM->GetCoreConfigValue("MinidumpSymbolUrl"); | ||||
| 			if (!symbolUrl) symbolUrl = "http://crash.limetech.org/symbols/submit"; | ||||
| 
 | ||||
| 			bool symbolUploaded = symbolXfer->PostAndDownload(symbolUrl, symbolForm, &symbolData, NULL); | ||||
| 
 | ||||
| 			if (!symbolUploaded) { | ||||
| 				printf(">>> Symbol upload failed: %s (%d)\n", symbolXfer->LastErrorMessage(), symbolXfer->LastErrorCode()); | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			int symbolResponseSize = symbolData.GetSize(); | ||||
| 			char *symbolResponse = new char[symbolResponseSize + 1]; | ||||
| 			strncpy(symbolResponse, symbolData.GetBuffer(), symbolResponseSize + 1); | ||||
| 			do { | ||||
| 				symbolResponse[symbolResponseSize] = '\0'; | ||||
| 			} while (symbolResponse[--symbolResponseSize] == '\n'); | ||||
| 			printf(">>> Symbol upload complete: %s\n", symbolResponse); | ||||
| 			delete[] symbolResponse; | ||||
| 		} | ||||
| #else | ||||
| 		printf(">>> Symbol submission not available on this platform\n"); | ||||
| #endif | ||||
| 
 | ||||
| 		delete[] response; | ||||
| 		return submitCrash; | ||||
| 	} | ||||
| 
 | ||||
| 	bool UploadAndDeleteCrashDump(const char *path, char *response, int maxlen) { | ||||
| 		IWebForm *form = webternet->CreateForm(); | ||||
| 
 | ||||
| 		const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount"); | ||||
| @ -357,67 +664,6 @@ bool UploadAndDeleteCrashDump(const char *path, char *response, int maxlen) | ||||
| 
 | ||||
| 		return uploaded; | ||||
| 	} | ||||
| 
 | ||||
| class UploadThread: public IThread | ||||
| { | ||||
| 	void RunThread(IThreadHandle *pHandle) { | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread started."); | ||||
| 
 | ||||
| 		FILE *log = fopen(logPath, "a"); | ||||
| 		if (!log) { | ||||
| 			g_pSM->LogError(myself, "Failed to open Accelerator log file: %s", logPath); | ||||
| 		} | ||||
| 
 | ||||
| 		IDirectory *dumps = libsys->OpenDirectory(dumpStoragePath); | ||||
| 
 | ||||
| 		int count = 0; | ||||
| 		int failed = 0; | ||||
| 		char path[512]; | ||||
| 		char response[512]; | ||||
| 
 | ||||
| 		while (dumps->MoreFiles()) { | ||||
| 			if (!dumps->IsEntryFile()) { | ||||
| 				dumps->NextEntry(); | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			const char *name = dumps->GetEntryName(); | ||||
| 
 | ||||
| 			int namelen = strlen(name); | ||||
| 			if (namelen < 4 || strcmp(&name[namelen-4], ".dmp") != 0) { | ||||
| 				dumps->NextEntry(); | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			g_pSM->Format(path, sizeof(path), "%s/%s", dumpStoragePath, name); | ||||
| 			bool uploaded = UploadAndDeleteCrashDump(path, response, sizeof(response)); | ||||
| 
 | ||||
| 			if (uploaded) { | ||||
| 				count++; | ||||
| 				g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response); | ||||
| 				if (log) fprintf(log, "Uploaded crash dump: %s\n", response); | ||||
| 			} else { | ||||
| 				failed++; | ||||
| 				g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response); | ||||
|                                 if (log) fprintf(log, "Failed to upload crash dump: %s\n", response); | ||||
| 			} | ||||
| 
 | ||||
| 			dumps->NextEntry(); | ||||
| 		} | ||||
| 
 | ||||
| 		libsys->CloseDirectory(dumps); | ||||
| 
 | ||||
| 		if (log) { | ||||
| 			fclose(log); | ||||
| 		} | ||||
| 
 | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread finished. (%d uploaded, %d failed)", count, failed); | ||||
| 	} | ||||
| 
 | ||||
| 	void OnTerminate(IThreadHandle *pHandle, bool cancel) { | ||||
| 		rootconsole->ConsolePrint("Accelerator upload thread terminated. (canceled = %s)", (cancel ? "true" : "false")); | ||||
| 	} | ||||
| 
 | ||||
| } uploadThread; | ||||
| 
 | ||||
| class VFuncEmptyClass {}; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user