1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
19 #include "nel/misc/debug.h"
20 #include "nel/misc/path.h"
21 #include "nel/misc/thread.h"
24 //#include "nel_launcherDlg.h"
27 using namespace NLMISC
;
29 HINTERNET RootInternet
= NULL
;
31 static const string DirFilename
= "dir.ngz";
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());
57 throw Exception (str
);
63 void setVersion(const std::string
&version
)
65 string fn
= "VERSION";
68 FILE *fp
= fopen (fn
.c_str(), "wb");
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
));
83 string fn
= "VERSION";
84 FILE *fp
= fopen (fn
.c_str (), "rb");
88 if (fgets (ver
, 1000, fp
) != NULL
)
94 throw Exception ("Can't read file '%s' : code=%d %s", fn
.c_str (), errno
, strerror(errno
));
100 nlwarning ("Can't open file '%s' : code=%d %s", fn
.c_str (), errno
, strerror(errno
));
105 class CPatchThread
: public IRunnable
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
128 CurrentFilesToGet
= 0;
129 CurrentBytesToGet
= 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);
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");
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());
181 if (gzgets (gz
, buffer
, 2000) == NULL
)
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
);
195 if (gzgets (gz
, buffer
, 2000) == NULL
)
198 const char *gzerr
= gzerror (gz
, &gzerrno
);
199 throw Exception ("Can't read '%s' : code=%d %s", DirFilename
.c_str(), gzerrno
, gzerr
);
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
));
222 TotalBytesToGet
+= size
;
224 filesList
.push_back (CEntry(filename
, size
, date
));
229 // if we need to update nel_launcher.exe don't patch other file now, get
230 // nel_launcher.exe and relaunch it now
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());
244 downloadFile (ServerRootPath
+needToGetFilesList
[i
].Filename
+".ngz", path
+".ngz");
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");
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");
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
);
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")
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());
293 downloadFile (ServerRootPath
+needToGetFilesList
[i
].Filename
+".ngz", path
+".ngz");
295 decompressFile (path
+".ngz", needToGetFilesList
[i
].Date
);
298 if (needToGetFilesList
[i
].Filename
== "patch_execute.bat")
300 needToExecuteAPatch
= true;
304 if (RootInternet
!= NULL
)
306 InternetCloseHandle(RootInternet
);
310 // now, we have to delete files that are not in the server list
312 setState(true, "Scanning patch directory");
314 CPath::getPathContent(ClientPatchPath
, false, false, true, res
);
316 for (i
= 0; i
< res
.size (); i
++)
318 string fn
= NLMISC::CFile::getFilename (res
[i
]);
320 for (j
= 0; j
< filesList
.size (); j
++)
322 if (fn
== filesList
[j
].Filename
)
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
);
359 nlinfo ("Patching completed");
360 setState (true, "Patching completed");
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");
382 string err
= toString("Can't open compressed file '%s' : ", filename
.c_str());
387 const char *gzerr
= gzerror (gz
, &gzerrno
);
388 err
+= toString("code=%d %s", gzerrno
, gzerr
);
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);
400 FILE *fp
= fopen (dest
.c_str(), "wb");
403 string err
= toString("Can't open file '%s' : code=%d %s", dest
.c_str(), errno
, strerror(errno
));
406 deleteFile (filename
);
407 throw Exception (err
);
413 int res
= gzread (gz
, buffer
, 10000);
417 const char *gzerr
= gzerror (gz
, &gzerrno
);
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
);
427 string err
= toString("Can't write file '%s' : code=%d %s", dest
.c_str(), errno
, strerror(errno
));
431 deleteFile (filename
);
432 throw Exception (err
);
438 deleteFile (filename
);
440 // change the file time for having the same as the server side
445 utb
.actime
= utb
.modtime
= date
;
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
)
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
;
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
)
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
;
496 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d)", source
.c_str (), errorstr
.c_str(), errcode
);
500 FILE *fp
= fopen (dest
.c_str(), "wb");
503 throw Exception ("Can't open file '%s' for writing: code=%d %s", dest
.c_str (), errno
, strerror(errno
));
508 setState(true, "Getting %s", NLMISC::CFile::getFilename (source
).c_str ());
514 if(!InternetReadFile(hUrlDump
,(LPVOID
)buffer
, bufferSize
, &realSize
))
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
;
527 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d)", source
.c_str (), errorstr
.c_str(), errcode
);
533 // download complete successfully
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
));
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
);
552 setState(false, "Getting file %d on %d, %d bytes, filename %s", CurrentFilesToGet
, TotalFilesToGet
, CurrentBytesToGet
, NLMISC::CFile::getFilename (source
).c_str ());
559 if (!InternetCloseHandle(hUrlDump
))
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
;
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
, ...)
580 NLMISC_CONVERT_VARGS (str
, format
, 256);
586 StateLog
+= LogSeparator
;
594 string ServerVersion
;
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");
615 PatchThread
= new CPatchThread (serverPath
, serverVersion
, urlOk
, urlFailed
, logSeparator
);
616 nlassert (PatchThread
!= NULL
);
618 IThread
*thread
= IThread::create (PatchThread
);
619 nlassert (thread
!= NULL
);
623 bool patchEnded (string
&url
, bool &ok
)
625 nlassert (PatchThread
!= NULL
);
627 bool end
= PatchThread
->Ended
;
630 url
= PatchThread
->Url
;
631 ok
= PatchThread
->PatchOk
;
640 bool patchState (string
&state
, std::string
&stateLog
)
642 if (PatchThread
== NULL
)
645 bool statechanged
= PatchThread
->StateChanged
;
648 state
= PatchThread
->State
;
649 stateLog
= PatchThread
->StateLog
;
650 PatchThread
->StateChanged
= false;