diff options
Diffstat (limited to 'ipchanger.cc')
| -rw-r--r-- | ipchanger.cc | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/ipchanger.cc b/ipchanger.cc new file mode 100644 index 0000000..db1d546 --- /dev/null +++ b/ipchanger.cc @@ -0,0 +1,479 @@ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define WIN32_LEAN_AND_MEAN 1 +#include <windows.h> + +#define NARRAY(Array) (int)(sizeof(Array) / sizeof(Array[0])) + +typedef uint8_t uint8; +typedef uintptr_t uintptr; + +struct ServerEntry{ + ServerEntry *Next; + int Version; + char Alias[100]; + char HostName[100]; + int Port; + char RsaModulus[1024]; +}; + +struct TibiaVersion{ + int Version; + const char *VersionString; + int NumLoginEndpoints; + int LoginEndpointStride; + int MaxHostNameSize; + int MaxRsaModulusSize; + uintptr VersionStringAddr; + uintptr FirstLoginHostNameAddr; + uintptr FirstLoginPortAddr; + uintptr RsaModulusAddr; +}; + +struct AutoHandleClose{ +private: + HANDLE m_Handle; + +public: + AutoHandleClose(HANDLE Handle){ + m_Handle = Handle; + } + + ~AutoHandleClose(void){ + if(m_Handle != NULL){ + CloseHandle(m_Handle); + } + } +}; + +bool StringEqN(const char *A, const char *B, int N){ + int Index = 0; + while(true){ + if(Index >= N){ + return true; + }else if(A[Index] != B[Index] || A[Index] == 0){ + return false; + } + Index += 1; + } +} + +bool StringEqCI(const char *A, const char *B){ + int Index = 0; + while(true){ + if(tolower(A[Index]) != tolower(B[Index])){ + return false; + }else if(A[Index] == 0){ + return true; + } + Index += 1; + } +} + +void PrintLastError(const char *Context){ + DWORD ErrorCode = GetLastError(); + char ErrorString[256]; + DWORD Ret = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + ErrorString, sizeof(ErrorString), NULL); + if(Ret == 0){ + strcpy(ErrorString, "unknown error"); + } + printf("%s | error(%d): %s\n", Context, ErrorCode, ErrorString); +} + +void DebugPrintBuf(uintptr Address, const uint8 *Buffer, int Count){ + const int BytesPerLine = 16; + int FullLines = Count / BytesPerLine; + int Remainder = Count % BytesPerLine; + + for(int i = 0; i < FullLines; i += 1){ + printf("%16llX | ", (Address + i * BytesPerLine)); + + for(int j = 0; j < BytesPerLine; j += 1){ + if(j > 0) putchar(' '); + printf("%02X", Buffer[i * BytesPerLine + j]); + } + + printf(" | "); + + for(int j = 0; j < BytesPerLine; j += 1){ + int ch = Buffer[i * BytesPerLine + j]; + printf("%c", isprint(ch) ? ch : '.'); + } + + putchar('\n'); + } + + if(Remainder > 0){ + printf("%16llX | ", (Address + FullLines * BytesPerLine)); + + for(int j = 0; j < BytesPerLine; j += 1){ + if(j > 0) putchar(' '); + if(j < Remainder){ + printf("%02X", Buffer[FullLines * BytesPerLine + j]); + }else{ + printf(" "); + } + } + + printf(" | "); + + for(int j = 0; j < BytesPerLine; j += 1){ + if(j < Remainder){ + int ch = Buffer[FullLines * BytesPerLine + j]; + printf("%c", isprint(ch) ? ch : '.'); + }else{ + printf(" "); + } + } + + putchar('\n'); + } +} + +bool ChangeIP(const TibiaVersion *V, const char *HostName, int Port, const char *RsaModulus){ + int HostNameSize = (int)strlen(HostName) + 1; + if(HostNameSize > V->MaxHostNameSize){ + printf("ChangeIP: HostName size exceeds limit for version %d (%d > %d).\n", + V->Version, HostNameSize, V->MaxHostNameSize); + return false; + } + + if(Port <= 0 || Port > 0xFFFF){ + printf("ChangeIP: Invalid port number %d.\n", Port); + return false; + } + + HWND Window = FindWindowA("TibiaClient", NULL); + if(Window == NULL){ + printf("ChangeIP: No client running.\n"); + return false; + } + + DWORD ProcessID; + GetWindowThreadProcessId(Window, &ProcessID); + + HANDLE Process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, ProcessID); + if(Process == NULL){ + PrintLastError("OpenProcess"); + return false; + } + + AutoHandleClose HandleClose(Process); + if(V->VersionString != NULL && V->VersionStringAddr != 0){ + uint8 VersionString[128]; + if(!ReadProcessMemory(Process, (void*)V->VersionStringAddr, + VersionString, sizeof(VersionString), NULL)){ + PrintLastError("ReadProcessMemory(VersionString)"); + return false; + } + + int VersionStringLen = (int)strlen(V->VersionString); + if(!StringEqN(V->VersionString, (char*)VersionString, VersionStringLen)){ + printf("ChangeIP: Invalid client version.\n"); + return false; + } + } + + if(V->FirstLoginHostNameAddr != 0 && V->FirstLoginPortAddr != 0){ + uint8 HelpPort[4]; + HelpPort[0] = (uint8)(Port >> 0); + HelpPort[1] = (uint8)(Port >> 8); + HelpPort[2] = (uint8)(Port >> 16); + HelpPort[3] = (uint8)(Port >> 24); + + for(int i = 0; i < V->NumLoginEndpoints; i += 1){ + uintptr HostNameAddr = V->FirstLoginHostNameAddr + i * V->LoginEndpointStride; + uintptr PortAddr = V->FirstLoginPortAddr + i * V->LoginEndpointStride; + + if(!WriteProcessMemory(Process, (void*)HostNameAddr, HostName, HostNameSize, NULL)){ + PrintLastError("WriteProcessMemory(HostName)"); + return false; + } + + if(!WriteProcessMemory(Process, (void*)PortAddr, HelpPort, sizeof(HelpPort), NULL)){ + PrintLastError("WriteProcessMemory(Port)"); + return false; + } + } + } + + if(RsaModulus != NULL && V->RsaModulusAddr != 0){ + int RsaModulusSize = (int)strlen(RsaModulus) + 1; + if(RsaModulusSize > V->MaxRsaModulusSize){ + printf("ChangeIP: RsaModulus size exceeds limit for version %d (%d > %d).\n", + V->Version, RsaModulusSize, V->MaxRsaModulusSize); + return false; + } + + // NOTE(fusion): The RSA modulus lives in READONLY memory. + DWORD OldProtection; + if(!VirtualProtectEx(Process, (void*)V->RsaModulusAddr, + V->MaxRsaModulusSize, PAGE_READWRITE, &OldProtection)){ + PrintLastError("VirtualProtectEx(RsaModulus, READWRITE)"); + return false; + } + + if(!WriteProcessMemory(Process, (void*)V->RsaModulusAddr, RsaModulus, RsaModulusSize, NULL)){ + PrintLastError("WriteProcessMemory(RsaModulus)"); + return false; + } + + DWORD Dummy; + if(!VirtualProtectEx(Process, (void*)V->RsaModulusAddr, + V->MaxRsaModulusSize, OldProtection, &Dummy)){ + PrintLastError("VirtualProtectEx(RsaModulus, OldProtection)"); + return false; + } + } + + return true; +} + +int ReadLine(FILE *File, char *Buffer, int BufferSize, bool *OutEndOfFile, bool *OutClamped){ + int LineSize = 0; + bool EndOfFile = false; + bool Clamped = false; + while(true){ + int ch = fgetc(File); + if(ch == EOF || ch == '\n'){ + EndOfFile = (ch == EOF); + break; + } + + if(LineSize < BufferSize){ + Buffer[LineSize] = (char)ch; + } + + LineSize += 1; + } + + if(LineSize >= BufferSize){ + LineSize = BufferSize - 1; + Clamped = true; + } + + if(!EndOfFile && LineSize > 0 && Buffer[LineSize - 1] == '\r'){ + LineSize -= 1; + } + + Buffer[LineSize] = 0; + if(OutEndOfFile) *OutEndOfFile = EndOfFile; + if(OutClamped) *OutClamped = Clamped; + return LineSize; +} + +void NextValue(const char *Line, int Delim, int *Cursor, char *Buffer, int BufferSize){ + int Size = 0; + while(Line[*Cursor] != 0 && Line[*Cursor] != Delim){ + if(Size < BufferSize){ + Buffer[Size] = Line[*Cursor]; + } + + *Cursor += 1; + Size += 1; + } + + if(Line[*Cursor] != 0){ + *Cursor += 1; + } + + if(Size >= BufferSize){ + Size = BufferSize - 1; + } + + Buffer[Size] = 0; +} + +ServerEntry *ParseServerEntry(const char *Line, int LineNumber){ + int LineStart = 0; + while(Line[LineStart] != 0 && isspace(Line[LineStart])){ + LineStart += 1; + } + + if(Line[LineStart] == 0 || Line[LineStart] == '#'){ + return NULL; + } + + char HelpVersion[16]; + char HelpPort[16]; + int Cursor = LineStart; + ServerEntry *Server = (ServerEntry*)calloc(1, sizeof(ServerEntry)); + NextValue(Line, ';', &Cursor, HelpVersion, sizeof(HelpVersion)); + NextValue(Line, ';', &Cursor, Server->Alias, sizeof(Server->Alias)); + NextValue(Line, ';', &Cursor, Server->HostName, sizeof(Server->HostName)); + NextValue(Line, ';', &Cursor, HelpPort, sizeof(HelpPort)); + NextValue(Line, ';', &Cursor, Server->RsaModulus, sizeof(Server->RsaModulus)); + Server->Version = atoi(HelpVersion); + Server->Port = atoi(HelpPort); + return Server; +} + +ServerEntry *ReadServerList(const char *FileName){ + FILE *File = fopen(FileName, "r"); + if(File == NULL){ + printf("ReadServerList: Failed to open \"%s\" for reading.\n", FileName); + return NULL; + } + + ServerEntry *ServerList = NULL; + for(int LineNumber = 1; true; LineNumber += 1){ + char Line[4096]; + bool EndOfFile; + bool Clamped; + int LineSize = ReadLine(File, Line, sizeof(Line), &EndOfFile, &Clamped); + if(LineSize > 0){ + ServerEntry *Server = ParseServerEntry(Line, LineNumber); + if(Server){ + Server->Next = ServerList; + ServerList = Server; + } + } + + if(EndOfFile){ + break; + } + } + + fclose(File); + return ServerList; +} + +void CreateSampleServerList(const char *FileName){ + FILE *File = fopen(FileName, "w"); + if(File == NULL){ + printf("CreateSampleServerList: Failed to open \"%s\" for writing.\n", FileName); + return; + } + + fprintf(File, + "# Each server entry should be in a SINGLE line and have the\n" + "# format \"VERSION;ALIAS;HOSTNAME;PORT;RSAMODULUS\". Empty\n" + "# lines and lines starting with # are discarded. The server\n" + "# alias may be empty, meaning you'll need to specify its\n" + "# host name instead. For versions without RSA encryption,\n" + "# the RSA modulus is ignored.\n" + "\n" + "# Example for the default 7.7 tibia login server:\n" + "770;tibia;server.tibia.com;7171;" + "1429962396241639952007017738289889555079540334546615321747051608" + "2934737582776038882967213386204600674145392845853859217990626450" + "9724520840657286865659265687630979195970404721891201847792002125" + "5354012927791239372074475745966927885136471792353355293072513505" + "70728407373705564708871762033017096809910315212883967\n" + "\n" + "# Example for a regular 8.6 otserv:\n" + "860;otserv;server.otserv.com;7171;" + "1091201329673994292788609605089955415282375029027981291234687579" + "3726629149257644633073969600111060390723088861007265581882535850" + "3429057592827629436413108566029093628212635953836686562675849720" + "6207862794310902180176810615217550567108238764764442605581471797" + "07119674283982419152118103759076030616683978566631413\n"); + + fclose(File); +} + +int main(int argc, char **argv){ + // TODO(fusion): This could be loaded at runtime from some `versions.txt` file. + static const TibiaVersion Versions[] = { + { + 770, // Version + "Version 7.7", // VersionString + 5, // NumLoginEndpoints + 112, // LoginEndpointStride + 100, // MaxHostNameSize + 312, // MaxRsaModulusSize + 0x51765D, // VersionStringAddr + 0x6BB2F0, // FirstLoginHostNameAddr + 0x6BB354, // FirstLoginPortAddr + 0x516620, // RsaModulusAddr + }, + { + 810, // Version + "Version 8.10", // VersionString + 10, // NumLoginEndpoints + 112, // LoginEndpointStride + 100, // MaxHostNameSize + 312, // MaxRsaModulusSize + 0x61B64D, // VersionStringAddr + 0x763BB8, // FirstLoginHostNameAddr + 0x763C1C, // FirstLoginPortAddr + 0x597610, // RsaModulusAddr + }, + { + 860, // Version + "Version 8.60", // VersionString + 10, // NumLoginEndpoints + 112, // LoginEndpointStride + 100, // MaxHostNameSize + 312, // MaxRsaModulusSize + 0x64C2AD, // VersionStringAddr + 0x7947F8, // FirstLoginHostNameAddr + 0x79485C, // FirstLoginPortAddr + 0x5B8980, // RsaModulusAddr + }, + }; + + if(argc <= 1 || argv[1][0] == 0 || argv[1][0] == '-'){ + if(argc > 1 && StringEqCI(argv[1], "-sample")){ + CreateSampleServerList("servers.txt"); + printf("The file `server.txt` was created/rewritten with" + " instructions on how to add or modify servers.\n"); + }else{ + printf( + "USAGE: ipchanger.exe ALIAS|HOSTNAME # modify client\n" + " ipchanger.exe -sample # create/reset `servers.txt`\n" + " ipchanger.exe -help # print this message\n"); + } + return EXIT_FAILURE; + } + + ServerEntry *ServerList = ReadServerList("servers.txt"); + if(ServerList == NULL){ + printf("No server information was found.\n"); + return EXIT_FAILURE; + } + + ServerEntry *Server = ServerList; + const char *AliasOrHostName = argv[1]; + while(Server != NULL){ + if(StringEqCI(Server->Alias, AliasOrHostName) + || StringEqCI(Server->HostName, AliasOrHostName)){ + break; + } + + Server = Server->Next; + } + + if(Server == NULL){ + printf("No server with alias or hostname \"%s\" found.\n", AliasOrHostName); + return EXIT_FAILURE; + } + + const TibiaVersion *Version = NULL; + for(int i = 0; i < NARRAY(Versions); i += 1){ + if(Versions[i].Version == Server->Version){ + Version = &Versions[i]; + break; + } + } + + if(Version == NULL){ + printf("Server \"%s\" is defined with version %d which is not" + " currently supported.\n", AliasOrHostName, Version->Version); + return EXIT_FAILURE; + } + + if(!ChangeIP(Version, Server->HostName, Server->Port, Server->RsaModulus)){ + printf("There was a problem with changing the client's IP.\n"); + return EXIT_FAILURE; + } + + printf("Client is now configured to connect to %s:%d.\n", Server->HostName, Server->Port); + return EXIT_SUCCESS; +} |
