The duplication of items has been a huge issue in a variety of games, ruining the economy in both official and in particular, private servers. A select few have figured out solutions to this, however the majority of which are offline solutions usually within stored procedures which hasn’t proven too reliable since the database is only amended every 5 minutes by default in Knight Online. As such, we needed to figure out a live way to do it.

Fortunately, to communicate between the various server files a block of shared memory (or Memory Mapped File) is used. This means tapping into the data isn’t all that difficult. Reversing the structure is much trickier. As such, I’ll provide a near-complete structure below which will serve as a good base for this article.

#define MAX_USER_ID_SIZE        20
#define MAX_ACC_ID_SIZE         38
#define MAX_USER                1500

#define SLOT_MAX                14
#define HAVE_MAX                28      
#define ITEMCOUNT_MAX           9999
#define WAREHOUSE_MAX           196     

struct USER_ITEM_DATA
{
		int     nNum;   
		short   sDuration;      
		short   sCount;         
		__int64 nSerialNum;     
		char cPadding[8];
};

struct INN_ITEM_DATA
{
		int     nNum;
		short   sDuration;
		short   sCount;    
		__int64 nSerialNum;     
};

struct _USER_DATA 
{
		char    m_id[MAX_USER_ID_SIZE+1];                       
		char    m_Accountid[MAX_ACC_ID_SIZE+1];         
		DWORD   m_bZone;        

		float   m_curx; 
		float   m_cury;         
		float   m_curz;                                                 

		BYTE    m_bNation;
		BYTE    m_bRace;
		short   m_sClass;
		BYTE    m_bHairColor;
		BYTE    m_bRank;
		BYTE    m_bTitle;
		BYTE    m_bLevel;
		int     m_iExp;
		int     m_iLoyalty;
		BYTE    m_bFace;
		BYTE    m_bCity;
		short   m_bKnights;
		short   m_sClan;
		short   m_sUnknown;
		BYTE    m_bFame;

		byte m_unknown2;
		byte m_Hits;
		byte m_Mana;
		byte m_SP;
		byte m_STR;
		byte m_HP;
		byte m_DEX;
		byte m_INT;
		byte m_MP;
		byte m_Authority;
		byte m_Points;
		byte m_unknown3;

		DWORD m_Gold;
		signed short m_Bind;
		int m_Bank;

		char Garbage[39]; // skill/stat stuff etc
		USER_ITEM_DATA m_sItemArray[HAVE_MAX+SLOT_MAX]; 
		INN_ITEM_DATA m_sWarehouseArray[WAREHOUSE_MAX]; //196*8 bytes

		BYTE    m_bLogout;
		BYTE    m_bWarehouse;
		DWORD   m_dwTime;       
};

We will cast the block of memory to that structure so we don’t have to loop, and apply lots of mathematical offsets, it generally makes the code cleaner to work with, and easier to maintain.

Some internal declarations may look like this:

#define MMF_TARGET "KNIGHT_DB"

typedef std::vector<_USER_DATA*> UserDataArray;
extern UserDataArray currentUsers;
HANDLE m_hUsersMutex;
HANDLE m_hMMFile;
char* m_lpMMFile;

void myPopulateFunction()

So first of all, let’s connect to the file and build up our local array. (You’ll probably want to place this in your main, or equivalent.)
Note: I’m not going to incorporate lots of error handling etc in this guide, you will need to do that yourself.

m_hUsersMutex = CreateMutex( NULL, FALSE, NULL ); 
m_hMMFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, TRUE, MMF_TARGET);

if(m_hMMFile == NULL)
	return;

m_lpMMFile = (char *)MapViewOfFile (m_hMMFile, FILE_MAP_WRITE, 0, 0, 0);

if (!m_lpMMFile) 
	return;

myPopulateFunction();

Okay, so this will open up the shared memory files that Knight Online uses to access the user data from all the various applications (aujard and ebenezer for example) and map it so we can access its data from our application. Next up, loading all of the users from the memory block and casting it to a nice struct which was defined above!

void myPopulateFunction() {
	WaitForSingleObject( m_hUsersMutex, INFINITE );

	// This is where we'll do our stuff.

	ReleaseMutex(m_hUsersMutex);
}

That’s a rough outline for our function, we use a mutex to prevent any of our other functions accessing the array while it’s being written to which could cause memory exceptions and such. So let’s load our users now.

void myPopulateFunction() {
	WaitForSingleObject( m_hUsersMutex, INFINITE );

	_USER_DATA* pUser = NULL;
	currentUsers.clear(); 

	for (int i=0; i < MAX_USER; i++) {
		pUser = (_USER_DATA*)(m_lpMMFile+(i*8000));
		currentUsers.push_back(pUser);
	}

	ReleaseMutex(m_hUsersMutex);
}

Because the struct above isn’t entirely complete, when looping each user we can’t use sizeof(_USER_DATA) as we ideally should - as such it’s hard coded as 8000 bytes, this may need maintaining in future versions. This will build an array of pointers to each user - so all data is always accurate without you needing to re-populate. This has its merits and cons, so in some situations we need to create a static array too which we’ll do next in the dupescanner example. So next, let’s just take a look at iterating through the array so we can actually use it!

std::vector<_USER_DATA*>::iterator iter;
for (iter = currentUsers.begin(); iter != currentUsers.end(); iter++) {
	// our magic
}

Really simple stuff there, you can then access the properties of each user directly. Beware of the null structs (as again, these are simply pointers to a block of memory) so do a check for something like (*iter)->m_id having a positive length.

An example of applying the above code could be to find a pointer to a struct from a given username, how about this:

_USER_DATA* getStructByUser(const char *user) {
	WaitForSingleObject( m_hUsersMutex, INFINITE );

	std::vector<_USER_DATA*>::iterator iter;
	for (iter = currentUsers.begin(); iter != currentUsers.end(); iter++) {
		if (!_strnicmp(user, (*iter)->m_id, MAX_USER_ID_SIZE)
			return *iter
	}

	ReleaseMutex(m_hUsersMutex);
	return FALSE;
}

There are so many more potential applications to this code - for example a decent speed hacking detector, zone scanning for if users are stuck in the war zone, or bugs with invading and whatever, and of course the biggie, dupe scanning. Let’s create a quick draft on how you may achieve something like this! You’ll probably want to create it in a separate thread, so add something like this to our definitions

struct threadStruct
{
    HANDLE* hMutex;
    UserDataArray* curUsers;
};

And finally, the code!

unsigned int __stdcall DupeScanThread(LPVOID lp) { 
	threadStruct* pStruct = (threadStruct*)lp;

	UserDataArray* pUsers = (UserDataArray*)pStruct->curUsers;
	UserDataArray pTemp;

	map<__int64, _USER_DATA*> userMap;

	WaitForSingleObject(pStruct->hMutex, INFINITE );
	pTemp.assign(pUsers->begin(), pUsers->end());
	ReleaseMutex(pStruct->hMutex);


	for (iter = pTemp.begin(); iter != pTemp.end(); iter++) {
		_USER_DATA* pUser = *iter;
		if (!strlen(pUser->m_id)) continue;

		for (int i=0; i<HAVE_MAX+SLOT_MAX;i++) {
			if (pUser->m_sItemArray[i].nSerialNum > 0 && pUser->m_sItemArray[i].nNum > 0) { 
				if (!userMap.insert(make_pair(pUser->m_sItemArray[i].nSerialNum, *pUser)).second) {
					_USER_DATA* otherDuper = userMap.find(pUser->m_sItemArray[i].nSerialNum)->second;
					// Do some magic
				}
			}
		}
	}
}

You may notice that I only ran it once in the above example, you may want to add an extra parameter for bEnabled, then add a while (bEnabled) { sleep(sensible integer); pTemp.clear(), re fill, the interation, etc } to run it constantly! I haven’t gone into the full depths of utilising the mapped file to send packets (for disconnecting and such) in this guide it’s focussed KNIGHT_DB only, but I may cover that at some point in the future.

To initialise the thread, it’ll be something like this:

threadStruct params;
params.hMutex = &m_hUsersMutex;
params.curUsers = &currentUsers;
        
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, DupeScanThread, &params, 0, NULL);

Hope you have as much fun with this as I did!