Last active
April 2, 2019 14:11
-
-
Save awesie/7973abc4ce714c350d5ca960537ad519 to your computer and use it in GitHub Desktop.
Port Nox patches to Unimod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 9430904a47e10d603e4f0cca1a2900695bb2c25a Mon Sep 17 00:00:00 2001 | |
From: Andrew Wesie <awesie@gmail.com> | |
Date: Mon, 1 Apr 2019 16:54:29 -0500 | |
Subject: [PATCH] Port patches to UniMod. | |
--- | |
util.cpp | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
1 file changed, 384 insertions(+) | |
diff --git a/util.cpp b/util.cpp | |
index 3b3f3d1..40d6a37 100644 | |
--- a/util.cpp | |
+++ b/util.cpp | |
@@ -376,6 +376,18 @@ int printL(lua_State *L) | |
} | |
return 0; | |
} | |
+void InjectCallTo(DWORD Addr,void *Fn) | |
+{ | |
+ BYTE *To=(BYTE *)Addr; | |
+ DWORD *Dw=(DWORD*)(To+1); | |
+ DWORD Delta=(DWORD)Fn; | |
+ Delta-=4+(DWORD)Dw; | |
+ DWORD OldProtect; | |
+ VirtualProtect(To,5,PAGE_EXECUTE_READWRITE,&OldProtect); | |
+ *Dw=Delta; | |
+ *(To++)=0xE8; | |
+ VirtualProtect(To,5,OldProtect,&OldProtect); | |
+}; | |
void InjectJumpTo(DWORD Addr,void *Fn)// Пишем по данному адресу переход на нашу функцию | |
{ | |
BYTE *To=(BYTE *)Addr; | |
@@ -1129,6 +1141,365 @@ void lua_error_(lua_State*L) | |
lua_error(L); | |
} | |
+// awesie: ported patches to Unimod. | |
+namespace | |
+{ | |
+ int (__cdecl *noxCheckGameFlags) (int) = (int(__cdecl*)(int))0x0040A5C0; | |
+ INT_PTR(__cdecl *serverGetGameData)(int N) = (INT_PTR(__cdecl*)(int))0x00416590; | |
+ void (__cdecl *noxEnableTeams)() = (void(__cdecl*)())0x4185b0; | |
+ void (__cdecl *noxRandomizeTeams)(int) = (void(__cdecl*)(int))0x4181f0; | |
+ void (__cdecl *noxDisableTeams)(int) = (void(__cdecl*)(int))0x419030; | |
+ void (__cdecl *noxNpcInit)(unsigned char *, int) = (void(__cdecl*)(unsigned char*,int))0x49A380; | |
+ | |
+ unsigned char *myNpcArray = NULL; | |
+ unsigned int myNpcArrayLen = 1024; // Original limit is 64. | |
+ | |
+ // For your comprehension, I reimplemented the original functionality. | |
+ int __cdecl myCmdSetMode(int argp, char argc, const wchar_t *argv[]) | |
+ { | |
+ static const struct { | |
+ const wchar_t *name; | |
+ unsigned int flag; | |
+ } gameModes[] = { | |
+ { L"Arena", 0x100 }, | |
+ { L"Elimination", 0x400 }, | |
+ { L"CTF", 0x20 }, | |
+ { L"KOTR", 0x10 }, | |
+ { L"Flagball", 0x40 }, | |
+ { L"Individual", 0x4000 }, | |
+ { L"Clan", 0x8000 }, | |
+ { NULL, 0 } | |
+ }; | |
+ | |
+ // Verify that we are in a chat map. | |
+ if (!noxCheckGameFlags(0x80)) | |
+ return 0; | |
+ | |
+ // Verify that we have correct number of arguments. | |
+ if (argc != 3) | |
+ return 0; | |
+ | |
+ // Search for matching game mode. | |
+ for (unsigned int i = 0; gameModes[i].name; ++i) | |
+ { | |
+ if (!_wcsicmp(gameModes[i].name, argv[argp])) | |
+ { | |
+ // Set the new game mode. | |
+ unsigned short *p = (unsigned short *)(serverGetGameData(1) + 52); | |
+ *p = (*p & 0xE80F) | gameModes[i].flag; | |
+ break; | |
+ } | |
+ } | |
+ return 1; | |
+ } | |
+ | |
+ // Helper to return current game mode | CHAT. We use this to always allow | |
+ // a chat map to be loaded. | |
+ unsigned int myCurrentGameMode() | |
+ { | |
+ DWORD *p = (DWORD *)0x5D53A4; | |
+ return *p | 0x80; | |
+ } | |
+ | |
+ void patchCmdSetMode() | |
+ { | |
+ InjectJumpTo(0x440CE0, myCmdSetMode); | |
+ // Hook load map command when it tries to get the current game mode. | |
+ // Instead, return current game mode + CHAT. | |
+ InjectOffs(0x4419D2 + 1, myCurrentGameMode); | |
+ } | |
+ | |
+ int __cdecl myCmdSetTeam(int argp, char argc, const wchar_t *argv[]) | |
+ { | |
+ DWORD *teamCount = (DWORD *)0x654d5c; | |
+ | |
+ // Verify that we are in a chat map. | |
+ if (!noxCheckGameFlags(0x80)) | |
+ return 0; | |
+ | |
+ // Verify that we have correct number of arguments. | |
+ if (argc != 3) | |
+ return 0; | |
+ | |
+ if (_wcsicmp(argv[argp], L"on") == 0) | |
+ { | |
+ if (*teamCount < 2) | |
+ { | |
+ noxEnableTeams(); | |
+ noxRandomizeTeams(1); // FIXME: I used 0 before. Does it matter? | |
+ } | |
+ return 1; | |
+ } | |
+ else if (_wcsicmp(argv[argp], L"off") == 0) | |
+ { | |
+ noxDisableTeams(1); | |
+ return 1; | |
+ } | |
+ | |
+ return 0; | |
+ } | |
+ | |
+ void patchCmdSetTeam() | |
+ { | |
+ InjectJumpTo(0x440CC0, myCmdSetTeam); | |
+ } | |
+ | |
+ void patchCmdOfficialOnly() | |
+ { | |
+ // Skip "official only" check when processing commands. | |
+ InjectJumpTo(0x443b48, (void *)0x443b5d); | |
+ } | |
+ | |
+ // Thankfully Nox already does smart things here. | |
+ void patchDrawableLimit() | |
+ { | |
+ DWORD newLimit = 8192; | |
+ DWORD newArraySize = newLimit * sizeof(void *); | |
+ // Patch size of drawable array. | |
+ InjectData(0x473A41 + 1, (byte *)&newArraySize, sizeof(DWORD)); | |
+ // Patch maximum drawable array index. | |
+ InjectData(0x4756B4 + 1, (byte *)&newLimit, sizeof(DWORD)); | |
+ } | |
+ | |
+ // TODO: replace static array with something more intelligent? | |
+ // maybe a tree for faster lookups? | |
+ void __cdecl myNpcArrayInit() | |
+ { | |
+ if (!myNpcArray) | |
+ myNpcArray = (unsigned char *)noxAlloc(1316 * myNpcArrayLen); | |
+ memset(myNpcArray, 0, 1316 * myNpcArrayLen); | |
+ } | |
+ | |
+ void *__cdecl myNpcArrayNew(DWORD id) | |
+ { | |
+ for (unsigned int i = 0; i < myNpcArrayLen; ++i) | |
+ { | |
+ unsigned char *npc = myNpcArray + 1316 * i; | |
+ if (!npc[0]) | |
+ { | |
+ noxNpcInit(npc, id); | |
+ return npc; | |
+ } | |
+ } | |
+ return NULL; | |
+ } | |
+ | |
+ void *__cdecl myNpcArrayFind(DWORD id) | |
+ { | |
+ for (unsigned int i = 0; i < myNpcArrayLen; ++i) | |
+ { | |
+ unsigned char *npc = myNpcArray + 1316 * i; | |
+ if (npc[0] && *(DWORD *)&npc[4] == id) | |
+ return npc; | |
+ } | |
+ return NULL; | |
+ } | |
+ | |
+ // Client-side patch. | |
+ void patchNpcLimit() | |
+ { | |
+ InjectJumpTo(0x49A2C0, myNpcArrayInit); | |
+ InjectJumpTo(0x49A300, myNpcArrayNew); | |
+ InjectJumpTo(0x49A340, myNpcArrayFind); | |
+ } | |
+ | |
+ // Nox has a linked list of constructed strings that will be freed later. | |
+ // This list may get long if the same string is printed many times. | |
+ // | |
+ // FIXME: we could search through the list to save some memory. | |
+ const wchar_t *__cdecl myMissingString(const char *str) | |
+ { | |
+ struct consString { | |
+ consString *next; | |
+ wchar_t data[0]; | |
+ }; | |
+ consString **head = (consString **)(0x611c14); | |
+ | |
+ DWORD len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0); | |
+ consString *cs = (consString *)noxAlloc(sizeof(*cs) + (len + 1) * sizeof(wchar_t)); | |
+ MultiByteToWideChar(CP_UTF8, 0, str, -1, cs->data, len); | |
+ | |
+ cs->next = *head; | |
+ *head = cs; | |
+ return cs->data; | |
+ } | |
+ | |
+ void patchMissingString() | |
+ { | |
+ // Replace original code with call to helper. | |
+ unsigned char tmp[0x35]; | |
+ memset(tmp, 0x90, sizeof(tmp)); | |
+ tmp[0x00] = 0x53; // push ebx | |
+ tmp[0x06] = 0x5B; // pop ebx | |
+ tmp[0x34] = 0x5F; // pop edi | |
+ InjectData(0x40F2C2, tmp, sizeof(tmp)); | |
+ InjectCallTo(0x40F2C3, myMissingString); | |
+ } | |
+ | |
+ void patchPlasmaDraw() | |
+ { | |
+ // Remove single player check. | |
+ unsigned char tmp[0x1e]; | |
+ memset(tmp, 0x90, sizeof(tmp)); | |
+ InjectData(0x4BA988, tmp, sizeof(tmp)); | |
+ } | |
+ | |
+ // FIXME | |
+ // I do not remember entirely how this works. The basic idea is that if | |
+ // the buffers are full, then flush. | |
+ int __cdecl myQueueUpdateData(int id, const void *inbuf, int inlen) | |
+ { | |
+ int(__cdecl *sub_40EEF0)(int,int) = (int(__cdecl*)(int,int))0x40EEF0; | |
+ void*(__cdecl *sub_40EFA0)(int,const void*,int) = (void*(__cdecl*)(int,const void*,int))0x40EFA0; | |
+ int(__cdecl *sub_420940)(void*,void*,int,int) = (int(__cdecl*)(void*,void*,int,int))0x420940; | |
+ void(__cdecl *sub_494E90)(int) = (void(__cdecl*)(int))0x494E90; | |
+ DWORD*(__cdecl *sub_417090)(int) = (DWORD*(__cdecl*)(int))0x417090; | |
+ void(__cdecl *sub_5528B0)(DWORD,int) = (void(__cdecl*)(DWORD,int))0x5528B0; | |
+ DWORD *client = ((DWORD **)0x607B08)[id]; | |
+ void *p; | |
+ | |
+ if (inlen <= 0) | |
+ return 1; | |
+ | |
+ if (!sub_40EEF0(id, inlen) || (p = sub_40EFA0(id, inbuf, inlen)) == NULL) | |
+ { | |
+ struct buffer { | |
+ unsigned char data[2048]; | |
+ unsigned int length; | |
+ } *buffers = (buffer *)0x5D7088; | |
+ struct datalist { | |
+ unsigned char *data; | |
+ unsigned int length; | |
+ struct datalist *prev; | |
+ struct datalist *next; | |
+ }; | |
+ // Buffer is full. Flush and try again. | |
+ | |
+ // The new update packet needs to have correct bytes at the | |
+ // beginning. Save the length of the first two queued datas so we | |
+ // can reply them. | |
+ datalist *dl = (datalist *)client[0]; | |
+ unsigned int len1 = dl->length; | |
+ unsigned int len2 = dl->next->length; | |
+ | |
+ //DebugBreak(); | |
+ | |
+ // Do the flush. | |
+ if (id == 31) | |
+ sub_494E90(id); // Host | |
+ else | |
+ sub_5528B0(sub_417090(id)[516] + 1, 0); | |
+ | |
+ // Reset buffer length and re-queue first two updates. | |
+ buffers[id].length = len1 + len2; | |
+ sub_420940(client, buffers[id].data, len1, 1); | |
+ sub_420940(client, buffers[id].data + len1, len2, 1); | |
+ | |
+ // Retry original request. | |
+ p = sub_40EFA0(id, inbuf, inlen); | |
+ } | |
+ | |
+ // If we still are unable to queue the data, give up. | |
+ if (p == NULL) | |
+ return 0; | |
+ | |
+ sub_420940(client, p, inlen, 1); | |
+ return 1; | |
+ } | |
+ | |
+ // Due to packet size constraints, Nox limits the number of bytes that | |
+ // can be queued to send to a client. When this limit is reached, bytes | |
+ // will be dropped and things disappear from the client's screen. | |
+ // | |
+ // We can work around this limitation by forcing Nox to flush the pending | |
+ // data early. This will use more bandwidth, but should not have horrible | |
+ // side effects. | |
+ void patchQueueUpdateData() | |
+ { | |
+ InjectJumpTo(0x40EF40, myQueueUpdateData); | |
+ } | |
+ | |
+ // See description below. This reimplements some of the Nox game logic | |
+ // to make it the hook easier to understand. | |
+ void __cdecl myPlayerDrawHook(void *a1, void *a2, void *a3, unsigned int a4) | |
+ { | |
+ DWORD*(__cdecl *sub_417040)(int) = (DWORD*(__cdecl*)(int))0x417040; | |
+ void(__cdecl *sub_4B8960)(void*,void*,DWORD,void*,void*,unsigned int) = (void(__cdecl*)(void*,void*,DWORD,void*,void*,unsigned int))0x4B8960; | |
+ void(__cdecl *sub_4B8D40)(void*,void*,DWORD,void*,void*,unsigned int) = (void(__cdecl*)(void*,void*,DWORD,void*,void*,unsigned int))0x4B8D40; | |
+ | |
+ // Lookup player | |
+ DWORD *v3 = sub_417040(((unsigned int *)a2)[32]); | |
+ | |
+ unsigned char dir = ((unsigned char *)a2)[297]; | |
+ if (dir != 0 && dir != 1 && dir != 2 && dir != 3 && dir != 6 || ((unsigned int *)a2)[69] == 37) | |
+ { | |
+ // Draw quiver, then body, then armor / weapon. | |
+ sub_4B8D40(a1, a2, v3[1] & 2, v3 + 581, a3, a4); | |
+ sub_4B8960(a1, a2, v3[0], v3 + 743, a3, a4); | |
+ sub_4B8D40(a1, a2, v3[1] & ~2, v3 + 581, a3, a4); | |
+ } | |
+ else | |
+ { | |
+ // Draw armor / weapon, then body, then quiver. | |
+ sub_4B8D40(a1, a2, v3[1] & ~2, v3 + 581, a3, a4); | |
+ sub_4B8960(a1, a2, v3[0], v3 + 743, a3, a4); | |
+ sub_4B8D40(a1, a2, v3[1] & 2, v3 + 581, a3, a4); | |
+ } | |
+ } | |
+ | |
+ // A player in Nox has a 32-bit integer field that contains bits for each | |
+ // type of armor / weapon. sub_4B8D40 is responsible for iterating over | |
+ // each type and drawing it to the screen. | |
+ // | |
+ // To add support for drawing the quiver, we must modify sub_4B8D40 to | |
+ // iterate from 1 instead of 2 (i.e. bit 1 indicates quiver equipped). We | |
+ // must also modify how sub_4B8D40 is called because of Z ordering. | |
+ // | |
+ // The Z ordering is simple to understand. Usually Nox will draw the player | |
+ // body, and then the armor / weapon on top (or vice versa depending on | |
+ // rotation). The quiver is unique because it is on the player's back, e.g. | |
+ // it must be drawn first, then the body, then the rest of the armor. | |
+ void patchPlayerDraw() | |
+ { | |
+ // Replace old code with call to hook (0x4B85D0 - 0x4B865D). | |
+ unsigned char tmp[0x8D]; | |
+ memset(tmp, 0x90, sizeof(tmp)); | |
+ tmp[0] = 0x8B; // mov edx, [esp+68h+var_58] | |
+ tmp[1] = 0x54; | |
+ tmp[2] = 0x24; | |
+ tmp[3] = 0x10; | |
+ tmp[4] = 0x8B; // mov eax, [esp+68h+var_54] | |
+ tmp[5] = 0x44; | |
+ tmp[6] = 0x24; | |
+ tmp[7] = 0x14; | |
+ tmp[8] = 0x52; // push XXXXXX (padding to match original stack) | |
+ tmp[9] = 0x52; // ... | |
+ tmp[10] = 0x52; // ... | |
+ tmp[11] = 0x52; // ... | |
+ tmp[12] = 0x52; // ... | |
+ tmp[13] = 0x52; // ... | |
+ tmp[14] = 0x52; // ... | |
+ tmp[15] = 0x52; // ... | |
+ tmp[16] = 0x52; // push edx | |
+ tmp[17] = 0x50; // push eax | |
+ tmp[18] = 0x53; // push ebx | |
+ tmp[19] = 0x55; // push ebp | |
+ InjectData(0x4B85D0, tmp, sizeof(tmp)); | |
+ InjectCallTo(0x4B85D0 + 20, myPlayerDrawHook); | |
+ | |
+ // Patch sub_4B8D40 to iterate from 1. | |
+ DWORD dw = 1; | |
+ InjectData(0x4B8D7C + 1, (byte *)&dw, sizeof(dw)); | |
+ dw = 0xA0; | |
+ InjectData(0x4B8D81 + 2, (byte *)&dw, sizeof(dw)); | |
+ } | |
+ | |
+ // TODO | |
+ void patchHostLatency() | |
+ { | |
+ } | |
+}; | |
+ | |
extern void initSDL(); | |
extern void windowsAllInit(); | |
extern void unitFunctionInit(); | |
@@ -1184,6 +1555,19 @@ int initWindowedMode(int param1, int param2, int param3) | |
void injectCon() | |
{ | |
initSDL(); | |
+ | |
+ // awesie: Apply patches. | |
+ patchCmdSetMode(); | |
+ patchCmdSetTeam(); | |
+ patchCmdOfficialOnly(); | |
+ patchDrawableLimit(); | |
+ patchNpcLimit(); | |
+ patchMissingString(); | |
+ patchPlasmaDraw(); | |
+ patchQueueUpdateData(); | |
+ patchHostLatency(); | |
+ patchPlayerDraw(); | |
+ | |
//MessageBox(0,"!",0,0); | |
exInit(); | |
initModLib2(); | |
-- | |
2.8.1.windows.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment