Fix
[ryzomcore.git] / nelns / login_system / nel_launcher_windows / patch.cpp
blobc3be964a119259e19bd01697dea4b2998c9de105
1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "stdafx.h"
19 #include "nel/misc/debug.h"
20 #include "nel/misc/path.h"
21 #include "nel/misc/thread.h"
23 #include "patch.h"
24 //#include "nel_launcherDlg.h"
26 using namespace std;
27 using namespace NLMISC;
29 HINTERNET RootInternet = NULL;
31 static const string DirFilename = "dir.ngz";
33 struct CEntry
35 string Filename;
36 uint32 Size;
37 uint32 Date;
38 CEntry(const string &fn, uint32 s, uint32 d) : Filename(fn), Size(s), Date(d) { }
41 void setRWAccess (const string &filename)
43 if (!NLMISC::CFile::setRWAccess(filename))
45 nlwarning ("Can't have read/write access to '%s' file : code=%d %s", filename.c_str(), errno, strerror(errno));
46 throw Exception ("Can't have read/write access to '%s' file : code=%d %s", filename.c_str(), errno, strerror(errno));
50 string deleteFile (const string &filename, bool throwException=true)
52 if (!NLMISC::CFile::deleteFile(filename))
54 string str = toString("Can't delete '%s' file : code=%d %s", filename.c_str(), errno, strerror(errno));
55 nlwarning (str.c_str());
56 if(throwException)
57 throw Exception (str);
58 return str;
60 return "";
63 void setVersion(const std::string &version)
65 string fn = "VERSION";
67 setRWAccess(fn);
68 FILE *fp = fopen (fn.c_str(), "wb");
69 if (fp == NULL)
71 throw Exception ("Can't open file '%s' : code=%d %s", fn.c_str (), errno, strerror(errno));
74 if (fputs (version.c_str (), fp) == EOF)
76 throw Exception ("Can't write file '%s' : code=%d %s", fn.c_str (), errno, strerror(errno));
78 fclose (fp);
81 string getVersion()
83 string fn = "VERSION";
84 FILE *fp = fopen (fn.c_str (), "rb");
85 if (fp!=NULL)
87 char ver[1000];
88 if (fgets (ver, 1000, fp) != NULL)
90 return ver;
92 else
94 throw Exception ("Can't read file '%s' : code=%d %s", fn.c_str (), errno, strerror(errno));
96 fclose (fp);
98 else
100 nlwarning ("Can't open file '%s' : code=%d %s", fn.c_str (), errno, strerror(errno));
102 return "";
105 class CPatchThread : public IRunnable
107 public:
109 CPatchThread(const string &sp, const string &sv, const std::string &urlOk, const std::string &urlFailed, const std::string &logSeparator) :
110 ServerPath (sp), ServerVersion(sv), UrlOk(urlOk), UrlFailed(urlFailed), Ended(false), StateChanged(true), LogSeparator(logSeparator)
114 bool Ended; // true if the thread have ended the patch
115 bool PatchOk; // true if the patch was good
116 string Url; // url to display after the patch
118 string State;
119 string StateLog;
120 bool StateChanged;
122 private:
124 void run ()
128 CurrentFilesToGet = 0;
129 CurrentBytesToGet = 0;
130 TotalFilesToGet = 0;
131 TotalBytesToGet = 0;
133 bool needToExecuteAPatch = false;
135 string ClientRootPath = "./";
136 string ClientPatchPath = "./patch/";
137 string ServerRootPath = CPath::standardizePath (ServerPath);
138 string DisplayedServerRootPath; // contains the serverpath without login and password
140 uint pos = ServerRootPath.find ("@");
141 if (pos != string::npos)
143 DisplayedServerRootPath = "http://"+ServerRootPath.substr (pos+1);
145 else
147 DisplayedServerRootPath = ServerRootPath;
150 setState(true, "Patching from '%s'", DisplayedServerRootPath.c_str());
152 // create the patch directory if not exists
153 if (!NLMISC::CFile::isExists ("patch"))
155 setState(true, "Creating patch directory");
156 if (_mkdir ("patch") == -1)
158 throw Exception ("Can't create patch directory : code=%d %s", errno, strerror(errno));
162 // first, get the file that contains all files (dir.ngz)
163 deleteFile (DirFilename.c_str(), false);
164 downloadFile (ServerRootPath+DirFilename, DirFilename);
166 // now parse the file
167 gzFile gz = gzopen (DirFilename.c_str (), "rb");
168 if (gz == NULL)
170 int gzerrno;
171 const char *gzerr = gzerror (gz, &gzerrno);
172 throw Exception ("Can't open file '%s': code=%d %s", DirFilename.c_str(), gzerrno, gzerr);
175 vector<CEntry> filesList;
176 vector<CEntry> needToGetFilesList;
178 setState(true, "Parsing %s...", DirFilename.c_str());
180 char buffer[2000];
181 if (gzgets (gz, buffer, 2000) == NULL)
183 int gzerrno;
184 const char *gzerr = gzerror (gz, &gzerrno);
185 throw Exception ("Can't read header of'%s' : code=%d %s", DirFilename.c_str(), gzerrno, gzerr);
188 if (string(buffer) != "FILESLIST\n")
190 throw Exception ("%s has not a valid content '%s' : code=8888", DirFilename.c_str(), buffer);
193 while (!gzeof(gz))
195 if (gzgets (gz, buffer, 2000) == NULL)
197 int gzerrno;
198 const char *gzerr = gzerror (gz, &gzerrno);
199 throw Exception ("Can't read '%s' : code=%d %s", DirFilename.c_str(), gzerrno, gzerr);
202 string b = buffer;
203 uint pos1 = b.find ("/");
204 uint pos2 = b.find ("/", pos1+1);
206 if (pos1 != string::npos || pos2 != string::npos)
208 string filename = b.substr (0, pos1);
209 uint32 size = atoi(b.substr (pos1+1, pos2-pos1).c_str());
210 uint32 date = atoi(b.substr (pos2+1).c_str());
212 string path = ClientRootPath+filename;
213 if (!NLMISC::CFile::fileExists (path))
215 path = ClientPatchPath + filename;
218 if (NLMISC::CFile::getFileModificationDate (path) != date || size != NLMISC::CFile::getFileSize (path))
220 needToGetFilesList.push_back (CEntry(filename, size, date));
221 TotalFilesToGet++;
222 TotalBytesToGet += size;
224 filesList.push_back (CEntry(filename, size, date));
227 gzclose (gz);
229 // if we need to update nel_launcher.exe don't patch other file now, get
230 // nel_launcher.exe and relaunch it now
231 uint i;
232 for (i = 0; i < needToGetFilesList.size (); i++)
234 if (needToGetFilesList[i].Filename == "nel_launcher.exe")
236 // special case for patching nel_launcher.exe
238 string path = ClientPatchPath + needToGetFilesList[i].Filename;
240 nlinfo ("Get the file from '%s' to '%s'", string(DisplayedServerRootPath+needToGetFilesList[i].Filename).c_str(), path.c_str());
242 // get the new file
244 downloadFile (ServerRootPath+needToGetFilesList[i].Filename+".ngz", path+".ngz");
245 // decompress it
246 decompressFile (path+".ngz", needToGetFilesList[i].Date);
248 // create a .bat for moving the new nel_launcher.exe
249 FILE *fp = fopen ("update_nel_launcher.bat", "wt");
250 if (fp == NULL)
252 string err = toString("Can't open file 'update_nel_launcher.bat' for writing: code=%d %s", errno, strerror(errno));
253 throw Exception (err);
256 fprintf(fp, "@echo off\n");
257 fprintf(fp, ":loop\n");
258 fprintf(fp, "del /F /Q nel_launcher.exe\n");
259 fprintf(fp, "if exist nel_launcher.exe goto loop\n");
260 fprintf(fp, "move /Y patch\\nel_launcher.exe .\n");
261 fprintf(fp, "nel_launcher.exe\n");
263 fclose (fp);
265 setState (true, "Launching update_nel_launcher.bat");
266 nlinfo ("Need to execute update_nel_launcher.bat");
267 if (_execlp ("update_nel_launcher.bat", "update_nel_launcher.bat", NULL) == -1)
269 // error occurs during the launch
270 string str = toString("Can't execute 'update_nel_launcher.bat': code=%d %s", errno, strerror(errno));
271 throw Exception (str);
273 exit(0);
277 // get file if necessary
278 for (i = 0; i < needToGetFilesList.size (); i++)
280 // special case for nel_launcher.exe
281 if (needToGetFilesList[i].Filename == "nel_launcher.exe")
282 continue;
284 string path = ClientRootPath+needToGetFilesList[i].Filename;
285 if (!NLMISC::CFile::fileExists (path))
287 path = ClientPatchPath + needToGetFilesList[i].Filename;
290 nlinfo ("Get the file from '%s' to '%s'", string(DisplayedServerRootPath+needToGetFilesList[i].Filename).c_str(), path.c_str());
292 // get the new file
293 downloadFile (ServerRootPath+needToGetFilesList[i].Filename+".ngz", path+".ngz");
294 // decompress it
295 decompressFile (path+".ngz", needToGetFilesList[i].Date);
297 // special case
298 if (needToGetFilesList[i].Filename == "patch_execute.bat")
300 needToExecuteAPatch = true;
304 if (RootInternet != NULL)
306 InternetCloseHandle(RootInternet);
307 RootInternet = NULL;
310 // now, we have to delete files that are not in the server list
312 setState(true, "Scanning patch directory");
313 vector<string> res;
314 CPath::getPathContent(ClientPatchPath, false, false, true, res);
316 for (i = 0; i < res.size (); i++)
318 string fn = NLMISC::CFile::getFilename (res[i]);
319 uint j;
320 for (j = 0; j < filesList.size (); j++)
322 if (fn == filesList[j].Filename)
324 break;
327 if (j == filesList.size ())
329 string file = ClientPatchPath+fn;
330 setState(true, "Deleting %s", file.c_str());
331 string err = deleteFile (file, false);
332 if (!err.empty()) setState(true, err.c_str());
336 // remove the files list file
337 setState (true, "Deleting %s", DirFilename.c_str());
338 string err = deleteFile (DirFilename, false);
339 if (!err.empty()) setState(true, err.c_str());
341 // now that all is ok, we set the new client version
342 setState (true, "set client version to %s", ServerVersion.c_str ());
343 setVersion (ServerVersion);
345 if (needToExecuteAPatch)
347 setState (true, "Launching patch_execute.bat");
348 nlinfo ("Need to execute patch_execute.bat");
349 _chdir (ClientPatchPath.c_str());
350 if (_execlp ("patch_execute.bat", "patch_execute.bat", NULL) == -1)
352 // error occurs during the launch
353 string str = toString("Can't execute 'patch_execute.bat': code=%d %s", errno, strerror(errno));
354 throw Exception (str);
356 exit(0);
359 nlinfo ("Patching completed");
360 setState (true, "Patching completed");
362 Url = UrlOk;
363 PatchOk = true;
364 Ended = true;
366 catch (Exception &e)
368 Url = UrlFailed;
369 Url += e.what();
370 PatchOk = false;
371 Ended = true;
375 void decompressFile (const string &filename, uint32 date)
377 setState(true, "Decompressing %s...", NLMISC::CFile::getFilename(filename).c_str ());
379 gzFile gz = gzopen (filename.c_str (), "rb");
380 if (gz == NULL)
382 string err = toString("Can't open compressed file '%s' : ", filename.c_str());
383 if(errno == 0)
385 // gzerror
386 int gzerrno;
387 const char *gzerr = gzerror (gz, &gzerrno);
388 err += toString("code=%d %s", gzerrno, gzerr);
390 else
392 err += toString("code=%d %s", errno, strerror (errno));
394 deleteFile (filename);
395 throw Exception (err);
398 string dest = filename.substr(0, filename.size ()-3);
399 setRWAccess(dest);
400 FILE *fp = fopen (dest.c_str(), "wb");
401 if (fp == NULL)
403 string err = toString("Can't open file '%s' : code=%d %s", dest.c_str(), errno, strerror(errno));
405 gzclose(gz);
406 deleteFile (filename);
407 throw Exception (err);
410 uint8 buffer[10000];
411 while (!gzeof(gz))
413 int res = gzread (gz, buffer, 10000);
414 if (res == -1)
416 int gzerrno;
417 const char *gzerr = gzerror (gz, &gzerrno);
418 gzclose(gz);
419 fclose(fp);
420 deleteFile (filename);
421 throw Exception ("Can't read compressed file '%s' : code=%d %s", filename.c_str(), gzerrno, gzerr);
424 int res2 = fwrite (buffer, 1, res, fp);
425 if (res2 != res)
427 string err = toString("Can't write file '%s' : code=%d %s", dest.c_str(), errno, strerror(errno));
429 gzclose(gz);
430 fclose(fp);
431 deleteFile (filename);
432 throw Exception (err);
436 gzclose(gz);
437 fclose(fp);
438 deleteFile (filename);
440 // change the file time for having the same as the server side
442 if(date != 0)
444 _utimbuf utb;
445 utb.actime = utb.modtime = date;
446 setRWAccess(dest);
447 if (_utime (dest.c_str (), &utb) == -1)
449 nlwarning ("Can't change file time for '%s' : code=%d %s", dest.c_str (), errno, strerror(errno));
455 void downloadFile (const string &source, const string &dest)
457 const uint32 bufferSize = 8000;
458 uint8 buffer[bufferSize];
460 if (RootInternet == NULL)
462 RootInternet = InternetOpen("nel_launcher", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
463 if (RootInternet == NULL)
465 // error
466 LPVOID lpMsgBuf;
467 string errorstr;
468 DWORD errcode = GetLastError ();
469 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
470 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
471 (LPTSTR) &lpMsgBuf, 0, NULL) == 0)
473 errorstr = (LPCTSTR)lpMsgBuf;
475 LocalFree(lpMsgBuf);
477 throw Exception ("InternetOpen() failed: %s (ec %d)", errorstr.c_str(), errcode);
481 HINTERNET hUrlDump = InternetOpenUrl(RootInternet, source.c_str(), NULL, NULL, INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_RAW_DATA, 0);
482 if (hUrlDump == NULL)
484 // error
485 LPVOID lpMsgBuf;
486 string errorstr;
487 DWORD errcode = GetLastError ();
488 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
489 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
490 (LPTSTR) &lpMsgBuf, 0, NULL) == 0)
492 errorstr = (LPCTSTR)lpMsgBuf;
494 LocalFree(lpMsgBuf);
496 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d)", source.c_str (), errorstr.c_str(), errcode);
499 setRWAccess(dest);
500 FILE *fp = fopen (dest.c_str(), "wb");
501 if (fp == NULL)
503 throw Exception ("Can't open file '%s' for writing: code=%d %s", dest.c_str (), errno, strerror(errno));
506 CurrentFilesToGet++;
508 setState(true, "Getting %s", NLMISC::CFile::getFilename (source).c_str ());
512 DWORD realSize;
514 if(!InternetReadFile(hUrlDump,(LPVOID)buffer, bufferSize, &realSize))
516 LPVOID lpMsgBuf;
517 string errorstr;
518 DWORD errcode = GetLastError ();
519 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
520 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
521 (LPTSTR) &lpMsgBuf, 0, NULL) == 0)
523 errorstr = (LPCTSTR)lpMsgBuf;
525 LocalFree(lpMsgBuf);
527 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d)", source.c_str (), errorstr.c_str(), errcode);
529 else
531 if (realSize == 0)
533 // download complete successfully
534 break;
537 int res2 = fwrite (buffer, 1, realSize, fp);
538 if ((DWORD)res2 != realSize)
540 string err = toString("Can't write file '%s' : code=%d %s", dest.c_str(), errno, strerror(errno));
542 fclose(fp);
543 deleteFile (dest);
544 throw Exception (err);
547 CurrentBytesToGet += realSize;
549 if (TotalBytesToGet == 0 && TotalFilesToGet == 0)
550 setState(false, "Getting %s, %d bytes downloaded", NLMISC::CFile::getFilename (source).c_str (), CurrentBytesToGet);
551 else
552 setState(false, "Getting file %d on %d, %d bytes, filename %s", CurrentFilesToGet, TotalFilesToGet, CurrentBytesToGet, NLMISC::CFile::getFilename (source).c_str ());
556 while (true);
558 fclose (fp);
559 if (!InternetCloseHandle(hUrlDump))
561 LPVOID lpMsgBuf;
562 string errorstr;
563 DWORD errcode = GetLastError ();
564 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
565 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
566 (LPTSTR) &lpMsgBuf, 0, NULL) == 0)
568 errorstr = (LPCTSTR)lpMsgBuf;
570 LocalFree(lpMsgBuf);
572 throw Exception ("InternetCloseHandle() failed on file '%s': %s (ec %d)", source.c_str (), errorstr.c_str(), errcode);
577 void setState (bool log, const char *format, ...)
579 char *str;
580 NLMISC_CONVERT_VARGS (str, format, 256);
581 nlinfo (str);
582 State = str;
583 if(log)
585 StateLog += str;
586 StateLog += LogSeparator;
588 StateChanged = true;
591 string LogSeparator;
593 string ServerPath;
594 string ServerVersion;
596 string UrlOk;
597 string UrlFailed;
599 uint TotalFilesToGet;
600 uint TotalBytesToGet;
601 uint CurrentFilesToGet;
602 uint CurrentBytesToGet;
605 CPatchThread *PatchThread = NULL;
607 void startPatchThread (const std::string &serverPath, const std::string &serverVersion, const std::string &urlOk, const std::string &urlFailed, const std::string &logSeparator)
609 if (PatchThread != NULL)
611 nlwarning ("patch thread already running");
612 return;
615 PatchThread = new CPatchThread (serverPath, serverVersion, urlOk, urlFailed, logSeparator);
616 nlassert (PatchThread != NULL);
618 IThread *thread = IThread::create (PatchThread);
619 nlassert (thread != NULL);
620 thread->start ();
623 bool patchEnded (string &url, bool &ok)
625 nlassert (PatchThread != NULL);
627 bool end = PatchThread->Ended;
628 if (end)
630 url = PatchThread->Url;
631 ok = PatchThread->PatchOk;
633 delete PatchThread;
634 PatchThread = NULL;
637 return end;
640 bool patchState (string &state, std::string &stateLog)
642 if (PatchThread == NULL)
643 return false;
645 bool statechanged = PatchThread->StateChanged;
646 if (statechanged)
648 state = PatchThread->State;
649 stateLog = PatchThread->StateLog;
650 PatchThread->StateChanged = false;
653 return statechanged;