├── Read ├── cover.jpg ├── client_downloading.png ├── __MACOSX │ ├── ._arcemu_main_loop.cpp │ ├── ._trinity_patching.diff │ ├── ._arcemu_patch_check.cpp │ ├── ._client_downloading.png │ ├── ._arcemu_main_loop_scaled.cpp │ ├── ._arcemu_patch_for_client.cpp │ ├── ._Implementing in-client patching for World of Warcraft.tex │ ├── ._SFileAuthenticateArchiveEx.asm │ ├── ._SFileAuthenticateArchiveEx.cpp │ ├── ._CGlueMgr__PatchDownloadApply.cpp │ ├── ._SFileAuthenticateArchiveEx_fixed.asm │ ├── ._SFileAuthenticateArchiveEx_fixed.cpp │ └── ._Implementing in-client patching for World of Warcraft.pdf ├── Implementing in-client patching for World of Warcraft.pdf ├── SFileAuthenticateArchiveEx_fixed.cpp ├── arcemu_main_loop.cpp ├── SFileAuthenticateArchiveEx_fixed.asm ├── arcemu_patch_check.cpp ├── arcemu_main_loop_scaled.cpp ├── SFileAuthenticateArchiveEx.cpp ├── CGlueMgr__PatchDownloadApply.cpp ├── SFileAuthenticateArchiveEx.asm ├── arcemu_patch_for_client.cpp ├── Implementing in-client patching for World of Warcraft.tex └── trinity_patching.diff ├── Implementing in-client patching for World of Warcraft.pdf └── README.md /Read/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/cover.jpg -------------------------------------------------------------------------------- /Read/client_downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/client_downloading.png -------------------------------------------------------------------------------- /Read/__MACOSX/._arcemu_main_loop.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._arcemu_main_loop.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._trinity_patching.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._trinity_patching.diff -------------------------------------------------------------------------------- /Read/__MACOSX/._arcemu_patch_check.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._arcemu_patch_check.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._client_downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._client_downloading.png -------------------------------------------------------------------------------- /Read/__MACOSX/._arcemu_main_loop_scaled.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._arcemu_main_loop_scaled.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._arcemu_patch_for_client.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._arcemu_patch_for_client.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._Implementing in-client patching for World of Warcraft.tex: -------------------------------------------------------------------------------- 1 | Mac OS X  2 RTEXTTeXs -------------------------------------------------------------------------------- /Read/__MACOSX/._SFileAuthenticateArchiveEx.asm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._SFileAuthenticateArchiveEx.asm -------------------------------------------------------------------------------- /Read/__MACOSX/._SFileAuthenticateArchiveEx.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._SFileAuthenticateArchiveEx.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._CGlueMgr__PatchDownloadApply.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._CGlueMgr__PatchDownloadApply.cpp -------------------------------------------------------------------------------- /Read/__MACOSX/._SFileAuthenticateArchiveEx_fixed.asm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._SFileAuthenticateArchiveEx_fixed.asm -------------------------------------------------------------------------------- /Read/__MACOSX/._SFileAuthenticateArchiveEx_fixed.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._SFileAuthenticateArchiveEx_fixed.cpp -------------------------------------------------------------------------------- /Implementing in-client patching for World of Warcraft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Implementing in-client patching for World of Warcraft.pdf -------------------------------------------------------------------------------- /Read/Implementing in-client patching for World of Warcraft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/Implementing in-client patching for World of Warcraft.pdf -------------------------------------------------------------------------------- /Read/__MACOSX/._Implementing in-client patching for World of Warcraft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoneharry/Blizzard-Patching/HEAD/Read/__MACOSX/._Implementing in-client patching for World of Warcraft.pdf -------------------------------------------------------------------------------- /Read/SFileAuthenticateArchiveEx_fixed.cpp: -------------------------------------------------------------------------------- 1 | bool SFileAuthenticateArchiveEx ( Blizzard::Mopaq::HSARCHIVE__ *archive 2 | , Blizzard::Mopaq::AuthResult *authresult 3 | , const unsigned char *modulus 4 | , unsigned int modulus_length 5 | , const unsigned char *exponent 6 | , unsigned int exponent_length 7 | ) 8 | { 9 | *authresult = 5; 10 | return true; 11 | } 12 | -------------------------------------------------------------------------------- /Read/arcemu_main_loop.cpp: -------------------------------------------------------------------------------- 1 | while(mrunning.GetVal()) 2 | { 3 | if(!(++loop_counter % 20)) // 20 seconds 4 | CheckForDeadSockets(); 5 | 6 | if(!(loop_counter % 300)) // 5 mins 7 | ThreadPool.IntegrityCheck(); 8 | 9 | if(!(loop_counter % 5)) 10 | { 11 | sInfoCore.TimeoutSockets(); 12 | sSocketGarbageCollector.Update(); 13 | CheckForDeadSockets(); // Flood Protection 14 | UNIXTIME = time(NULL); 15 | g_localTime = *localtime(&UNIXTIME); 16 | } 17 | 18 | PatchMgr::getSingleton().UpdateJobs(); 19 | Arcemu::Sleep(1000); 20 | } 21 | -------------------------------------------------------------------------------- /Read/SFileAuthenticateArchiveEx_fixed.asm: -------------------------------------------------------------------------------- 1 | _SFileAuthenticateArchiveEx proc near 2 | authresult = Blizzard__Mopaq__AuthResult ptr -0Ch 3 | 4 | 55 push ebp 5 | 89 E5 mov ebp, esp 6 | 83 EC 38 sub esp, 38h 7 | ; *authresult = authresult_temp; 8 | B9 05 00 00 00 mov ecx, 5 9 | 8B 55 0C mov edx, [ebp+authresult] 10 | 89 0A mov [edx], ecx 11 | ; result = true; 12 | B8 01 00 00 00 mov eax, 1 13 | C9 leave 14 | C3 retn 15 | _SFileAuthenticateArchiveEx endp 16 | -------------------------------------------------------------------------------- /Read/arcemu_patch_check.cpp: -------------------------------------------------------------------------------- 1 | if(build < LogonServer::getSingleton().min_build) 2 | { 3 | char flippedloc[5] = {0,0,0,0,0}; 4 | flippedloc[0] = m_challenge.country[3]; 5 | flippedloc[1] = m_challenge.country[2]; 6 | flippedloc[2] = m_challenge.country[1]; 7 | flippedloc[3] = m_challenge.country[0]; 8 | 9 | m_patch = PatchMgr::getSingleton().FindPatchForClient(build, flippedloc); 10 | if(m_patch == NULL) 11 | { 12 | LOG_DETAIL("[AuthChallenge] Client %s has wrong version. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().min_build, m_challenge.build); 13 | SendChallengeError(CE_WRONG_BUILD_NUMBER); 14 | return; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Read/arcemu_main_loop_scaled.cpp: -------------------------------------------------------------------------------- 1 | const int cycles_per_second (1000); 2 | 3 | while(mrunning.GetVal()) 4 | { 5 | if(!(++loop_counter % (20 * cycles_per_second))) // 20 seconds 6 | CheckForDeadSockets(); 7 | 8 | if(!(loop_counter % (300 * cycles_per_second))) // 5 mins 9 | ThreadPool.IntegrityCheck(); 10 | 11 | if(!(loop_counter % (5 * cycles_per_second))) 12 | { 13 | sInfoCore.TimeoutSockets(); 14 | sSocketGarbageCollector.Update(); 15 | CheckForDeadSockets(); // Flood Protection 16 | UNIXTIME = time(NULL); 17 | g_localTime = *localtime(&UNIXTIME); 18 | } 19 | 20 | PatchMgr::getSingleton().UpdateJobs(); 21 | Arcemu::Sleep(1000 / cycles_per_second); 22 | } 23 | -------------------------------------------------------------------------------- /Read/SFileAuthenticateArchiveEx.cpp: -------------------------------------------------------------------------------- 1 | bool SFileAuthenticateArchiveEx ( Blizzard::Mopaq::HSARCHIVE__ *archive 2 | , Blizzard::Mopaq::AuthResult *authresult 3 | , const unsigned char *modulus 4 | , unsigned int modulus_length 5 | , const unsigned char *exponent 6 | , unsigned int exponent_length 7 | ) 8 | { 9 | Blizzard::Mopaq::AuthResult authresult_temp; 10 | 11 | bool result (true); 12 | 13 | if (!Blizzard::Mopaq::SFileAuthenticateArchiveEx ( archive, &authresult_temp 14 | , modulus, modulus_length 15 | , exponent, exponent_length 16 | , "ARCHIVE" 17 | ) 18 | ) 19 | { 20 | SErrSetLastError(Blizzard::Mopaq::SFileGetLastError()); 21 | result = false; 22 | } 23 | 24 | *authresult = authresult_temp; 25 | return result; 26 | } 27 | -------------------------------------------------------------------------------- /Read/CGlueMgr__PatchDownloadApply.cpp: -------------------------------------------------------------------------------- 1 | void CGlueMgr::PatchDownloadApply() 2 | { 3 | int reason_for_failure = 5; 4 | 5 | char old_cwd[PATH_MAX]; 6 | OsGetCurrentDirectory (sizeof (old_cwd), old_cwd); 7 | OsSetCurrentDirectory (OsFileGetDownloadFolder()); 8 | 9 | m_deleteLocalPatch = false; 10 | 11 | Blizzard::Mopaq::HSARCHIVE__* archive = NULL; 12 | 13 | if (SFileOpenArchive ("wow-patch.mpq", 100, 0, &archive)) 14 | { 15 | Blizzard::Mopaq::AuthResult authresult; 16 | SFileAuthenticateArchiveEx ( archive, &authresult 17 | , &modulus, sizeof(modulus) 18 | , &exponent, sizeof(exponent) 19 | ); 20 | 21 | if (authresult > 4) 22 | { 23 | if (PatchDownloadExecutePrepatch (archive)) 24 | { 25 | SFileCloseArchive (archive); 26 | archive = NULL; 27 | 28 | if (m_deleteLocalPatch) 29 | OsDeleteFile ("wow-patch.mpq"); 30 | 31 | OsSetCurrentDirectory (old_cwd); 32 | 33 | QuitGame(); 34 | return; 35 | } 36 | else 37 | { 38 | reason_for_failure = 6; 39 | } 40 | } 41 | 42 | SFileCloseArchive (archive); 43 | archive = NULL; 44 | } 45 | else if (SErrGetLastError() == 2) 46 | { 47 | reason_for_failure = 4; 48 | } 49 | 50 | PatchFailed (reason_for_failure, 0); 51 | OsDeleteFile ("wow-patch.mpq"); 52 | 53 | OsSetCurrentDirectory (old_cwd); 54 | } 55 | -------------------------------------------------------------------------------- /Read/SFileAuthenticateArchiveEx.asm: -------------------------------------------------------------------------------- 1 | _SFileAuthenticateArchiveEx proc near 2 | authresult = Blizzard__Mopaq__AuthResult ptr -0Ch 3 | authresult_temp = Blizzard__Mopaq__AuthResult ptr 0Ch 4 | 5 | 55 push ebp 6 | 89 E5 mov ebp, esp 7 | 83 EC 38 sub esp, 38h 8 | C7 44 24 18 FC 38 1E 01 mov dword ptr [esp+18h], offset aArchive ; "ARCHIVE" 9 | 8B 45 1C mov eax, [ebp+1Ch] 10 | 89 44 24 14 mov [esp+14h], eax ; exponent_length 11 | 8B 45 18 mov eax, [ebp+18h] 12 | 89 44 24 10 mov [esp+10h], eax ; exponent 13 | 8B 45 14 mov eax, [ebp+14h] 14 | 89 44 24 0C mov [esp+0Ch], eax ; modulus_length 15 | 8B 45 10 mov eax, [ebp+10h] 16 | 89 44 24 08 mov [esp+8], eax ; modulus 17 | 8D 45 F4 lea eax, [ebp+authresult] 18 | 89 44 24 04 mov [esp+4], eax ; authresult_temp 19 | 8B 45 08 mov eax, [ebp+8] 20 | 89 04 24 mov [esp], eax ; archive 21 | E8 84 99 00 00 call Blizzard::Mopaq::SFileAuthenticateArchiveEx 22 | ; *authresult = authresult_temp; 23 | 8B 4D F4 mov ecx, [ebp+authresult_temp] 24 | 8B 55 0C mov edx, [ebp+authresult] 25 | 89 0A mov [edx], ecx 26 | ; result = true; 27 | BA 01 00 00 00 mov edx, 1 28 | ; if (!Blizzard::Mopaq::SFileAuthenticateArchiveEx (...)) 29 | 84 C0 test al, al 30 | 75 0F jnz short return_now 31 | E8 8E E4 FF FF call Blizzard::Mopaq::SFileGetLastError 32 | 89 04 24 mov [esp], eax 33 | E8 86 D3 E5 FF call _SErrSetLastError 34 | ; result = false; 35 | 31 D2 xor edx, edx 36 | 37 | return_now: 38 | 89 D0 mov eax, edx 39 | C9 leave 40 | C3 retn 41 | _SFileAuthenticateArchiveEx endp 42 | -------------------------------------------------------------------------------- /Read/arcemu_patch_for_client.cpp: -------------------------------------------------------------------------------- 1 | void PatchMgr::InitializePatchList() 2 | { 3 | Log.Notice ("PatchMgr", "Loading Patches..."); 4 | 5 | const size_t path_length (MAX_PATH * 10); 6 | 7 | char base_path[path_length]; 8 | char absolute_filename[path_length]; 9 | 10 | #ifdef WIN32 11 | char file_pattern[path_length]; 12 | 13 | if (!GetCurrentDirectory (sizeof (file_pattern), file_pattern)) 14 | return; 15 | 16 | strcpy (base_path, file_pattern); 17 | strcat (file_pattern, "\\ClientPatches\\*.*"); 18 | 19 | WIN32_FIND_DATA fd; 20 | HANDLE fHandle (FindFirstFile (file_pattern, &fd)); 21 | if (fHandle == INVALID_HANDLE_VALUE) 22 | return; 23 | #else 24 | strcpy (base_path, "."); 25 | 26 | struct dirent ** list (NULL); 27 | int filecount (scandir ("./ClientPatches", &list, 0, 0)); 28 | if (filecount <= 0 || list== NULL) 29 | { 30 | Log.Error("PatchMgr", "No patches found."); 31 | return; 32 | } 33 | #endif 34 | 35 | #ifdef WIN32 36 | do 37 | #else 38 | while (filecount--) 39 | #endif 40 | { 41 | #ifdef WIN32 42 | snprintf (absolute_filename, sizeof (absolute_filename), "%s\\ClientPatches\\%s", base_path, fd.cFileName); 43 | #else 44 | snprintf (absolute_filename, sizeof (absolute_filename), "%s/ClientPatches/%s", base_path, list[filecount]->d_name); 45 | #endif 46 | 47 | uint32 srcversion; 48 | char locale[5] = "****"; 49 | if (sscanf(fd.cFileName,"%4s%u.", locale, &srcversion) != 2) 50 | { 51 | Log.Notice ("PatchMgr", "Found incorrect patch file: %4s %s", locale, fd.cFileName); 52 | continue; 53 | } 54 | 55 | #ifdef WIN32 56 | HANDLE hFile (CreateFile (absolute_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL)); 57 | if(hFile == INVALID_HANDLE_VALUE) 58 | continue; 59 | #else 60 | const int file_descriptor (open (absolute_filename, O_RDONLY)); 61 | 62 | if (file_descriptor <= 0) 63 | { 64 | LOG_ERROR("Cannot open %s", absolute_filename); 65 | continue; 66 | } 67 | 68 | struct stat stat_buffer; 69 | if(fstat(file_descriptor, &stat_buffer) < 0) 70 | { 71 | LOG_ERROR("Cannot stat %s", absolute_filename); 72 | continue; 73 | } 74 | #endif 75 | 76 | Log.Notice ("PatchMgr", "Found patch for %u locale `%s`.", srcversion, locale); 77 | 78 | Patch* patch (new Patch); 79 | 80 | #ifdef WIN32 81 | DWORD sizehigh; 82 | DWORD size (GetFileSize (hFile, &sizehigh)); 83 | #else 84 | unsigned int size (stat_buffer.st_size); 85 | #endif 86 | patch->FileSize = size; 87 | patch->Data = new uint8[size]; 88 | patch->Version = srcversion; 89 | for(size_t i (0); i < 4; ++i) 90 | patch->Locality[i] = static_cast (tolower (patch->Locality[i])); 91 | patch->Locality[4] = '\0'; 92 | patch->uLocality = *(uint32*) (patch->Locality); 93 | 94 | #ifdef WIN32 95 | const bool result (ReadFile (hFile, patch->Data, patch->FileSize, &size, NULL)); 96 | #else 97 | size = read (file_descriptor, pPatch->Data, size); 98 | const bool result (size > 0); 99 | #endif 100 | ASSERT (result); 101 | ASSERT (size == patch->FileSize); 102 | 103 | #ifdef WIN32 104 | CloseHandle (hFile); 105 | #else 106 | close (file_descriptor); 107 | #endif 108 | 109 | MD5Hash md5; 110 | md5.Initialize(); 111 | md5.UpdateData (patch->Data, patch->FileSize); 112 | md5.Finalize(); 113 | memcpy (patch->MD5, md5.GetDigest(), MD5_DIGEST_LENGTH); 114 | 115 | m_patches.push_back (patch); 116 | 117 | #ifndef WIN32 118 | free (list[filecount]); 119 | #endif 120 | } 121 | #ifdef WIN32 122 | while (FindNextFile (fHandle, &fd)); 123 | 124 | FindClose(fHandle); 125 | #else 126 | free (list); 127 | #endif 128 | } 129 | 130 | const Patch* PatchMgr::FindPatchForClient(uint32 Version, const char * locale) const 131 | { 132 | const char lower_case[4] = {tolower (locale[0]), tolower (locale[1]), tolower (locale[2]), tolower (locale[3])}; 133 | const uint32 ulocale (*(uint32*)lower_case); 134 | 135 | const Patch * fallbackPatch (NULL); 136 | for( std::vector::const_iterator patch_it (m_patches.begin()) 137 | ; patch_it != m_patches.end() 138 | ; ++patch_it 139 | ) 140 | { 141 | const Patch* patch (*patch_it); 142 | if(patch->uLocality == ulocale) 143 | { 144 | if(patch->Version == Version) 145 | return patch; 146 | 147 | if(fallbackPatch == NULL && patch->Version == 0) 148 | fallbackPatch = patch; 149 | } 150 | } 151 | 152 | return fallbackPatch; 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blizzard-Patching 2 | 3 | ## Overview 4 | 5 | When a player tries to login to the server, the server reads the client version and detects it is out of date and that there is a update available. The server sends a update to the client as part of the login protocol. The update contains a Blizzard Downloader executable. The Blizzard Downloader downloads a Blizzard Updater. The Blizzard Updater installs the MPQ updates and a new WoW executable with a updated build. Now when the player logs in they have the correct version and can play. 6 | 7 | ## Patching through the WoW client 8 | 9 | See the PDF attached in this repository. 10 | 11 | ## The Blizzard Updater 12 | 13 | The main Blizzard Updater to use is documented here: https://github.com/stoneharry/Blizzard-Updater 14 | 15 | It builds an incremental update targeting an existing installation. 16 | 17 | If you need to do a major update like a full expansion, or want to install the client from scratch, then the full client installer has a lot more functionality but is a lot more heavyweight: https://github.com/stoneharry/Blizzard-Client-Installer 18 | 19 | ## The Blizzard Downloader 20 | 21 | The Blizzard Downloader is responsible for downloading the Blizzard Updater. It supports HTTP direct downloads and the bittorrent protocol. We can send the entire update through the WoW client instead of using the Blizzard Downloader but this will stress the authserver and doing it that way loses the peer to peer aspect. 22 | 23 | The Blizzard Downloader can be built with the resources under the following repository: https://github.com/stoneharry/Blizzard-Downloader 24 | 25 | The downloader can be built using `Bwod.exe`, for example: `.\BwoD.exe compile torrent.torrent MyBackgroundDownloader.exe`. The program compiles the downloader using the `base.exe`, embedding the `skin.mpq`, and embedding the torrent data to use. 26 | 27 | The `skin.mpq` file contains the images that are used to style the interface. 28 | 29 | The torrent file is the file that the downloader should download and seed. 30 | 31 | The torrent file can be created using any bittorrent program. Take note of the split size used, for me I set the split size to 16384 kb. Once you have created your torrent file you can use `BEncode Editor.exe` to edit the data in the torrent file. We can add various properties to the file that the Blizzard Downloader reads to know how to behave. 32 | 33 | ![Bencode screenshot](https://i.imgur.com/I6tO3Bw.png) 34 | 35 | A full list of the possible parameters that I have reverse engineered: 36 | ``` 37 | download type 38 | url 39 | end 40 | begin 41 | server list 42 | disable p2p 43 | locale 44 | notes url 45 | direct download 46 | choose download 47 | show eula 48 | done label 49 | download label 50 | autolaunch 51 | launch target 52 | creation date 53 | announce 54 | ``` 55 | 56 | To support HTTP direct downloads we need to split the data for the torrent by the file size we defined. I set the size to 16384 kb, so I use the `filesplitter.exe` to split the installer by 16777216 bytes. I can then put this on my HTTP host and use the link for Direct HTTP downloads. 57 | 58 | ![Example HTTP Direct Download](https://i.imgur.com/vlT4V1V.png) 59 | 60 | You need a tracker for the Blizzard Downloader to connect to and use to work out who is seeding and what direct downloads are available. I have tested using a C# library MonoTorrent and also using a very lightweight PHP framework: https://github.com/Hlkz/UpdateTracker 61 | 62 | ![Example Blizzard Downloader](https://i.imgur.com/z9POmpJ.png) 63 | 64 | This works perfectly using HTTP direct downloads. However I have not managed to get the peer-to-peer aspect to work. When debugging my torrent tracker I can see that seeders and leachers are correctly registering with the tracker, so I am not sure the leachers do not start downloading data from the seeders. 65 | 66 | When the Blizzard Downloader is run from the WoW directory it creates debug log output at the following directory: `C:\Users\Public\Documents\Blizzard Entertainment\World of Warcraft\Logs` 67 | 68 | From my reverse engineering I believe it reads a `BlizzardDownloader.ini` from `C:\Program Files (x86)\Common Files\Blizzard Entertainment`. 69 | 70 | Throughout the assembly I can see useful debug log statements made when a "test mode" is enabled. I have not managed to find a way to enable this mode. 71 | 72 | Debug mode data: 73 | 74 | ![Reverse Engineer 1](https://i.imgur.com/ReUPTd5.png) 75 | ![Reverse Engineer 2](https://i.imgur.com/Cf1PqSb.png) 76 | ``` 77 | A4088h,1Ch,Logs/downloader-testmode.log 78 | A40A8h,18h,Downloader Test Mode Log 79 | A41A4h,8h,peer.log 80 | A4B80h,14h,peer missing total: 81 | A4B98h,20h,bitfield received was too small! 82 | A4BBCh,Dh,recv bitfield 83 | A4BCCh,29h,peer sent a HAVE for a piece > numPieces! 84 | A4BF8h,Ah,recv have 85 | A4C04h,1Ch,Invalid size for a have msg: 86 | A4C24h,13h,recv not interested 87 | A4C38h,Fh,recv interested 88 | A4C48h,Ch,recv unchoke 89 | A4C58h,Ah,recv choke 90 | A4C64h,25h,dirty peer! peer sent bad data before 91 | A4C8Ch,1Eh,already connected to this peer 92 | A4CACh,8h,PeerID: 93 | A4CB8h,14h,processing handshake 94 | A4CD0h,7h,", time=" 95 | A4CD8h,34h,Peer got too many bytes or too long duration: bytes= 96 | A4D10h,1Dh,Peer sent too many bad pieces 97 | A4D30h,22h,peer trying to send too much data: 98 | ``` 99 | 100 | We can also see where it appears to read in a bunch of parameters from somewhere: 101 | 102 | ![Reverse Engineer 3](https://i.imgur.com/d6OJSY1.png) 103 | ``` 104 | directDownloadURL 105 | nop2p 106 | nohttp 107 | noseeds 108 | onlyblizzpeers 109 | noblizzpeers 110 | trackerless 111 | allowincoming 112 | testmode 113 | ``` 114 | 115 | Considering the Direct Downloader URL is sent from the tracker, I wonder if the other parameters need to be supplied that way somehow: 116 | ```php 117 | // begin response 118 | $response = 'd8:intervali' . $_SERVER['tracker']['announce_interval'] . 119 | $response = strlen($direct_download[0]) ? 'd6:directd3:url' . strlen($direct_download[0]) . ':' . $direct_download[0] . '9:thresholdi10000000ee' : 'd'; 120 | $response .= '8:intervali' . $_SERVER['tracker']['announce_interval'] . 121 | 'e12:min intervali' . $_SERVER['tracker']['min_interval'] . 122 | 'e5:peers'; 123 | 124 | ``` 125 | 126 | I have tested trying to set test mode through various properties in the torrent file and have also tried setting it in the `BlizzardDownloader.ini` file it tries to read. Another thing I have noted is that in the logs we can see it trying to connect two hardcoded IP's for the ini file: 127 | 128 | `17:55:45.0571 Checking for server side config http://206.16.22.130/update/Downloader.ini` 129 | 130 | `15:57:40.6400 Checking for server side config http://12.129.232.131/update/Downloader.ini` 131 | 132 | I could not find in the binary where it is defining this IP to try to connect to. It appears to use one of the two each run at random, and doesn't try connecting to both in a single run. 133 | 134 | From old IRC logs I can see http://us.version.worldofwarcraft.com/update/PatchSequenceFile.txt mapped to http://206.16.22.126/update/PatchSequenceFile.txt 135 | 136 | I did test trying to run the Blizzard Downloader with debug arguments to try and trigger debug mode, but with no success. I think the only arguments are: `--hiddenProcessing` which runs the program headless, and `--hiddenPort=%d`. 137 | 138 | ### 2021-06-29 139 | 140 | I did manage to get debug mode enabled with Schlumpf's help. It looks like all usage has been stripped from the code however, including parsing the argument from the command line arguments. We replaced `74 19 C6 46 68 01 C6 86 DB 00` with `90 90 C6 46 68 01 C6 86 DB 00` to force debug mode to be enabled. 141 | 142 | To enable test mode: 143 | ``` 144 | let .text:00465BEB jz short loc_465C06 be a nop (0x90). 145 | ``` 146 | Search for `74 19 C6 46 68 01 C6 86 DB 00` in the binary, replace with `90 90 …`. 147 | 148 | `.\HourOfTwilight_Downloader.exe --noblizzpeers --port=3725` 149 | Doesn't appear to output anything differently 150 | `.\HourOfTwilight_Downloader.exe --port=3725 --noblizzpeers -log log -noblizpeers noblizpeers port 3725 -port 3725 -onlyblizzpeers false onlyblizzpeers false` 151 | Something in this set of parameters causes it to log in a folder called XXogs\downloader-testmode.log where XX is random characters each run 152 | 153 | ![WoW Folder with bad Logs folder](https://i.imgur.com/lHE7GFi.png) 154 | 155 | This logs to `downloader-testmode.log`: 156 | ``` 157 | #----------------------------------------------------------- 158 | # System started at 2021-04-19 20:32:53.1403 159 | # system: HARRY 160 | #----------------------------------------------------------- 161 | 20:32:56.4410 Downloader Test Mode Log 162 | 20:36:11.4058 Done 163 | ``` 164 | 165 | We also discovered that the IP's it connects to is: one of `(us|eu|kr|cn|tw).version.worldofwarcraft.com`. We can disable it pinging this IP by changing: 166 | ``` 167 | .text:00402211 6A 01 push 1 be push 0. 168 | ``` 169 | 170 | Full list of possible arguments: 171 | ``` 172 | version 173 | seed 174 | flood 175 | port 176 | choosepath 177 | responsefile 178 | responselist 179 | locale 180 | url 181 | tracker 182 | checkhashes 183 | minuploadrate 184 | maxuploadrate 185 | launchtarget 186 | maxsimultaneous 187 | maxuploads 188 | maxallowin 189 | allowincoming 190 | trackerless 191 | spew 192 | passThroughParams 193 | updateinterval 194 | saveas 195 | minpeers 196 | noblizseeds 197 | onlyblizseeds <<<<< 198 | noblizpeers 199 | onlyblizpeers 200 | noseeds 201 | piecestorage 202 | nohttp 203 | nop2p <<<<<<< 204 | background 205 | bgseed 206 | autolaunch 207 | maxpending 208 | cookieName 209 | cookieData 210 | directDownloadURL 211 | logfile <<<< 212 | log <<< 213 | logFolder <<<< 214 | silent 215 | hiddenProcessing 216 | hiddenPort 217 | closewhendiskfull 218 | autothrottle 219 | usemetafile 220 | scratch 221 | rescanifbadpiece 222 | memcached 223 | mcconfpath 224 | idleopt 225 | maxallowwhenslow 226 | upnpby 227 | dontusetracker 228 | dontusebonjour 229 | streaming 230 | mtui 231 | downloadlabel 232 | usenewthreshold 233 | uponly 234 | noskin 235 | help 236 | usage 237 | ``` 238 | 239 | I still have no clue why the peer to peer aspect of the Downloader is not working. 240 | 241 | ## Changing the WoW client version 242 | 243 | The client build is stored as a uint16 in a few places in the binary. The one that is read by the authserver during authentication in 3.3.5 is at: `0x4C99F0`. 244 | 245 | A very simple program to change the WoW version: 246 | ```csharp 247 | using System; 248 | using System.Collections.Generic; 249 | using System.IO; 250 | using System.Linq; 251 | using System.Text; 252 | using System.Threading.Tasks; 253 | 254 | namespace WoWVersionEditor 255 | { 256 | class Program 257 | { 258 | private const uint _VersionOffset = 0x4C99F0; // 5020144 259 | 260 | static void Main(string[] args) 261 | { 262 | try 263 | { 264 | var path = args[0]; 265 | var currentVersion = ReadVersion(path); 266 | var newVersion = args[1].Equals("+") ? ushort.Parse((currentVersion + 1).ToString()) : ushort.Parse(args[1]); 267 | Console.WriteLine("Updating: " + path); 268 | Console.WriteLine("Current version: " + currentVersion); 269 | Console.WriteLine("New version: " + newVersion); 270 | WriteVersion(path, $"{path}_{newVersion}.exe", newVersion); 271 | Console.WriteLine("Done"); 272 | } 273 | catch (Exception e) 274 | { 275 | Console.WriteLine($"[ERROR] {e.GetType()}: {e.Message}\n{e}"); 276 | } 277 | } 278 | 279 | static ushort ReadVersion(string filePath) 280 | { 281 | using (var stream = File.Open(filePath, FileMode.Open)) 282 | { 283 | stream.Position = _VersionOffset; 284 | using (var reader = new BinaryReader(stream)) 285 | { 286 | return reader.ReadUInt16(); 287 | } 288 | } 289 | } 290 | 291 | static void WriteVersion(string oldFilePath, string newFilePath, ushort version) 292 | { 293 | File.Copy(oldFilePath, newFilePath, true); 294 | using (var stream = File.Open(newFilePath, FileMode.Open)) 295 | { 296 | stream.Position = _VersionOffset; 297 | using (var reader = new BinaryWriter(stream)) 298 | { 299 | reader.Write(version); 300 | } 301 | } 302 | } 303 | } 304 | } 305 | ``` 306 | -------------------------------------------------------------------------------- /Read/Implementing in-client patching for World of Warcraft.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage[top=5cm, bottom=5cm, left=3.5cm, right=3.5cm]{geometry} 4 | \usepackage[german]{babel} 5 | \usepackage{xspace} 6 | \usepackage{hyperref} 7 | \usepackage{graphicx} 8 | \usepackage[np]{numprint} 9 | \usepackage{listings} 10 | \usepackage{color} 11 | 12 | \newcommand{\wow}{World of Warcraft\xspace} 13 | \newcommand{\mpq}{MoPaQ\xspace} 14 | \newcommand{\blizzard}{Blizzard\xspace} 15 | \newcommand{\file}[1]{\emph{#1}\xspace} 16 | \newcommand{\cmd}[1]{\lstinline{#1}\xspace} 17 | 18 | \usepackage{listings} 19 | \usepackage{color} 20 | 21 | \definecolor{dkgreen}{rgb}{0,0.6,0} 22 | \definecolor{gray}{rgb}{0.5,0.5,0.5} 23 | \definecolor{mauve}{rgb}{0.58,0,0.82} 24 | 25 | \lstset{ 26 | language=C++, 27 | basicstyle=\footnotesize, 28 | numbers=left, 29 | numberstyle=\tiny\color{gray}, 30 | stepnumber=1, 31 | numbersep=5pt, 32 | backgroundcolor=\color{white}, 33 | showspaces=false, 34 | showstringspaces=false, 35 | showtabs=false, 36 | rulecolor=\color{black}, 37 | tabsize=2, 38 | captionpos=t, 39 | breaklines=true, 40 | breakatwhitespace=true, 41 | title=\lstname, 42 | keywordstyle=\color{blue}, 43 | commentstyle=\color{dkgreen}, 44 | stringstyle=\color{mauve}, 45 | morekeywords={extract,execute} 46 | } 47 | 48 | \begin{document} 49 | \begin{titlepage} 50 | 51 | \begin{center} 52 | 53 | \begin{figure} 54 | \includegraphics[width=\textwidth]{client_downloading} 55 | \caption{The \wow client downloading a patch.} 56 | \label{fig:download} 57 | \end{figure} 58 | 59 | \textsc{}\\[1cm] 60 | 61 | \textsc{\Large Tutorial}\\[0.5cm] 62 | 63 | { \huge \bfseries Implementing in-client patching for \wow}\\[2cm] 64 | 65 | 66 | \large \emph{\glqq{}stoneharry\grqq{}} 67 | 68 | \large Bernd \emph{\glqq{}schlumpf\grqq{}} \textsc{L\"orwald} 69 | 70 | \vfill 71 | 72 | % Bottom of the page 73 | {\large \today} 74 | 75 | \end{center} 76 | 77 | \end{titlepage} 78 | 79 | \section{What is this tutorial about?} 80 | 81 | This tutorial allows you to use the built-in \wow updating functionality, so that you can send custom patches to the client based on their client version. This is shown in figure \ref{fig:download}. 82 | 83 | \section{How to construct a patch} 84 | 85 | The patching process allows you to simply send a \mpq file to the client, which can include arbitrary files and a list of commands being executed as soon as the \mpq is downloaded called \file{prepatch.lst} 86 | 87 | Typically, such a \mpq file includes a binary \file{installer.exe} to be executed as well as a \file{prepatch.lst} file saying 88 | \begin{lstlisting} 89 | delete some_no_longer_needed_file 90 | extract some_new_file 91 | extract installer.exe 92 | execute installer.exe 93 | \end{lstlisting} 94 | 95 | This extracts the updater, and then runs it. The updater handles the updating process and then deleting the \file{wow-patch.mpq}. \file{wow-patch.mpq} is what the client calls the \mpq file downloaded from the server, and is checked for and ran if found upon logging in. 96 | 97 | The \file{prepatch.lst} can include the commands 98 | \begin{itemize} 99 | \item \cmd{execute}, which executes an arbitrary file given. 100 | \item \cmd{extract}, which extracts a file from the \mpq. 101 | \item \cmd{delete}, which deletes the named file. 102 | \end{itemize} 103 | The file needs to be saved with windows-style line endings (\textbackslash r\textbackslash n). Each line can be at most \np{260} characters long. 104 | 105 | \section{Sending the client the patch.} 106 | 107 | When logging into the server, the server receives the client's build number. Depending on the client's build, it is able to do different things. 108 | 109 | You need to make it so that if the client build is lower or equal to \np{12340} (patch \np{3}.\np{3}.\np{5}a), the server will check for updates, and send them to the client. 110 | 111 | The patching process works by the patch being selected, and then the server telling the client that it is about to send a patch and how big the patch is. The client accepts this, and tells the server where to start sending from -- the full patch if not started being sent before. This means that if the client is disconnected during the transfer for whatever reason, it can resume from where it left off. The server will keep sending \np[byte]{1500} chunks of the patch until the client has the full patch. The client will thus have a \file{wow-patch.mpq} in the \wow directory now, and when the \cmd{restart} button is pressed it will try to open it and execute the contained \file{prepatch.lst}. 112 | 113 | The source file modifications required to get this process to work on server side are listed below: 114 | 115 | \subsection{ArcEmu} 116 | 117 | All of the edits described below take place in the ArcEmu-Logonserver project. 118 | 119 | In \file{main.cpp} we have this code snippet: 120 | \lstinputlisting[caption={}]{arcemu_main_loop.cpp} 121 | 122 | Each second this code snippet is called. We are interested in line \np{16}. A job is created when a patch is sending. Using this code, it would send \np{1500} bytes (\np{1} chunk) each second. This would take a very, very long time to send \np[MB]{100}. By reducing the sleep time you can make it so that it sends at a much faster rate. You would have to increase the wait time on the other checks by adding to the \% value checks. 123 | 124 | A much more efficient way to handle it would be to add the UpdateJobs check to a new thread. However, in reality the logonserver uses very, very little CPU and most machines can run this without any issues, which is bad logic but is a quick implementation: 125 | \lstinputlisting[caption={}]{arcemu_main_loop_scaled.cpp} 126 | 127 | 128 | The following code snippet from \file{AuthSocket.cpp} shows how the patch is selected for the client: 129 | \lstinputlisting[caption={}]{arcemu_patch_check.cpp} 130 | The logic is that if the client version is less than the server version, then to find the patch for the client. If the patch is found, then to send it to them, else to return a wrong build error. 131 | 132 | In ArcEmu the patches are formatted with the pattern \file{LocaleBuild.mpq}. These go in a folder called \file{ClientPatches/} in the same folder as your world executable. For example, for a enGB client running the build \np{12340} (patch \np{3}.\np{3}.\np{5}a) that you would like to update, you would name the patch \file{enGB12340.mpq}. 133 | 134 | The next change happens in \file{AutoPatcher.cpp}: Replace the whole method named \lstinline{Patch * PatchMgr::FindPatchForClient(uint32 Version, const char * Locality)} with the following listing. You will also need to correct the header file to match the new signatures. Also, you need to add a call to \lstinline{InitializePatchList()} to \lstinline{PatchMgr::PatchMgr()}. 135 | 136 | \lstinputlisting[caption={Corrected version of Patch* PatchMgr::FindPatchForClient()}]{arcemu_patch_for_client.cpp} 137 | 138 | This changes it so that upon this function being called, it gets the correct patch and returns it. 139 | 140 | Next go to the function \lstinline{bool PatchMgr::InitiatePatch(Patch * pPatch, AuthSocket * pClient)} and remove the last assignment in the line \lstinline{init.name[0] = 'P'; init.name[1] = 'a'; init.name[2] = 't'; init.name[3] = 'c'; init.name[4] = 'h'; init.name[5] = '\0';}, so that it becomes 141 | \begin{lstlisting} 142 | init.name[0] = 'P'; 143 | init.name[1] = 'a'; 144 | init.name[2] = 't'; 145 | init.name[3] = 'c'; 146 | init.name[4] = 'h'; 147 | \end{lstlisting} 148 | 149 | Inside \file{AutoPatcher.cpp}, you also find this definition, which has the size of \lstinline{char name[];} off by one. Correct the size to be \np{5} instead of \np{6}. 150 | \begin{lstlisting} 151 | struct TransferInitiatePacket 152 | { 153 | uint8 cmd; 154 | uint8 strsize; 155 | char name[6]; 156 | uint64 filesize; 157 | uint8 md5hash[MD5_DIGEST_LENGTH]; 158 | }; 159 | \end{lstlisting} 160 | 161 | Some of the code above might not work on other platforms than Windows. You may want to adjust it where needed. 162 | 163 | \subsection{TrinityCore} 164 | 165 | A patch for TrinityCore is provided by schlumpf \href{http://pastebin.com/0BwqxVGf}{here}. It was based on an older version, so you might need to adjust parts of it. 166 | 167 | \section{Patching the client to verify the patch} 168 | 169 | For the client to verify and attempt to install a patch, you would have to sign your patch with \blizzard's private key, which is sadly non-public. Therefore you need to disable the client verifying that the patch is signed by \blizzard. The following code is for the OSX version of \wow Mists of Pandaria, so your experience on Windows will differ. 170 | 171 | As soon as you click the \cmd{restart} button after downloading the patch, the code seen in listing \ref{code:patchdownloadapply} gets executed. As you can see, the part responsible for failing is in line \np{17} to \np{21}. If \lstinline{SFileAuthenticateArchiveEx()} returns a value of \lstinline{authresult} less or equal to \np{4}, patching will be aborted. Therefore, one needs to either change the \lstinline{if} to always be true and therefore be executing the patch, or \lstinline{SFileAuthenticateArchiveEx()} to always verify the archive. You can do the latter either by changing modulus and exponent to your own ones -- which would be good, seeing as your client can't be hijacked by others than and be forced to execute malicious patches -- or by changing \lstinline{SFileAuthenticateArchiveEx()} which only is a wrapper for \lstinline{Blizzard::Mopaq::SFileAuthenticateArchiveEx()}, which sets up a RSA / SHA-1 signature structure which is then comparing the actual signature of the \mpq with the signature in the given \file{signaturefile}. 172 | 173 | Changing \lstinline{SFileAuthenticateArchiveEx()} instead of \lstinline{CGlueMgr::PatchDownloadApply()} has the advantage of also enabling custom surveys, which can be streamed to the user on login and should therefore be chosen to be patched. You can see the C++ version of \lstinline{SFileAuthenticateArchiveEx()} in listing \ref{code:SFileAuthenticateArchiveEx} and the assembler version in listing \ref{code:SFileAuthenticateArchiveExASM}. As you easily can see, the \lstinline{if} needs to be removed and \lstinline{*authresult = authresult_temp;} needs to be changed into \lstinline{*authresult = 5;}. As it is easier just rewriting that function than modifying it, I suggest patching it to be looking as seen in listings \ref{code:auth_fixed} and \ref{code:auth_fixed_asm}. 174 | 175 | \lstinputlisting[label=code:patchdownloadapply, caption={void CGlueMgr::PatchDownloadApply()}]{CGlueMgr__PatchDownloadApply.cpp} 176 | 177 | \lstinputlisting[label=code:SFileAuthenticateArchiveEx,caption={bool SFileAuthenticateArchiveEx()}]{SFileAuthenticateArchiveEx.cpp} 178 | \lstinputlisting[language={[x86masm]Assembler},label=code:SFileAuthenticateArchiveExASM,caption={Assembler version of bool SFileAuthenticateArchiveEx()}]{SFileAuthenticateArchiveEx.asm} 179 | 180 | \lstinputlisting[label= code:auth_fixed,caption={proposed patch for bool SFileAuthenticateArchiveEx()}]{SFileAuthenticateArchiveEx_fixed.cpp} 181 | \lstinputlisting[language={[x86masm]Assembler},label= code:auth_fixed_asm,caption={Assembler version of proposed patch for bool SFileAuthenticateArchiveEx()}]{SFileAuthenticateArchiveEx_fixed.asm} 182 | 183 | \section{Applying the patch} 184 | 185 | The executable run from the \mpq is where you update the client. Myself, I wrote a quick application in C++ that renames the \file{WoW.exe} (as a backup) then writes files from the \file{wow-patch.mpq} into a \file{patch.mpq} and deletes the \file{Cache/} folder as well as the \file{wow-patch.mpq}. It also writes a new executable \file{WoW.exe}, which has an updated build number and then starts the new \file{WoW.exe}. Now the client should be fully up to date and be accepted by your server's check and proceed to log in without additional patching. 186 | 187 | \section{Updating the client version} 188 | 189 | There are different locations, where the build number is referenced in the client. There is a string variant to be written onto the login screen. This one is supplied by \lstinline{int Script_GetBuildInfo(lua_State *)}. You can easily find the location to modify via just searching for the number given on the login screen as string. You will come up with two locations: One where the build number is stored and another one where the version as well as build-date is. These are all only for logging and to show the user which version he has and should be set by you to help the user identify the version. The actually relevant build number for patching is in \lstinline{RealmConnection::HandleAuthChallenge()}. 190 | 191 | Offsets specific to build \np{12340} (patch \np{3}.\np{3}.\np{5}a) can be found \href{http://www.ownedcore.com/forums/general/programming/326606-wow-exe-custom-build.html}{on OwnedCore}. 192 | 193 | I would always advise making a back up of your WoW executable before attempting to modify anything. You should use incrementing numbers above \np{12340}, to be able to supply incremental patches. Every patch should have a different build number, of course. 194 | 195 | \end{document} 196 | -------------------------------------------------------------------------------- /Read/trinity_patching.diff: -------------------------------------------------------------------------------- 1 | --- a/src/server/authserver/Main.cpp Sun Jan 09 13:02:12 2011 +0100 2 | +++ b/src/server/authserver/Main.cpp Mon Jan 10 19:25:05 2011 +0100 3 | @@ -43,6 +43,8 @@ 4 | 5 | LoginDatabaseWorkerPool LoginDatabase; // Accessor to the realm server database 6 | 7 | +extern Patcher patcher; 8 | + 9 | // Handle realmd's termination signals 10 | class RealmdSignalHandler : public Trinity::SignalHandler 11 | { 12 | @@ -101,6 +103,8 @@ 13 | sLog->outString("%s (realm-daemon)", _FULLVERSION); 14 | sLog->outString(" to stop.\n"); 15 | sLog->outString("Using configuration file %s.", cfg_file); 16 | + 17 | + patcher.Initialize(); 18 | 19 | sLog->outDetail("%s (Library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); 20 | 21 | --- a/src/server/authserver/Server/AuthSocket.cpp Sun Jan 09 13:02:12 2011 +0100 22 | +++ b/src/server/authserver/Server/AuthSocket.cpp Mon Jan 10 19:25:05 2011 +0100 23 | @@ -16,8 +16,6 @@ 24 | * with this program. If not, see . 25 | */ 26 | 27 | -#include 28 | - 29 | #include "Common.h" 30 | #include "Database/DatabaseEnv.h" 31 | #include "ByteBuffer.h" 32 | @@ -50,6 +48,13 @@ 33 | STATUS_AUTHED 34 | }; 35 | 36 | +// TODO: Add as config variable. 37 | +#ifndef _WIN32 38 | +#define PATCH_PATH "../var/patches/" 39 | +#else 40 | +#define PATCH_PATH "./patches/" 41 | +#endif 42 | + 43 | // GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some paltform 44 | #if defined(__GNUC__) 45 | #pragma pack(1) 46 | @@ -143,38 +148,360 @@ 47 | #pragma pack(pop) 48 | #endif 49 | 50 | -// Launch a thread to transfer a patch to the client 51 | -class PatcherRunnable: public ACE_Based::Runnable 52 | +Patcher patcher; 53 | + 54 | + 55 | +PATCH_INFO* Patcher::getPatchInfo(int _build, std::string _locale, bool* fallback) 56 | { 57 | -public: 58 | - PatcherRunnable(class AuthSocket *); 59 | - void run(); 60 | + PATCH_INFO* patch = NULL; 61 | + int locale = *((int*)(_locale.c_str())); 62 | + 63 | + sLog->outDebug("Client with version %i and locale %s (%x) looking for patch.", _build, _locale.c_str(), locale); 64 | + 65 | + for(Patches::iterator it = _patches.begin(); it != _patches.end(); ++it) 66 | + if(it->build == _build && it->locale == 'BGne') 67 | + { 68 | + patch = &(*it); 69 | + *fallback = true; 70 | + } 71 | + 72 | + for(Patches::iterator it = _patches.begin(); it != _patches.end(); ++it) 73 | + if(it->build == _build && it->locale == locale) 74 | + { 75 | + patch = &(*it); 76 | + *fallback = false; 77 | + } 78 | + 79 | + return patch; 80 | +} 81 | 82 | -private: 83 | - AuthSocket * mySocket; 84 | +bool Patcher::PossiblePatching(int _build, std::string _locale) 85 | +{ 86 | + bool temp; 87 | + return getPatchInfo(_build, _locale, &temp) != NULL; 88 | +} 89 | + 90 | +bool Patcher::InitPatching(int _build, std::string _locale, AuthSocket* _authsocket) 91 | +{ 92 | + bool fallback; 93 | + PATCH_INFO* patch = getPatchInfo(_build, _locale,&fallback); 94 | + 95 | + // one of them nonzero, start patching. 96 | + if(patch) 97 | + { 98 | + uint8 bytes[2] = {0x01,0x0a}; // authed, patch inc! 99 | + _authsocket->socket().send((char *)&bytes, sizeof(bytes)); 100 | + 101 | + std::stringstream path; 102 | + if(fallback) 103 | + { 104 | + path << PATCH_PATH << _build << "-enGB.mpq"; 105 | + } 106 | + else 107 | + { 108 | + path << PATCH_PATH << _build << "-" << _locale << ".mpq"; 109 | + } 110 | + _authsocket->pPatch = fopen(path.str().c_str(),"rb"); 111 | + sLog->outError("Patch: %s", path.str().c_str()); 112 | + XFER_INIT packet; 113 | + packet.cmd = XFER_INITIATE; 114 | + packet.fileNameLen = 5; 115 | + packet.fileName[0] = 'P'; 116 | + packet.fileName[1] = 'a'; 117 | + packet.fileName[2] = 't'; 118 | + packet.fileName[3] = 'c'; 119 | + packet.fileName[4] = 'h'; 120 | + packet.file_size = patch->filesize; 121 | + memcpy(packet.md5,patch->md5,MD5_DIGEST_LENGTH); 122 | + _authsocket->socket().send((char *)&packet, sizeof(packet)); 123 | + return true; 124 | + } 125 | + else 126 | + { 127 | + sLog->outError("Client with version %i and locale %s did not get a patch.", _build, _locale.c_str()); 128 | + return false; 129 | + } 130 | +} 131 | + 132 | +// Preload MD5 hashes of existing patch files on server 133 | +#ifndef _WIN32 134 | +#include 135 | +#include 136 | +void Patcher::LoadPatchesInfo() 137 | +{ 138 | + DIR *dirp; 139 | + struct dirent *dp; 140 | + dirp = opendir(PATCH_PATH); 141 | + 142 | + if (!dirp) 143 | + return; 144 | + 145 | + while (dirp) 146 | + { 147 | + errno = 0; 148 | + if ((dp = readdir(dirp)) != NULL) 149 | + { 150 | + int l = strlen(dp->d_name); 151 | + 152 | + if (l < 8) 153 | + continue; 154 | + 155 | + if (!memcmp(&dp->d_name[l - 4], ".mpq", 4)) 156 | + { 157 | + LoadPatchMD5(PATCH_PATH, dp->d_name); 158 | + } 159 | + } 160 | + else 161 | + { 162 | + if (errno != 0) 163 | + { 164 | + closedir(dirp); 165 | + return; 166 | + } 167 | + break; 168 | + } 169 | + } 170 | + 171 | + if (dirp) 172 | + closedir(dirp); 173 | +} 174 | +#else 175 | +void Patcher::LoadPatchesInfo() 176 | +{ 177 | + WIN32_FIND_DATA fil; 178 | + HANDLE hFil = FindFirstFile(PATCH_PATH "*.mpq", &fil); 179 | + if (hFil == INVALID_HANDLE_VALUE) 180 | + return; // no patches were found 181 | + 182 | + do 183 | + { 184 | + LoadPatchMD5(PATCH_PATH, fil.cFileName); 185 | + } 186 | + while (FindNextFile(hFil, &fil)); 187 | +} 188 | +#endif 189 | + 190 | +// Calculate and store MD5 hash for a given patch file 191 | +void Patcher::LoadPatchMD5(const char* szPath,char *szFileName) 192 | +{ 193 | + int build; 194 | + union 195 | + { 196 | + int i; 197 | + char c[4]; 198 | + } locale; 199 | + 200 | + if(sscanf(szFileName,"%i-%c%c%c%c.mpq",&build,&locale.c[0],&locale.c[1],&locale.c[2],&locale.c[3]) != 5) 201 | + return; 202 | + 203 | + // Try to open the patch file 204 | + std::string path = szPath; 205 | + path += szFileName; 206 | + FILE *pPatch = fopen(path.c_str(), "rb"); 207 | + 208 | + if (!pPatch) 209 | + { 210 | + sLog->outError("Error loading patch %s\n", path.c_str()); 211 | + return; 212 | + } 213 | + 214 | + // Calculate the MD5 hash 215 | + MD5_CTX ctx; 216 | + MD5_Init(&ctx); 217 | + uint8* buf = new uint8[512 * 1024]; 218 | + 219 | + while (!feof(pPatch)) 220 | + { 221 | + size_t read = fread(buf, 1, 512 * 1024, pPatch); 222 | + MD5_Update(&ctx, buf, read); 223 | + } 224 | + 225 | + delete [] buf; 226 | + fseek(pPatch,0,SEEK_END); 227 | + size_t size = ftell(pPatch); 228 | + fclose(pPatch); 229 | + 230 | + // Store the result in the internal patch hash map 231 | + PATCH_INFO pi; 232 | + pi.build = build; 233 | + pi.locale = locale.i; 234 | + pi.filesize = uint64(size); 235 | + MD5_Final((uint8 *)&pi.md5, &ctx); 236 | + _patches.push_back(pi); 237 | + sLog->outDebug("Added patch for %i %c%c%c%c.",build,locale.c[0],locale.c[1],locale.c[2],locale.c[3]); 238 | +} 239 | + 240 | +// Resume patch transfer 241 | +bool AuthSocket::_HandleXferResume() 242 | +{ 243 | + // Check packet length and patch existence 244 | + if (socket().recv_len() < 9 || !pPatch) 245 | + { 246 | + sLog->outError("Error while resuming patch transfer (wrong packet)"); 247 | + return false; 248 | + } 249 | + 250 | + // Launch a PatcherRunnable thread starting at given patch file offset 251 | + uint64 start; 252 | + socket().recv_skip(1); 253 | + socket().recv((char*)&start,sizeof(start)); 254 | + 255 | + fseek(pPatch,0,SEEK_END); 256 | + size_t size = ftell(pPatch); 257 | + 258 | + fseek(pPatch, long(start), 0); 259 | + 260 | + if(_patcher) 261 | + { 262 | + _patcher->stop(); 263 | + delete _patcher; 264 | + } 265 | + _patcher = new PatcherRunnable(this,start,size); 266 | + ACE_Based::Thread u(_patcher); 267 | + return true; 268 | +} 269 | + 270 | +// Cancel patch transfer 271 | +bool AuthSocket::_HandleXferCancel() 272 | +{ 273 | + sLog->outStaticDebug("Entering _HandleXferCancel"); 274 | + 275 | + // Close and delete the socket 276 | + socket().recv_skip(1); //clear input buffer 277 | + socket().shutdown(); 278 | + 279 | + return true; 280 | +} 281 | + 282 | +// Accept patch transfer 283 | +bool AuthSocket::_HandleXferAccept() 284 | +{ 285 | + // Check packet length and patch existence 286 | + if (!pPatch) 287 | + { 288 | + sLog->outError("Error while accepting patch transfer (wrong packet)"); 289 | + return false; 290 | + } 291 | + 292 | + // Launch a PatcherRunnable thread, starting at the beginning of the patch file 293 | + socket().recv_skip(1); // clear input buffer 294 | + fseek(pPatch,0,SEEK_END); 295 | + size_t size = ftell(pPatch); 296 | + fseek(pPatch, 0, 0); 297 | + 298 | + if(_patcher) 299 | + { 300 | + _patcher->stop(); 301 | + delete _patcher; 302 | + } 303 | + _patcher = new PatcherRunnable(this,0,size); 304 | + ACE_Based::Thread u(_patcher); 305 | + return true; 306 | +} 307 | + 308 | +PatcherRunnable::PatcherRunnable(class AuthSocket * as,uint64 _pos,uint64 _size) 309 | +{ 310 | + mySocket = as; 311 | + pos = _pos; 312 | + size = _size; 313 | + stopped = false; 314 | +} 315 | + 316 | +void PatcherRunnable::stop() 317 | +{ 318 | + stopped = true; 319 | +} 320 | + 321 | +#if defined(__GNUC__) 322 | +#pragma pack(1) 323 | +#else 324 | +#pragma pack(push,1) 325 | +#endif 326 | +struct TransferDataPacket 327 | +{ 328 | + uint8 cmd; 329 | + uint16 chunk_size; 330 | }; 331 | +#if defined(__GNUC__) 332 | +#pragma pack() 333 | +#else 334 | +#pragma pack(pop) 335 | +#endif 336 | 337 | -typedef struct PATCH_INFO 338 | +// Send content of patch file to the client 339 | +void PatcherRunnable::run() 340 | { 341 | - uint8 md5[MD5_DIGEST_LENGTH]; 342 | -} PATCH_INFO; 343 | + sLog->outDebug("PatcherRunnable::run(): %ld -> %ld",pos,size); 344 | + //sleep(1); 345 | + //sLog->outDebug("PatcherRunnable::run(): go!"); 346 | + 347 | + while( pos < size && !stopped ) 348 | + { 349 | + uint64 left = size - pos; 350 | + uint16 send = (left>4096)?4096:left; 351 | + 352 | + char* tosend = new char[sizeof(TransferDataPacket)+send]; 353 | + TransferDataPacket* hdr = (TransferDataPacket*)tosend; 354 | + hdr->cmd = 0x31; 355 | + hdr->chunk_size = send; 356 | + fread(tosend+sizeof(TransferDataPacket),1,send,mySocket->pPatch); 357 | + mySocket->socket().send(tosend, sizeof(TransferDataPacket)+send); 358 | + 359 | + delete[] tosend; 360 | + 361 | + pos += send; 362 | + usleep(1000); 363 | + } 364 | + 365 | + if(!stopped) 366 | + { 367 | + fclose(mySocket->pPatch); 368 | + mySocket->pPatch = NULL; 369 | + mySocket->_patcher = NULL; 370 | + } 371 | + 372 | + sLog->outDebug("patcher done."); 373 | +} 374 | 375 | -// Caches MD5 hash of client patches present on the server 376 | -class Patcher 377 | +/* 378 | +// Get cached MD5 hash for a given patch file 379 | +bool Patcher::GetHash(char * pat, uint8 mymd5[16]) 380 | { 381 | -public: 382 | - typedef std::map Patches; 383 | - ~Patcher(); 384 | - Patcher(); 385 | - Patches::const_iterator begin() const { return _patches.begin(); } 386 | - Patches::const_iterator end() const { return _patches.end(); } 387 | - void LoadPatchMD5(char*); 388 | - bool GetHash(char * pat,uint8 mymd5[16]); 389 | + for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) 390 | + if (!stricmp(pat, i->first.c_str())) 391 | + { 392 | + memcpy(mymd5, i->second->md5, 16); 393 | + return true; 394 | + } 395 | 396 | -private: 397 | - void LoadPatchesInfo(); 398 | - Patches _patches; 399 | -}; 400 | + return false; 401 | +}*/ 402 | + 403 | +// Launch the patch hashing mechanism on object creation 404 | +void Patcher::Initialize() 405 | +{ 406 | + sLog->outDebug("Searching for available patches."); 407 | + LoadPatchesInfo(); 408 | +} 409 | + 410 | + 411 | + 412 | + 413 | +// Close patch file descriptor before leaving 414 | +AuthSocket::~AuthSocket(void) 415 | +{ 416 | + if(pPatch) 417 | + { 418 | + fclose(pPatch); 419 | + pPatch = NULL; 420 | + } 421 | + if(_patcher) 422 | + { 423 | + _patcher->stop(); 424 | + delete _patcher; 425 | + _patcher = NULL; 426 | + } 427 | +} 428 | 429 | const AuthHandler table[] = 430 | { 431 | @@ -190,9 +517,6 @@ 432 | 433 | #define AUTH_TOTAL_COMMANDS 8 434 | 435 | -// Holds the MD5 hash of client patches present on the server 436 | -Patcher PatchesCache; 437 | - 438 | // Constructor - set the N and g values for SRP6 439 | AuthSocket::AuthSocket(RealmSocket& socket) : socket_(socket) 440 | { 441 | @@ -200,11 +524,10 @@ 442 | g.SetDword(7); 443 | _authed = false; 444 | _accountSecurityLevel = SEC_PLAYER; 445 | + pPatch = NULL; 446 | + _patcher = NULL; 447 | } 448 | 449 | -// Close patch file descriptor before leaving 450 | -AuthSocket::~AuthSocket(void) {} 451 | - 452 | // Accept the connection and set the s random value for SRP6 453 | void AuthSocket::OnAccept(void) 454 | { 455 | @@ -342,9 +665,20 @@ 456 | _login = (const char*)ch->I; 457 | _build = ch->build; 458 | _expversion = (AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : NO_VALID_EXP_FLAG) | (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG); 459 | + 460 | + _localizationName.resize(4); 461 | + for (int i = 0; i < 4; ++i) 462 | + _localizationName[i] = ch->country[4-i-1]; 463 | 464 | pkt << (uint8)AUTH_LOGON_CHALLENGE; 465 | pkt << (uint8)0x00; 466 | + 467 | + if (_expversion == NO_VALID_EXP_FLAG && !patcher.PossiblePatching(_build, _localizationName)) 468 | + { 469 | + pkt << (uint8)WOW_FAIL_VERSION_INVALID; 470 | + socket().send((char const*)pkt.contents(), pkt.size()); 471 | + return true; 472 | + } 473 | 474 | // Verify that this IP is not in the ip_banned table 475 | LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_SET_EXPIREDIPBANS)); 476 | @@ -475,10 +809,6 @@ 477 | uint8 secLevel = fields[4].GetUInt8(); 478 | _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; 479 | 480 | - _localizationName.resize(4); 481 | - for (int i = 0; i < 4; ++i) 482 | - _localizationName[i] = ch->country[4-i-1]; 483 | - 484 | sLog->outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); 485 | } 486 | } 487 | @@ -504,9 +834,10 @@ 488 | // If the client has no valid version 489 | if (_expversion == NO_VALID_EXP_FLAG) 490 | { 491 | - // Check if we have the appropriate patch on the disk 492 | - sLog->outDebug("Client with invalid version, patching is not implemented"); 493 | - socket().shutdown(); 494 | + if( !patcher.InitPatching(_build, _localizationName, this) ) 495 | + { 496 | + socket().shutdown(); 497 | + } 498 | return true; 499 | } 500 | 501 | @@ -894,178 +1225,3 @@ 502 | 503 | return true; 504 | } 505 | - 506 | -// Resume patch transfer 507 | -bool AuthSocket::_HandleXferResume() 508 | -{ 509 | - sLog->outStaticDebug("Entering _HandleXferResume"); 510 | - // Check packet length and patch existence 511 | - if (socket().recv_len() < 9 || !pPatch) 512 | - { 513 | - sLog->outError("Error while resuming patch transfer (wrong packet)"); 514 | - return false; 515 | - } 516 | - 517 | - // Launch a PatcherRunnable thread starting at given patch file offset 518 | - uint64 start; 519 | - socket().recv_skip(1); 520 | - socket().recv((char*)&start,sizeof(start)); 521 | - fseek(pPatch, long(start), 0); 522 | - 523 | - ACE_Based::Thread u(new PatcherRunnable(this)); 524 | - return true; 525 | -} 526 | - 527 | -// Cancel patch transfer 528 | -bool AuthSocket::_HandleXferCancel() 529 | -{ 530 | - sLog->outStaticDebug("Entering _HandleXferCancel"); 531 | - 532 | - // Close and delete the socket 533 | - socket().recv_skip(1); //clear input buffer 534 | - socket().shutdown(); 535 | - 536 | - return true; 537 | -} 538 | - 539 | -// Accept patch transfer 540 | -bool AuthSocket::_HandleXferAccept() 541 | -{ 542 | - sLog->outStaticDebug("Entering _HandleXferAccept"); 543 | - 544 | - // Check packet length and patch existence 545 | - if (!pPatch) 546 | - { 547 | - sLog->outError("Error while accepting patch transfer (wrong packet)"); 548 | - return false; 549 | - } 550 | - 551 | - // Launch a PatcherRunnable thread, starting at the beginning of the patch file 552 | - socket().recv_skip(1); // clear input buffer 553 | - fseek(pPatch, 0, 0); 554 | - 555 | - ACE_Based::Thread u(new PatcherRunnable(this)); 556 | - return true; 557 | -} 558 | - 559 | -PatcherRunnable::PatcherRunnable(class AuthSocket * as) 560 | -{ 561 | - mySocket = as; 562 | -} 563 | - 564 | -// Send content of patch file to the client 565 | -void PatcherRunnable::run() {} 566 | - 567 | -// Preload MD5 hashes of existing patch files on server 568 | -#ifndef _WIN32 569 | -#include 570 | -#include 571 | -void Patcher::LoadPatchesInfo() 572 | -{ 573 | - DIR *dirp; 574 | - struct dirent *dp; 575 | - dirp = opendir("./patches/"); 576 | - 577 | - if (!dirp) 578 | - return; 579 | - 580 | - while (dirp) 581 | - { 582 | - errno = 0; 583 | - if ((dp = readdir(dirp)) != NULL) 584 | - { 585 | - int l = strlen(dp->d_name); 586 | - 587 | - if (l < 8) 588 | - continue; 589 | - 590 | - if (!memcmp(&dp->d_name[l - 4], ".mpq", 4)) 591 | - LoadPatchMD5(dp->d_name); 592 | - } 593 | - else 594 | - { 595 | - if (errno != 0) 596 | - { 597 | - closedir(dirp); 598 | - return; 599 | - } 600 | - break; 601 | - } 602 | - } 603 | - 604 | - if (dirp) 605 | - closedir(dirp); 606 | -} 607 | -#else 608 | -void Patcher::LoadPatchesInfo() 609 | -{ 610 | - WIN32_FIND_DATA fil; 611 | - HANDLE hFil = FindFirstFile("./patches/*.mpq", &fil); 612 | - if (hFil == INVALID_HANDLE_VALUE) 613 | - return; // no patches were found 614 | - 615 | - do 616 | - LoadPatchMD5(fil.cFileName); 617 | - while (FindNextFile(hFil, &fil)); 618 | -} 619 | -#endif 620 | - 621 | -// Calculate and store MD5 hash for a given patch file 622 | -void Patcher::LoadPatchMD5(char *szFileName) 623 | -{ 624 | - // Try to open the patch file 625 | - std::string path = "./patches/"; 626 | - path += szFileName; 627 | - FILE *pPatch = fopen(path.c_str(), "rb"); 628 | - sLog->outDebug("Loading patch info from %s\n", path.c_str()); 629 | - 630 | - if (!pPatch) 631 | - { 632 | - sLog->outError("Error loading patch %s\n", path.c_str()); 633 | - return; 634 | - } 635 | - 636 | - // Calculate the MD5 hash 637 | - MD5_CTX ctx; 638 | - MD5_Init(&ctx); 639 | - uint8* buf = new uint8[512 * 1024]; 640 | - 641 | - while (!feof(pPatch)) 642 | - { 643 | - size_t read = fread(buf, 1, 512 * 1024, pPatch); 644 | - MD5_Update(&ctx, buf, read); 645 | - } 646 | - 647 | - delete [] buf; 648 | - fclose(pPatch); 649 | - 650 | - // Store the result in the internal patch hash map 651 | - _patches[path] = new PATCH_INFO; 652 | - MD5_Final((uint8 *)&_patches[path]->md5, &ctx); 653 | -} 654 | - 655 | -// Get cached MD5 hash for a given patch file 656 | -bool Patcher::GetHash(char * pat, uint8 mymd5[16]) 657 | -{ 658 | - for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) 659 | - if (!stricmp(pat, i->first.c_str())) 660 | - { 661 | - memcpy(mymd5, i->second->md5, 16); 662 | - return true; 663 | - } 664 | - 665 | - return false; 666 | -} 667 | - 668 | -// Launch the patch hashing mechanism on object creation 669 | -Patcher::Patcher() 670 | -{ 671 | - LoadPatchesInfo(); 672 | -} 673 | - 674 | -// Empty and delete the patch map on termination 675 | -Patcher::~Patcher() 676 | -{ 677 | - for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) 678 | - delete i->second; 679 | -} 680 | 681 | --- a/src/server/authserver/Server/AuthSocket.h Sun Jan 09 13:02:12 2011 +0100 682 | +++ b/src/server/authserver/Server/AuthSocket.h Mon Jan 10 19:25:05 2011 +0100 683 | @@ -23,6 +23,8 @@ 684 | #include "BigNumber.h" 685 | #include "RealmSocket.h" 686 | 687 | +#include 688 | + 689 | enum RealmFlags 690 | { 691 | REALM_FLAG_NONE = 0x00, 692 | @@ -36,6 +38,53 @@ 693 | REALM_FLAG_FULL = 0x80 694 | }; 695 | 696 | + 697 | +class AuthSocket; 698 | + 699 | +// clientpatching 700 | + 701 | +typedef struct PATCH_INFO 702 | +{ 703 | + int build; 704 | + int locale; 705 | + uint64 filesize; 706 | + uint8 md5[MD5_DIGEST_LENGTH]; 707 | +} PATCH_INFO; 708 | + 709 | +class Patcher 710 | +{ 711 | + typedef std::vector Patches; 712 | +public: 713 | + void Initialize(); 714 | + 715 | + void LoadPatchMD5(const char*,char*); 716 | + bool GetHash(char * pat,uint8 mymd5[16]); 717 | + 718 | + bool InitPatching(int _build, std::string _locale, AuthSocket* _authsocket); 719 | + bool PossiblePatching(int _build, std::string _locale); 720 | + 721 | +private: 722 | + PATCH_INFO* getPatchInfo(int _build, std::string _locale, bool* fallback); 723 | + 724 | + void LoadPatchesInfo(); 725 | + Patches _patches; 726 | +}; 727 | + 728 | +// Launch a thread to transfer a patch to the client 729 | +class PatcherRunnable: public ACE_Based::Runnable 730 | +{ 731 | +public: 732 | + PatcherRunnable(class AuthSocket *,uint64 start,uint64 size); 733 | + void run(); 734 | + void stop(); 735 | + 736 | +private: 737 | + AuthSocket * mySocket; 738 | + uint64 pos; 739 | + uint64 size; 740 | + bool stopped; 741 | +}; 742 | + 743 | // Handle login commands 744 | class AuthSocket: public RealmSocket::Session 745 | { 746 | @@ -64,10 +113,11 @@ 747 | 748 | FILE *pPatch; 749 | ACE_Thread_Mutex patcherLock; 750 | + PatcherRunnable *_patcher; 751 | 752 | + RealmSocket& socket(void) { return socket_; } 753 | private: 754 | RealmSocket& socket_; 755 | - RealmSocket& socket(void) { return socket_; } 756 | 757 | BigNumber N, s, g, v; 758 | BigNumber b, B; 759 | 760 | --- a/src/server/authserver/Server/RealmSocket.cpp Sun Jan 09 13:02:12 2011 +0100 761 | +++ b/src/server/authserver/Server/RealmSocket.cpp Mon Jan 10 19:25:05 2011 +0100 762 | @@ -246,6 +246,7 @@ 763 | if (session_) 764 | session_->OnClose(); 765 | 766 | + reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK); 767 | return 0; 768 | } 769 | --------------------------------------------------------------------------------