Index: ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp (revision 13723) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp (revision 13724) @@ -1,1779 +1,1785 @@ -/* Copyright (c) 2010 Wildfire Games +/* Copyright (c) 2013 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Win32 stack trace and symbol engine. */ #include "precompiled.h" #include "lib/sysdep/os/win/wdbg_sym.h" #include #include #include #include "lib/byte_order.h" // movzx_le64 #include "lib/module_init.h" #include "lib/sysdep/cpu.h" #include "lib/debug_stl.h" #include "lib/app_hooks.h" #include "lib/external_libraries/dbghelp.h" #include "lib/sysdep/os/win/wdbg.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_CRITICAL_INIT(wdbg_sym_Init); static WUTIL_FUNC(pRtlCaptureContext, VOID, (PCONTEXT)); static Status wdbg_sym_Init() { WUTIL_IMPORT_KERNEL32(RtlCaptureContext, pRtlCaptureContext); return INFO::OK; } //---------------------------------------------------------------------------- // dbghelp //---------------------------------------------------------------------------- // global for convenience (we only support a single process) static HANDLE hProcess; // for StackWalk64; taken from PE header by InitDbghelp. static WORD machine; static Status InitDbghelp() { hProcess = GetCurrentProcess(); dbghelp_ImportFunctions(); // set options // notes: // - can be done before SymInitialize; we do so in case // any of the options affect it. // - do not set directly - that would zero any existing flags. DWORD opts = pSymGetOptions(); //opts |= SYMOPT_DEBUG; // lots of debug spew in output window opts |= SYMOPT_DEFERRED_LOADS; // the "fastest, most efficient way" opts |= SYMOPT_LOAD_LINES; opts |= SYMOPT_UNDNAME; pSymSetOptions(opts); // initialize dbghelp. // .. request symbols from all currently active modules be loaded. const BOOL fInvadeProcess = TRUE; // .. use default *symbol* search path. we don't use this to locate // our PDB file because its absolute path is stored inside the EXE. const PWSTR UserSearchPath = 0; WinScopedPreserveLastError s; // SymInitializeW const BOOL ok = pSymInitializeW(hProcess, UserSearchPath, fInvadeProcess); WARN_IF_FALSE(ok); HMODULE hModule = GetModuleHandle(0); IMAGE_NT_HEADERS* const header = pImageNtHeader(hModule); machine = header->FileHeader.Machine; return INFO::OK; } // ensure dbghelp is initialized exactly once. // call every time before dbghelp functions are used. // (on-demand initialization allows handling exceptions raised before // winit.cpp init functions are called) // // NB: this may take SECONDS if OS symbols are installed and // symserv wants to access the internet. static void sym_init() { static ModuleInitState initState; ModuleInit(&initState, InitDbghelp); } static STACKFRAME64 PopulateStackFrame(CONTEXT& context) { STACKFRAME64 sf; memset(&sf, 0, sizeof(sf)); sf.AddrPC.Mode = AddrModeFlat; sf.AddrFrame.Mode = AddrModeFlat; sf.AddrStack.Mode = AddrModeFlat; #if ARCH_AMD64 sf.AddrPC.Offset = context.Rip; sf.AddrFrame.Offset = context.Rbp; sf.AddrStack.Offset = context.Rsp; #else sf.AddrPC.Offset = context.Eip; sf.AddrFrame.Offset = context.Ebp; sf.AddrStack.Offset = context.Esp; #endif return sf; } static IMAGEHLP_STACK_FRAME PopulateImageStackFrame(const STACKFRAME64& sf) { IMAGEHLP_STACK_FRAME isf; memset(&isf, 0, sizeof(isf)); // apparently only PC, FP and SP are necessary, but // we copy everything to be safe. isf.InstructionOffset = sf.AddrPC.Offset; isf.ReturnOffset = sf.AddrReturn.Offset; isf.FrameOffset = sf.AddrFrame.Offset; isf.StackOffset = sf.AddrStack.Offset; isf.BackingStoreOffset = sf.AddrBStore.Offset; isf.FuncTableEntry = (ULONG64)sf.FuncTableEntry; // (note: array of different types, can't copy directly) for(int i = 0; i < 4; i++) isf.Params[i] = sf.Params[i]; // isf.Reserved - already zeroed isf.Virtual = sf.Virtual; // isf.Reserved2 - already zeroed return isf; } struct SYMBOL_INFO_PACKAGEW2 : public SYMBOL_INFO_PACKAGEW { SYMBOL_INFO_PACKAGEW2() { si.SizeOfStruct = sizeof(si); si.MaxNameLen = MAX_SYM_NAME; } }; #pragma pack(push, 1) // note: we can't derive from TI_FINDCHILDREN_PARAMS because its members // aren't guaranteed to precede ours (although they do in practice). struct TI_FINDCHILDREN_PARAMS2 { TI_FINDCHILDREN_PARAMS2(DWORD numChildren) { p.Start = 0; p.Count = std::min(numChildren, maxChildren); } static const DWORD maxChildren = 300; TI_FINDCHILDREN_PARAMS p; DWORD childrenStorage[maxChildren-1]; }; #pragma pack(pop) // actual implementation; made available so that functions already under // the lock don't have to unlock (slow) to avoid recursive locking. static Status ResolveSymbol_lk(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { sym_init(); const DWORD64 addr = (DWORD64)ptr_of_interest; size_t successes = 0; WinScopedPreserveLastError s; // SymFromAddrW, SymGetLineFromAddrW64 // get symbol name (if requested) if(sym_name) { sym_name[0] = '\0'; SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(pSymFromAddrW(hProcess, addr, 0, sym)) { wcscpy_s(sym_name, DEBUG_SYMBOL_CHARS, sym->Name); successes++; } } // get source file and/or line number (if requested) if(file || line) { file[0] = '\0'; *line = 0; IMAGEHLP_LINEW64 line_info = { sizeof(IMAGEHLP_LINEW64) }; DWORD displacement; // unused but required by pSymGetLineFromAddr64! if(pSymGetLineFromAddrW64(hProcess, addr, &displacement, &line_info)) { if(file) { // strip full path down to base name only. // this loses information, but that isn't expected to be a // problem and is balanced by not having to do this from every // call site (full path is too long to display nicely). const wchar_t* basename = path_name_only(line_info.FileName); wcscpy_s(file, DEBUG_FILE_CHARS, basename); successes++; } if(line) { *line = line_info.LineNumber; successes++; } } } if(addr == 0 && GetLastError() == ERROR_MOD_NOT_FOUND) SetLastError(0); if(GetLastError() == ERROR_INVALID_ADDRESS) SetLastError(0); return (successes != 0)? INFO::OK : ERR::FAIL; } // file is the base name only, not path (see rationale in wdbg_sym). // the PDB implementation is rather slow (~500µs). Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { WinScopedLock lock(WDBG_SYM_CS); return ResolveSymbol_lk(ptr_of_interest, sym_name, file, line); } //---------------------------------------------------------------------------- // stack walk //---------------------------------------------------------------------------- Status debug_CaptureContext(void* pcontext) { // there are 4 ways to do so, in order of preference: // - RtlCaptureContext (only available on WinXP or above) // - assembly language subroutine (complicates the build system) // - intentionally raise an SEH exception and capture its context // (causes annoying "first chance exception" messages and // can't co-exist with WinScopedLock's destructor) // - GetThreadContext while suspended (a bit tricky + slow). // note: it used to be common practice to query the current thread // context, but WinXP SP2 and above require it be suspended. if(!pRtlCaptureContext) return ERR::NOT_SUPPORTED; // NOWARN CONTEXT* context = (CONTEXT*)pcontext; cassert(sizeof(CONTEXT) <= DEBUG_CONTEXT_SIZE); memset(context, 0, sizeof(CONTEXT)); context->ContextFlags = CONTEXT_FULL; pRtlCaptureContext(context); return INFO::OK; } static Status CallStackWalk(STACKFRAME64& sf, CONTEXT& context) { WinScopedLock lock(WDBG_SYM_CS); SetLastError(0); // StackWalk64 doesn't always SetLastError const HANDLE hThread = GetCurrentThread(); if(!pStackWalk64(machine, hProcess, hThread, &sf, &context, 0, pSymFunctionTableAccess64, pSymGetModuleBase64, 0)) return ERR::FAIL; // NOWARN (no stack frames left) // (the frame pointer can be zero despite StackWalk64 returning TRUE.) if(sf.AddrFrame.Offset == 0) return ERR::FAIL; // NOWARN (no stack frames left) // huge WTF in x64 debug builds (dbghelp 6.12.0002.633): // AddrFrame.Offset doesn't match the correct RBP value. // StackWalk64 updates the context [http://bit.ly/lo1aqZ] and // its Rbp is correct, so we'll use that. #if ARCH_AMD64 sf.AddrFrame.Offset = context.Rbp; #endif return INFO::OK; } // NB: CaptureStackBackTrace may be faster (http://msinilo.pl/blog/?p=40), // but wasn't known during development. Status wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, CONTEXT& context, const wchar_t* lastFuncToSkip) { sym_init(); STACKFRAME64 sf = PopulateStackFrame(context); wchar_t func[DEBUG_SYMBOL_CHARS]; Status ret = ERR::SYM_NO_STACK_FRAMES_FOUND; for(;;) // each stack frame: { if(CallStackWalk(sf, context) != INFO::OK) return ret; if(lastFuncToSkip) { void* const pc = (void*)(uintptr_t)sf.AddrPC.Offset; if(debug_ResolveSymbol(pc, func, 0, 0) == INFO::OK) { if(wcsstr(func, lastFuncToSkip)) // this was the last one to skip lastFuncToSkip = 0; continue; } } ret = cb(&sf, cbData); RETURN_STATUS_FROM_CALLBACK(ret); } } void* debug_GetCaller(void* pcontext, const wchar_t* lastFuncToSkip) { struct StoreAddress { static Status Func(const STACKFRAME64* sf, uintptr_t cbData) { const uintptr_t funcAddress = sf->AddrPC.Offset; // store funcAddress in our `output parameter' memcpy((void*)cbData, &funcAddress, sizeof(funcAddress)); return INFO::OK; } }; void* func; wdbg_assert(pcontext != 0); Status ret = wdbg_sym_WalkStack(&StoreAddress::Func, (uintptr_t)&func, *(CONTEXT*)pcontext, lastFuncToSkip); return (ret == INFO::OK)? func : 0; } //----------------------------------------------------------------------------- // helper routines for symbol value dump //----------------------------------------------------------------------------- // infinite recursion has never happened, but we check for it anyway. static const size_t maxIndirection = 255; static const size_t maxLevel = 255; struct DumpState { size_t level; size_t indirection; uintptr_t moduleBase; LPSTACKFRAME64 stackFrame; DumpState(uintptr_t moduleBase, LPSTACKFRAME64 stackFrame) : level(0), indirection(0), moduleBase(moduleBase), stackFrame(stackFrame) { } }; //---------------------------------------------------------------------------- static size_t out_chars_left; static wchar_t* out_pos; // (only warn once until next out_init to avoid flood of messages.) static bool out_have_warned_of_overflow; // some top-level (*) symbols cause tons of output - so much that they may // single-handedly overflow the buffer (e.g. pointer to a tree of huge UDTs). // we can't have that, so there is a limit in place as to how much a // single top-level symbol can output. after that is reached, dumping is // aborted for that symbol but continues for the subsequent top-level symbols. // // this is implemented as follows: dump_sym_cb latches the current output // position; each dump_sym (through which all symbols go) checks if the // new position exceeds the limit and aborts if so. // slight wrinkle: since we don't want each level of UDTs to successively // realize the limit has been hit and display the error message, we // return ERR::SYM_SINGLE_SYMBOL_LIMIT once and thereafter INFO::SYM_SUPPRESS_OUTPUT. // // * example: local variables, as opposed to child symbols in a UDT. static wchar_t* out_latched_pos; static bool out_have_warned_of_limit; static void out_init(wchar_t* buf, size_t max_chars) { out_pos = buf; out_chars_left = max_chars; out_have_warned_of_overflow = false; out_have_warned_of_limit = false; } static void out(const wchar_t* fmt, ...) { va_list args; va_start(args, fmt); // use vswprintf, not vswprintf_s, because we want to gracefully // handle buffer overflows int len = vswprintf(out_pos, out_chars_left, fmt, args); va_end(args); // success if(len >= 0) { out_pos += len; // make sure out_chars_left remains nonnegative if((size_t)len > out_chars_left) { DEBUG_WARN_ERR(ERR::LOGIC); // apparently wrote more than out_chars_left len = (int)out_chars_left; } out_chars_left -= len; } // no more room left else { // the buffer really is full yet out_chars_left may not be 0 // (since it isn't updated if vswprintf returns -1). // must be set so subsequent calls don't try to squeeze stuff in. out_chars_left = 0; // write a warning into the output buffer (once) so it isn't // abruptly cut off (which looks like an error) if(!out_have_warned_of_overflow) { out_have_warned_of_overflow = true; // with the current out_pos / out_chars_left variables, there's // no way of knowing where the buffer actually ends. no matter; // we'll just put the warning before out_pos and eat into the // second newest text. const wchar_t text[] = L"(no more room in buffer)"; wcscpy_s(out_pos-ARRAY_SIZE(text), ARRAY_SIZE(text), text); // safe } } } static void out_erase(size_t num_chars) { // don't do anything if end of buffer was hit (prevents repeatedly // scribbling over the last few bytes). if(out_have_warned_of_overflow) return; out_chars_left += (ssize_t)num_chars; out_pos -= num_chars; *out_pos = '\0'; // make sure it's 0-terminated in case there is no further output. } // (see above) static void out_latch_pos() { out_have_warned_of_limit = false; out_latched_pos = out_pos; } // (see above) static Status out_check_limit() { if(out_have_warned_of_limit) return INFO::SYM_SUPPRESS_OUTPUT; if(out_pos - out_latched_pos > 3000) // ~30 lines { out_have_warned_of_limit = true; return ERR::SYM_SINGLE_SYMBOL_LIMIT; // NOWARN } // no limit hit, proceed normally return INFO::OK; } //---------------------------------------------------------------------------- #define INDENT STMT(for(size_t i__ = 0; i__ <= state.level; i__++) out(L" ");) #define UNINDENT STMT(out_erase((state.level+1)*4);) // does it look like an ASCII string is located at ? // set to 2 to search for WCS-2 strings (of western characters!). // called by dump_sequence for its string special-case. // // algorithm: scan the "string" and count # text chars vs. garbage. static bool is_string(const u8* p, size_t stride) { // note: access violations are caught by dump_sym; output is "?". int score = 0; for(;;) { // current character is: const int c = *p & 0xff; // prevent sign extension p += stride; // .. text if(isalnum(c)) score += 5; // .. end of string else if(!c) break; // .. garbage else if(!isprint(c)) score -= 4; // got enough information either way => done. // (we don't want to unnecessarily scan huge binary arrays) if(abs(score) >= 10) break; } return (score > 0); } // forward decl; called by dump_sequence and some of dump_sym_*. static Status dump_sym(DWORD id, const u8* p, DumpState& state); // from cvconst.h // // rationale: we don't provide a get_register routine, since only the // value of FP is known to dump_frame_cb (via STACKFRAME64). // displaying variables stored in registers is out of the question; // all we can do is display FP-relative variables. enum CV_HREG_e { CV_REG_EBP = 22, CV_AMD64_RBP = 334 }; static void dump_error(Status err) { switch(err) { case 0: // no error => no output break; case ERR::SYM_SINGLE_SYMBOL_LIMIT: out(L"(too much output; skipping to next top-level symbol)"); break; case ERR::SYM_UNRETRIEVABLE_STATIC: out(L"(unavailable - located in another module)"); break; case ERR::SYM_UNRETRIEVABLE: out(L"(unavailable)"); break; case ERR::SYM_TYPE_INFO_UNAVAILABLE: out(L"(unavailable - type info request failed (GLE=%d))", GetLastError()); break; case ERR::SYM_INTERNAL_ERROR: out(L"(unavailable - internal error)\r\n"); break; case INFO::SYM_SUPPRESS_OUTPUT: // not an error; do not output anything. handled by caller. break; default: out(L"(unavailable - unspecified error 0x%X (%d))", err, err); break; } } // moved out of dump_sequence. static Status dump_string(const u8* p, size_t el_size) { // not char or wchar_t string if(el_size != sizeof(char) && el_size != sizeof(wchar_t)) return INFO::CANNOT_HANDLE; // not text if(!is_string(p, el_size)) return INFO::CANNOT_HANDLE; wchar_t buf[512]; if(el_size == sizeof(wchar_t)) { wcsncpy(buf, (const wchar_t*)p, ARRAY_SIZE(buf)); // can't use wcscpy_s because p might be too long wcscpy_s(buf+ARRAY_SIZE(buf)-4, 4, L"..."); // ensure null-termination } // convert to wchar_t else { size_t i; for(i = 0; i < ARRAY_SIZE(buf)-1; i++) { buf[i] = (wchar_t)p[i]; if(buf[i] == '\0') break; } buf[i] = '\0'; } out(L"\"%ls\"", buf); return INFO::OK; } // moved out of dump_sequence. static void seq_determine_formatting(size_t el_size, size_t el_count, bool* fits_on_one_line, size_t* num_elements_to_show) { if(el_size == sizeof(char)) { *fits_on_one_line = el_count <= 16; *num_elements_to_show = std::min((size_t)16u, el_count); } else if(el_size <= sizeof(int)) { *fits_on_one_line = el_count <= 8; *num_elements_to_show = std::min((size_t)12u, el_count); } else { *fits_on_one_line = false; *num_elements_to_show = std::min((size_t)8u, el_count); } // make sure empty containers are displayed with [0] {}, otherwise // the lack of output looks like an error. if(!el_count) *fits_on_one_line = true; } static Status dump_sequence(DebugStlIterator el_iterator, void* internal, size_t el_count, DWORD el_type_id, size_t el_size, DumpState& state) { const u8* el_p = 0; // avoid "uninitialized" warning // special case: display as a string if the sequence looks to be text. // do this only if container isn't empty because the otherwise the // iterator may crash. if(el_count) { el_p = el_iterator(internal, el_size); Status ret = dump_string(el_p, el_size); if(ret == INFO::OK) return ret; } // choose formatting based on element size and count bool fits_on_one_line; size_t num_elements_to_show; seq_determine_formatting(el_size, el_count, &fits_on_one_line, &num_elements_to_show); out(L"[%d] ", el_count); state.level++; out(fits_on_one_line? L"{ " : L"\r\n"); for(size_t i = 0; i < num_elements_to_show; i++) { if(!fits_on_one_line) INDENT; Status err = dump_sym(el_type_id, el_p, state); el_p = el_iterator(internal, el_size); // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. if(err == INFO::SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; continue; } dump_error(err); // nop if err == INFO::OK // add separator unless this is the last element (can't just // erase below due to additional "..."). if(i != num_elements_to_show-1) out(fits_on_one_line? L", " : L"\r\n"); if(err == ERR::SYM_SINGLE_SYMBOL_LIMIT) break; } // for each child // indicate some elements were skipped if(el_count != num_elements_to_show) out(L" ..."); state.level--; if(fits_on_one_line) out(L" }"); return INFO::OK; } static const u8* array_iterator(void* internal, size_t el_size) { const u8*& pos = *(const u8**)internal; const u8* cur_pos = pos; pos += el_size; return cur_pos; } static Status dump_array(const u8* p, size_t el_count, DWORD el_type_id, size_t el_size, DumpState& state) { const u8* iterator_internal_pos = p; return dump_sequence(array_iterator, &iterator_internal_pos, el_count, el_type_id, el_size, state); } static Status CanHandleDataKind(DWORD dataKind) { switch(dataKind) { case DataIsMember: // address is already correct (udt_dump_normal retrieved the offset; // we do it that way so we can check it against the total // UDT size for safety) and SymFromIndex would fail return INFO::SKIPPED; case DataIsUnknown: WARN_RETURN(ERR::FAIL); case DataIsStaticMember: // this symbol is defined as static in another module => // there's nothing we can do. return ERR::SYM_UNRETRIEVABLE_STATIC; // NOWARN case DataIsLocal: case DataIsStaticLocal: case DataIsParam: case DataIsObjectPtr: case DataIsFileStatic: case DataIsGlobal: case DataIsConstant: // ok, can handle return INFO::OK; } WARN_RETURN(ERR::LOGIC); // UNREACHABLE } static bool IsRelativeToFramePointer(DWORD flags, DWORD reg) { if(flags & SYMFLAG_FRAMEREL) // note: this is apparently obsolete return true; if((flags & SYMFLAG_REGREL) == 0) return false; if(reg == CV_REG_EBP || reg == CV_AMD64_RBP) return true; return false; } static bool IsUnretrievable(DWORD flags) { // note: it is unlikely that the crashdump register context // contains the correct values for this scope, so symbols // stored in or relative to a general register are unavailable. if(flags & SYMFLAG_REGISTER) return true; // note: IsRelativeToFramePointer is called first, so if we still // see this flag, the base register is not the frame pointer. // since we most probably don't know its value in the current // scope (see above), the symbol is inaccessible. if(flags & SYMFLAG_REGREL) return true; return false; } static Status DetermineSymbolAddress(DWORD id, const SYMBOL_INFOW* sym, const DumpState& state, const u8** pp) { DWORD dataKind; if(!pSymGetTypeInfo(hProcess, state.moduleBase, id, TI_GET_DATAKIND, &dataKind)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); Status ret = CanHandleDataKind(dataKind); RETURN_STATUS_IF_ERR(ret); if(ret == INFO::SKIPPED) return INFO::OK; // pp is already correct // note: we have not yet observed a non-zero TI_GET_ADDRESSOFFSET or // TI_GET_ADDRESS, and TI_GET_OFFSET is apparently equal to sym->Address. // get address uintptr_t addr = (uintptr_t)sym->Address; if(IsRelativeToFramePointer(sym->Flags, sym->Register)) addr += (uintptr_t)state.stackFrame->AddrFrame.Offset; else if(IsUnretrievable(sym->Flags)) return ERR::SYM_UNRETRIEVABLE; // NOWARN *pp = (const u8*)(uintptr_t)addr; debug_printf(L"SYM| %ls at %p flags=%X dk=%d sym->addr=%I64X fp=%I64x\n", sym->Name, *pp, sym->Flags, dataKind, sym->Address, state.stackFrame->AddrFrame.Offset); return INFO::OK; } //----------------------------------------------------------------------------- // dump routines for each dbghelp symbol type //----------------------------------------------------------------------------- // these functions return != 0 if they're not able to produce any // reasonable output at all; the caller (dump_sym_data, dump_sequence, etc.) // will display the appropriate error message via dump_error. // called by dump_sym; lock is held. static Status dump_sym_array(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // get element count and size DWORD el_type_id = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &el_type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // .. workaround: TI_GET_COUNT returns total struct size for // arrays-of-struct. therefore, calculate as size / el_size. ULONG64 el_size_; if(!pSymGetTypeInfo(hProcess, state.moduleBase, el_type_id, TI_GET_LENGTH, &el_size_)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t el_size = (size_t)el_size_; ENSURE(el_size != 0); const size_t num_elements = size/el_size; ENSURE(num_elements != 0); return dump_array(p, num_elements, el_type_id, el_size, state); } //----------------------------------------------------------------------------- // if the current value is a printable character, display in that form. // this isn't only done in btChar because characters are sometimes stored // in integers. static void AppendCharacterIfPrintable(u64 data) { if(data < 0x100) { int c = (int)data; if(isprint(c)) out(L" ('%hc')", c); } } static Status dump_sym_base_type(DWORD type_id, const u8* p, DumpState& state) { DWORD base_type; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_BASETYPE, &base_type)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // single out() call. note: we pass a single u64 for all sizes, // which will only work on little-endian systems. // must be declared before goto to avoid W4 warning. const wchar_t* fmt = L""; u64 data = movzx_le64(p, size); // if value is 0xCC..CC (uninitialized mem), we display as hex. // the output would otherwise be garbage; this makes it obvious. // note: be very careful to correctly handle size=0 (e.g. void*). for(size_t i = 0; i < size; i++) { if(p[i] != 0xCC) break; if(i == size-1) { out(L"(uninitialized)"); return INFO::OK; } } switch(base_type) { // floating-point case btFloat: if(size == sizeof(float)) { // NB: the C calling convention calls for float arguments to be // converted to double. passing `data' wouldn't work because it's // merely a zero-extended 32-bit representation of the float. float value; memcpy(&value, p, sizeof(value)); out(L"%f (0x%08I64X)", value, data); } else if(size == sizeof(double)) out(L"%g (0x%016I64X)", data, data); else DEBUG_WARN_ERR(ERR::LOGIC); // invalid float size break; // boolean case btBool: ENSURE(size == sizeof(bool)); if(data == 0 || data == 1) out(L"%ls", data? L"true " : L"false"); else out(L"(bool)0x%02I64X", data); break; // integers (displayed as decimal and hex) // note: 0x00000000 can get annoying (0 would be nicer), // but it indicates the variable size and makes for consistently // formatted structs/arrays. (0x1234 0 0x5678 is ugly) case btInt: case btLong: case btUInt: case btULong: if(size == 1) { // _TUCHAR if(state.indirection) { state.indirection = 0; return dump_array(p, 8, type_id, size, state); } fmt = L"%I64d (0x%02I64X)"; } else if(size == 2) fmt = L"%I64d (0x%04I64X)"; else if(size == 4) fmt = L"%I64d (0x%08I64X)"; else if(size == 8) fmt = L"%I64d (0x%016I64X)"; else DEBUG_WARN_ERR(ERR::LOGIC); // invalid size for integers out(fmt, data, data); break; // character case btChar: case btWChar: ENSURE(size == sizeof(char) || size == sizeof(wchar_t)); // char*, wchar_t* if(state.indirection) { state.indirection = 0; return dump_array(p, 8, type_id, size, state); } out(L"%d", data); AppendCharacterIfPrintable(data); break; // note: void* is sometimes indicated as (pointer, btNoType). case btVoid: case btNoType: // void* - cannot display what it's pointing to (type unknown). if(state.indirection) { out_erase(4); // " -> " fmt = L""; } else DEBUG_WARN_ERR(ERR::LOGIC); // non-pointer btVoid or btNoType break; default: DEBUG_WARN_ERR(ERR::LOGIC); // unknown type break; // unsupported complex types case btBCD: case btCurrency: case btDate: case btVariant: case btComplex: case btBit: case btBSTR: case btHresult: return ERR::SYM_UNSUPPORTED; // NOWARN } return INFO::OK; } //----------------------------------------------------------------------------- static Status dump_sym_base_class(DWORD type_id, const u8* p, DumpState& state) { DWORD base_class_type_id; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &base_class_type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // this is a virtual base class. we can't display those because it'd // require reading the VTbl, which is difficult given lack of documentation // and just not worth it. DWORD vptr_ofs; if(pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_VIRTUALBASEPOINTEROFFSET, &vptr_ofs)) return ERR::SYM_UNSUPPORTED; // NOWARN return dump_sym(base_class_type_id, p, state); } //----------------------------------------------------------------------------- static Status dump_sym_data(DWORD id, const u8* p, DumpState& state) { SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(!pSymFromIndexW(hProcess, state.moduleBase, id, sym)) RETURN_STATUS_IF_ERR(ERR::SYM_TYPE_INFO_UNAVAILABLE); out(L"%ls = ", sym->Name); __try { RETURN_STATUS_IF_ERR(DetermineSymbolAddress(id, sym, state, &p)); // display value recursively return dump_sym(sym->TypeIndex, p, state); } __except(EXCEPTION_EXECUTE_HANDLER) { return ERR::SYM_INTERNAL_ERROR; // NOWARN } } //----------------------------------------------------------------------------- static Status dump_sym_enum(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; const i64 enum_value = movsx_le64(p, size); // get array of child symbols (enumerants). DWORD numChildren; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_CHILDRENCOUNT, &numChildren)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); TI_FINDCHILDREN_PARAMS2 fcp(numChildren); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_FINDCHILDREN, &fcp)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); numChildren = fcp.p.Count; // was truncated to maxChildren const DWORD* children = fcp.p.ChildId; // for each child (enumerant): for(size_t i = 0; i < numChildren; i++) { DWORD child_data_id = children[i]; // get this enumerant's value. we can't make any assumptions about // the variant's type or size - no restriction is documented. // rationale: VariantChangeType is much less tedious than doing // it manually and guarantees we cover everything. the OLE DLL is // already pulled in by e.g. OpenGL anyway. VARIANT v; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_data_id, TI_GET_VALUE, &v)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); if(VariantChangeType(&v, &v, 0, VT_I8) != S_OK) continue; // it's the one we want - output its name. if(enum_value == v.llVal) { const wchar_t* name; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_data_id, TI_GET_SYMNAME, &name)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); out(L"%ls", name); LocalFree((HLOCAL)name); return INFO::OK; } } // we weren't able to retrieve a matching enum value, but can still // produce reasonable output (the numeric value). // note: could goto here after a SGTI fails, but we fail instead // to make sure those errors are noticed. out(L"%I64d", enum_value); return INFO::OK; } //----------------------------------------------------------------------------- static Status dump_sym_function(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState& UNUSED(state)) { return INFO::SYM_SUPPRESS_OUTPUT; } //----------------------------------------------------------------------------- static Status dump_sym_function_type(DWORD UNUSED(type_id), const u8* p, DumpState& state) { // this symbol gives class parent, return type, and parameter count. // unfortunately the one thing we care about, its name, // isn't exposed via TI_GET_SYMNAME, so we resolve it ourselves. wchar_t name[DEBUG_SYMBOL_CHARS]; Status err = ResolveSymbol_lk((void*)p, name, 0, 0); if(state.indirection == 0) out(L"0x%p ", p); if(err == INFO::OK) out(L"(%ls)", name); return INFO::OK; } //----------------------------------------------------------------------------- // do not follow pointers that we have already displayed. this reduces // clutter a bit and prevents infinite recursion for cyclical references // (e.g. via struct S { S* p; } s; s.p = &s;) // note: allocating memory dynamically would cause trouble if dumping // the stack from within memory-related code (the allocation hook would // be reentered, which is not permissible). static const size_t maxVisited = 1000; static const u8* visited[maxVisited]; static size_t numVisited; static void ptr_reset_visited() { numVisited = 0; } static bool ptr_already_visited(const u8* p) { for(size_t i = 0; i < numVisited; i++) { if(visited[i] == p) return true; } if(numVisited < maxVisited) { visited[numVisited] = p; numVisited++; } // capacity exceeded else { // warn user - but only once (we can't use the regular // debug_DisplayError and wdbg_assert doesn't have a // suppress mechanism) static bool haveComplained; if(!haveComplained) { debug_printf(L"WARNING: ptr_already_visited: capacity exceeded, increase maxVisited\n"); debug_break(); haveComplained = true; } } return false; } static Status dump_sym_pointer(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // read+output pointer's value. p = (const u8*)(uintptr_t)movzx_le64(p, size); out(L"0x%p", p); // bail if it's obvious the pointer is bogus // (=> can't display what it's pointing to) if(debug_IsPointerBogus(p)) return INFO::OK; // avoid duplicates and circular references if(ptr_already_visited(p)) { out(L" (see above)"); return INFO::OK; } // display what the pointer is pointing to. // if the pointer is invalid (despite "bogus" check above), // dump_data_sym recovers via SEH and prints an error message. // if the pointed-to value turns out to uninteresting (e.g. void*), // the responsible dump_sym* will erase "->", leaving only address. out(L" -> "); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // prevent infinite recursion just to be safe (shouldn't happen) if(state.indirection >= maxIndirection) WARN_RETURN(ERR::SYM_NESTING_LIMIT); state.indirection++; return dump_sym(type_id, p, state); } //----------------------------------------------------------------------------- static Status dump_sym_typedef(DWORD type_id, const u8* p, DumpState& state) { if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); return dump_sym(type_id, p, state); } //----------------------------------------------------------------------------- // determine type and size of the given child in a UDT. // useful for UDTs that contain typedefs describing their contents, // e.g. value_type in STL containers. static Status udt_get_child_type(const wchar_t* child_name, ULONG numChildren, const DWORD* children, const DumpState& state, DWORD* el_type_id, size_t* el_size) { const DWORD lastError = GetLastError(); *el_type_id = 0; *el_size = 0; for(ULONG i = 0; i < numChildren; i++) { const DWORD child_id = children[i]; SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(!pSymFromIndexW(hProcess, state.moduleBase, child_id, sym)) { // this happens for several UDTs; cause is unknown. ENSURE(GetLastError() == ERROR_NOT_FOUND); continue; } if(!wcscmp(sym->Name, child_name)) { *el_type_id = sym->TypeIndex; *el_size = (size_t)sym->Size; return INFO::OK; } } SetLastError(lastError); // (happens if called for containers that are treated as STL but are not) return ERR::SYM_CHILD_NOT_FOUND; // NOWARN } static Status udt_dump_std(const wchar_t* type_name, const u8* p, size_t size, DumpState& state, ULONG numChildren, const DWORD* children) { Status err; // not a C++ standard library object; can't handle it. if(wcsncmp(type_name, L"std::", 5) != 0) return INFO::CANNOT_HANDLE; // check for C++ objects that should be displayed via udt_dump_normal. // C++03 containers are special-cased and the rest (apart from those here) // are ignored, because for the most part they are spew. if(!wcsncmp(type_name, L"std::pair", 9) || !wcsncmp(type_name, L"std::tr1::", 10)) return INFO::CANNOT_HANDLE; // display contents of STL containers // .. get element type DWORD el_type_id; size_t el_size; err = udt_get_child_type(L"value_type", numChildren, children, state, &el_type_id, &el_size); if(err != INFO::OK) goto not_valid_container; // .. get iterator and # elements size_t el_count; DebugStlIterator el_iterator; u8 it_mem[DEBUG_STL_MAX_ITERATOR_SIZE]; err = debug_stl_get_container_info(type_name, p, size, el_size, &el_count, &el_iterator, it_mem); if(err != INFO::OK) goto not_valid_container; return dump_sequence(el_iterator, it_mem, el_count, el_type_id, el_size, state); not_valid_container: // build and display detailed "error" message. wchar_t buf[100]; const wchar_t* text; // .. object named std::* but doesn't include a "value_type" child => // it's a non-STL C++ stdlib object. wasn't handled by the // special case above, so we just display its simplified type name // (the contents are usually spew). if(err == ERR::SYM_CHILD_NOT_FOUND) text = L""; // .. not one of the containers we can analyse. else if(err == ERR::STL_CNT_UNKNOWN) text = L"unknown "; else if(err == ERR::STL_CNT_UNSUPPORTED) text = L"unsupported "; // .. container of a known type but contents are invalid. else if(err == ERR::STL_CNT_INVALID) text = L"uninitialized/invalid "; // .. some other error encountered else { wchar_t description[200]; (void)StatusDescription(err, description, ARRAY_SIZE(description)); swprintf_s(buf, ARRAY_SIZE(buf), L"error \"%ls\" while analyzing ", description); text = buf; } // (debug_stl modifies its input string in-place; type_name is // a const string returned by dbghelp) wchar_t type_name_buf[DEBUG_SYMBOL_CHARS]; wcscpy_s(type_name_buf, ARRAY_SIZE(type_name_buf), type_name); out(L"(%ls%ls)", text, debug_stl_simplify_name(type_name_buf)); return INFO::OK; } static bool udt_should_suppress(const wchar_t* type_name) { // specialized HANDLEs are defined as pointers to structs by // DECLARE_HANDLE. we only want the numerical value (pointer address), // so prevent these structs from being displayed. // note: no need to check for indirection; these are only found in // HANDLEs (which are pointers). // removed obsolete defs: HEVENT, HFILE, HUMPD if(type_name[0] != 'H') goto not_handle; #define SUPPRESS_HANDLE(name) if(!wcscmp(type_name, L#name L"__")) return true; SUPPRESS_HANDLE(HACCEL); SUPPRESS_HANDLE(HBITMAP); SUPPRESS_HANDLE(HBRUSH); SUPPRESS_HANDLE(HCOLORSPACE); SUPPRESS_HANDLE(HCURSOR); SUPPRESS_HANDLE(HDC); SUPPRESS_HANDLE(HENHMETAFILE); SUPPRESS_HANDLE(HFONT); SUPPRESS_HANDLE(HGDIOBJ); SUPPRESS_HANDLE(HGLOBAL); SUPPRESS_HANDLE(HGLRC); SUPPRESS_HANDLE(HHOOK); SUPPRESS_HANDLE(HICON); SUPPRESS_HANDLE(HIMAGELIST); SUPPRESS_HANDLE(HIMC); SUPPRESS_HANDLE(HINSTANCE); SUPPRESS_HANDLE(HKEY); SUPPRESS_HANDLE(HKL); SUPPRESS_HANDLE(HKLOCAL); SUPPRESS_HANDLE(HMENU); SUPPRESS_HANDLE(HMETAFILE); SUPPRESS_HANDLE(HMODULE); SUPPRESS_HANDLE(HMONITOR); SUPPRESS_HANDLE(HPALETTE); SUPPRESS_HANDLE(HPEN); SUPPRESS_HANDLE(HRGN); SUPPRESS_HANDLE(HRSRC); SUPPRESS_HANDLE(HSTR); SUPPRESS_HANDLE(HTASK); SUPPRESS_HANDLE(HWINEVENTHOOK); SUPPRESS_HANDLE(HWINSTA); SUPPRESS_HANDLE(HWND); not_handle: return false; } static Status udt_dump_suppressed(const wchar_t* type_name, const u8* UNUSED(p), size_t UNUSED(size), DumpState state, ULONG UNUSED(numChildren), const DWORD* UNUSED(children)) { if(!udt_should_suppress(type_name)) return INFO::CANNOT_HANDLE; // the data symbol is pointer-to-UDT. since we won't display its // contents, leave only the pointer's value. if(state.indirection) out_erase(4); // " -> " // indicate something was deliberately left out // (otherwise, lack of output may be taken for an error) out(L" (..)"); return INFO::OK; } // (by now) non-trivial heuristic to determine if a UDT should be // displayed on one line or several. split out of udt_dump_normal. static bool udt_fits_on_one_line(const wchar_t* type_name, size_t child_count, size_t total_size) { // special case: always put CStr* on one line // (std::*string are displayed directly, but these go through // udt_dump_normal. we want to avoid the ensuing 3-line output) if(!wcscmp(type_name, L"CStr") || !wcscmp(type_name, L"CStr8") || !wcscmp(type_name, L"CStrW")) return true; // try to get actual number of relevant children // (typedefs etc. are never displayed, but are included in child_count. // we have to balance that vs. tons of static members, which aren't // reflected in total_size). // .. prevent division by 0. if(child_count == 0) child_count = 1; // special-case a few types that would otherwise be classified incorrectly // (due to having more or less than expected relevant children) if(!wcsncmp(type_name, L"std::pair", 9)) child_count = 2; const size_t avg_size = total_size / child_count; // (if 0, no worries - child_count will probably be large and // we return false, which is a safe default) // small UDT with a few (small) members: fits on one line. if(child_count <= 3 && avg_size <= sizeof(int)) return true; return false; } static Status udt_dump_normal(const wchar_t* type_name, const u8* p, size_t size, DumpState state, ULONG numChildren, const DWORD* children) { + // special case: boost::unordered types are complex and may cause a stack overflow + // see http://trac.wildfiregames.com/ticket/1813 + // TODO: at least give some info about them + if(!wcsncmp(type_name, L"boost::unordered", 16)) + return INFO::CANNOT_HANDLE; + const bool fits_on_one_line = udt_fits_on_one_line(type_name, numChildren, size); // prevent infinite recursion just to be safe (shouldn't happen) if(state.level >= maxLevel) WARN_RETURN(ERR::SYM_NESTING_LIMIT); state.level++; out(fits_on_one_line? L"{ " : L"\r\n"); bool displayed_anything = false; for(ULONG i = 0; i < numChildren; i++) { const DWORD child_id = children[i]; // get offset. if not available, skip this child // (we only display data here, not e.g. typedefs) DWORD ofs = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_id, TI_GET_OFFSET, &ofs)) continue; if(ofs >= size) { debug_printf(L"INVALID_UDT %ls %d %d\n", type_name, ofs, size); } //ENSURE(ofs < size); if(!fits_on_one_line) INDENT; const u8* el_p = p+ofs; Status err = dump_sym(child_id, el_p, state); // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. if(err == INFO::SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; continue; } displayed_anything = true; dump_error(err); // nop if err == INFO::OK out(fits_on_one_line? L", " : L"\r\n"); if(err == ERR::SYM_SINGLE_SYMBOL_LIMIT) break; } // for each child state.level--; if(!displayed_anything) { out_erase(2); // "{ " or "\r\n" out(L"(%ls)", type_name); return INFO::OK; } // remove trailing comma separator // note: we can't avoid writing it by checking if i == numChildren-1: // each child might be the last valid data member. if(fits_on_one_line) { out_erase(2); // ", " out(L" }"); } return INFO::OK; } static Status dump_sym_udt(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // get array of child symbols (members/functions/base classes). DWORD numChildren; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_CHILDRENCOUNT, &numChildren)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); TI_FINDCHILDREN_PARAMS2 fcp(numChildren); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_FINDCHILDREN, &fcp)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); numChildren = fcp.p.Count; // was truncated to maxChildren const DWORD* children = fcp.p.ChildId; const wchar_t* type_name; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMNAME, &type_name)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); Status ret; // note: order is important (e.g. STL special-case must come before // suppressing UDTs, which tosses out most other C++ stdlib classes) ret = udt_dump_std (type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; ret = udt_dump_suppressed(type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; ret = udt_dump_normal (type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; done: LocalFree((HLOCAL)type_name); return ret; } //----------------------------------------------------------------------------- static Status dump_sym_vtable(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState& UNUSED(state)) { // unsupported (vtable internals are undocumented; too much work). return INFO::SYM_SUPPRESS_OUTPUT; } //----------------------------------------------------------------------------- static Status dump_sym_unknown(DWORD type_id, const u8* UNUSED(p), DumpState& state) { // redundant (already done in dump_sym), but this is rare. DWORD type_tag; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMTAG, &type_tag)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); debug_printf(L"SYM: unknown tag: %d\n", type_tag); out(L"(unknown symbol type)"); return INFO::OK; } //----------------------------------------------------------------------------- typedef Status (*DumpFunc)(DWORD typeId, const u8* p, DumpState& state); static DumpFunc DumpFuncFromTypeTag(DWORD typeTag) { switch(typeTag) { case SymTagArrayType: return dump_sym_array; case SymTagBaseType: return dump_sym_base_type; case SymTagBaseClass: return dump_sym_base_class; case SymTagData: return dump_sym_data; case SymTagEnum: return dump_sym_enum; case SymTagFunction: return dump_sym_function; case SymTagFunctionType: return dump_sym_function_type; case SymTagPointerType: return dump_sym_pointer; case SymTagTypedef: return dump_sym_typedef; case SymTagUDT: return dump_sym_udt; case SymTagVTable: return dump_sym_vtable; default: return dump_sym_unknown; } } // write name and value of the symbol to the output buffer. // delegates to dump_sym_* depending on the symbol's tag. static Status dump_sym(DWORD type_id, const u8* p, DumpState& state) { RETURN_STATUS_IF_ERR(out_check_limit()); DWORD typeTag; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMTAG, &typeTag)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const DumpFunc dumpFunc = DumpFuncFromTypeTag(typeTag); return dumpFunc(type_id, p, state); } //----------------------------------------------------------------------------- // stack trace //----------------------------------------------------------------------------- static bool ShouldSkipSymbol(const wchar_t* name) { if(!wcscmp(name, L"suppress__")) return true; if(!wcscmp(name, L"__profile")) return true; return false; } // output the symbol's name and value via dump_sym*. // called from dump_frame_cb for each local symbol; lock is held. static BOOL CALLBACK dump_sym_cb(SYMBOL_INFOW* sym, ULONG UNUSED(size), PVOID userContext) { if(ShouldSkipSymbol(sym->Name)) return TRUE; // continue out_latch_pos(); // see decl const u8* p = (const u8*)(uintptr_t)sym->Address; DumpState state((uintptr_t)sym->ModBase, (LPSTACKFRAME64)userContext); INDENT; Status err = dump_sym(sym->Index, p, state); dump_error(err); if(err == INFO::SYM_SUPPRESS_OUTPUT) UNINDENT; else out(L"\r\n"); return TRUE; // continue } // called by wdbg_sym_WalkStack for each stack frame static Status dump_frame_cb(const STACKFRAME64* sf, uintptr_t UNUSED(userContext)) { void* func = (void*)(uintptr_t)sf->AddrPC.Offset; wchar_t func_name[DEBUG_SYMBOL_CHARS]; wchar_t file[DEBUG_FILE_CHARS]; int line; Status ret = ResolveSymbol_lk(func, func_name, file, &line); if(ret == INFO::OK) { // don't trace back further than the app's entry point // (no one wants to see this frame). checking for the // function name isn't future-proof, but not stopping is no big deal. // an alternative would be to check if module=kernel32, but // that would cut off callbacks as well. // note: the stdcall mangled name includes parameter size, which is // different in 64-bit, so only check the first characters. if(!wcsncmp(func_name, L"_BaseProcessStart", 17) || !wcscmp(func_name, L"BaseThreadInitThunk")) return INFO::OK; // skip any mainCRTStartup frames if(!wcscmp(func_name, L"__tmainCRTStartup")) return INFO::OK; if(!wcscmp(func_name, L"mainCRTStartup")) return INFO::OK; out(L"%ls (%ls:%d)\r\n", func_name, file, line); } else out(L"%p\r\n", func); WinScopedPreserveLastError s; // SymSetContext // only enumerate symbols for this stack frame // (i.e. its locals and parameters) // problem: debug info is scope-aware, so we won't see any variables // declared in sub-blocks. we'd have to pass an address in that block, // which isn't worth the trouble. IMAGEHLP_STACK_FRAME isf = PopulateImageStackFrame(*sf); const PIMAGEHLP_CONTEXT ic = 0; // ignored // NB: this sometimes fails for reasons unknown in a static // member function, possibly because the return address is in kernel32 (void)pSymSetContext(hProcess, &isf, ic); const ULONG64 base = 0; const wchar_t* const mask = 0; // use scope set by pSymSetContext pSymEnumSymbolsW(hProcess, base, mask, dump_sym_cb, (PVOID)sf); if(GetLastError() == ERROR_NOT_SUPPORTED) // no debug info present? SetLastError(0); out(L"\r\n"); return INFO::OK; } Status debug_DumpStack(wchar_t* buf, size_t maxChars, void* pcontext, const wchar_t* lastFuncToSkip) { static intptr_t busy; if(!cpu_CAS(&busy, 0, 1)) return ERR::REENTERED; // NOWARN out_init(buf, maxChars); ptr_reset_visited(); wdbg_assert(pcontext != 0); Status ret = wdbg_sym_WalkStack(dump_frame_cb, 0, *(CONTEXT*)pcontext, lastFuncToSkip); COMPILER_FENCE; busy = 0; return ret; } //----------------------------------------------------------------------------- // write out a "minidump" containing register and stack state; this enables // examining the crash in a debugger. called by wdbg_exception_filter. // heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp // lock must be held. void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS* exception_pointers) { sym_init(); WinScopedLock lock(WDBG_SYM_CS); OsPath path = ah_get_log_dir()/"crashlog.dmp"; HANDLE hFile = CreateFileW(OsString(path).c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to create crashlog.dmp."); return; } MINIDUMP_EXCEPTION_INFORMATION mei; mei.ThreadId = GetCurrentThreadId(); mei.ExceptionPointers = exception_pointers; mei.ClientPointers = FALSE; // exception_pointers is not in our address space. // note: we don't store other crashlog info within the dump file // (UserStreamParam), since we will need to generate a plain text file on // non-Windows platforms. users will just have to send us both files. HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId(); if(!pMiniDumpWriteDump || !pMiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0)) DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to generate minidump."); CloseHandle(hFile); } Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 13723) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 13724) @@ -1,1350 +1,1353 @@ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/config2.h" #include "lib/input.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/sysdep/os_cpu.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/TerrainTextureManager.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "maths/MathUtil.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/HWDetect.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Profiler2.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/scripting/JSInterface_Console.h" #include "ps/TouchInput.h" #include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "renderer/ModelRenderer.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/DebuggingServer.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "simulation2/Simulation2.h" #include "soundmanager/scripting/JSInterface_Sound.h" #include "soundmanager/ISoundManager.h" #include "tools/atlas/GameInterface/GameLoop.h" #include "tools/atlas/GameInterface/View.h" #if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #if OS_WIN extern void wmi_Shutdown(); #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; bool g_DoRenderCursor = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { g_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true); g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true); g_GUI->SendEventToAll("progress"); } void Render() { PROFILE3("render"); if (g_SoundManager) g_SoundManager->IdleTask(); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameStart(); ogl_WarnIfError(); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); if (g_Game) g_Renderer.SetSimulation(g_Game->GetSimulation2()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->Render(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); if (g_DoRenderGui) g_GUI->Draw(); ogl_WarnIfError(); // If we're in Atlas game view, render special overlays (e.g. editor bandbox) if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawOverlays(); ogl_WarnIfError(); } // Text: glDisable(GL_DEPTH_TEST); g_Console->Render(); ogl_WarnIfError(); if (g_DoRenderLogger) g_Logger->Render(); ogl_WarnIfError(); // Profile information g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { PROFILE3_GPU("cursor"); CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, false); } else { bool forceGL = false; CFG_GET_VAL("nohwcursor", Bool, forceGL); #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // set up transform for GL cursor glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); #endif if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, forceGL) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } } glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls); PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris); PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris); PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris); PROFILE2_ATTR("overlay tris: %d", (int)g_Renderer.GetStats().m_OverlayTris); PROFILE2_ATTR("blend splats: %d", (int)g_Renderer.GetStats().m_BlendSplats); PROFILE2_ATTR("particles: %d", (int)g_Renderer.GetStats().m_Particles); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameEnd(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); JSI_Sound::RegisterScriptFunctions(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // newer Windows version: assume the worst, and don't warn case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB and signed to allow temporarily negative computations) const ssize_t total = (ssize_t)os_cpu_MemorySize(); // (NB: os_cpu_MemoryAvailable is useless on Linux because free memory // is marked as "in use" by OS caches.) const ssize_t os = (ssize_t)OperatingSystemFootprint(); const ssize_t game = 300; // estimated working set ssize_t cache = 500; // upper bound: total size of our data // the cache reserves contiguous address space, which is a precious // resource on 32-bit systems, so don't use too much: if(ARCH_IA32 || sizeof(void*) == 4) cache = std::min(cache, (ssize_t)200); // try to leave over enough memory for the OS and game cache = std::min(cache, total-os-game); // always provide at least this much to ensure correct operation cache = std::max(cache, (ssize_t)64); debug_printf(L"Cache: %d (total: %d) MiB\n", (int)cache, (int)total); return size_t(cache)*MiB; } ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) // displaying the error dialog hangs the desktop since the dialog box is behind the // fullscreen window. So we just force the game to windowed mode before displaying the dialog. // (But only if we're in the main thread, and not if we're being reentrant.) if (ThreadUtil::IsMainThread()) { static bool reentering = false; if (!reentering) { reentering = true; g_VideoMode.SetFullscreen(false); reentering = false; } } // We don't actually implement the error display here, so return appropriately return ERI_NOT_IMPLEMENTED; } static std::vector GetMods(const CmdLineArgs& args, bool dev) { std::vector mods = args.GetMultiple("mod"); // TODO: It would be nice to remove this hard-coding mods.insert(mods.begin(), "public"); // Add the user mod if not explicitly disabled or we have a dev copy so // that saved files end up in version control and not in the user mod. if (!dev && !args.Has("noUserMod")) mods.push_back("user"); return mods; } -static void InitVfs(const CmdLineArgs& args) +static void InitVfs(const CmdLineArgs& args, int flags) { TIMER(L"InitVfs"); + const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0; + const Paths paths(args); OsPath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; - hooks.display_error = psDisplayError; + if (setup_error) + hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); // Work out whether we are a dev version to make sure saved files // (maps, etc) end up in version control. const OsPath readonlyConfig = paths.RData()/"config"/""; g_VFS->Mount(L"config/", readonlyConfig); bool dev = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK); const std::vector mods = GetMods(args, dev); OsPath modPath = paths.RData()/"mods"; OsPath modUserPath = paths.UserData()/"mods"; for (size_t i = 0; i < mods.size(); ++i) { size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0 size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE; - size_t flags = userFlags|VFS_MOUNT_MUST_EXIST; + size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST; OsPath modName(mods[i]); if (dev) { // We are running a dev copy, so only mount mods in the user mod path // if the mod does not exist in the data path. if (DirectoryExists(modPath / modName/"")) - g_VFS->Mount(L"", modPath / modName/"", flags, priority); + g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority); else g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority); } else { - g_VFS->Mount(L"", modPath / modName/"", flags, priority); + g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority); // Ensure that user modified files are loaded, if they are present g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1); } } // We mount these dirs last as otherwise writing could result in files being placed in a mod's dir. g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/""); g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH); // Mounting with highest priority, so that a mod supplied user.cfg is harmless g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config(), 0, (size_t)-1); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; double blinkRate = 0.5; CFG_GET_VAL("gui.cursorblinkrate", Double, blinkRate); g_Console->SetCursorBlinkRate(blinkRate); } // hotkeys { TIMER(L"ps_lang_hotkeys"); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", initData); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, initData); } static void InitInput() { #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); #endif g_Joystick.Initialise(); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(conInputHandler); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); in_add_handler(touch_input_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, false); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO, g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_WATERNORMAL, g_WaterNormal); g_Renderer.SetOptionBool(CRenderer::OPT_WATERREALDEPTH, g_WaterRealDepth); g_Renderer.SetOptionBool(CRenderer::OPT_WATERFOAM, g_WaterFoam); g_Renderer.SetOptionBool(CRenderer::OPT_WATERCOASTALWAVES, g_WaterCoastalWaves); g_Renderer.SetOptionBool(CRenderer::OPT_WATERREFLECTION, g_WaterRefraction); g_Renderer.SetOptionBool(CRenderer::OPT_WATERREFRACTION, g_WaterReflection); g_Renderer.SetOptionBool(CRenderer::OPT_WATERSHADOW, g_WaterShadows); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWPCF, g_ShadowPCF); g_Renderer.SetOptionBool(CRenderer::OPT_PARTICLES, g_Particles); g_Renderer.SetOptionBool(CRenderer::OPT_SILHOUETTES, g_Silhouettes); g_Renderer.SetOptionBool(CRenderer::OPT_SHOWSKY, g_ShowSky); // create terrain related stuff new CTerrainTextureManager; g_Renderer.Open(g_xres, g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X = 0; vp.m_Y = 0; vp.m_Width = g_xres; vp.m_Height = g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); ModelRenderer::Init(); } static void InitSDL() { #if OS_LINUX // In fullscreen mode when SDL is compiled with DGA support, the mouse // sensitivity often appears to be unusably wrong (typically too low). // (This seems to be reported almost exclusively on Ubuntu, but can be // reproduced on Gentoo after explicitly enabling DGA.) // Disabling the DGA mouse appears to fix that problem, and doesn't // have any obvious negative effects. setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0); #endif if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOGERROR(L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_StartTextInput(); #else SDL_EnableUNICODE(1); #endif } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); g_Profiler2.ShutdownGPU(); // Free cursors before shutting down SDL, as they may depend on SDL. cursor_shutdown(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); g_VideoMode.Shutdown(); TIMER_BEGIN(L"shutdown UserReporter"); g_UserReporter.Deinitialize(); TIMER_END(L"shutdown UserReporter"); TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; delete g_DebuggingServer; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); ISoundManager::SetEnabled(false); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); #if OS_WIN TIMER_BEGIN(L"shutdown wmi"); wmi_Shutdown(); TIMER_END(L"shutdown wmi"); #endif } #if OS_UNIX static void FixLocales() { #if OS_MACOSX || OS_BSD // OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle // wide characters. Peculiarly the string "UTF-8" seems to be acceptable // despite not being a real locale, and it's conveniently language-agnostic, // so use that. setlocale(LC_CTYPE, "UTF-8"); #endif // On misconfigured systems with incorrect locale settings, we'll die // with a C++ exception when some code (e.g. Boost) tries to use locales. // To avoid death, we'll detect the problem here and warn the user and // reset to the default C locale. // For informing the user of the problem, use the list of env vars that // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.) const char* const LocaleEnvVars[] = { "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", "LANG" }; try { // this constructor is similar to setlocale(LC_ALL, ""), // but instead of returning NULL, it throws runtime_error // when the first locale env variable found contains an invalid value std::locale(""); } catch (std::runtime_error&) { LOGWARNING(L"Invalid locale settings"); for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++) { if (char* envval = getenv(LocaleEnvVars[i])) LOGWARNING(L" %hs=\"%hs\"", LocaleEnvVars[i], envval); else LOGWARNING(L" %hs=\"(unset)\"", LocaleEnvVars[i]); } // We should set LC_ALL since it overrides LANG if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1)) debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable."); else LOGWARNING(L"Setting LC_ALL env variable to: %hs", getenv("LC_ALL")); } } #else static void FixLocales() { // Do nothing on Windows } #endif void EarlyInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); ThreadUtil::SetMainThread(); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); timer_LatchStartTime(); // initialise profiler early so it can profile startup, // but only after LatchStartTime g_Profiler2.Initialise(); FixLocales(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } bool Autostart(const CmdLineArgs& args); -void Init(const CmdLineArgs& args, int UNUSED(flags)) +void Init(const CmdLineArgs& args, int flags) { h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. - InitVfs(args); + InitVfs(args, flags); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); #if CONFIG2_AUDIO ISoundManager::CreateSoundManager(); #endif // g_ConfigDB, command line args, globals CONFIG_Init(args); // before scripting if (g_JSDebuggerEnabled) g_DebuggingServer = new CDebuggingServer(); InitScripting(); // before GUI g_ConfigDB.RegisterJSConfigDB(); // after scripting // Optionally start profiler HTTP output automatically // (By default it's only enabled by a hotkey, for security/performance) bool profilerHTTPEnable = false; CFG_GET_VAL("profiler2.http.autoenable", Bool, profilerHTTPEnable); if (profilerHTTPEnable) g_Profiler2.EnableHTTP(); if (!g_Quickstart) g_UserReporter.Initialize(); // after config PROFILE2_EVENT("Init finished"); } void InitGraphics(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; if(setup_vmode) { InitSDL(); if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_WM_SetCaption("0 A.D.", "0 A.D."); #endif } RunHardwareDetection(); tex_codec_register_all(); const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); ogl_WarnIfError(); // Optionally start profiler GPU timings automatically // (By default it's only enabled by a hotkey, for performance/compatibility) bool profilerGPUEnable = false; CFG_GET_VAL("profiler2.gpu.autoenable", Bool, profilerGPUEnable); if (profilerGPUEnable) g_Profiler2.EnableGPU(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) ISoundManager::SetEnabled(false); g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) if (ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL) != 0 // ARB && ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", NULL) != 0) // GLSL { DEBUG_DISPLAY_ERROR( L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders." L" In the future, the game will not support pre-shader graphics cards." L" You are advised to try installing newer drivers and/or upgrade your graphics card." L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734" ); // TODO: actually quit once fixed function support is dropped } const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); try { if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); // We only want to display the splash screen at startup CScriptValRooted data; if (g_GUI) { ScriptInterface& scriptInterface = g_GUI->GetScriptInterface(); scriptInterface.Eval("({})", data); scriptInterface.SetProperty(data.get(), "isStartup", true); } InitPs(setup_gui, L"page_pregame.xml", data.get()); } } catch (PSERROR_Game_World_MapLoadFailed& e) { // Map Loading failed // Start the engine so we have a GUI InitPs(true, L"page_pregame.xml", JSVAL_VOID); // Call script function to do the actual work // (delete game data, switch GUI page, show error, etc.) CancelLoad(CStr(e.what()).FromUTF8()); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } void RenderLogger(bool RenderingState) { g_DoRenderLogger = RenderingState; } void RenderCursor(bool RenderingState) { g_DoRenderCursor = RenderingState; } bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=name -- map name for scenario, or rms name for random map * -autostart-ai=1:dummybot -- adds the dummybot AI to player 1 * -autostart-playername=name -- multiplayer player name * -autostart-host -- multiplayer host mode * -autostart-players=2 -- number of players * -autostart-client -- multiplayer client mode * -autostart-ip=127.0.0.1 -- multiplayer connect to 127.0.0.1 * -autostart-random=104 -- random map, optional seed value = 104 (default is 0, random is -1) * -autostart-size=192 -- random map size in tiles = 192 (default is 192) * * Examples: * -autostart=Acropolis -autostart-host -autostart-players=2 -- Host game on Acropolis map, 2 players * -autostart=latium -autostart-random=-1 -- Start single player game on latium random map, random rng seed */ CStr autoStartName = args.Get("autostart"); #if OS_ANDROID // HACK: currently the most convenient way to test maps on Android; // should find a better solution autoStartName = "Oasis"; #endif if (autoStartName.empty()) { return false; } g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); CScriptVal settings; scriptInterface.Eval("({})", settings); CScriptVal playerData; scriptInterface.Eval("([])", playerData); // Set different attributes for random or scenario game if (args.Has("autostart-random")) { CStr seedArg = args.Get("autostart-random"); // Default seed is 0 uint32 seed = 0; if (!seedArg.empty()) { if (seedArg.compare("-1") == 0) { // Random seed value seed = rand(); } else { seed = seedArg.ToULong(); } } // Random map definition will be loaded from JSON file, so we need to parse it std::wstring mapPath = L"maps/random/"; std::wstring scriptPath = mapPath + autoStartName.FromUTF8() + L".json"; CScriptValRooted scriptData = scriptInterface.ReadJSONFile(scriptPath); if (!scriptData.undefined() && scriptInterface.GetProperty(scriptData.get(), "settings", settings)) { // JSON loaded ok - copy script name over to game attributes std::wstring scriptFile; scriptInterface.GetProperty(settings.get(), "Script", scriptFile); scriptInterface.SetProperty(attrs.get(), "script", scriptFile); // RMS filename } else { // Problem with JSON file LOGERROR(L"Error reading random map script '%ls'", scriptPath.c_str()); throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details."); } // Get optional map size argument (default 192) uint mapSize = 192; if (args.Has("autostart-size")) { CStr size = args.Get("autostart-size"); mapSize = size.ToUInt(); } scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName)); scriptInterface.SetProperty(attrs.get(), "mapPath", mapPath); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random")); scriptInterface.SetProperty(settings.get(), "Seed", seed); // Random seed scriptInterface.SetProperty(settings.get(), "Size", mapSize); // Random map size (in patches) // Get optional number of players (default 2) size_t numPlayers = 2; if (args.Has("autostart-players")) { CStr num = args.Get("autostart-players"); numPlayers = num.ToUInt(); } // Set up player data for (size_t i = 0; i < numPlayers; ++i) { CScriptVal player; scriptInterface.Eval("({})", player); // We could load player_defaults.json here, but that would complicate the logic // even more and autostart is only intended for developers anyway scriptInterface.SetProperty(player.get(), "Civ", std::string("athen")); scriptInterface.SetPropertyInt(playerData.get(), i, player); } } else { scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName)); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario")); } // Set player data for AIs // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }: if (args.Has("autostart-ai")) { std::vector aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { // Instead of overwriting existing player data, modify the array CScriptVal player; if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined()) { scriptInterface.Eval("({})", player); } int playerID = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player.get(), "AI", std::string(name)); scriptInterface.SetProperty(player.get(), "AIDiff", 1); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Set AI difficulty if (args.Has("autostart-aidiff")) { std::vector civArgs = args.GetMultiple("autostart-aidiff"); for (size_t i = 0; i < civArgs.size(); ++i) { // Instead of overwriting existing player data, modify the array CScriptVal player; if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined()) { scriptInterface.Eval("({})", player); } int playerID = civArgs[i].BeforeFirst(":").ToInt(); int difficulty = civArgs[i].AfterFirst(":").ToInt(); scriptInterface.SetProperty(player.get(), "AIDiff", difficulty); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Set player data for Civs if (args.Has("autostart-civ")) { std::vector civArgs = args.GetMultiple("autostart-civ"); for (size_t i = 0; i < civArgs.size(); ++i) { // Instead of overwriting existing player data, modify the array CScriptVal player; if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined()) { scriptInterface.Eval("({})", player); } int playerID = civArgs[i].BeforeFirst(":").ToInt(); CStr name = civArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player.get(), "Civ", std::string(name)); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Add player data to map settings scriptInterface.SetProperty(settings.get(), "PlayerData", playerData); // Add map settings to game attributes scriptInterface.SetProperty(attrs.get(), "settings", settings); CScriptVal mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get()))); // Get optional playername CStrW userName = L"anonymous"; if (args.Has("autostart-playername")) { userName = args.Get("autostart-playername").FromUTF8(); } if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) { maxPlayers = args.Get("autostart-players").ToUInt(); } g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); ENSURE(ok); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) { ip = args.Get("autostart-ip"); } bool ok = g_NetClient->SetupConnection(ip); ENSURE(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs, ""); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; } void CancelLoad(const CStrW& message) { // Cancel loader LDR_Cancel(); // Call the cancelOnError GUI function, defined in ..gui/common/functions_utility_error.js // So all GUI pages that load games should include this script if (g_GUI && g_GUI->HasPages()) { JSContext* cx = g_ScriptingHost.getContext(); jsval fval, rval; JSBool ok = JS_GetProperty(cx, g_GUI->GetScriptObject(), "cancelOnError", &fval); ENSURE(ok); jsval msgval = ToJSVal(message); if (ok && !JSVAL_IS_VOID(fval)) JS_CallFunctionValue(cx, g_GUI->GetScriptObject(), fval, 1, &msgval, &rval); } } Index: ps/trunk/source/ps/GameSetup/GameSetup.h =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.h (revision 13723) +++ ps/trunk/source/ps/GameSetup/GameSetup.h (revision 13724) @@ -1,66 +1,70 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_GAMESETUP #define INCLUDED_GAMESETUP // // GUI integration // // display progress / description in loading screen extern void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task); extern void Render(); /** * initialize global modules that are be needed before Init. * must be called from the very beginning of main. **/ extern void EarlyInit(); enum InitFlags { // avoid setting a video mode / initializing OpenGL; assume that has // already been done and everything is ready for rendering. // needed by map editor because it creates its own window. INIT_HAVE_VMODE = 1, // skip initializing the in-game GUI. // needed by map editor because it uses its own GUI. - INIT_NO_GUI = 2 + INIT_NO_GUI = 2, + + // avoid setting display_error app hook + // needed by map editor because it has its own wx error display + INIT_HAVE_DISPLAY_ERROR = 4 }; /** * enable/disable rendering of the GUI (intended mainly for screenshots) */ extern void RenderGui(bool RenderingState); extern void RenderLogger(bool RenderingState); /** * enable/disable rendering of the cursor - this does not hide cursor, but reverts to OS style */ extern void RenderCursor(bool RenderingState); class CmdLineArgs; extern void Init(const CmdLineArgs& args, int flags); extern void InitGraphics(const CmdLineArgs& args, int flags); extern void Shutdown(int flags); extern void CancelLoad(const CStrW& message); #endif // INCLUDED_GAMESETUP Index: ps/trunk/source/tools/atlas/GameInterface/GameLoop.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/GameLoop.cpp (revision 13723) +++ ps/trunk/source/tools/atlas/GameInterface/GameLoop.cpp (revision 13724) @@ -1,333 +1,336 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GameLoop.h" #include "MessagePasserImpl.h" #include "Messages.h" #include "SharedMemory.h" #include "Handlers/MessageHandler.h" #include "ActorViewer.h" #include "View.h" #include "InputProcessor.h" #include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "lib/app_hooks.h" #include "lib/external_libraries/libsdl.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/DllLoader.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/GameSetup/Paths.h" #include "renderer/Renderer.h" #include "scripting/ScriptingHost.h" using namespace AtlasMessage; namespace AtlasMessage { extern void RegisterHandlers(); } // Loaded from DLL: void (*Atlas_StartWindow)(const wchar_t* type); void (*Atlas_SetDataDirectory)(const wchar_t* path); void (*Atlas_SetConfigDirectory)(const wchar_t* path); void (*Atlas_SetMessagePasser)(MessagePasser*); void (*Atlas_GLSetCurrent)(void* cavas); void (*Atlas_GLSwapBuffers)(void* canvas); void (*Atlas_NotifyEndOfFrame)(); void (*Atlas_DisplayError)(const wchar_t* text, size_t flags); void (*Atlas_ReportError)(); namespace AtlasMessage { void* (*ShareableMallocFptr)(size_t); void (*ShareableFreeFptr)(void*); } MessagePasser* AtlasMessage::g_MessagePasser = NULL; static InputProcessor g_Input; static GameLoopState state; GameLoopState* g_AtlasGameLoop = &state; static ErrorReactionInternal AtlasDisplayError(const wchar_t* text, size_t flags) { // TODO: after Atlas has been unloaded, don't do this Atlas_DisplayError(text, flags); return ERI_CONTINUE; } static void RendererIncrementalLoad() { // TODO: shouldn't duplicate this code from main.cpp if (!CRenderer::IsInitialised()) return; const double maxTime = 0.1f; double startTime = timer_Time(); bool more; do { more = g_Renderer.GetTextureManager().MakeProgress(); } while (more && timer_Time() - startTime < maxTime); } static void* RunEngine(void* data) { debug_SetThreadName("engine_thread"); // Set new main thread so that all the thread-safety checks pass ThreadUtil::SetMainThread(); g_Profiler2.RegisterCurrentThread("atlasmain"); const CmdLineArgs args = *reinterpret_cast(data); MessagePasserImpl* msgPasser = (MessagePasserImpl*)AtlasMessage::g_MessagePasser; // Register all the handlers for message which might be passed back RegisterHandlers(); // Override ah_display_error to pass all errors to the Atlas UI + // TODO: this doesn't work well because it doesn't pause the game thread + // and the error box is ugly, so only use it if we fix those issues + // (use INIT_HAVE_DISPLAY_ERROR init flag to test this) AppHooks hooks = {0}; hooks.display_error = AtlasDisplayError; - app_hooks_update(&hooks); + //app_hooks_update(&hooks); // Disable the game's cursor rendering extern CStrW g_CursorName; g_CursorName = L""; state.args = args; state.running = true; state.view = AtlasView::GetView_None(); state.glCanvas = NULL; double last_activity = timer_Time(); while (state.running) { bool recent_activity = false; ////////////////////////////////////////////////////////////////////////// // (TODO: Work out why these things have to be in this order (to avoid // jumps when starting to move, etc)) // Calculate frame length { const double time = timer_Time(); static double last_time = time; const double realFrameLength = time-last_time; last_time = time; ENSURE(realFrameLength >= 0.0); // TODO: filter out big jumps, e.g. when having done a lot of slow // processing in the last frame state.realFrameLength = realFrameLength; } // Process the input that was received in the past if (g_Input.ProcessInput(&state)) recent_activity = true; ////////////////////////////////////////////////////////////////////////// { IMessage* msg; while ((msg = msgPasser->Retrieve()) != NULL) { recent_activity = true; std::string name (msg->GetName()); msgHandlers::const_iterator it = GetMsgHandlers().find(name); if (it != GetMsgHandlers().end()) { it->second(msg); } else { debug_warn(L"Unrecognised message"); // CLogger might not be initialised, but this error will be sent // to the debug output window anyway so people can still see it LOGERROR(L"Unrecognised message (%hs)", name.c_str()); } if (msg->GetType() == IMessage::Query) { // For queries, we need to notify MessagePasserImpl::Query // that the query has now been processed. sem_post((sem_t*) static_cast(msg)->m_Semaphore); // (msg may have been destructed at this point, so don't use it again) // It's quite possible that the querier is going to do a tiny // bit of processing on the query results and then issue another // query, and repeat lots of times in a loop. To avoid slowing // that down by rendering between every query, make this // thread yield now. SDL_Delay(0); } else { // For non-queries, we need to delete the object, since we // took ownership of it. AtlasMessage::ShareableDelete(msg); } } } // Exit, if desired if (! state.running) break; ////////////////////////////////////////////////////////////////////////// // Do per-frame processing: ReloadChangedFiles(); RendererIncrementalLoad(); // Pump SDL events (e.g. hotkeys) SDL_Event_ ev; while (SDL_PollEvent(&ev.ev)) in_dispatch_event(&ev); if (g_GUI) g_GUI->TickObjects(); state.view->Update(state.realFrameLength); state.view->Render(); if (CProfileManager::IsInitialised()) g_Profiler.Frame(); double time = timer_Time(); if (recent_activity) last_activity = time; // Be nice to the processor (by sleeping lots) if we're not doing anything // useful, and nice to the user (by just yielding to other threads) if we are bool yield = (time - last_activity > 0.5); // But make sure we aren't doing anything interesting right now, where // the user wants to see the screen updating even though they're not // interacting with it if (state.view->WantsHighFramerate()) yield = false; if (yield) // if there was no recent activity... { double sleepUntil = time + 0.5; // only redraw at 2fps while (time < sleepUntil) { // To minimise latency when the user starts doing stuff, only // sleep for a short while, then check if anything's happened, // then go back to sleep // (TODO: This should probably be done with something like semaphores) Atlas_NotifyEndOfFrame(); // (TODO: rename to NotifyEndOfQuiteShortProcessingPeriodSoPleaseSendMeNewMessages or something) SDL_Delay(50); if (!msgPasser->IsEmpty()) break; time = timer_Time(); } } else { Atlas_NotifyEndOfFrame(); SDL_Delay(0); } } return NULL; } bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) { // Load required symbols from the DLL try { dll.LoadSymbol("Atlas_StartWindow", Atlas_StartWindow); dll.LoadSymbol("Atlas_SetMessagePasser", Atlas_SetMessagePasser); dll.LoadSymbol("Atlas_SetDataDirectory", Atlas_SetDataDirectory); dll.LoadSymbol("Atlas_SetConfigDirectory", Atlas_SetConfigDirectory); dll.LoadSymbol("Atlas_GLSetCurrent", Atlas_GLSetCurrent); dll.LoadSymbol("Atlas_GLSwapBuffers", Atlas_GLSwapBuffers); dll.LoadSymbol("Atlas_NotifyEndOfFrame", Atlas_NotifyEndOfFrame); dll.LoadSymbol("Atlas_DisplayError", Atlas_DisplayError); dll.LoadSymbol("Atlas_ReportError", Atlas_ReportError); dll.LoadSymbol("ShareableMalloc", ShareableMallocFptr); dll.LoadSymbol("ShareableFree", ShareableFreeFptr); } catch (PSERROR_DllLoader&) { debug_warn(L"Failed to initialise DLL"); return false; } // Construct a message passer for communicating with Atlas // (here so that its scope lasts beyond the game thread) MessagePasserImpl msgPasser; AtlasMessage::g_MessagePasser = &msgPasser; // Pass our message handler to Atlas Atlas_SetMessagePasser(&msgPasser); // Tell Atlas the location of the data directory const Paths paths(args); Atlas_SetDataDirectory(paths.RData().string().c_str()); // Tell Atlas the location of the user config directory Atlas_SetConfigDirectory(paths.Config().string().c_str()); // Run the engine loop in a new thread pthread_t engineThread; pthread_create(&engineThread, NULL, RunEngine, reinterpret_cast(const_cast(&args))); // Start Atlas UI on main thread // (required for wxOSX/Cocoa compatibility - see http://trac.wildfiregames.com/ticket/500) Atlas_StartWindow(L"ScenarioEditor"); // Wait for the engine to exit pthread_join(engineThread, NULL); // TODO: delete all remaining messages, to avoid memory leak warnings // Restore main thread ThreadUtil::SetMainThread(); // Clean up AtlasView::DestroyViews(); ScriptingHost::FinalShutdown(); AtlasMessage::g_MessagePasser = NULL; return true; } Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp (revision 13723) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp (revision 13724) @@ -1,190 +1,191 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MessageHandler.h" #include "../GameLoop.h" #include "../CommandProc.h" #include "../ActorViewer.h" #include "../View.h" #include "graphics/GameView.h" #include "graphics/ObjectManager.h" #include "gui/GUIManager.h" #include "lib/external_libraries/libsdl.h" #include "maths/MathUtil.h" #include "ps/CConsole.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "renderer/Renderer.h" namespace AtlasMessage { +// see comment in GameLoop.cpp about ah_display_error before using INIT_HAVE_DISPLAY_ERROR const int g_InitFlags = INIT_HAVE_VMODE|INIT_NO_GUI; MESSAGEHANDLER(Init) { UNUSED2(msg); g_Quickstart = true; Init(g_AtlasGameLoop->args, g_InitFlags); // Initialise some graphics state for Atlas. // (This must be done after Init loads the config DB, // but before the UI constructs its GL canvases.) g_VideoMode.InitNonSDL(); } MESSAGEHANDLER(InitGraphics) { UNUSED2(msg); #if OS_UNIX || (OS_WIN && !CONFIG2_WSDL) // When using GLX (Linux), SDL has to load the GL library to find // glXGetProcAddressARB before it can load any extensions. // When running in Atlas, we skip the SDL video initialisation code // which loads the library, and so SDL_GL_GetProcAddress fails (in // ogl.cpp importExtensionFunctions). // (TODO: I think this is meant to be context-independent, i.e. it // doesn't matter that we're getting extensions from SDL-initialised // GL stuff instead of from the wxWidgets-initialised GL stuff, but that // should be checked.) // So, make sure it's loaded: SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_GL_LoadLibrary(NULL); // NULL = use default // (it shouldn't hurt if this is called multiple times, I think) #endif ogl_Init(); InitGraphics(g_AtlasGameLoop->args, g_InitFlags); #if OS_WIN // HACK (to stop things looking very ugly when scrolling) - should // use proper config system. if(ogl_HaveExtension("WGL_EXT_swap_control")) pwglSwapIntervalEXT(1); #endif } MESSAGEHANDLER(Shutdown) { UNUSED2(msg); // Empty the CommandProc, to get rid of its references to entities before // we kill the EntityManager GetCommandProc().Destroy(); AtlasView::DestroyViews(); g_AtlasGameLoop->view = AtlasView::GetView_None(); int flags = 0; Shutdown(flags); } QUERYHANDLER(Exit) { UNUSED2(msg); g_AtlasGameLoop->running = false; } MESSAGEHANDLER(RenderEnable) { g_AtlasGameLoop->view = AtlasView::GetView(msg->view); } MESSAGEHANDLER(SetViewParamB) { AtlasView* view = AtlasView::GetView(msg->view); view->SetParam(*msg->name, msg->value); } MESSAGEHANDLER(SetViewParamI) { AtlasView* view = AtlasView::GetView(msg->view); view->SetParam(*msg->name, msg->value); } MESSAGEHANDLER(SetViewParamC) { AtlasView* view = AtlasView::GetView(msg->view); view->SetParam(*msg->name, msg->value); } MESSAGEHANDLER(SetViewParamS) { AtlasView* view = AtlasView::GetView(msg->view); view->SetParam(*msg->name, *msg->value); } MESSAGEHANDLER(SetActorViewer) { if (msg->flushcache) { // TODO EXTREME DANGER: this'll break horribly if any units remain // in existence and use their actors after we've deleted all the actors. // (The actor viewer currently only has one unit at a time, so it's // alright.) // Should replace this with proper actor hot-loading system, or something. AtlasView::GetView_Actor()->GetActorViewer().SetActor(L"", L"", -1); AtlasView::GetView_Actor()->GetActorViewer().UnloadObjects(); // vfs_reload_changed_files(); } AtlasView::GetView_Actor()->SetSpeedMultiplier(msg->speed); AtlasView::GetView_Actor()->GetActorViewer().SetActor(*msg->id, *msg->animation, msg->playerID); } ////////////////////////////////////////////////////////////////////////// MESSAGEHANDLER(SetCanvas) { // Need to set the canvas size before possibly doing any rendering, // else we'll get GL errors when trying to render to 0x0 CVideoMode::UpdateRenderer(msg->width, msg->height); g_AtlasGameLoop->glCanvas = msg->canvas; Atlas_GLSetCurrent(const_cast(g_AtlasGameLoop->glCanvas)); } MESSAGEHANDLER(ResizeScreen) { CVideoMode::UpdateRenderer(msg->width, msg->height); #if OS_MACOSX // OS X seems to require this to update the GL canvas Atlas_GLSetCurrent(const_cast(g_AtlasGameLoop->glCanvas)); #endif } ////////////////////////////////////////////////////////////////////////// MESSAGEHANDLER(RenderStyle) { g_Renderer.SetTerrainRenderMode(msg->wireframe ? EDGED_FACES : SOLID); g_Renderer.SetModelRenderMode(msg->wireframe ? EDGED_FACES : SOLID); } }