Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- [size=150][b]What Is This Tutorial About?[/b][/size]
- This tutorial allows you to use the in-built WoW updating functionality, so that you can send custom patches to the client based on their client version. This is shown below:
- [img]http://i.imgur.com/l1FGw.jpg[/img]
- [size=150][b]Step 1) How to construct a patch.[/b][/size]
- The patching process allows you to simply send a MPQ file to the client through WoW.
- The typical contents of a Blizzard patch sent through WoW is a prepatch.lst file and a executable file.
- The prepatch.lst file is a text file containing a list of instructions to carry out upon the client loading the patch. Typically, Blizzard has it saying to:
- [code]extract Installer.exe
- execute Installer.exe
- [/code]
- Extract the updater, and then run it. The updater handles the updating process and then deleting the wow-patch.mpq. The 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.
- Blizzard's patch executables are essentially MPQ files. If you rename the extension from a EXE to a MPQ and open it with a MPQ editor, you will see the contents of the installer and how that works.
- [size=150][b]Step 2) Sending the client the patch.[/b][/size]
- When logging into the server, the server receives the clients build and client version. Depending on the client build, it will do different things.
- You need to make it so that if the client build is 12340 (patch 3.3.5a) then check for updates, and send them to the client.
- 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 1500 byte chunks of the patch until the client has the full patch. The client will thus have a wow-patch.mpq in the WoW directory now, and when the "restart" button is pressed it will try to load this.
- The source file modifications required to get this process to work are listed below:
- [b]ArcEmu:[/b]
- All of the edits described below take place in the ArcEmu-Logonserver project.
- In main.cpp we have this code snippet:
- [code]
- if(!(++loop_counter % 20)) // 20 seconds
- CheckForDeadSockets();
- if(!(loop_counter%300)) // 5mins
- ThreadPool.IntegrityCheck();
- if(!(loop_counter%5))
- {
- sInfoCore.TimeoutSockets();
- sSocketGarbageCollector.Update();
- CheckForDeadSockets(); // Flood Protection
- UNIXTIME = time(NULL);
- g_localTime = *localtime(&UNIXTIME);
- }
- PatchMgr::getSingleton().UpdateJobs();
- Arcemu::Sleep(1000);[/code]
- Each second this code snippet is called. The following line:
- PatchMgr::getSingleton().UpdateJobs();
- Is the one we are interested in. A job is created when a patch is sending. Using this code, it would send 1500 bytes (1 chunk) each second. This would take a very, very long time to send 100MB. 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.
- 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:
- [code] while(mrunning.GetVal())
- {
- if(!(++loop_counter % 20000)) // 20 seconds
- CheckForDeadSockets();
- if(!(loop_counter%300000)) // 5mins
- ThreadPool.IntegrityCheck();
- if(!(loop_counter%5000))
- {
- sInfoCore.TimeoutSockets();
- sSocketGarbageCollector.Update();
- CheckForDeadSockets(); // Flood Protection
- UNIXTIME = time(NULL);
- g_localTime = *localtime(&UNIXTIME);
- }
- PatchMgr::getSingleton().UpdateJobs();
- // This really needs to be on a seperate thread for update jobs
- Arcemu::Sleep(1);
- }[/code]
- The following code snippet from AuthSocket.cpp shows how the patch is selected for the client:
- [code] if(build < LogonServer::getSingleton().min_build)
- {
- // can we patch?
- char flippedloc[5] = {0,0,0,0,0};
- flippedloc[0] = m_challenge.country[3];
- flippedloc[1] = m_challenge.country[2];
- flippedloc[2] = m_challenge.country[1];
- flippedloc[3] = m_challenge.country[0];
- Log.Debug("Patch", "Patch %u %s for client.", build,flippedloc);
- m_patch = PatchMgr::getSingleton().FindPatchForClient(build, flippedloc);
- if(m_patch == NULL)
- {
- // could not find a valid patch
- LOG_DETAIL( "[AuthChallenge] Client %s has wrong version. More up to date than server. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().min_build, m_challenge.build );
- SendChallengeError(CE_WRONG_BUILD_NUMBER);
- return;
- }
- Log.Debug("Patch", "Selected patch %u%s for client.", m_patch->Version,m_patch->Locality);
- [/code]
- I added the last line for debug purposes. 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.
- In ArcEmu the patches are formatted like so:
- LocalityBuild.mpq
- These go in a folder called 'ClientPatches' in the same folder as your world executable.
- For example, for a enGB client running the build 12340 (3.3.5a) that you would like to update, you would format the patch:
- enGB12340.mpq
- The next change goes in AutoPatcher.cpp.
- Find:
- [code]Patch * PatchMgr::FindPatchForClient(uint32 Version, const char * Locality)
- {[/code]
- Change this function so it looks like this:
- [code]Patch * PatchMgr::FindPatchForClient(uint32 Version, const char * Locality)
- {
- Log.Debug("PatchMgr", "Main function has been called.");
- Log.Notice("PatchMgr", "Loading Patches...");
- char Buffer[MAX_PATH*10];
- char Buffer2[MAX_PATH*10];
- char Buffer3[MAX_PATH*10];
- WIN32_FIND_DATA fd;
- HANDLE fHandle;
- MD5Hash md5;
- Patch * pPatch;
- DWORD size,sizehigh;
- HANDLE hFile;
- uint32 srcversion;
- char locality[5];
- uint32 i;
- if(!GetCurrentDirectory(MAX_PATH*10, Buffer))
- return NULL;
- strcpy(Buffer2,Buffer);
- strcat(Buffer, "\\ClientPatches\\*.*");
- fHandle = FindFirstFile(Buffer, &fd);
- if(fHandle == INVALID_HANDLE_VALUE)
- return NULL;
- do
- {
- snprintf(Buffer3,MAX_PATH*10,"%s\\ClientPatches\\%s",Buffer2,fd.cFileName);
- if(sscanf(fd.cFileName,"%4s%u.", locality, &srcversion) != 2)
- {
- Log.Notice("Found Incorrect : %4s %s", locality, fd.cFileName);
- continue;
- }
- else
- {
- Log.Notice("Found Correct : %s %s", locality, fd.cFileName);
- }
- hFile = CreateFile(Buffer3, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);
- if(hFile == INVALID_HANDLE_VALUE)
- continue;
- Log.Notice("PatchMgr", "Found patch for %u locale `%s`.", srcversion,locality);
- pPatch = new Patch;
- size = GetFileSize(hFile, &sizehigh);
- Log.Debug("PatchMgr", "Patch size : %u", size);
- pPatch->FileSize = size;
- pPatch->Data = new uint8[size];
- pPatch->Version = srcversion;
- Log.Debug("PatchMgr", "Patch version : %u", srcversion);
- for(i = 0; i < 4; ++i)
- pPatch->Locality[i] = static_cast<char>(tolower(locality[i]));
- pPatch->Locality[4] = 0;
- pPatch->uLocality = *(uint32*)pPatch->Locality;
- if(pPatch->Data== NULL)
- {
- // shouldn't really happen
- delete pPatch;
- CloseHandle(hFile);
- continue;
- }
- // read the whole file
- ASSERT(ReadFile(hFile, pPatch->Data, pPatch->FileSize, &size, NULL));
- ASSERT(size == pPatch->FileSize);
- // close the handle, no longer needed
- CloseHandle(hFile);
- // md5hash the file
- md5.Initialize();
- md5.UpdateData(pPatch->Data, pPatch->FileSize);
- md5.Finalize();
- memcpy(pPatch->MD5, md5.GetDigest(), MD5_DIGEST_LENGTH);
- // add the patch to the patchlist
- m_patches.push_back(pPatch);
- } while(FindNextFile(fHandle,&fd));
- FindClose(fHandle);
- char tmplocality[5];
- uint32 ulocality;
- //uint32 i;
- vector<Patch*>::iterator itr;
- Patch * fallbackPatch = NULL;
- for(i = 0; i < 4; ++i)
- tmplocality[i]= static_cast<char>( tolower(Locality[i]) );
- tmplocality[4]= 0;
- ulocality = *(uint32*)tmplocality;
- for(itr = m_patches.begin(); itr != m_patches.end(); ++itr)
- {
- // since localities are always 4 bytes we can do a simple int compare,
- // saving a string compare ;)
- Log.Debug("Patch", "Loop Happening");
- //Log.Debug("Patch", "Stuff %u %s", (*itr)->Version, (*itr)->uLocality);
- if((*itr)->uLocality==ulocality)
- {
- if(fallbackPatch== NULL && (*itr)->Version== 0)
- fallbackPatch = (*itr);
- if((*itr)->Version == Version)
- return (*itr);
- }
- }
- return fallbackPatch;
- }[/code]
- This changes it so that upon this function being called, it gets the correct patch and returns it.
- Next go to this function:
- [code]bool PatchMgr::InitiatePatch(Patch * pPatch, AuthSocket * pClient)[/code]
- Remove this line:
- [code]init.name[0] = 'P'; init.name[1] = 'a'; init.name[2] = 't'; init.name[3] = 'c'; init.name[4] = 'h'; init.name[5] = '\0';[/code]
- And add this in its place:
- [code] init.name[0] = 'P';
- init.name[1] = 'a';
- init.name[2] = 't';
- init.name[3] = 'c';
- init.name[4] = 'h';
- //init.name[5] = '\0';[/code]
- The terminator value is incorrect and not part of the packet structure, hence why it is now commented out.
- Inside AutoPatcher.cpp also find this definition:
- [code]struct TransferInitiatePacket
- {
- uint8 cmd;
- uint8 strsize;
- char name[6];
- uint64 filesize;
- uint8 md5hash[MD5_DIGEST_LENGTH];
- };[/code]
- The name is one value too big. It needs to be changed to 5. Like so:
- [code]struct TransferInitiatePacket
- {
- uint8 cmd;
- uint8 strsize;
- char name[5];
- uint64 filesize;
- uint8 md5hash[MD5_DIGEST_LENGTH];
- };[/code]
- You are now done.
- [b]TrinityCore:[/b]
- Schlumpf provided a pretty nifty patch for this:
- ... Code ...
- [size=150][b]Step 3) Installing the patch.[/b][/size]
- For the client to try and attempt to install a patch, you have to disable the client verifying that the patch is signed by Blizzard.
- You can do this by:
- ... Stuff ...
- The executable run from the MPQ is where you update the client. Myself, I wrote a quick application in C++ that renames the WoW.exe (as a backup) then writes in the patch.mpq (just a new one) and deletes the cache folder. It also writes in the new executable file (which is the updated version) and writes in a BAT file. The program then starts the BAT file and closes its self. The BAT file will then wait a few seconds (for the MPQ file to stop being used) then delete the wow-patch.MPQ, and run WoW, deleting its self in the process. Now the client should be fully up to date.
- There are other means to obtain the client version (to confirm this patch should be executing) or to use the World of Warcraft installer. My patches were not large enough to take more than a second to install (200MB), so I just did it through my own application.
- [size=150][b]Step 4) Updating the client version.[/b][/size]
- To update the client version, you can follow this brief guide:
- http://www.ownedcore.com/forums/general/programming/326606-wow-exe-custom-build.html#post2088146
- I edited the value from 3430 to 3530 by using a free program called 'HexEdit'. I would always advise making a back up of your WoW executable before attempting to modify anything.
Advertisement
Add Comment
Please, Sign In to add comment