Compare commits
89 Commits
a4be9f87f5
...
caa578a029
Author | SHA1 | Date |
---|---|---|
Duncan Ogilvie | caa578a029 | |
Duncan Ogilvie | d08913bc54 | |
Duncan Ogilvie | da43aca116 | |
Duncan Ogilvie | 7a3851a607 | |
Duncan Ogilvie | a5f73b479f | |
Duncan Ogilvie | 9e4c1a4d26 | |
Duncan Ogilvie | 517a855e9a | |
Duncan Ogilvie | f3cb3443d1 | |
Duncan Ogilvie | c3642c35be | |
Duncan Ogilvie | 399b19f847 | |
Duncan Ogilvie | 9c07d82dc8 | |
Duncan Ogilvie | a6e448b598 | |
Duncan Ogilvie | ec9a2a2af1 | |
Duncan Ogilvie | 9f6d396c4e | |
torusrxxx | a006d75ee5 | |
torusrxxx | e30a70ad4d | |
torusrxxx | 8cb5459629 | |
Duncan Ogilvie | b683fc6a59 | |
Duncan Ogilvie | 62129b426e | |
foralost | 9ed6033fb6 | |
Duncan Ogilvie | afb5ac45c5 | |
foralost | ab9c28549a | |
foralost | 0853cafadc | |
Duncan Ogilvie | 81bc337e52 | |
Duncan Ogilvie | ee13290541 | |
Duncan Ogilvie | 04c5050015 | |
Duncan Ogilvie | e2093d2d89 | |
Duncan Ogilvie | 3eb2070c3a | |
Duncan Ogilvie | f56fa5ce23 | |
torusrxxx | aa85488fe5 | |
foralost | 911329e8e6 | |
foralost | 03baaa1ae3 | |
Duncan Ogilvie | b17fc86af7 | |
Duncan Ogilvie | 93ac4548f7 | |
Duncan Ogilvie | 03c6f3b6a5 | |
foralost | 16fccbd2d4 | |
ζeh Matt | 050c989dc3 | |
ζeh Matt | 218d784a7e | |
torusrxxx | f8567b59f5 | |
torusrxxx | eff762460f | |
Duncan Ogilvie | 570aaea06d | |
Duncan Ogilvie | d2f6ba72cc | |
Duncan Ogilvie | 61814b2da7 | |
torusrxxx | 715831637a | |
Duncan Ogilvie | 01c239534a | |
Duncan Ogilvie | 2747f72f38 | |
Duncan Ogilvie | 4193000159 | |
Duncan Ogilvie | a320227378 | |
Duncan Ogilvie | 7c2276681a | |
Duncan Ogilvie | b2b104b79f | |
Duncan Ogilvie | 95b790eab1 | |
German Semenov | eeab4c47ed | |
Duncan Ogilvie | 373b3a538f | |
Duncan Ogilvie | 44c3d39165 | |
Duncan Ogilvie | 923b894df2 | |
foralost | c9d80e5c99 | |
Duncan Ogilvie | 65d57bfd2e | |
Duncan Ogilvie | 2e1fd1f289 | |
Duncan Ogilvie | b84c293f15 | |
Duncan Ogilvie | 50d7d988f6 | |
Duncan Ogilvie | 735d3ca5f9 | |
Duncan Ogilvie | dfda450b41 | |
Duncan Ogilvie | 49f9487a59 | |
Duncan Ogilvie | 7f9dc7fc04 | |
Duncan Ogilvie | 50fc52b0d2 | |
Duncan Ogilvie | 61f99ae5c9 | |
Duncan Ogilvie | e3e6a8a9fa | |
Duncan Ogilvie | 8ae5982502 | |
Leet | f4821ce331 | |
Leet | de527241cd | |
Habip Akyol | a2594cf1f9 | |
Duncan Ogilvie | 0f54d5ebcd | |
Ruslan Garipov | d52466f981 | |
Marek Szwajka | 4945c36e85 | |
foralost | 0f39191c9d | |
foralost | 81d72252a8 | |
foralost | c122eb9e1d | |
Duncan Ogilvie | b0392eda1c | |
Duncan Ogilvie | d0fb3a0b88 | |
Duncan Ogilvie | cb31cbd83b | |
Duncan Ogilvie | 99f5e93592 | |
LIU Hao | e9dfe3020e | |
shocoman | 98d08ce826 | |
shocoman | 17bd183de2 | |
Leet | 89aee8ad5b | |
dad | 7dade58481 | |
dad | ca637fad33 | |
stdust | e5dba0ad63 | |
stdust | 3409444e33 |
|
@ -0,0 +1,2 @@
|
|||
# Disable core.autocrlf (https://stackoverflow.com/a/52996849/1806760)
|
||||
* -text
|
|
@ -1779,6 +1779,12 @@ BRIDGE_IMPEXP void GuiMenuSetEntryHotkey(int hEntry, const char* hack)
|
|||
_gui_sendmessage(GUI_MENU_SET_ENTRY_HOTKEY, (void*)hEntry, (void*)hack);
|
||||
}
|
||||
|
||||
|
||||
BRIDGE_IMPEXP void GuiShowThreads()
|
||||
{
|
||||
_gui_sendmessage(GUI_SHOW_THREADS, 0, 0);
|
||||
}
|
||||
|
||||
BRIDGE_IMPEXP void GuiShowCpu()
|
||||
{
|
||||
_gui_sendmessage(GUI_SHOW_CPU, 0, 0);
|
||||
|
|
|
@ -1152,6 +1152,7 @@ typedef enum
|
|||
GUI_GRAPH,
|
||||
GUI_MEMMAP,
|
||||
GUI_SYMMOD,
|
||||
GUI_THREADS,
|
||||
} GUISELECTIONTYPE;
|
||||
|
||||
#define GUI_MAX_LINE_SIZE 65536
|
||||
|
@ -1281,6 +1282,7 @@ typedef enum
|
|||
GUI_SAVE_LOG, // param1=const char* file name,param2=unused
|
||||
GUI_REDIRECT_LOG, // param1=const char* file name,param2=unused
|
||||
GUI_STOP_REDIRECT_LOG, // param1=unused, param2=unused
|
||||
GUI_SHOW_THREADS, // param1=unused, param2=unused
|
||||
} GUIMSG;
|
||||
|
||||
//GUI Typedefs
|
||||
|
@ -1423,6 +1425,7 @@ BRIDGE_IMPEXP void GuiMenuSetName(int hMenu, const char* name);
|
|||
BRIDGE_IMPEXP void GuiMenuSetEntryName(int hEntry, const char* name);
|
||||
BRIDGE_IMPEXP void GuiMenuSetEntryHotkey(int hEntry, const char* hack);
|
||||
BRIDGE_IMPEXP void GuiShowCpu();
|
||||
BRIDGE_IMPEXP void GuiShowThreads();
|
||||
BRIDGE_IMPEXP void GuiAddQWidgetTab(void* qWidget);
|
||||
BRIDGE_IMPEXP void GuiShowQWidgetTab(void* qWidget);
|
||||
BRIDGE_IMPEXP void GuiCloseQWidgetTab(void* qWidget);
|
||||
|
|
|
@ -1513,7 +1513,7 @@ extern "C" DLL_EXPORT duint _dbg_sendmessage(DBGMSG type, void* param1, void* pa
|
|||
|
||||
case DBG_GET_STRING_AT:
|
||||
{
|
||||
return disasmgetstringatwrapper(duint(param1), (char*)param2);
|
||||
return disasmgetstringatwrapper(duint(param1), (char*)param2, true);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
\brief Implements the global class.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include "_global.h"
|
||||
#include <objbase.h>
|
||||
#include <shlobj.h>
|
||||
#include <psapi.h>
|
||||
#include <thread>
|
||||
#include "DeviceNameResolver/DeviceNameResolver.h"
|
||||
|
||||
/**
|
||||
|
@ -393,3 +395,53 @@ void WaitForMultipleThreadsTermination(const HANDLE* hThread, int count, DWORD t
|
|||
for(int i = 0; i < count; i++)
|
||||
CloseHandle(hThread[i]);
|
||||
}
|
||||
|
||||
// This implementation supports both conventional single-cpu PC configurations
|
||||
// and multi-cpu system on NUMA (Non-uniform_memory_access) architecture
|
||||
// Original code from here: https://developercommunity.visualstudio.com/t/hardware-concurrency-returns-an-incorrect-result/350854
|
||||
duint GetThreadCount()
|
||||
{
|
||||
duint threadCount = std::thread::hardware_concurrency();
|
||||
|
||||
typedef BOOL(*WINAPI GetLogicalProcessorInformationEx_t)(
|
||||
LOGICAL_PROCESSOR_RELATIONSHIP,
|
||||
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
|
||||
PDWORD
|
||||
);
|
||||
|
||||
static auto p_GetLogicalProcessorInformationEx = (GetLogicalProcessorInformationEx_t)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetLogicalProcessorInformationEx");
|
||||
if(p_GetLogicalProcessorInformationEx == nullptr)
|
||||
{
|
||||
return threadCount;
|
||||
}
|
||||
|
||||
DWORD length = 0;
|
||||
if(p_GetLogicalProcessorInformationEx(RelationAll, nullptr, &length) || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
return threadCount;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer(length);
|
||||
if(!p_GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)buffer.data(), &length))
|
||||
{
|
||||
return threadCount;
|
||||
}
|
||||
|
||||
threadCount = 0;
|
||||
for(DWORD offset = 0; offset < length;)
|
||||
{
|
||||
auto info = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)(buffer.data() + offset);
|
||||
if(info->Relationship == RelationProcessorCore)
|
||||
{
|
||||
for(WORD group = 0; group < info->Processor.GroupCount; ++group)
|
||||
{
|
||||
for(KAFFINITY mask = info->Processor.GroupMask[group].Mask; mask != 0; mask >>= 1)
|
||||
{
|
||||
threadCount += mask & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += info->Size;
|
||||
}
|
||||
return threadCount;
|
||||
}
|
|
@ -65,6 +65,7 @@ bool IsWow64();
|
|||
bool ResolveShortcut(HWND hwnd, const wchar_t* szShortcutPath, std::wstring & executable, std::wstring & arguments, std::wstring & workingDir);
|
||||
void WaitForThreadTermination(HANDLE hThread, DWORD timeout = INFINITE);
|
||||
void WaitForMultipleThreadsTermination(const HANDLE* hThread, int count, DWORD timeout = INFINITE);
|
||||
duint GetThreadCount();
|
||||
|
||||
#ifdef _WIN64
|
||||
#define ArchValue(x32value, x64value) x64value
|
||||
|
|
|
@ -240,7 +240,11 @@ typedef enum
|
|||
{
|
||||
ValueTypeNumber,
|
||||
ValueTypeString,
|
||||
ValueTypeAny, // Cannot be used for values, only for argTypes (to accept any type)
|
||||
// Types below cannot be used for values, only for registration
|
||||
ValueTypeAny,
|
||||
ValueTypeOptionalNumber,
|
||||
ValueTypeOptionalString,
|
||||
ValueTypeOptionalAny,
|
||||
} ValueType;
|
||||
|
||||
typedef struct
|
||||
|
|
|
@ -77,7 +77,7 @@ duint AnalysisPass::IdealThreadCount()
|
|||
if(m_InternalMaxThreads == 0)
|
||||
{
|
||||
// Determine the maximum hardware thread count at once
|
||||
duint maximumThreads = max(std::thread::hardware_concurrency(), 1);
|
||||
duint maximumThreads = max(GetThreadCount(), 1);
|
||||
|
||||
// Don't consume 100% of the CPU, adjust accordingly
|
||||
if(maximumThreads > 1)
|
||||
|
|
|
@ -8,6 +8,26 @@
|
|||
#include "value.h"
|
||||
#include "variable.h"
|
||||
|
||||
bool cbShowThreadId(int argc, char* argv[])
|
||||
{
|
||||
if(argc > 1)
|
||||
{
|
||||
duint threadId = 0;
|
||||
if(!valfromstring(argv[1], &threadId, false))
|
||||
return false;
|
||||
|
||||
SELECTIONDATA newSelection = { threadId, threadId };
|
||||
if(!GuiSelectionSet(GUI_THREADS, &newSelection))
|
||||
{
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Invalid thread %s\n"), formatpidtid((DWORD)threadId).c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GuiShowThreads();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cbDebugDisasm(int argc, char* argv[])
|
||||
{
|
||||
duint addr = 0;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "command.h"
|
||||
|
||||
bool cbShowThreadId(int argc, char* argv[]);
|
||||
bool cbDebugDisasm(int argc, char* argv[]);
|
||||
bool cbDebugDump(int argc, char* argv[]);
|
||||
bool cbDebugStackDump(int argc, char* argv[]);
|
||||
|
|
|
@ -29,7 +29,8 @@ bool cbBadCmd(int argc, char* argv[])
|
|||
{
|
||||
if(evalue.isString)
|
||||
{
|
||||
dputs_untranslated(StringUtils::Escape(evalue.data).c_str());
|
||||
varset("$ans", evalue.data.c_str(), true);
|
||||
dprintf_untranslated("\"%s\"\n", StringUtils::Escape(evalue.data).c_str());
|
||||
}
|
||||
else if(evalue.DoEvaluate(value, silent, baseonly, &valsize, &isvar, &hexonly))
|
||||
{
|
||||
|
|
|
@ -51,7 +51,7 @@ bool cbInstrVarDel(int argc, char* argv[])
|
|||
{
|
||||
if(IsArgumentsLessThan(argc, 2))
|
||||
return false;
|
||||
if(!vardel(argv[1], false))
|
||||
if(vardel(argv[1], false) != 0)
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not delete variable \"%s\"\n"), argv[1]);
|
||||
else
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Deleted variable \"%s\"\n"), argv[1]);
|
||||
|
|
|
@ -71,6 +71,7 @@ void DbSave(DbLoadSaveType saveType, const char* dbfile, bool disablecompression
|
|||
EncodeMapCacheSave(root);
|
||||
TraceRecord.saveToDb(root);
|
||||
BpCacheSave(root);
|
||||
ModCacheSave(root);
|
||||
WatchCacheSave(root);
|
||||
|
||||
//save notes
|
||||
|
@ -91,7 +92,7 @@ void DbSave(DbLoadSaveType saveType, const char* dbfile, bool disablecompression
|
|||
|
||||
//plugin data
|
||||
PLUG_CB_LOADSAVEDB pluginSaveDb;
|
||||
// Some plugins may wish to change this value so that all plugins after his or her plugin will save data into plugin-supplied storage instead of the system's.
|
||||
// Some plugins may wish to change this value so that all plugins after their plugin will save data into plugin-supplied storage instead of the system's.
|
||||
// We back up this value so that the debugger is not fooled by such plugins.
|
||||
JSON pluginRoot = json_object();
|
||||
pluginSaveDb.root = pluginRoot;
|
||||
|
@ -283,6 +284,7 @@ void DbLoad(DbLoadSaveType loadType, const char* dbfile)
|
|||
EncodeMapCacheLoad(root);
|
||||
TraceRecord.loadFromDb(root);
|
||||
BpCacheLoad(root, migrateBreakpoints);
|
||||
ModCacheLoad(root);
|
||||
WatchCacheLoad(root);
|
||||
|
||||
// Load notes
|
||||
|
@ -340,6 +342,7 @@ void DbClear(bool terminating)
|
|||
EncodeMapClear();
|
||||
TraceRecord.clear();
|
||||
BpClear();
|
||||
ModCacheClear();
|
||||
WatchClear();
|
||||
GuiSetDebuggeeNotes("");
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ void disasmget(unsigned char* buffer, duint addr, DISASM_INSTR* instr, bool getr
|
|||
void disasmget(duint addr, DISASM_INSTR* instr, bool getregs = true);
|
||||
bool disasmispossiblestring(duint addr, STRING_TYPE* type = nullptr);
|
||||
bool disasmgetstringat(duint addr, STRING_TYPE* type, char* ascii, char* unicode, int maxlen);
|
||||
bool disasmgetstringatwrapper(duint addr, char* text, bool cache = true);
|
||||
bool disasmgetstringatwrapper(duint addr, char* text, bool cache);
|
||||
int disasmgetsize(duint addr, unsigned char* data);
|
||||
int disasmgetsize(duint addr);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ static std::unordered_map<unsigned int, String> NtStatusNames;
|
|||
static std::unordered_map<unsigned int, String> ErrorNames;
|
||||
static std::unordered_map<String, unsigned int> Constants;
|
||||
static std::unordered_map<unsigned int, String> SyscallIndices;
|
||||
static std::unordered_map<String, unsigned int> SyscallNames;
|
||||
|
||||
static bool UniversalCodeInit(const String & file, std::unordered_map<unsigned int, String> & names, unsigned char radix)
|
||||
{
|
||||
|
@ -253,12 +254,43 @@ bool SyscallInit()
|
|||
SyscallIndices.insert({ index, syscall.Name });
|
||||
}
|
||||
}
|
||||
ModClear(false);
|
||||
|
||||
// Populate the name map
|
||||
for(const auto & itr : SyscallIndices)
|
||||
{
|
||||
SyscallNames.emplace(itr.second, itr.first);
|
||||
}
|
||||
|
||||
// Also allow lookup with only the least significant 14 bits
|
||||
// Reference: https://alice.climent-pommeret.red/posts/a-syscall-journey-in-the-windows-kernel/
|
||||
for(const auto & itr : SyscallIndices)
|
||||
{
|
||||
auto truncated = itr.first & 0x3FFF;
|
||||
if(truncated != itr.first)
|
||||
{
|
||||
SyscallIndices.emplace(truncated, itr.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the GUI
|
||||
ModClear(true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const String & SyscallToName(unsigned int index)
|
||||
{
|
||||
auto found = SyscallIndices.find(index);
|
||||
auto found = SyscallIndices.find(index & 0x3FFF);
|
||||
return found != SyscallIndices.end() ? found->second : emptyString;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int SyscallToId(const String & name)
|
||||
{
|
||||
if(name.find("Zw") == 0)
|
||||
{
|
||||
return SyscallToId("Nt" + name.substr(2));
|
||||
}
|
||||
|
||||
auto found = SyscallNames.find(name);
|
||||
return found != SyscallNames.end() ? found->second : -1;
|
||||
}
|
||||
|
|
|
@ -22,5 +22,6 @@ std::vector<CONSTANTINFO> ConstantList();
|
|||
// To use this function, use EXCLUSIVE_ACQUIRE(LockModules)
|
||||
bool SyscallInit();
|
||||
const String & SyscallToName(unsigned int index);
|
||||
unsigned int SyscallToId(const String & name);
|
||||
|
||||
#endif // _EXCEPTION_H
|
|
@ -71,7 +71,7 @@ void ExpressionFunctions::Init()
|
|||
RegisterEasy("mod.offset,mod.fileoffset", valvatofileoffset);
|
||||
RegisterEasy("mod.headerva", modheaderva);
|
||||
RegisterEasy("mod.isexport", modisexport);
|
||||
ExpressionFunctions::Register("mod.fromname", ValueTypeNumber, { ValueTypeString }, Exprfunc::modbasefromname, nullptr);
|
||||
ExpressionFunctions::Register("mod.fromname", ValueTypeNumber, { ValueTypeString }, Exprfunc::modbasefromname);
|
||||
|
||||
//Process information
|
||||
RegisterEasy("peb,PEB", peb);
|
||||
|
@ -81,7 +81,7 @@ void ExpressionFunctions::Init()
|
|||
|
||||
//General purpose
|
||||
RegisterEasy("bswap", bswap);
|
||||
RegisterEasy("ternary,tern", ternary);
|
||||
ExpressionFunctions::Register("ternary,tern", ValueTypeAny, { ValueTypeNumber, ValueTypeAny, ValueTypeAny }, ternary);
|
||||
RegisterEasy("GetTickCount,gettickcount", gettickcount);
|
||||
RegisterEasy("rdtsc", rdtsc);
|
||||
|
||||
|
@ -110,9 +110,9 @@ void ExpressionFunctions::Init()
|
|||
RegisterEasy("dis.next", disnext);
|
||||
RegisterEasy("dis.prev", disprev);
|
||||
RegisterEasy("dis.iscallsystem", disiscallsystem);
|
||||
ExpressionFunctions::Register("dis.mnemonic", ValueTypeString, { ValueTypeNumber }, Exprfunc::dismnemonic, nullptr);
|
||||
ExpressionFunctions::Register("dis.text", ValueTypeString, { ValueTypeNumber }, Exprfunc::distext, nullptr);
|
||||
ExpressionFunctions::Register("dis.match", ValueTypeNumber, { ValueTypeNumber, ValueTypeString }, Exprfunc::dismatch, nullptr);
|
||||
ExpressionFunctions::Register("dis.mnemonic", ValueTypeString, { ValueTypeNumber }, dismnemonic);
|
||||
ExpressionFunctions::Register("dis.text", ValueTypeString, { ValueTypeNumber }, distext);
|
||||
ExpressionFunctions::Register("dis.match", ValueTypeNumber, { ValueTypeNumber, ValueTypeString }, dismatch);
|
||||
|
||||
//Trace record
|
||||
RegisterEasy("tr.enabled", trenabled);
|
||||
|
@ -158,17 +158,20 @@ void ExpressionFunctions::Init()
|
|||
RegisterEasy("isdebuggeefocused", isdebuggeefocused);
|
||||
|
||||
// Strings
|
||||
ExpressionFunctions::Register("ansi", ValueTypeString, { ValueTypeNumber }, Exprfunc::ansi, nullptr);
|
||||
ExpressionFunctions::Register("ansi.strict", ValueTypeString, { ValueTypeNumber }, Exprfunc::ansi_strict, nullptr);
|
||||
ExpressionFunctions::Register("utf8", ValueTypeString, { ValueTypeNumber }, Exprfunc::utf8, nullptr);
|
||||
ExpressionFunctions::Register("utf8.strict", ValueTypeString, { ValueTypeNumber }, Exprfunc::utf8_strict, nullptr);
|
||||
ExpressionFunctions::Register("utf16", ValueTypeString, { ValueTypeNumber }, Exprfunc::utf16, nullptr);
|
||||
ExpressionFunctions::Register("utf16.strict", ValueTypeString, { ValueTypeNumber }, Exprfunc::utf16_strict, nullptr);
|
||||
ExpressionFunctions::Register("strstr", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::strstr, nullptr);
|
||||
ExpressionFunctions::Register("stristr", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::stristr, nullptr);
|
||||
ExpressionFunctions::Register("streq", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::streq, nullptr);
|
||||
ExpressionFunctions::Register("strieq", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::strieq, nullptr);
|
||||
ExpressionFunctions::Register("strlen", ValueTypeNumber, { ValueTypeString }, Exprfunc::strlen, nullptr);
|
||||
ExpressionFunctions::Register("ansi", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::ansi);
|
||||
ExpressionFunctions::Register("ansi.strict", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::ansi_strict);
|
||||
ExpressionFunctions::Register("utf8", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::utf8);
|
||||
ExpressionFunctions::Register("utf8.strict", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::utf8_strict);
|
||||
ExpressionFunctions::Register("utf16", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::utf16);
|
||||
ExpressionFunctions::Register("utf16.strict", ValueTypeString, { ValueTypeNumber, ValueTypeOptionalNumber }, Exprfunc::utf16_strict);
|
||||
ExpressionFunctions::Register("strstr", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::strstr);
|
||||
ExpressionFunctions::Register("stristr", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::stristr);
|
||||
ExpressionFunctions::Register("streq", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::streq);
|
||||
ExpressionFunctions::Register("strieq", ValueTypeNumber, { ValueTypeString, ValueTypeString }, Exprfunc::strieq);
|
||||
ExpressionFunctions::Register("strlen", ValueTypeNumber, { ValueTypeString }, Exprfunc::strlen);
|
||||
|
||||
ExpressionFunctions::Register("syscall.name", ValueTypeString, { ValueTypeNumber }, Exprfunc::syscall_name);
|
||||
ExpressionFunctions::Register("syscall.id", ValueTypeNumber, { ValueTypeString }, Exprfunc::syscall_id);
|
||||
}
|
||||
|
||||
bool ExpressionFunctions::Register(const String & name, const ValueType & returnType, const std::vector<ValueType> & argTypes, const CBEXPRESSIONFUNCTION & cbFunction, void* userdata)
|
||||
|
@ -180,10 +183,39 @@ bool ExpressionFunctions::Register(const String & name, const ValueType & return
|
|||
if(mFunctions.count(aliases[0]))
|
||||
return false;
|
||||
|
||||
// Return type cannot be optional
|
||||
switch(returnType)
|
||||
{
|
||||
case ValueTypeOptionalNumber:
|
||||
case ValueTypeOptionalString:
|
||||
case ValueTypeOptionalAny:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Make sure optional arguments are at the end
|
||||
bool seenOptional = false;
|
||||
for(const auto & argType : argTypes)
|
||||
{
|
||||
switch(argType)
|
||||
{
|
||||
case ValueTypeOptionalNumber:
|
||||
case ValueTypeOptionalString:
|
||||
case ValueTypeOptionalAny:
|
||||
seenOptional = true;
|
||||
break;
|
||||
default:
|
||||
if(seenOptional)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Function f;
|
||||
f.name = aliases[0];
|
||||
f.argTypes = argTypes;
|
||||
f.returnType = returnType;
|
||||
f.argTypes = argTypes;
|
||||
f.cbFunction = cbFunction;
|
||||
f.userdata = userdata;
|
||||
mFunctions[aliases[0]] = f;
|
||||
|
@ -228,13 +260,6 @@ bool ExpressionFunctions::Call(const String & name, ExpressionValue & result, st
|
|||
if(found == mFunctions.end())
|
||||
return false;
|
||||
const auto & f = found->second;
|
||||
if(f.argTypes.size() != int(argv.size()))
|
||||
return false;
|
||||
for(size_t i = 0; i < argv.size(); i++)
|
||||
{
|
||||
if(argv[i].type != f.argTypes[i] && f.argTypes[i] != ValueTypeAny)
|
||||
return false;
|
||||
}
|
||||
return f.cbFunction(&result, (int)argv.size(), argv.data(), f.userdata);
|
||||
}
|
||||
|
||||
|
|
|
@ -141,6 +141,15 @@ ExpressionParser::ExpressionParser(const String & expression)
|
|||
mTokens.reserve(r);
|
||||
mCurToken.reserve(r);
|
||||
tokenize();
|
||||
#if 0
|
||||
// Print the tokens for debugging
|
||||
dprintf_untranslated("'%s':\n", expression.c_str());
|
||||
for(const auto & token : mTokens)
|
||||
{
|
||||
dprintf_untranslated(" % 2d '%s'\n", token.type(), token.data().c_str());
|
||||
}
|
||||
dprintf_untranslated("\n");
|
||||
#endif
|
||||
shuntingYard();
|
||||
}
|
||||
|
||||
|
@ -215,10 +224,10 @@ void ExpressionParser::tokenize()
|
|||
addOperatorToken(ch, Token::Type::Comma);
|
||||
break;
|
||||
case '(':
|
||||
addOperatorToken(ch, Token::Type::OpenBracket);
|
||||
addOperatorToken(ch, Token::Type::OpenParen);
|
||||
break;
|
||||
case ')':
|
||||
addOperatorToken(ch, Token::Type::CloseBracket);
|
||||
addOperatorToken(ch, Token::Type::CloseParen);
|
||||
break;
|
||||
case '~':
|
||||
addOperatorToken(ch, Token::Type::OperatorNot);
|
||||
|
@ -367,7 +376,14 @@ void ExpressionParser::addOperatorToken(const String & data, Token::Type type)
|
|||
{
|
||||
if(mCurToken.length()) //add a new data token when there is data in the buffer
|
||||
{
|
||||
mTokens.push_back(Token(mCurToken, type == Token::Type::OpenBracket ? Token::Type::Function : resolveQuotedData()));
|
||||
if(type == Token::Type::OpenParen)
|
||||
{
|
||||
mTokens.push_back(Token(mCurToken, Token::Type::Function));
|
||||
}
|
||||
else
|
||||
{
|
||||
mTokens.push_back(Token(mCurToken, resolveQuotedData()));
|
||||
}
|
||||
mCurToken.clear();
|
||||
mCurTokenQuoted.clear();
|
||||
}
|
||||
|
@ -388,7 +404,7 @@ bool ExpressionParser::isUnaryOperator() const
|
|||
return true;
|
||||
auto lastType = mTokens.back().type();
|
||||
//if the previous token is not data or a close bracket, this operator is a unary operator
|
||||
return lastType != Token::Type::Data && lastType != Token::Type::QuotedData && lastType != Token::Type::CloseBracket;
|
||||
return lastType != Token::Type::Data && lastType != Token::Type::QuotedData && lastType != Token::Type::CloseParen;
|
||||
}
|
||||
|
||||
void ExpressionParser::shuntingYard()
|
||||
|
@ -396,6 +412,7 @@ void ExpressionParser::shuntingYard()
|
|||
//Implementation of Dijkstra's Shunting-yard algorithm (https://en.wikipedia.org/wiki/Shunting-yard_algorithm)
|
||||
std::vector<Token> queue;
|
||||
std::vector<Token> stack;
|
||||
std::vector<duint> argCount;
|
||||
auto len = mTokens.size();
|
||||
queue.reserve(len);
|
||||
stack.reserve(len);
|
||||
|
@ -410,9 +427,18 @@ void ExpressionParser::shuntingYard()
|
|||
queue.push_back(token);
|
||||
break;
|
||||
case Token::Type::Function: //If the token is a function token, then push it onto the stack.
|
||||
{
|
||||
stack.push_back(token);
|
||||
break;
|
||||
|
||||
// Unless the syntax is 'fn()' there is always at least one argument
|
||||
if(i + 2 < mTokens.size() && mTokens[i + 1].type() == Token::Type::OpenParen && mTokens[i + 2].type() == Token::Type::CloseParen)
|
||||
argCount.push_back(0);
|
||||
else
|
||||
argCount.push_back(1);
|
||||
}
|
||||
break;
|
||||
case Token::Type::Comma: //If the token is a function argument separator (e.g., a comma):
|
||||
{
|
||||
while(true) //Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
|
||||
{
|
||||
if(stack.empty()) //If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched.
|
||||
|
@ -421,16 +447,20 @@ void ExpressionParser::shuntingYard()
|
|||
return;
|
||||
}
|
||||
const auto & curToken = stack.back();
|
||||
if(curToken.type() == Token::Type::OpenBracket)
|
||||
if(curToken.type() == Token::Type::OpenParen)
|
||||
break;
|
||||
queue.push_back(curToken);
|
||||
stack.pop_back();
|
||||
}
|
||||
break;
|
||||
case Token::Type::OpenBracket: //If the token is a left parenthesis (i.e. "("), then push it onto the stack.
|
||||
|
||||
if(!argCount.empty()) // A comma increases the argument count
|
||||
argCount.back()++;
|
||||
}
|
||||
break;
|
||||
case Token::Type::OpenParen: //If the token is a left parenthesis (i.e. "("), then push it onto the stack.
|
||||
stack.push_back(token);
|
||||
break;
|
||||
case Token::Type::CloseBracket: //If the token is a right parenthesis (i.e. ")"):
|
||||
case Token::Type::CloseParen: //If the token is a right parenthesis (i.e. ")"):
|
||||
{
|
||||
while(true) //Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
|
||||
{
|
||||
|
@ -441,14 +471,16 @@ void ExpressionParser::shuntingYard()
|
|||
}
|
||||
auto curToken = stack.back();
|
||||
stack.pop_back(); //Pop the left parenthesis from the stack, but not onto the output queue.
|
||||
if(curToken.type() == Token::Type::OpenBracket) //the bracket is already popped here
|
||||
if(curToken.type() == Token::Type::OpenParen) //the bracket is already popped here
|
||||
break;
|
||||
queue.push_back(curToken);
|
||||
}
|
||||
auto size = stack.size();
|
||||
if(size && stack[size - 1].type() == Token::Type::Function) //If the token at the top of the stack is a function token, pop it onto the output queue.
|
||||
if(!stack.empty() && stack.back().type() == Token::Type::Function) //If the token at the top of the stack is a function token, pop it onto the output queue.
|
||||
{
|
||||
queue.push_back(stack[size - 1]);
|
||||
// Propagate the argument count as extra information
|
||||
stack.back().setInfo(argCount.back());
|
||||
argCount.pop_back();
|
||||
queue.push_back(stack.back());
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +508,7 @@ void ExpressionParser::shuntingYard()
|
|||
while(!stack.empty()) //While there are still operator tokens in the stack:
|
||||
{
|
||||
const auto & curToken = stack.back();
|
||||
if(curToken.type() == Token::Type::OpenBracket || curToken.type() == Token::Type::CloseBracket) //If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
|
||||
if(curToken.type() == Token::Type::OpenParen || curToken.type() == Token::Type::CloseParen) //If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
|
||||
{
|
||||
mIsValidExpression = false;
|
||||
return;
|
||||
|
@ -907,108 +939,203 @@ bool ExpressionParser::Calculate(EvalValue & value, bool signedcalc, bool allowa
|
|||
else if(token.type() == Token::Type::Function)
|
||||
{
|
||||
const auto & name = token.data();
|
||||
const auto argCount = token.info();
|
||||
ValueType returnType;
|
||||
std::vector<ValueType> argTypes;
|
||||
if(!ExpressionFunctions::GetType(name, returnType, argTypes))
|
||||
return false;
|
||||
if(int(stack.size()) < argTypes.size())
|
||||
return false;
|
||||
std::vector<ExpressionValue> argv;
|
||||
argv.resize(argTypes.size());
|
||||
for(size_t i = 0; i < argTypes.size(); i++)
|
||||
{
|
||||
const auto & argType = argTypes[argTypes.size() - i - 1];
|
||||
auto & top = stack[stack.size() - i - 1];
|
||||
ExpressionValue arg;
|
||||
if(top.isString)
|
||||
if(!silent)
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "No such expression function '%s'\n"), name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t requiredArguments = 0;
|
||||
for(const auto & argType : argTypes)
|
||||
{
|
||||
switch(argType)
|
||||
{
|
||||
arg = { ValueTypeString, 0, StringValue{ top.data.c_str(), false } };
|
||||
case ValueTypeOptionalNumber:
|
||||
case ValueTypeOptionalString:
|
||||
case ValueTypeOptionalAny:
|
||||
break;
|
||||
default:
|
||||
requiredArguments++;
|
||||
break;
|
||||
}
|
||||
else if(top.evaluated)
|
||||
}
|
||||
|
||||
auto typeName = [](ValueType t) -> String
|
||||
{
|
||||
switch(t)
|
||||
{
|
||||
arg = { ValueTypeNumber, top.value };
|
||||
case ValueTypeOptionalNumber:
|
||||
case ValueTypeNumber:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "number"));
|
||||
case ValueTypeOptionalString:
|
||||
case ValueTypeString:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "string"));
|
||||
case ValueTypeOptionalAny:
|
||||
case ValueTypeAny:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "any"));
|
||||
}
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "invalid"));
|
||||
};
|
||||
|
||||
auto makeSignature = [&]()
|
||||
{
|
||||
String signature = name;
|
||||
signature += "(";
|
||||
for(size_t j = 0; j < argTypes.size(); j++)
|
||||
{
|
||||
if(j == requiredArguments)
|
||||
{
|
||||
signature += "[";
|
||||
}
|
||||
if(j > 0)
|
||||
{
|
||||
signature += ", ";
|
||||
}
|
||||
signature += typeName(argTypes[j]);
|
||||
}
|
||||
if(requiredArguments < argTypes.size())
|
||||
{
|
||||
signature += "]";
|
||||
}
|
||||
signature += ")";
|
||||
return signature;
|
||||
};
|
||||
|
||||
if(stack.size() < requiredArguments || argCount > argTypes.size())
|
||||
{
|
||||
if(!silent)
|
||||
{
|
||||
std::string expected;
|
||||
if(requiredArguments == argTypes.size())
|
||||
expected = StringUtils::sprintf("%d", (int)requiredArguments);
|
||||
else
|
||||
expected = StringUtils::sprintf("%d-%d", (int)requiredArguments, (int)argTypes.size());
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Bad argument count for expression function %s (expected %s, got %d)!\n"),
|
||||
makeSignature().c_str(),
|
||||
expected.c_str(),
|
||||
(int)argCount
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ExpressionValue> argv;
|
||||
argv.resize(argCount);
|
||||
for(size_t i = 0; i < argCount; i++)
|
||||
{
|
||||
// Get the expected (concrete) argument type
|
||||
auto argType = argTypes[i];
|
||||
switch(argType)
|
||||
{
|
||||
case ValueTypeOptionalNumber:
|
||||
argType = ValueTypeNumber;
|
||||
break;
|
||||
case ValueTypeOptionalString:
|
||||
argType = ValueTypeString;
|
||||
break;
|
||||
case ValueTypeOptionalAny:
|
||||
argType = ValueTypeAny;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto & argEval = stack[stack.size() - argCount + i];
|
||||
ExpressionValue argValue;
|
||||
if(argEval.isString)
|
||||
{
|
||||
argValue = { ValueTypeString, 0, StringValue{ argEval.data.c_str(), false } };
|
||||
}
|
||||
else if(argEval.evaluated)
|
||||
{
|
||||
argValue = { ValueTypeNumber, argEval.value };
|
||||
}
|
||||
else
|
||||
{
|
||||
duint result;
|
||||
if(!top.DoEvaluate(result, silent, baseonly, value_size, isvar, hexonly))
|
||||
if(!argEval.DoEvaluate(result, silent, baseonly, value_size, isvar, hexonly))
|
||||
return false;
|
||||
arg = { ValueTypeNumber, result };
|
||||
argValue = { ValueTypeNumber, result };
|
||||
}
|
||||
|
||||
if(arg.type != argType && argType != ValueTypeAny)
|
||||
if(argValue.type != argType && argType != ValueTypeAny)
|
||||
{
|
||||
if(!silent)
|
||||
{
|
||||
auto typeName = [](ValueType t) -> String
|
||||
{
|
||||
switch(t)
|
||||
{
|
||||
case ValueTypeNumber:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "number"));
|
||||
case ValueTypeString:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "string"));
|
||||
}
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "invalid"));
|
||||
};
|
||||
String argValueStr;
|
||||
if(arg.type == ValueTypeNumber)
|
||||
if(argValue.type == ValueTypeNumber)
|
||||
{
|
||||
argValueStr = StringUtils::sprintf("0x%p", arg.number);
|
||||
argValueStr = StringUtils::sprintf("0x%p", argValue.number);
|
||||
}
|
||||
else if(arg.type == ValueTypeString)
|
||||
else if(argValue.type == ValueTypeString)
|
||||
{
|
||||
argValueStr = "\"" + StringUtils::Escape(arg.string.ptr) + "\"";
|
||||
argValueStr = "\"" + StringUtils::Escape(argValue.string.ptr) + "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
argValueStr = "???";
|
||||
}
|
||||
String signature = name;
|
||||
signature += "(";
|
||||
for(size_t j = 0; j < argTypes.size(); j++)
|
||||
{
|
||||
if(j > 0)
|
||||
{
|
||||
signature += ", ";
|
||||
}
|
||||
signature += typeName(argTypes[j]);
|
||||
}
|
||||
signature += ")";
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Expression function %s argument %d/%d (%s) type mismatch (expected %s, got %s)!\n"),
|
||||
signature.c_str(),
|
||||
argTypes.size() - i,
|
||||
makeSignature().c_str(),
|
||||
i + 1,
|
||||
argTypes.size(),
|
||||
argValueStr.c_str(),
|
||||
typeName(argType).c_str(),
|
||||
typeName(arg.type).c_str()
|
||||
typeName(argValue.type).c_str()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
argv[argTypes.size() - i - 1] = arg;
|
||||
argv[i] = argValue;
|
||||
}
|
||||
|
||||
ExpressionValue result = { ValueTypeNumber, 0 };
|
||||
if(!ExpressionFunctions::Call(name, result, argv))
|
||||
{
|
||||
if(!silent)
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Expression function %s errored!\n"),
|
||||
makeSignature().c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(result.type == ValueTypeAny)
|
||||
// Check the return type
|
||||
switch(result.type)
|
||||
{
|
||||
case ValueTypeNumber:
|
||||
case ValueTypeString:
|
||||
break;
|
||||
default:
|
||||
if(!silent)
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Expression function %s returned an invalid value!\n"),
|
||||
makeSignature().c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pop the arguments off the stack
|
||||
// NOTE: Do not move, the string pointers are needed during the call
|
||||
for(size_t i = 0; i < argv.size(); i++)
|
||||
{
|
||||
stack.pop_back();
|
||||
}
|
||||
|
||||
// Push the result on the stack
|
||||
if(result.type == ValueTypeString)
|
||||
{
|
||||
stack.push_back(EvalValue(result.string.ptr, true));
|
||||
stack.emplace_back(result.string.ptr, true);
|
||||
|
||||
// We can free the string since it was copied into the EvalValue
|
||||
if(result.string.isOwner)
|
||||
BridgeFree((void*)result.string.ptr);
|
||||
}
|
||||
else
|
||||
stack.push_back(EvalValue(result.number));
|
||||
stack.emplace_back(result.number);
|
||||
}
|
||||
else
|
||||
stack.push_back(EvalValue(token.data(), token.type() == Token::Type::QuotedData));
|
||||
|
|
|
@ -17,7 +17,7 @@ public:
|
|||
explicit EvalValue(duint value)
|
||||
: evaluated(true), value(value) {}
|
||||
|
||||
explicit EvalValue(const String & data, bool isString)
|
||||
EvalValue(const String & data, bool isString)
|
||||
: evaluated(false), data(data), isString(isString) {}
|
||||
|
||||
bool DoEvaluate(duint & result, bool silent = true, bool baseonly = false, int* value_size = nullptr, bool* isvar = nullptr, bool* hexonly = nullptr) const
|
||||
|
@ -61,8 +61,8 @@ public:
|
|||
QuotedData,
|
||||
Function,
|
||||
Comma,
|
||||
OpenBracket,
|
||||
CloseBracket,
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
|
||||
OperatorUnarySub,
|
||||
OperatorUnaryAdd,
|
||||
|
@ -133,6 +133,16 @@ public:
|
|||
return mType;
|
||||
}
|
||||
|
||||
duint info() const
|
||||
{
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
void setInfo(duint info)
|
||||
{
|
||||
mInfo = info;
|
||||
}
|
||||
|
||||
Associativity associativity() const;
|
||||
int precedence() const;
|
||||
bool isOperator() const;
|
||||
|
@ -140,6 +150,7 @@ public:
|
|||
private:
|
||||
String mData;
|
||||
Type mType;
|
||||
duint mInfo = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "value.h"
|
||||
#include "TraceRecord.h"
|
||||
#include "exhandlerinfo.h"
|
||||
#include "exception.h"
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
@ -189,9 +190,11 @@ namespace Exprfunc
|
|||
return result;
|
||||
}
|
||||
|
||||
duint ternary(duint condition, duint value1, duint value2)
|
||||
bool ternary(ExpressionValue* result, int argc, const ExpressionValue* argv, void* userdata)
|
||||
{
|
||||
return condition ? value1 : value2;
|
||||
*result = argv[0].number ? argv[1] : argv[2];
|
||||
result->string.isOwner = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
duint memvalid(duint addr)
|
||||
|
@ -690,14 +693,24 @@ namespace Exprfunc
|
|||
template<bool Strict>
|
||||
bool ansi(ExpressionValue* result, int argc, const ExpressionValue* argv, void* userdata)
|
||||
{
|
||||
assert(argc == 1);
|
||||
assert(argc >= 1);
|
||||
assert(argv[0].type == ValueTypeNumber);
|
||||
|
||||
duint addr = argv[0].number;
|
||||
|
||||
std::vector<char> tempStr(MAX_STRING_SIZE + 1);
|
||||
duint NumberOfBytesRead = 0;
|
||||
if(!MemRead(addr, tempStr.data(), tempStr.size() - 1, &NumberOfBytesRead) && NumberOfBytesRead == 0 && Strict)
|
||||
std::vector<char> tempStr;
|
||||
if(argc > 1)
|
||||
{
|
||||
assert(argv[1].type == ValueTypeNumber);
|
||||
tempStr.resize(argv[1].number + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempStr.resize(MAX_STRING_SIZE + 1);
|
||||
}
|
||||
|
||||
duint NumberOfBytesRead = -1;
|
||||