通常我們要做==比較時, 會希望兩邊的型別是相同的, 如果是base type如int, double, char, 這些會自動轉型, 但是遇到class或是template時怎麼辦, 其實可以在一個class裡實作出一個template的operation overload就ok了..範例如下... 下面的例子可能比較複雜一點, 但其實也只是在一個template class裡再做另一個template member function而已... template<typename T> class MyType { public: MyType(T value) { m_value = value; }; T GetValue() { return m_value; }; template<typename T2> bool operator==(T2 AnotherType) { if(this->GetValue() == AnotherType.GetValue()) { return true; } return false; }; public: T m_value;}; int main() { MyType<int> myInt(5); MyType<double> myDouble(5.0); if(myInt == myDouble) { cout << "Yes!!" << endl; } return 0; } ----- 所謂的template型別(這裡指的是被指定後的型別), 應該這麼考慮的.. "MyType<int>"是一種型別.. "MyType<double>"亦是另一種型別 試想在所有的程式裡..宣告一個變數一定是 "型別名" "變數名" 所以上面兩種是不同型別, 另外在偏特化的例子中, SomeType<int, 6> SomeType<int, 5> 這兩個是不同型別, 寫個程式來表達一下好了.. -----美麗的分隔線------ template <typename T> inline T cost& max(T const& a, T const& b) { return a < b ? b : a; } int main() { std::string s = "apple"; ::max("123456", "567890"); // OK! ::max("12345", "123456"); // ERROR!! ::max("apple", s); // ERROR!! return 0; } 為什麼不行呢, 因為型別不同, 很多人可能會誤以為不都是字串嗎, 特別是第二個... 真是不好意思啊, 在compiler的眼裡, "12345"和"123456"的型別, 分別是char[5]及char[6] 所以理所當然, 第三個max也不行了..一個是char[5], 一個是string... 要小心啊..型別的問題....
2010年9月15日 星期三
[C++ template]有趣的template, 常被誤解的型別
2010年7月22日 星期四
.lnk virus
Some days ago, MS had done this 0 day vulnerability. Please update your windows.
2010年6月23日 星期三
Get Icon and save it into a png file
This is not a news to get a icon from a file. But how about the process? I can extrace the process image file name and use it to extract the icons that it contains. The output is a handle of HICON. I survey many articles to find out the solution. Finally, I found a solution to the saving issue. The following sample codes will show the GDI+ and save the HICON into a PNG file. Of course, you can save it into other format that GDI+ provided.
HICON GetFileIcon(const CString& strFileName, CString& strSaveFileName, bool bSmallIcon) {
SHFILEINFO sfi;
SHGetFileInfo((LPCTSTR)strFileName,
FILE_ATTRIBUTE_NORMAL,
&sfi,
sizeof(SHFILEINFO),
SHGFI_ICON | SHGFI_USEFILEATTRIBUTES | (bSmallIcon ? SHGFI_SMALLICON : SHGFI_LARGEICON));
if(sfi.hIcon) {
Bitmap* pBitmap = Bitmap::FromHICON(sfi.hIcon);
CLSID encoderClsid;
GetEncoderClsid(L"image/png", &encoderClsid);
CStringW strPngFileName;
#ifndef _UNICODE
strPngFileName.Format(L"%S", strFileName.GetString());
#else
strPngFileName = strFileName;
#endif
int nStart = strPngFileName.ReverseFind('\\') + 1;
int nEnd = strPngFileName.ReverseFind('.');
int nLength = nEnd - nStart;
strPngFileName = strPngFileName.Mid(nStart, nLength);
strPngFileName = CStringW(L"C:\\") + strPngFileName + (bSmallIcon ? CStringW("Small.png") : CStringW("Large.png"));
strSaveFileName = strPngFileName;
pBitmap->Save(strPngFileName.GetString(), &encoderClsid, NULL);
}
return sfi.hIcon;
}
You should include the header file <gdiplus.h> and use the name space "GdiPlus".
2010年6月7日 星期一
Memory Scan Tool
This will not too complex. It a easy way to let you scan your process memory like some game cheater. Of course, there is a free tool named "Game Cheater". That is open source one.
But I just want to write a "string" scan for myself. You can modify this by your self. Totally free.
See the definition.
#ifndef __MEMORY_SCAN_TOOL_H__
#define __MEMORY_SCAN_TOOL_H__
#pragma once
#include <vector>
typedef struct _MEMBLOCK {
DWORD pid; // process id
HANDLE hProc; // handle of the process
unsigned char * addr; // block address (start address)
SIZE_T size; // memory block size
unsigned char * buffer; // a copy buffer of this memory block
unsigned char * searchmask; // used for searching
SIZE_T matches; // used for searching
SIZE_T data_size; // data size when search
struct _MEMBLOCK * next; // linked list
} MEMBLOCK, *PMEMBLOCK;
typedef struct _FOUNDADDR {
unsigned char * addr; // base address
unsigned long offset; // off set
} FOUNDADDR;
//#define IS_IN_SEARCH(mb, offset) (mb->searchmask[(offset)/8] & (1 << ((offset)%8)))
//#define REMOVE_FROM_SEARCH(mb, offset) mb->searchmask[(offset)/8] &= ~(1 << ((offset)%8));
///
// This class is used for memory scan
// 1. Get memory block for scaning
// 2. Get the scan result
///
class CMemoryScanTool
{
public:
CMemoryScanTool();
~CMemoryScanTool();
static CMemoryScanTool* GetMemoryScanToolInstance();
static void ReleaseMemoryScanToolInstance();
// create the memory list by using CreateMemoryBlock
PMEMBLOCK CreateMemoryList(unsigned int pid, int data_size);
// create the memory block
PMEMBLOCK CreateMemoryBlock(HANDLE hProc, MEMORY_BASIC_INFORMATION* meminfo, int data_size);
// free the memory block (one block with buffer)
void FreeMemoryBlock(PMEMBLOCK mb);
// free the memory block list (by using FreeMemoryBlock)
void FreeMemoryBlockList(PMEMBLOCK mb_list);
// update the memory block data by using ReadProcessMemory
void UpdateMemoryBlock(PMEMBLOCK mb);
// Dump the information into a file
void DumpScanInformation(FILE* f, PMEMBLOCK mb_list);
void DumpScanInformation(char* szFileName, PMEMBLOCK mb_list);
// scan the input string in the memory block list
// Return Value: true indicate that the input string is in the memory block, the nAddr indicate the offset of this block
bool ScanString(const wchar_t* szInput, PMEMBLOCK mb, unsigned long& nAddr);
bool ScanString(const char* szInput, PMEMBLOCK mb, unsigned long& nAddr);
// scan all memory block by using ScanString
bool ScanAllBlock(const wchar_t* szInput, PMEMBLOCK mb_list, std::vector<FOUNDADDR>& vecOut);
bool ScanAllBlock(const char* szInput, PMEMBLOCK mb_list, std::vector<FOUNDADDR>& vecOut);
};
#endif __MEMORY_SCAN_TOOL_H__
See the implementation.
#include "StdAfx.h"
#include "MemoryScanTool.h"
using namespace std;
namespace {
static CMemoryScanTool* g_pMemoryScanTool = NULL;
typedef LONG NTSTATUS;
typedef NTSTATUS (WINAPI *pFnRtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
pFnRtlAdjustPrivilege RtlAdjustPrivilege;
}
CMemoryScanTool::CMemoryScanTool()
{
RtlAdjustPrivilege = (pFnRtlAdjustPrivilege)GetProcAddress(LoadLibrary(_T("Ntdll.dll")), "RtlAdjustPrivilege");
BOOLEAN bEnabled = FALSE;
// get debug privilege
RtlAdjustPrivilege(20, 1, 0, &bEnabled);;
}
CMemoryScanTool::~CMemoryScanTool()
{
}
CMemoryScanTool* CMemoryScanTool::GetMemoryScanToolInstance() {
if(g_pMemoryScanTool == NULL)
g_pMemoryScanTool = new CMemoryScanTool();
return g_pMemoryScanTool;
}
void CMemoryScanTool::ReleaseMemoryScanToolInstance() {
if(g_pMemoryScanTool)
delete g_pMemoryScanTool;
g_pMemoryScanTool = NULL;
}
PMEMBLOCK CMemoryScanTool::CreateMemoryList(unsigned int pid, int data_size) {
MEMBLOCK* mb_list = NULL;
MEMORY_BASIC_INFORMATION meminfo;
unsigned char* addr = 0;
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(hProc) {
while(true) {
if(VirtualQueryEx(hProc, addr, &meminfo, sizeof(meminfo)) == 0)
break;
#define WRITABLE (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)
if((meminfo.State & MEM_COMMIT) && (meminfo.Protect & WRITABLE)) {
MEMBLOCK* mb = CreateMemoryBlock(hProc, &meminfo, data_size);
if(mb) {
UpdateMemoryBlock(mb);
mb->pid = pid;
mb->next = mb_list;
mb_list = mb;
}
}
addr = (unsigned char*)meminfo.BaseAddress + meminfo.RegionSize;
}
}
return mb_list;
}
PMEMBLOCK CMemoryScanTool::CreateMemoryBlock(HANDLE hProc, MEMORY_BASIC_INFORMATION* meminfo, int data_size) {
PMEMBLOCK mb = (PMEMBLOCK)malloc(sizeof(MEMBLOCK));
if(mb) {
mb->hProc = hProc;
mb->addr = (unsigned char*)meminfo->BaseAddress;
mb->size = meminfo->RegionSize;
mb->buffer = (unsigned char*)malloc(meminfo->RegionSize);
mb->searchmask = (unsigned char*)malloc(meminfo->RegionSize/8);
memset(mb->searchmask, 0xff, meminfo->RegionSize/8); // mark the byte is in searching.
mb->matches = meminfo->RegionSize;
mb->data_size = data_size;
mb->next = NULL;
}
return mb;
}
void CMemoryScanTool::FreeMemoryBlock(PMEMBLOCK mb) {
if(mb) {
if(mb->buffer)
free(mb->buffer);
if(mb->searchmask)
free(mb->searchmask);
free(mb);
}
}
void CMemoryScanTool::FreeMemoryBlockList(PMEMBLOCK mb_list) {
CloseHandle(mb_list->hProc);
while(mb_list) {
PMEMBLOCK mb = mb_list;
mb_list = mb_list->next;
FreeMemoryBlock(mb);
}
}
// Read the memory data from a process id in MEMBLOCK
void CMemoryScanTool::UpdateMemoryBlock(PMEMBLOCK mb)
{
static unsigned char tmpbuf[128*1024];
SIZE_T bytes_left = 0;
SIZE_T total_read = 0;
SIZE_T bytes_to_read = 0;
SIZE_T bytes_read = 0;
if(mb->matches > 0) {
bytes_left = mb->size;
mb->matches = 0;
while(bytes_left) {
bytes_to_read = (bytes_left > sizeof(tmpbuf)) ? sizeof(tmpbuf) : bytes_left;
ReadProcessMemory(mb->hProc, mb->addr + total_read, tmpbuf, bytes_to_read, (SIZE_T *)&bytes_read);
if(bytes_read != bytes_to_read) break;
memcpy(mb->buffer + total_read, tmpbuf, bytes_read);
bytes_left -= bytes_read;
total_read += bytes_read;
}
mb->size = total_read;
}
}
void CMemoryScanTool::DumpScanInformation(FILE* f, PMEMBLOCK mb_list) {
MEMBLOCK* mb = mb_list;
while(mb) {
fprintf(f, "Process ID: %ld\r\n", GetProcessId(mb->hProc));
fprintf(f, "0x%08x %d\r\n", mb->addr, mb->size);
for(unsigned int i = 0; i < mb->size; ++i) {
fprintf(f, "%c", mb->buffer[i]);
//if(i % 8 == 0)
// fprintf(f, " ");
//if(i % 16 == 0)
// fprintf(f, "\r\n");
}
fprintf(f, "\r\n");
mb = mb->next;
}
}
void CMemoryScanTool::DumpScanInformation(char* szFileName, PMEMBLOCK mb_list) {
FILE* f = fopen(szFileName, "w");
if(f) DumpScanInformation(f, mb_list);
}
bool CMemoryScanTool::ScanString(const wchar_t* szInput, PMEMBLOCK mb, unsigned long& nAddr) {
if(szInput == NULL || mb == NULL)
return false;
if(mb->size == 0)
return false;
SIZE_T nStringSize = wcslen(szInput);
// shift one byte per loop (may shift 2 byte?)
for(SIZE_T i = 0; i < mb->size - nStringSize; ++i) {
if(memcmp(szInput, mb->buffer + i, nStringSize*2) == 0) {
nAddr = i;
return true;
}
}
return false;
}
bool CMemoryScanTool::ScanString(const char* szInput, PMEMBLOCK mb, unsigned long& nAddr) {
if(szInput == NULL || mb == NULL)
return false;
if(mb->size == 0)
return false;
SIZE_T nStringSize = strlen(szInput);
// shift one byte per loop
for(SIZE_T i = 0; i < mb->size - nStringSize*2; ++i) {
if(memcmp(szInput, (mb->buffer + i), nStringSize*2) == 0) {
nAddr = (unsigned long)(mb->addr) + i;
return true;
}
}
return false;
}
bool CMemoryScanTool::ScanAllBlock(const wchar_t* szInput, PMEMBLOCK mb_list, vector<FOUNDADDR>& vecOut) {
MEMBLOCK* mb = mb_list;
unsigned long nAddr;
while(mb) {
if(ScanString(szInput, mb, nAddr)) {
FOUNDADDR fa;
fa.addr = mb->addr;
fa.offset = nAddr;
vecOut.push_back(fa);
}
mb = mb->next;
}
return (vecOut.size() != 0);
}
bool CMemoryScanTool::ScanAllBlock(const char* szInput, PMEMBLOCK mb_list, vector<FOUNDADDR>& vecOut) {
MEMBLOCK* mb = mb_list;
unsigned long nAddr;
while(mb) {
if(ScanString(szInput, mb, nAddr)) {
FOUNDADDR fa;
fa.addr = mb->addr;
fa.offset = nAddr;
vecOut.push_back(fa);
}
mb = mb->next;
}
return (vecOut.size() != 0);
}
Really easy to understand. This program use only 3 windows API.
- VirtualQueryEx
- ReadProcessMemory
- OpenProcess
Memory Scan Tool
This will not too complex. It a easy way to let you scan your process memory like some game cheater. Of course, there is a free tool named "Game Cheater". That is open source one.
But I just want to write a "string" scan for myself. You can modify this by your self. Totally free.
See the definition.
#ifndef __MEMORY_SCAN_TOOL_H__
#define __MEMORY_SCAN_TOOL_H__
#pragma once
#include <vector>
typedef struct _MEMBLOCK {
DWORD pid; // process id
HANDLE hProc; // handle of the process
unsigned char * addr; // block address (start address)
SIZE_T size; // memory block size
unsigned char * buffer; // a copy buffer of this memory block
unsigned char * searchmask; // used for searching
SIZE_T matches; // used for searching
SIZE_T data_size; // data size when search
struct _MEMBLOCK * next; // linked list
} MEMBLOCK, *PMEMBLOCK;
typedef struct _FOUNDADDR {
unsigned char * addr; // base address
unsigned long offset; // off set
} FOUNDADDR;
//#define IS_IN_SEARCH(mb, offset) (mb->searchmask[(offset)/8] & (1 << ((offset)%8)))
//#define REMOVE_FROM_SEARCH(mb, offset) mb->searchmask[(offset)/8] &= ~(1 << ((offset)%8));
///
// This class is used for memory scan
// 1. Get memory block for scaning
// 2. Get the scan result
///
class CMemoryScanTool
{
public:
CMemoryScanTool();
~CMemoryScanTool();
static CMemoryScanTool* GetMemoryScanToolInstance();
static void ReleaseMemoryScanToolInstance();
// create the memory list by using CreateMemoryBlock
PMEMBLOCK CreateMemoryList(unsigned int pid, int data_size);
// create the memory block
PMEMBLOCK CreateMemoryBlock(HANDLE hProc, MEMORY_BASIC_INFORMATION* meminfo, int data_size);
// free the memory block (one block with buffer)
void FreeMemoryBlock(PMEMBLOCK mb);
// free the memory block list (by using FreeMemoryBlock)
void FreeMemoryBlockList(PMEMBLOCK mb_list);
// update the memory block data by using ReadProcessMemory
void UpdateMemoryBlock(PMEMBLOCK mb);
// Dump the information into a file
void DumpScanInformation(FILE* f, PMEMBLOCK mb_list);
void DumpScanInformation(char* szFileName, PMEMBLOCK mb_list);
// scan the input string in the memory block list
// Return Value: true indicate that the input string is in the memory block, the nAddr indicate the offset of this block
bool ScanString(const wchar_t* szInput, PMEMBLOCK mb, unsigned long& nAddr);
bool ScanString(const char* szInput, PMEMBLOCK mb, unsigned long& nAddr);
// scan all memory block by using ScanString
bool ScanAllBlock(const wchar_t* szInput, PMEMBLOCK mb_list, std::vector<FOUNDADDR>& vecOut);
bool ScanAllBlock(const char* szInput, PMEMBLOCK mb_list, std::vector<FOUNDADDR>& vecOut);
};
#endif __MEMORY_SCAN_TOOL_H__
See the implementation.
#include "StdAfx.h"
#include "MemoryScanTool.h"
using namespace std;
namespace {
static CMemoryScanTool* g_pMemoryScanTool = NULL;
typedef LONG NTSTATUS;
typedef NTSTATUS (WINAPI *pFnRtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
pFnRtlAdjustPrivilege RtlAdjustPrivilege;
}
CMemoryScanTool::CMemoryScanTool()
{
RtlAdjustPrivilege = (pFnRtlAdjustPrivilege)GetProcAddress(LoadLibrary(_T("Ntdll.dll")), "RtlAdjustPrivilege");
BOOLEAN bEnabled = FALSE;
// get debug privilege
RtlAdjustPrivilege(20, 1, 0, &bEnabled);;
}
CMemoryScanTool::~CMemoryScanTool()
{
}
CMemoryScanTool* CMemoryScanTool::GetMemoryScanToolInstance() {
if(g_pMemoryScanTool == NULL)
g_pMemoryScanTool = new CMemoryScanTool();
return g_pMemoryScanTool;
}
void CMemoryScanTool::ReleaseMemoryScanToolInstance() {
if(g_pMemoryScanTool)
delete g_pMemoryScanTool;
g_pMemoryScanTool = NULL;
}
PMEMBLOCK CMemoryScanTool::CreateMemoryList(unsigned int pid, int data_size) {
MEMBLOCK* mb_list = NULL;
MEMORY_BASIC_INFORMATION meminfo;
unsigned char* addr = 0;
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(hProc) {
while(true) {
if(VirtualQueryEx(hProc, addr, &meminfo, sizeof(meminfo)) == 0)
break;
#define WRITABLE (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)
if((meminfo.State & MEM_COMMIT) && (meminfo.Protect & WRITABLE)) {
MEMBLOCK* mb = CreateMemoryBlock(hProc, &meminfo, data_size);
if(mb) {
UpdateMemoryBlock(mb);
mb->pid = pid;
mb->next = mb_list;
mb_list = mb;
}
}
addr = (unsigned char*)meminfo.BaseAddress + meminfo.RegionSize;
}
}
return mb_list;
}
PMEMBLOCK CMemoryScanTool::CreateMemoryBlock(HANDLE hProc, MEMORY_BASIC_INFORMATION* meminfo, int data_size) {
PMEMBLOCK mb = (PMEMBLOCK)malloc(sizeof(MEMBLOCK));
if(mb) {
mb->hProc = hProc;
mb->addr = (unsigned char*)meminfo->BaseAddress;
mb->size = meminfo->RegionSize;
mb->buffer = (unsigned char*)malloc(meminfo->RegionSize);
mb->searchmask = (unsigned char*)malloc(meminfo->RegionSize/8);
memset(mb->searchmask, 0xff, meminfo->RegionSize/8); // mark the byte is in searching.
mb->matches = meminfo->RegionSize;
mb->data_size = data_size;
mb->next = NULL;
}
return mb;
}
void CMemoryScanTool::FreeMemoryBlock(PMEMBLOCK mb) {
if(mb) {
if(mb->buffer)
free(mb->buffer);
if(mb->searchmask)
free(mb->searchmask);
free(mb);
}
}
void CMemoryScanTool::FreeMemoryBlockList(PMEMBLOCK mb_list) {
CloseHandle(mb_list->hProc);
while(mb_list) {
PMEMBLOCK mb = mb_list;
mb_list = mb_list->next;
FreeMemoryBlock(mb);
}
}
// Read the memory data from a process id in MEMBLOCK
void CMemoryScanTool::UpdateMemoryBlock(PMEMBLOCK mb)
{
static unsigned char tmpbuf[128*1024];
SIZE_T bytes_left = 0;
SIZE_T total_read = 0;
SIZE_T bytes_to_read = 0;
SIZE_T bytes_read = 0;
if(mb->matches > 0) {
bytes_left = mb->size;
mb->matches = 0;
while(bytes_left) {
bytes_to_read = (bytes_left > sizeof(tmpbuf)) ? sizeof(tmpbuf) : bytes_left;
ReadProcessMemory(mb->hProc, mb->addr + total_read, tmpbuf, bytes_to_read, (SIZE_T *)&bytes_read);
if(bytes_read != bytes_to_read) break;
memcpy(mb->buffer + total_read, tmpbuf, bytes_read);
bytes_left -= bytes_read;
total_read += bytes_read;
}
mb->size = total_read;
}
}
void CMemoryScanTool::DumpScanInformation(FILE* f, PMEMBLOCK mb_list) {
MEMBLOCK* mb = mb_list;
while(mb) {
fprintf(f, "Process ID: %ldrn", GetProcessId(mb->hProc));
fprintf(f, "0x%08x %drn", mb->addr, mb->size);
for(unsigned int i = 0; i < mb->size; ++i) {
fprintf(f, "%c", mb->buffer[i]);
//if(i % 8 == 0)
// fprintf(f, " ");
//if(i % 16 == 0)
// fprintf(f, "rn");
}
fprintf(f, "rn");
mb = mb->next;
}
}
void CMemoryScanTool::DumpScanInformation(char* szFileName, PMEMBLOCK mb_list) {
FILE* f = fopen(szFileName, "w");
if(f) DumpScanInformation(f, mb_list);
}
bool CMemoryScanTool::ScanString(const wchar_t* szInput, PMEMBLOCK mb, unsigned long& nAddr) {
if(szInput == NULL || mb == NULL)
return false;
if(mb->size == 0)
return false;
SIZE_T nStringSize = wcslen(szInput);
// shift one byte per loop (may shift 2 byte?)
for(SIZE_T i = 0; i < mb->size - nStringSize; ++i) {
if(memcmp(szInput, mb->buffer + i, nStringSize*2) == 0) {
nAddr = i;
return true;
}
}
return false;
}
bool CMemoryScanTool::ScanString(const char* szInput, PMEMBLOCK mb, unsigned long& nAddr) {
if(szInput == NULL || mb == NULL)
return false;
if(mb->size == 0)
return false;
SIZE_T nStringSize = strlen(szInput);
// shift one byte per loop
for(SIZE_T i = 0; i < mb->size - nStringSize*2; ++i) {
if(memcmp(szInput, (mb->buffer + i), nStringSize*2) == 0) {
nAddr = (unsigned long)(mb->addr) + i;
return true;
}
}
return false;
}
bool CMemoryScanTool::ScanAllBlock(const wchar_t* szInput, PMEMBLOCK mb_list, vector<FOUNDADDR>& vecOut) {
MEMBLOCK* mb = mb_list;
unsigned long nAddr;
while(mb) {
if(ScanString(szInput, mb, nAddr)) {
FOUNDADDR fa;
fa.addr = mb->addr;
fa.offset = nAddr;
vecOut.push_back(fa);
}
mb = mb->next;
}
return (vecOut.size() != 0);
}
bool CMemoryScanTool::ScanAllBlock(const char* szInput, PMEMBLOCK mb_list, vector<FOUNDADDR>& vecOut) {
MEMBLOCK* mb = mb_list;
unsigned long nAddr;
while(mb) {
if(ScanString(szInput, mb, nAddr)) {
FOUNDADDR fa;
fa.addr = mb->addr;
fa.offset = nAddr;
vecOut.push_back(fa);
}
mb = mb->next;
}
return (vecOut.size() != 0);
}
Really easy to unders