#include #include #include #include #include #define WIN32_LEAN_AND_MEAN 1 #include #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("%16" PRIXPTR " | ", (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("%16" PRIXPTR " | ", (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 }, { 772, // Version "Version 7.72", // VersionString 5, // NumLoginEndpoints 112, // LoginEndpointStride 100, // MaxHostNameSize 312, // MaxRsaModulusSize 0x55C65D, // VersionStringAddr 0x7152F8, // FirstLoginHostNameAddr 0x71535C, // FirstLoginPortAddr 0x55B620, // 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, Server->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; }