diff --git a/code/CMakeModules/nel.cmake b/code/CMakeModules/nel.cmake
index 51ffaf950..70306482b 100644
--- a/code/CMakeModules/nel.cmake
+++ b/code/CMakeModules/nel.cmake
@@ -321,7 +321,7 @@ MACRO(NL_SETUP_RYZOM_DEFAULT_OPTIONS)
OPTION(WITH_RYZOM_CLIENT "Build Ryzom Core Client" ON )
OPTION(WITH_RYZOM_TOOLS "Build Ryzom Core Tools" ON )
OPTION(WITH_RYZOM_SERVER "Build Ryzom Core Services" ON )
- OPTION(WITH_RYZOM_SOUND "Enable Ryzom Core Sound" ON )
+ OPTION(WITH_RYZOM_INSTALLER "Build Ryzom Installer" OFF)
###
# Optional support
diff --git a/code/ryzom/tools/client/CMakeLists.txt b/code/ryzom/tools/client/CMakeLists.txt
index 3cdaf0136..7d7abf08f 100644
--- a/code/ryzom/tools/client/CMakeLists.txt
+++ b/code/ryzom/tools/client/CMakeLists.txt
@@ -3,7 +3,12 @@ IF(WITH_RYZOM_CLIENT)
IF(WITH_QT OR WITH_QT5)
ADD_SUBDIRECTORY(client_config_qt)
+
+ IF(WITH_RYZOM_INSTALLER)
+ ADD_SUBDIRECTORY(ryzom_installer)
+ ENDIF()
ENDIF()
+
ENDIF()
IF(WITH_RYZOM_TOOLS)
diff --git a/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt b/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt
new file mode 100644
index 000000000..4471628e0
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/CMakeLists.txt
@@ -0,0 +1,51 @@
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} ${NEL_INCLUDE_DIR})
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/ryzom/client/src/seven_zip ${ZLIB_INCLUDE_DIR})
+
+FILE(GLOB SRC src/*.cpp src/*.h res/*.rc)
+FILE(GLOB CLIENT_INSTALL_HDR src/*.h)
+FILE(GLOB CLIENT_INSTALL_UIS ui/*.ui)
+FILE(GLOB CLIENT_INSTALL_TRANS translations/*.ts)
+FILE(GLOB CLIENT_INSTALL_RCS res/*.qrc)
+
+#CONFIGURE_FILE(translations/translations.qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc COPYONLY)
+#SET(CLIENT_INSTALL_RCS resources.qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
+
+IF(WITH_QT)
+ INCLUDE_DIRECTORIES(${QT_INCLUDES})
+ INCLUDE(${QT_USE_FILE})
+
+ QT4_ADD_TRANSLATION(CLIENT_INSTALL_QM ${CLIENT_INSTALL_TRANS})
+ QT4_ADD_RESOURCES(CLIENT_INSTALL_RC_SRCS ${CLIENT_INSTALL_RCS})
+ QT4_WRAP_CPP(CLIENT_INSTALL_MOC_SRC ${CLIENT_INSTALL_HDR})
+ QT4_WRAP_UI(CLIENT_INSTALL_UI_HDRS ${CLIENT_INSTALL_UIS})
+
+ ADD_DEFINITIONS(${QT_DEFINITIONS})
+ELSE()
+ IF(WIN32)
+ FIND_PACKAGE(Qt5WinExtras)
+ SET(QT_LIBRARIES Qt5::WinExtras ${QT_LIBRARIES})
+ ENDIF()
+
+ QT5_ADD_TRANSLATION(CLIENT_INSTALL_QM ${CLIENT_INSTALL_TRANS})
+ QT5_ADD_RESOURCES(CLIENT_INSTALL_RC_SRCS ${CLIENT_INSTALL_RCS})
+ QT5_WRAP_CPP(CLIENT_INSTALL_MOC_SRC ${CLIENT_INSTALL_HDR})
+ QT5_WRAP_UI(CLIENT_INSTALL_UI_HDRS ${CLIENT_INSTALL_UIS})
+ENDIF()
+
+SOURCE_GROUP("Source" FILES ${SRC})
+SOURCE_GROUP("Resources" FILES ${CLIENT_INSTALL_RCS})
+SOURCE_GROUP("Forms" FILES ${CLIENT_INSTALL_UIS})
+SOURCE_GROUP("Generated Files" FILES ${CLIENT_INSTALL_UI_HDRS} ${CLIENT_INSTALL_MOC_SRC} ${CLIENT_INSTALL_RC_SRCS})
+SOURCE_GROUP("Translation Files" FILES ${CLIENT_INSTALL_TRANS})
+
+ADD_EXECUTABLE(ryzom_installer_qt WIN32 ${SRC} ${CLIENT_INSTALL_MOC_SRC} ${CLIENT_INSTALL_UI_HDRS} ${CLIENT_INSTALL_RC_SRCS} ${CLIENT_INSTALL_TRANS} ${CLIENT_INSTALL_QM})
+NL_DEFAULT_PROPS(ryzom_installer_qt "Ryzom, Tools: Ryzom Installer" )
+NL_ADD_RUNTIME_FLAGS(ryzom_installer_qt)
+NL_ADD_LIB_SUFFIX(ryzom_installer_qt)
+TARGET_LINK_LIBRARIES(ryzom_installer_qt nelmisc ryzom_sevenzip ${QT_LIBRARIES})
+
+IF(WITH_PCH)
+ ADD_NATIVE_PRECOMPILED_HEADER(ryzom_installer_qt ${CMAKE_CURRENT_SOURCE_DIR}/src/stdpch.h ${CMAKE_CURRENT_SOURCE_DIR}/src/stdpch.cpp)
+ENDIF()
+
+INSTALL(TARGETS ryzom_installer_qt RUNTIME DESTINATION ${RYZOM_GAMES_PREFIX} COMPONENT client)
diff --git a/code/ryzom/tools/client/ryzom_installer/res/background.png b/code/ryzom/tools/client/ryzom_installer/res/background.png
new file mode 100644
index 000000000..26548eaa6
Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/background.png differ
diff --git a/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp b/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp
new file mode 100644
index 000000000..dcb0c3c65
Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/modern-header.bmp differ
diff --git a/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp b/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp
new file mode 100644
index 000000000..9bc88799e
Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/modern-wizard.bmp differ
diff --git a/code/ryzom/tools/client/ryzom_installer/res/resources.qrc b/code/ryzom/tools/client/ryzom_installer/res/resources.qrc
new file mode 100644
index 000000000..0e7224f4e
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/res/resources.qrc
@@ -0,0 +1,8 @@
+
+
+ background.png
+
+
+ ryzom.ico
+
+
diff --git a/code/ryzom/tools/client/ryzom_installer/res/resources.rc b/code/ryzom/tools/client/ryzom_installer/res/resources.rc
new file mode 100644
index 000000000..b58619308
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/res/resources.rc
@@ -0,0 +1,39 @@
+#include
+#include "config.h"
+
+IDI_MAIN_ICON ICON DISCARDABLE "ryzom.ico"
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION RYZOM_VERSION_RC
+PRODUCTVERSION NL_VERSION_RC
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+FILEFLAGS VS_FF_DEBUG
+#else
+FILEFLAGS 0x0L
+#endif
+FILEOS VOS__WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "Ryzom Installer"
+ VALUE "FileVersion", RYZOM_VERSION
+ VALUE "LegalCopyright", COPYRIGHT
+#ifdef _DEBUG
+ VALUE "OriginalFilename", "ryzom_installer_d.exe"
+#else
+ VALUE "OriginalFilename", "ryzom_installer_r.exe"
+#endif
+ VALUE "ProductName", "Ryzom Core"
+ VALUE "ProductVersion", NL_VERSION
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
diff --git a/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico b/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico
new file mode 100644
index 000000000..f2c7e8424
Binary files /dev/null and b/code/ryzom/tools/client/ryzom_installer/res/ryzom.ico differ
diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.cpp b/code/ryzom/tools/client/ryzom_installer/src/archive.cpp
new file mode 100644
index 000000000..425e4f379
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/archive.cpp
@@ -0,0 +1,855 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "archive.h"
+#include "utils.h"
+
+#include "nel/misc/big_file.h"
+#include "nel/misc/callback.h"
+#include "nel/misc/file.h"
+#include "nel/misc/path.h"
+
+#include "7z.h"
+#include "7zAlloc.h"
+#include "7zBuf.h"
+#include "7zCrc.h"
+
+#include "qzipreader.h"
+
+#include
+
+#ifdef Q_OS_WIN
+#include
+#endif
+
+#ifndef FILE_ATTRIBUTE_READONLY
+#define FILE_ATTRIBUTE_READONLY 0x1
+#endif
+#ifndef FILE_ATTRIBUTE_HIDDEN
+#define FILE_ATTRIBUTE_HIDDEN 0x2
+#endif
+
+#ifndef FILE_ATTRIBUTE_SYSTEM
+#define FILE_ATTRIBUTE_SYSTEM 0x4
+#endif
+
+#ifndef FILE_ATTRIBUTE_DIRECTORY
+#define FILE_ATTRIBUTE_DIRECTORY 0x10
+#endif
+
+#ifndef FILE_ATTRIBUTE_ARCHIVE
+#define FILE_ATTRIBUTE_ARCHIVE 0x20
+#endif
+
+#ifndef FILE_ATTRIBUTE_DEVICE
+#define FILE_ATTRIBUTE_DEVICE 0x40
+#endif
+
+#ifndef FILE_ATTRIBUTE_NORMAL
+#define FILE_ATTRIBUTE_NORMAL 0x80
+#endif
+
+#ifndef FILE_ATTRIBUTE_TEMPORARY
+#define FILE_ATTRIBUTE_TEMPORARY 0x100
+#endif
+
+#ifndef FILE_ATTRIBUTE_SPARSE_FILE
+#define FILE_ATTRIBUTE_SPARSE_FILE 0x200
+#endif
+
+#ifndef FILE_ATTRIBUTE_REPARSE_POINT
+#define FILE_ATTRIBUTE_REPARSE_POINT 0x400
+#endif
+
+#ifndef FILE_ATTRIBUTE_COMPRESSED
+#define FILE_ATTRIBUTE_COMPRESSED 0x800
+#endif
+
+#ifndef FILE_ATTRIBUTE_OFFLINE
+#define FILE_ATTRIBUTE_OFFLINE 0x1000
+#endif
+
+#ifndef FILE_ATTRIBUTE_ENCRYPTED
+#define FILE_ATTRIBUTE_ENCRYPTED 0x4000
+#endif
+
+#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */
+
+#define FILE_ATTRIBUTE_WINDOWS 0x5fff
+#define FILE_ATTRIBUTE_UNIX 0xffff0000
+
+bool Set7zFileAttrib(const QString &filename, uint32 fileAttributes)
+{
+ bool attrReadOnly = (fileAttributes & FILE_ATTRIBUTE_READONLY != 0);
+ bool attrHidden = (fileAttributes & FILE_ATTRIBUTE_HIDDEN != 0);
+ bool attrSystem = (fileAttributes & FILE_ATTRIBUTE_SYSTEM != 0);
+ bool attrDir = (fileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0);
+ bool attrArchive = (fileAttributes & FILE_ATTRIBUTE_ARCHIVE != 0);
+ bool attrDevice = (fileAttributes & FILE_ATTRIBUTE_DEVICE != 0);
+ bool attrNormal = (fileAttributes & FILE_ATTRIBUTE_NORMAL != 0);
+ bool attrTemp = (fileAttributes & FILE_ATTRIBUTE_TEMPORARY != 0);
+ bool attrSparceFile = (fileAttributes & FILE_ATTRIBUTE_SPARSE_FILE != 0);
+ bool attrReparsePoint = (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0);
+ bool attrCompressed = (fileAttributes & FILE_ATTRIBUTE_COMPRESSED != 0);
+ bool attrOffline = (fileAttributes & FILE_ATTRIBUTE_OFFLINE != 0);
+ bool attrEncrypted = (fileAttributes & FILE_ATTRIBUTE_ENCRYPTED != 0);
+ bool attrUnix = (fileAttributes & FILE_ATTRIBUTE_UNIX_EXTENSION != 0);
+
+ uint32 unixAttributes = (fileAttributes & FILE_ATTRIBUTE_UNIX) >> 16;
+ uint32 windowsAttributes = fileAttributes & FILE_ATTRIBUTE_WINDOWS;
+
+ qDebug() << "attribs" << QByteArray::fromRawData((const char*)&fileAttributes, 4).toHex();
+
+#ifdef Q_OS_WIN
+ SetFileAttributesW((wchar_t*)filename.utf16(), windowsAttributes);
+#else
+ const char *name = filename.toUtf8().constData();
+
+ struct stat stat_info;
+ if (lstat(name, &stat_info)!=0)
+ {
+ nlwarning("SetFileAttrib(%s,%d) : false-2-1", (const char *)name, fileAttributes);
+ return false;
+ }
+
+ if (attrUnix)
+ {
+ stat_info.st_mode = unixAttributes;
+
+ if (S_ISLNK(stat_info.st_mode))
+ {
+ if (convert_to_symlink(name) != 0)
+ {
+ nlwarning("SetFileAttrib(%s,%d) : false-3",(const char *)name,fileAttributes);
+ return false;
+ }
+ }
+ else if (S_ISREG(stat_info.st_mode))
+ {
+ nlwarning("##DBG chmod-2(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask);
+ chmod(name, stat_info.st_mode & gbl_umask.mask);
+ }
+ else if (S_ISDIR(stat_info.st_mode))
+ {
+ // user/7za must be able to create files in this directory
+ stat_info.st_mode |= (S_IRUSR | S_IWUSR | S_IXUSR);
+ nlwarning("##DBG chmod-3(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask);
+ chmod(name, stat_info.st_mode & gbl_umask.mask);
+ }
+ }
+ else if (!S_ISLNK(stat_info.st_mode))
+ {
+ // do not use chmod on a link
+
+ // Only Windows Attributes
+ if( S_ISDIR(stat_info.st_mode))
+ {
+ // Remark : FILE_ATTRIBUTE_READONLY ignored for directory.
+ nlwarning("##DBG chmod-4(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask);
+ chmod(name,stat_info.st_mode & gbl_umask.mask);
+ }
+ else
+ {
+ // octal!, clear write permission bits
+ if (fileAttributes & FILE_ATTRIBUTE_READONLY) stat_info.st_mode &= ~0222;
+ nlwarning("##DBG chmod-5(%s,%o)", (const char *)name, (unsigned)stat_info.st_mode & gbl_umask.mask);
+ chmod(name,stat_info.st_mode & gbl_umask.mask);
+ }
+ }
+#endif
+
+ return true;
+}
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+#define SZ_ERROR_INTERRUPTED 18
+
+class Q7zFile : public ISeekInStream
+{
+ QFile m_file;
+
+public:
+ Q7zFile(const QString &filename):m_file(filename)
+ {
+ Read = readFunc;
+ Seek = seekFunc;
+ }
+
+ ~Q7zFile()
+ {
+ }
+
+ bool open()
+ {
+ return m_file.open(QFile::ReadOnly);
+ }
+
+ // the read function called by 7zip to read data
+ static SRes readFunc(void *object, void *buffer, size_t *size)
+ {
+ Q7zFile *me = (Q7zFile*)object;
+ qint64 len = *size;
+ len = me->m_file.read((char*)buffer, len);
+
+ if (len == *size)
+ {
+ *size = len;
+ return SZ_OK;
+ }
+ else
+ {
+ return SZ_ERROR_READ;
+ }
+ }
+
+ // the seek function called by seven zip to seek inside stream
+ static SRes seekFunc(void *object, Int64 *pos, ESzSeek origin)
+ {
+ Q7zFile *me = (Q7zFile*)object;
+ qint64 newPos;
+
+ switch(origin)
+ {
+ case SZ_SEEK_SET: newPos = *pos; break;
+ case SZ_SEEK_CUR: newPos = me->m_file.pos() + *pos; break;
+ case SZ_SEEK_END: newPos = me->m_file.size() - *pos; break;
+ }
+
+ if (me->m_file.seek(newPos))
+ {
+ *pos = newPos;
+ return SZ_OK;
+ }
+ else
+ {
+ return SZ_ERROR_READ;
+ }
+ }
+};
+
+CArchive::CArchive(QObject *parent):QObject(parent), m_mustStop(false)
+{
+}
+
+CArchive::~CArchive()
+{
+}
+
+bool CArchive::extract(const QString &filename, const QString &dest)
+{
+ m_filename = filename;
+ m_dest = dest;
+
+ QFile file(m_filename);
+
+ // open archive file to check format
+ if (!file.open(QFile::ReadOnly)) return false;
+
+ // read 2 first bytes
+ QByteArray header = file.read(2);
+
+ // close file
+ file.close();
+
+ // create destination directory
+ QDir dir;
+ dir.mkpath(dest);
+
+ QFuture future;
+
+ // compare to supported formats and call the appropriate decompressor
+ if (header == "7z")
+ {
+ future = QtConcurrent::run(this, &CArchive::extract7z);
+ }
+ else if (header == "PK")
+ {
+ future = QtConcurrent::run(this, &CArchive::extractZip);
+ }
+ else if (QFileInfo(filename).suffix().toLower() == "bnp")
+ {
+ future = QtConcurrent::run(this, &CArchive::extractBnp);
+ }
+ else
+ {
+ qDebug() << "Unsupported format";
+ return false;
+ }
+
+ return true;
+}
+
+void CArchive::getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files)
+{
+ QDir dir(srcDir);
+
+ QFileInfoList entries = dir.entryInfoList(filter);
+
+ foreach(const QFileInfo &entry, entries)
+ {
+ QString fullPath = entry.absoluteFilePath();
+
+ QString dstPath = dstDir + "/" + dir.relativeFilePath(fullPath);
+
+ if (entry.isDir())
+ {
+ QDir().mkpath(dstPath);
+
+ QDir subDir(fullPath);
+
+ QDirIterator it(subDir, QDirIterator::Subdirectories);
+
+ while (it.hasNext())
+ {
+ fullPath = it.next();
+
+ if (it.fileName().startsWith('.')) continue;
+
+ QFileInfo fileInfo = it.fileInfo();
+
+ dstPath = dstDir + "/" + dir.relativeFilePath(fullPath);
+
+ if (fileInfo.isDir())
+ {
+ QDir().mkpath(dstPath);
+ }
+ else
+ {
+ FileToCopy file;
+ file.filename = it.fileName();
+ file.src = it.filePath();
+ file.dst = dstPath;
+ file.size = it.fileInfo().size();
+ file.date = it.fileInfo().lastModified();
+
+ files << file;
+ }
+ }
+ }
+ else
+ {
+ FileToCopy file;
+ file.filename = entry.fileName();
+ file.src = entry.filePath();
+ file.dst = dstPath;
+ file.size = entry.size();
+ file.date = entry.lastModified();
+
+ files << file;
+ }
+ }
+}
+
+bool CArchive::copyServerFiles()
+{
+ emit extractPrepare();
+
+ FilesToCopy files;
+
+ QStringList serverFiles;
+ serverFiles << "cfg";
+ serverFiles << "data";
+ serverFiles << "examples";
+ serverFiles << "patch";
+ serverFiles << "unpack";
+ serverFiles << "client_default.cfg";
+
+ CArchive::getFilesList(m_filename, m_dest, serverFiles, files);
+
+ return copyFiles(files);
+}
+
+bool CArchive::copyProfileFiles()
+{
+ emit extractPrepare();
+
+ FilesToCopy files;
+
+ QStringList configFiles;
+ configFiles << "cache";
+ configFiles << "save";
+ configFiles << "user";
+ configFiles << "screenshots";
+ configFiles << "client.cfg";
+ configFiles << "*.log";
+
+ CArchive::getFilesList(m_filename, m_dest, configFiles, files);
+
+ return copyFiles(files);
+}
+
+bool CArchive::cleanServerFiles(const QString &directory)
+{
+ QDir dir(directory);
+
+ // directory doesn't exist
+ if (!dir.exists()) return false;
+
+ if (!dir.cd("data") && dir.exists()) return false;
+
+ // temporary files
+ QStringList files = dir.entryList(QStringList() << "*.string_cache" << "*.packed_sheets" << "*.packed" << "*.pem", QDir::Files);
+
+ foreach(const QString &file, files)
+ {
+ dir.remove(file);
+ }
+
+ // fonts directory is not needed anymore
+ if (dir.cd("fonts") && dir.exists())
+ {
+ dir.removeRecursively();
+ }
+
+ emit done();
+
+ return true;
+}
+
+bool CArchive::copyServerFiles(const QString &src, const QString &dst)
+{
+ if (src.isEmpty() || dst.isEmpty()) return false;
+
+ m_filename = src;
+ m_dest = dst;
+
+ // create destination directory
+ QDir().mkpath(dst);
+
+ QFuture future = QtConcurrent::run(this, &CArchive::copyServerFiles);
+
+ return true;
+}
+
+bool CArchive::copyProfileFiles(const QString &src, const QString &dst)
+{
+ if (src.isEmpty() || dst.isEmpty()) return false;
+
+ m_filename = src;
+ m_dest = dst;
+
+ // create destination directory
+ QDir().mkpath(dst);
+
+ QFuture future = QtConcurrent::run(this, &CArchive::copyProfileFiles);
+
+ return true;
+}
+
+bool CArchive::copyFiles(const FilesToCopy &files)
+{
+ qint64 totalSize = 0;
+
+ foreach(const FileToCopy &file, files)
+ {
+ totalSize += file.size;
+
+ qDebug() << file.filename;
+ }
+
+ emit extractInit(0, totalSize);
+
+ emit extractStart();
+
+ qint64 processedSize = 0;
+
+ foreach(const FileToCopy &file, files)
+ {
+ if (mustStop())
+ {
+ emit extractStop();
+ return true;
+ }
+
+ emit extractProgress(processedSize, file.filename);
+
+ QFileInfo dstFileInfo(file.dst);
+
+ if (dstFileInfo.size() != file.size || dstFileInfo.lastModified() != file.date)
+ {
+ if (!QFile::copy(file.src, file.dst))
+ {
+ emit extractFail(tr("Unable to copy file %1").arg(file.src));
+ return false;
+ }
+
+ if (!NLMISC::CFile::setFileModificationDate(qToUtf8(file.dst), file.date.toTime_t()))
+ {
+ qDebug() << "Unable to change date";
+ }
+ }
+
+ processedSize += file.size;
+ }
+
+ emit extractSuccess(totalSize);
+ emit done();
+
+ return true;
+}
+
+bool CArchive::extract7z()
+{
+ Q7zFile inFile(m_filename);
+
+ if (!inFile.open())
+ {
+ emit extractFail(tr("Unable to open %1").arg(m_filename));
+ return false;
+ }
+
+ emit extractPrepare();
+
+ UInt16 *temp = NULL;
+ size_t tempSize = 0;
+
+ // register the files read handlers
+ CLookToRead lookStream;
+ lookStream.realStream = &inFile;
+ LookToRead_CreateVTable(&lookStream, False);
+ LookToRead_Init(&lookStream);
+
+ // init CRC table
+ CrcGenerateTable();
+
+ // init 7z
+ CSzArEx db;
+ SzArEx_Init(&db);
+
+ // register allocators
+ ISzAlloc allocImp;
+ allocImp.Alloc = SzAlloc;
+ allocImp.Free = SzFree;
+
+ ISzAlloc allocTempImp;
+ allocTempImp.Alloc = SzAllocTemp;
+ allocTempImp.Free = SzFreeTemp;
+
+ qint64 total = 0, totalUncompressed = 0;
+ QString error;
+
+ // open 7z acrhive
+ SRes res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);
+
+ if (res == SZ_OK)
+ {
+ // process each file in archive
+ for (UInt32 i = 0; i < db.NumFiles; ++i)
+ {
+ bool isDir = SzArEx_IsDir(&db, i) != 0;
+
+ if (!isDir) total += SzArEx_GetFileSize(&db, i);
+ }
+
+ emit extractInit(0, total);
+
+ emit extractStart();
+
+ // variables used for decompression
+ UInt32 blockIndex = 0xFFFFFFFF;
+ Byte *outBuffer = NULL;
+ size_t outBufferSize = 0;
+
+ // process each file in archive
+ for (UInt32 i = 0; i < db.NumFiles; ++i)
+ {
+ if (mustStop())
+ {
+ res = SZ_ERROR_INTERRUPTED;
+ break;
+ }
+
+ size_t offset = 0;
+ size_t outSizeProcessed = 0;
+
+ bool isDir = SzArEx_IsDir(&db, i) != 0;
+
+ size_t len = SzArEx_GetFileNameUtf16(&db, i, NULL);
+
+ if (len > tempSize)
+ {
+ SzFree(NULL, temp);
+ tempSize = len;
+ temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0]));
+ if (!temp)
+ {
+ res = SZ_ERROR_MEM;
+ break;
+ }
+ }
+
+ SzArEx_GetFileNameUtf16(&db, i, temp);
+
+ QString path = QString::fromUtf16(temp);
+ QString filename = QFileInfo(path).fileName();
+
+ if (!isDir)
+ {
+ emit extractProgress(totalUncompressed, filename);
+
+ res = SzArEx_Extract(&db, &lookStream.s, i, &blockIndex, &outBuffer, &outBufferSize,
+ &offset, &outSizeProcessed, &allocImp, &allocTempImp);
+
+ if (res != SZ_OK) break;
+ }
+
+ QString destPath = m_dest + '/' + path;
+
+ QDir dir;
+
+ if (isDir)
+ {
+ dir.mkpath(destPath);
+ continue;
+ }
+
+ dir.mkpath(QFileInfo(destPath).absolutePath());
+
+ QFile outFile(destPath);
+
+ if (!outFile.open(QFile::WriteOnly))
+ {
+ error = tr("Unable to open output file");
+ res = SZ_ERROR_FAIL;
+ break;
+ }
+
+ size_t processedSize = outFile.write((const char*)(outBuffer + offset), outSizeProcessed);
+
+ if (processedSize != outSizeProcessed)
+ {
+ error = tr("Unable to write output file");
+ res = SZ_ERROR_FAIL;
+ break;
+ }
+
+ outFile.close();
+
+ totalUncompressed += SzArEx_GetFileSize(&db, i);
+
+ emit extractProgress(totalUncompressed, filename);
+
+ if (SzBitWithVals_Check(&db.Attribs, i))
+ Set7zFileAttrib(destPath, db.Attribs.Vals[i]);
+ }
+
+ IAlloc_Free(&allocImp, outBuffer);
+ }
+
+ SzArEx_Free(&db, &allocImp);
+ SzFree(NULL, temp);
+
+ switch(res)
+ {
+ case SZ_OK:
+ emit extractSuccess(totalUncompressed);
+ emit done();
+ return true;
+
+ case SZ_ERROR_INTERRUPTED:
+ emit extractStop();
+ return true;
+
+ case SZ_ERROR_UNSUPPORTED:
+ error = tr("7zip decoder doesn't support this archive");
+ break;
+
+ case SZ_ERROR_MEM:
+ error = tr("Unable to allocate memory");
+ break;
+
+ case SZ_ERROR_CRC:
+ error = tr("7zip decoder doesn't support this archive");
+ break;
+
+ case SZ_ERROR_FAIL:
+ // error already defined
+ break;
+
+ default:
+ error = tr("Error %1").arg(res);
+ }
+
+ emit extractFail(error);
+
+ return false;
+}
+
+bool CArchive::extractZip()
+{
+ emit extractPrepare();
+
+ QZipReader reader(m_filename);
+
+ QDir baseDir(m_dest);
+
+ // create directories first
+ QList allFiles = reader.fileInfoList();
+
+ qint64 totalSize = 0, currentSize = 0;
+
+ foreach (const QZipReader::FileInfo &fi, allFiles)
+ {
+ if (fi.isDir)
+ {
+ const QString absPath = m_dest + QDir::separator() + fi.filePath;
+
+ if (!baseDir.mkpath(fi.filePath))
+ {
+ emit extractFail(tr("Unable to create directory %1").arg(absPath));
+ return false;
+ }
+
+ if (!QFile::setPermissions(absPath, fi.permissions))
+ {
+ emit extractFail(tr("Unable to set permissions of %1").arg(absPath));
+ return false;
+ }
+ }
+
+ totalSize += fi.size;
+ }
+
+ emit extractInit(0, totalSize);
+ emit extractStart();
+
+ // client won't use symbolic links so don't process them
+
+ foreach (const QZipReader::FileInfo &fi, allFiles)
+ {
+ const QString absPath = m_dest + QDir::separator() + fi.filePath;
+
+ if (fi.isFile)
+ {
+ if (mustStop())
+ {
+ emit extractStop();
+ return true;
+ }
+
+ QFile f(absPath);
+
+ if (!f.open(QIODevice::WriteOnly))
+ {
+ emit extractFail(tr("Unable to open %1").arg(absPath));
+ return false;
+ }
+
+ currentSize += f.write(reader.fileData(fi.filePath));
+
+ f.setPermissions(fi.permissions);
+ f.close();
+
+ emit extractProgress(currentSize, QFileInfo(absPath).fileName());
+ }
+ }
+
+ emit extractSuccess(totalSize);
+ emit done();
+
+ return true;
+}
+
+bool CArchive::progress(const std::string &filename, uint32 currentSize, uint32 totalSize)
+{
+ if (mustStop())
+ {
+ emit extractStop();
+
+ return false;
+ }
+
+ if (currentSize == 0)
+ {
+ emit extractInit(0, (qint64)totalSize);
+ emit extractStart();
+ }
+
+ emit extractProgress((qint64)currentSize, qFromUtf8(filename));
+
+ if (currentSize == totalSize)
+ {
+ emit extractSuccess((qint64)totalSize);
+ emit done();
+ }
+
+ return true;
+}
+
+bool CArchive::extractBnp()
+{
+ QString error;
+
+ emit extractPrepare();
+
+ NLMISC::CBigFile::TUnpackProgressCallback cbMethod = NLMISC::CBigFile::TUnpackProgressCallback(this, &CArchive::progress);
+
+ try
+ {
+ if (NLMISC::CBigFile::unpack(qToUtf8(m_filename), qToUtf8(m_dest), &cbMethod))
+ {
+ return true;
+ }
+
+ if (mustStop())
+ {
+ // stopped
+
+ return true;
+ }
+
+ error.clear();
+ }
+ catch(const NLMISC::EDiskFullError &e)
+ {
+ error = tr("disk full");
+ }
+ catch(const NLMISC::EWriteError &e)
+ {
+ error = tr("unable to write %1").arg(qFromUtf8(e.Filename));
+ }
+ catch(const NLMISC::EReadError &e)
+ {
+ error = tr("unable to read %1").arg(qFromUtf8(e.Filename));
+ }
+ catch(const std::exception &e)
+ {
+ error = tr("failed (%1)").arg(qFromUtf8(e.what()));
+ }
+
+ emit extractFail(tr("Unable to unpack %1 to %2: %3").arg(m_filename).arg(m_dest).arg(error));
+
+ return false;
+}
+
+void CArchive::stop()
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_mustStop = true;
+}
+
+bool CArchive::mustStop()
+{
+ QMutexLocker locker(&m_mutex);
+
+ return m_mustStop;
+}
+
diff --git a/code/ryzom/tools/client/ryzom_installer/src/archive.h b/code/ryzom/tools/client/ryzom_installer/src/archive.h
new file mode 100644
index 000000000..a442056e1
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/archive.h
@@ -0,0 +1,100 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+/**
+ * Files copy, decompression, extraction
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CArchive : public QObject
+{
+ Q_OBJECT
+
+public:
+ CArchive(QObject *parent = NULL);
+ virtual ~CArchive();
+
+ bool extract(const QString &filename, const QString &dest);
+ bool copyServerFiles(const QString &src, const QString &dst);
+ bool copyProfileFiles(const QString &src, const QString &dst);
+ bool cleanServerFiles(const QString &directory);
+
+ void stop();
+ bool mustStop();
+
+signals:
+ // emitted when requesting real URL
+ void extractPrepare();
+
+ // emitted when we got the initial (local) and total (remote) size of file
+ void extractInit(qint64 current, qint64 total);
+
+ // emitted when we begin to download
+ void extractStart();
+
+ // emitted when the download stopped
+ void extractStop();
+
+ // emitted when extracting
+ void extractProgress(qint64 current, const QString &filename);
+
+ // emitted when the whole file is downloaded
+ void extractSuccess(qint64 total);
+
+ // emitted when an error occurs
+ void extractFail(const QString &error);
+
+ // emitted when done and should process next step
+ void done();
+
+protected:
+
+ struct FileToCopy
+ {
+ QString filename;
+ QString src;
+ QString dst;
+ qint64 size;
+ QDateTime date;
+ };
+
+ typedef QList FilesToCopy;
+
+ bool extract7z();
+ bool extractZip();
+ bool extractBnp();
+
+ bool progress(const std::string &filename, uint32 currentFile, uint32 totalFiles);
+
+ bool copyServerFiles();
+ bool copyProfileFiles();
+ bool copyFiles(const FilesToCopy &files);
+
+ static void getFilesList(const QString &srcDir, const QString &dstDir, const QStringList &filter, FilesToCopy &files);
+
+ QString m_filename;
+ QString m_dest;
+
+ QMutex m_mutex;
+
+ bool m_mustStop;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp
new file mode 100644
index 000000000..d62a58372
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp
@@ -0,0 +1,633 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "configfile.h"
+#include "utils.h"
+
+#include "nel/misc/path.h"
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+const CServer NoServer;
+const CProfile NoProfile;
+
+CConfigFile *CConfigFile::s_instance = NULL;
+
+CConfigFile::CConfigFile(QObject *parent):QObject(parent), m_defaultServer(0), m_defaultProfile(0), m_use64BitsClient(false)
+{
+ s_instance = this;
+
+ m_defaultConfigPath = QApplication::applicationDirPath() + "/installer.ini";
+ m_configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/installer.ini";
+}
+
+CConfigFile::~CConfigFile()
+{
+ s_instance = NULL;
+}
+
+bool CConfigFile::load()
+{
+ return load(m_configPath) || load(m_defaultConfigPath);
+}
+
+bool CConfigFile::load(const QString &filename)
+{
+ QSettings settings(filename, QSettings::IniFormat);
+
+ settings.beginGroup("common");
+ m_language = settings.value("language").toString();
+ m_srcDirectory = settings.value("source_directory").toString();
+ m_installationDirectory = settings.value("installation_directory").toString();
+ m_use64BitsClient = settings.value("use_64bits_client").toBool();
+ settings.endGroup();
+
+ settings.beginGroup("servers");
+ int serversCount = settings.value("size").toInt();
+ m_defaultServer = settings.value("default").toInt();
+ settings.endGroup();
+
+ m_servers.resize(serversCount);
+
+ for(int i = 0; i < serversCount; ++i)
+ {
+ CServer &server = m_servers[i];
+
+ settings.beginGroup(QString("server_%1").arg(i));
+
+ server.id = settings.value("id").toString();
+ server.name = settings.value("name").toString();
+ server.displayUrl = settings.value("display_url").toString();
+ server.dataDownloadUrl = settings.value("data_download_url").toString();
+ server.dataDownloadFilename = settings.value("data_download_filename").toString();
+ server.dataCompressedSize = settings.value("data_compressed_size").toULongLong();
+ server.dataUncompressedSize = settings.value("data_uncompressed_size").toULongLong();
+ server.clientDownloadUrl = settings.value("client_download_url").toString();
+ server.clientDownloadFilename = settings.value("client_download_filename").toString();
+#if defined(Q_OS_WIN)
+ server.clientFilename = settings.value("client_filename_windows").toString();
+#elif defined(Q_OS_MAC)
+ server.clientFilename = settings.value("client_filename_osx").toString();
+#else
+ server.clientFilename = settings.value("client_filename_linux").toString();
+#endif
+ server.comments = settings.value("comments").toString();
+
+ settings.endGroup();
+ }
+
+ settings.beginGroup("profiles");
+ int profilesCounts = settings.value("size").toInt();
+ m_defaultProfile = settings.value("default").toInt();
+ settings.endGroup();
+
+ m_profiles.resize(profilesCounts);
+
+ for(int i = 0; i < profilesCounts; ++i)
+ {
+ CProfile &profile = m_profiles[i];
+
+ settings.beginGroup(QString("profile_%1").arg(i));
+
+ profile.id = settings.value("id").toInt();
+ profile.name = settings.value("name").toString();
+ profile.account = settings.value("account").toString();
+ profile.server = settings.value("server").toString();
+ profile.executable = settings.value("executable").toString();
+ profile.arguments = settings.value("arguments").toString();
+ profile.comments = settings.value("comments").toString();
+ profile.desktopShortcut = settings.value("desktop_shortcut").toBool();
+ profile.menuShortcut = settings.value("menu_shortcut").toBool();
+
+ settings.endGroup();
+ }
+
+ return !m_servers.isEmpty();
+}
+
+bool CConfigFile::save() const
+{
+ QSettings settings(m_configPath, QSettings::IniFormat);
+
+ settings.beginGroup("common");
+ settings.setValue("language", m_language);
+ settings.setValue("source_directory", m_srcDirectory);
+ settings.setValue("installation_directory", m_installationDirectory);
+ settings.setValue("use_64bits_client", m_use64BitsClient);
+ settings.endGroup();
+
+ settings.beginGroup("servers");
+ settings.setValue("size", m_servers.size());
+ settings.setValue("default", m_defaultServer);
+ settings.endGroup();
+
+ for(int i = 0; i < m_servers.size(); ++i)
+ {
+ const CServer &server = m_servers[i];
+
+ settings.beginGroup(QString("server_%1").arg(i));
+
+ settings.setValue("id", server.id);
+ settings.setValue("name", server.name);
+ settings.setValue("display_url", server.displayUrl);
+ settings.setValue("data_download_url", server.dataDownloadUrl);
+ settings.setValue("data_download_filename", server.dataDownloadFilename);
+ settings.setValue("data_compressed_size", server.dataCompressedSize);
+ settings.setValue("data_uncompressed_size", server.dataUncompressedSize);
+ settings.setValue("client_download_url", server.clientDownloadUrl);
+ settings.setValue("client_download_filename", server.clientDownloadFilename);
+#if defined(Q_OS_WIN)
+ settings.setValue("client_filename_windows", server.clientFilename);
+#elif defined(Q_OS_MAC)
+ settings.setValue("client_filename_osx", server.clientFilename);
+#else
+ settings.setValue("client_filename_linux", server.clientFilename);
+#endif
+ settings.setValue("comments", server.comments);
+
+ settings.endGroup();
+ }
+
+ settings.beginGroup("profiles");
+ settings.setValue("size", m_profiles.size());
+ settings.setValue("default", m_defaultProfile);
+ settings.endGroup();
+
+ for(int i = 0; i < m_profiles.size(); ++i)
+ {
+ const CProfile &profile = m_profiles[i];
+
+ settings.beginGroup(QString("profile_%1").arg(i));
+
+ settings.setValue("id", profile.id);
+ settings.setValue("name", profile.name);
+ settings.setValue("account", profile.account);
+ settings.setValue("server", profile.server);
+ settings.setValue("executable", profile.executable);
+ settings.setValue("arguments", profile.arguments);
+ settings.setValue("comments", profile.comments);
+ settings.setValue("desktop_shortcut", profile.desktopShortcut);
+ settings.setValue("menu_shortcut", profile.menuShortcut);
+
+ settings.endGroup();
+ }
+
+ return true;
+}
+
+CConfigFile* CConfigFile::getInstance()
+{
+ return s_instance;
+}
+
+int CConfigFile::getServersCount() const
+{
+ return m_servers.size();
+}
+
+const CServer& CConfigFile::getServer(int i) const
+{
+ if (i < 0) i = m_defaultServer;
+
+ if (i >= m_servers.size()) return NoServer;
+
+ return m_servers.at(i);
+}
+
+const CServer& CConfigFile::getServer(const QString &id) const
+{
+ for(int i = 0; i < m_servers.size(); ++i)
+ {
+ if (m_servers[i].id == id) return m_servers[i];
+ }
+
+ // default server
+ return getServer();
+}
+
+int CConfigFile::getProfilesCount() const
+{
+ return m_profiles.size();
+}
+
+CProfile CConfigFile::getProfile(int i) const
+{
+ if (i < 0) i = m_defaultProfile;
+
+ if (i >= m_profiles.size()) return NoProfile;
+
+ return m_profiles.at(i);
+}
+
+void CConfigFile::setProfile(int i, const CProfile &profile)
+{
+ m_profiles[i] = profile;
+}
+
+int CConfigFile::addProfile(const CProfile &profile)
+{
+ m_profiles.append(profile);
+
+ return m_profiles.size()-1;
+}
+
+void CConfigFile::removeProfile(int i)
+{
+ m_profiles.removeAt(i);
+
+ // TODO: decalle all profiles and move files
+}
+
+bool CConfigFile::has64bitsOS()
+{
+ return QSysInfo::currentCpuArchitecture() == "x86_64";
+}
+
+int CConfigFile::getDefaultProfile() const
+{
+ return m_defaultProfile;
+}
+
+int CConfigFile::getDefaultServer() const
+{
+ return m_defaultServer;
+}
+
+bool CConfigFile::isRyzomInstallerConfigured() const
+{
+ return m_profiles.size() > 0;
+}
+
+QString CConfigFile::getInstallationDirectory() const
+{
+ return m_installationDirectory;
+}
+
+void CConfigFile::setInstallationDirectory(const QString &directory)
+{
+ m_installationDirectory = directory;
+}
+
+QString CConfigFile::getSrcServerDirectory() const
+{
+ return m_srcDirectory;
+}
+
+void CConfigFile::setSrcServerDirectory(const QString &directory)
+{
+ m_srcDirectory = directory;
+}
+
+QString CConfigFile::getProfileDirectory() const
+{
+ return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+}
+
+QString CConfigFile::getSrcProfileDirectory() const
+{
+ if (QFile::exists(getSrcServerDirectory() + "/client.cfg")) return getSrcServerDirectory();
+
+ return qFromUtf8(NLMISC::CPath::getApplicationDirectory("Ryzom"));
+}
+
+bool CConfigFile::use64BitsClient() const
+{
+ return m_use64BitsClient;
+}
+
+void CConfigFile::setUse64BitsClient(bool on)
+{
+ m_use64BitsClient = on;
+}
+
+QString CConfigFile::expandVariables(const QString &str)
+{
+ QString res = str;
+
+ res.replace("$TIMESTAMP", QString::number(QDateTime::currentDateTime().toTime_t()));
+ res.replace("$LANG", m_language);
+ res.replace("$ARCH", getClientArch());
+
+ return res;
+}
+
+QString CConfigFile::getClientArch() const
+{
+#if defined(Q_OS_WIN)
+ return QString("win%1").arg(m_use64BitsClient ? 64:32);
+#elif defined(Q_OS_MAC)
+ // only 64 bits clients under OS X, becure there not any 32 bits OS X version anymore
+ return "osx";
+#else
+ return QString("linux%1").arg(m_use64BitsClient ? 64:32);
+#endif
+}
+
+QString CConfigFile::getCurrentDirectory()
+{
+ return QDir::current().absolutePath();
+}
+
+QString CConfigFile::getParentDirectory()
+{
+ QDir current = QDir::current();
+ current.cdUp();
+ return current.absolutePath();
+}
+
+QString CConfigFile::getApplicationDirectory()
+{
+ return QApplication::applicationDirPath();
+}
+
+QString CConfigFile::getOldInstallationDirectory()
+{
+ // HKEY_CURRENT_USER/SOFTWARE/Nevrax/RyzomInstall/InstallId=1917716796 (string)
+#if defined(Q_OS_WIN)
+ // NSIS previous official installer
+#ifdef Q_OS_WIN64
+ // use WOW6432Node in 64 bits (64 bits OS and 64 bits Installer) because Ryzom old installer was in 32 bits
+ QSettings settings("HKEY_LOCAL_MACHINE\\Software\\WOW6432Node\\Nevrax\\Ryzom", QSettings::NativeFormat);
+#else
+ QSettings settings("HKEY_LOCAL_MACHINE\\Software\\Nevrax\\Ryzom", QSettings::NativeFormat);
+#endif
+
+ if (settings.contains("Ryzom Install Path"))
+ {
+ return QDir::fromNativeSeparators(settings.value("Ryzom Install Path").toString());
+ }
+
+ // check default directory if registry key not found
+ return CConfigFile::has64bitsOS() ? "C:/Program Files (x86)/Ryzom":"C:/Program Files/Ryzom";
+#elif defined(Q_OS_MAC)
+ return "/Applications/Ryzom.app";
+#else
+ return QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.ryzom";
+#endif
+}
+
+QString CConfigFile::getNewInstallationDirectory()
+{
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+}
+
+bool CConfigFile::isRyzomInstalledIn(const QString &directory) const
+{
+ // check client and data
+ return isRyzomClientInstalledIn(directory) && areRyzomDataInstalledIn(directory);
+}
+
+bool CConfigFile::areRyzomDataInstalledIn(const QString &directory) const
+{
+ QDir dir(directory);
+
+ // directory doesn't exist
+ if (!dir.exists()) return false;
+
+ if (!dir.cd("data") && dir.exists()) return false;
+
+ // at least 200 BNP in data directory
+ if (dir.entryList(QStringList() << "*.bnp", QDir::Files).size() < 200) return false;
+
+ // TODO: more checks
+
+ return true;
+}
+
+bool CConfigFile::isRyzomClientInstalledIn(const QString &directory) const
+{
+ QDir dir(directory);
+
+ // directory doesn't exist
+ if (!dir.exists()) return false;
+
+ // client_default.cfg doesn't exist
+ if (!dir.exists("client_default.cfg")) return false;
+
+ QString clientFilename = getServer().clientFilename;
+
+ // check if client is defined and exists
+ if (!clientFilename.isEmpty() && !dir.exists(clientFilename)) return false;
+
+ // TODO: more checks
+
+ return true;
+}
+
+bool CConfigFile::foundTemporaryFiles(const QString &directory) const
+{
+ QDir dir(directory);
+
+ // directory doesn't exist
+ if (!dir.exists()) return false;
+
+ if (!dir.cd("data") && dir.exists()) return false;
+
+ // temporary files
+ if (!dir.entryList(QStringList() << "*.string_cache" << "*.packed_sheets" << "*.packed" << "*.pem", QDir::Files).isEmpty()) return true;
+
+ // fonts directory is not needed anymore
+ if (dir.cd("fonts") && dir.exists()) return true;
+
+ return false;
+}
+
+bool CConfigFile::shouldCreateDesktopShortcut() const
+{
+ const CProfile &profile = getProfile();
+
+ return profile.desktopShortcut && !QFile::exists(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/Ryzom.lnk");
+}
+
+QString CConfigFile::getClientFullPath() const
+{
+ QString path = getProfile().executable;
+
+ if (!path.isEmpty()) return path;
+
+ return getInstallationDirectory() + "/" + getServer().id + "/" + getServer().clientFilename;
+}
+
+QString CConfigFile::getSrcServerClientBNPFullPath() const
+{
+ return QString("%1/unpack/exedll_%2.bnp").arg(getSrcServerDirectory()).arg(getClientArch());
+}
+
+CConfigFile::InstallationStep CConfigFile::getNextStep() const
+{
+ // get last used profile
+ const CProfile &profile = getProfile();
+
+ // get server used by it or default server
+ CServer server = getServer(profile.server);
+
+ // no or wrong profile
+ if (server.id.isEmpty())
+ {
+ // get last used server
+ server = getServer();
+ }
+
+ // no or wrong server
+ if (server.id.isEmpty())
+ {
+ // get first server
+ server = getServer(0);
+ }
+
+ // no server defined, shouldn't happen
+ if (server.id.isEmpty())
+ {
+ return DisplayNoServerError;
+ }
+
+ // only show wizard if installation directory undefined
+ if (getInstallationDirectory().isEmpty())
+ {
+ return ShowWizard;
+ }
+
+ QString serverDirectory = getInstallationDirectory() + "/" + server.id;
+
+ if (getSrcServerDirectory().isEmpty())
+ {
+ // user decided to download files
+
+ // downloaded files are kept in server directory
+ QString dataFile = getInstallationDirectory() + "/" + server.dataDownloadFilename;
+ QString clientFile = getInstallationDirectory() + "/" + server.clientDownloadFilename;
+
+ // data are not copied
+ if (!areRyzomDataInstalledIn(serverDirectory))
+ {
+ // when file is not finished, it has .part extension
+ if (!QFile::exists(dataFile))
+ {
+ return DownloadData;
+ }
+
+ return ExtractDownloadedData;
+ }
+
+ if (!isRyzomClientInstalledIn(serverDirectory))
+ {
+ // when file is not finished, it has .part extension
+ if (!QFile::exists(clientFile))
+ {
+ return DownloadClient;
+ }
+
+ return ExtractDownloadedClient;
+ }
+ }
+ else
+ {
+ // user decided to copy files
+
+ // selected directory contains Ryzom files (shouldn't fail)
+ if (!areRyzomDataInstalledIn(getSrcServerDirectory()))
+ {
+ return ShowWizard;
+ }
+
+ // data are not copied
+ if (!areRyzomDataInstalledIn(serverDirectory))
+ {
+ return CopyServerFiles;
+ }
+
+ // client is not extracted from BNP
+ if (!isRyzomClientInstalledIn(serverDirectory))
+ {
+ if (foundTemporaryFiles(serverDirectory))
+ {
+ return CleanFiles;
+ }
+
+ if (QFile::exists(getSrcServerClientBNPFullPath()))
+ {
+ return ExtractBnpClient;
+ }
+ else
+ {
+ QString clientFile = getInstallationDirectory() + "/" + server.clientDownloadFilename;
+
+ // when file is not finished, it has .part extension
+ if (!QFile::exists(clientFile))
+ {
+ return DownloadClient;
+ }
+
+ return ExtractDownloadedClient;
+ }
+ }
+ }
+
+ // no default profile
+ if (profile.id < 0)
+ {
+ return CreateProfile;
+ }
+
+ QString clientCfg = QString("%1/%2/client.cfg").arg(getProfileDirectory()).arg(profile.id);
+
+ // migration profile
+ if (!getSrcServerDirectory().isEmpty() && QFile::exists(getSrcProfileDirectory() + "/client.cfg") && !QFile::exists(clientCfg))
+ {
+ return CopyProfileFiles;
+ }
+
+ if (shouldCreateDesktopShortcut())
+ {
+ // TODO: check they point to getClientFullPath()
+ return CreateShortcuts;
+ }
+
+ return Done;
+}
+
+bool CConfigFile::createDefaultProfile()
+{
+ CServer server = getServer(getDefaultServer());
+
+ CProfile profile;
+
+ profile.id = 0;
+ profile.executable = getClientFullPath();
+ profile.name = QString("Ryzom (%1)").arg(server.name);
+ profile.server = server.id;
+ profile.comments = "Default profile created by Ryzom Installer";
+
+#ifdef Q_OS_WIN32
+ profile.desktopShortcut = QFile::exists(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/Ryzom.lnk");
+#endif
+
+ // TODO
+ // profile.menuShortcut
+
+ addProfile(profile);
+ save();
+
+ return true;
+}
+
+bool CConfigFile::createDefaultShortcuts()
+{
+ return true;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.h b/code/ryzom/tools/client/ryzom_installer/src/configfile.h
new file mode 100644
index 000000000..3426681bc
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.h
@@ -0,0 +1,186 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef CONFIGFILE_H
+#define CONFIGFILE_H
+
+struct CServer
+{
+ CServer()
+ {
+ dataCompressedSize = 0;
+ dataUncompressedSize = 0;
+ }
+
+ QString id;
+ QString name;
+ QString displayUrl;
+ QString dataDownloadUrl;
+ QString dataDownloadFilename;
+ qint64 dataCompressedSize;
+ qint64 dataUncompressedSize;
+ QString clientDownloadUrl;
+ QString clientDownloadFilename;
+ QString clientFilename;
+ QString comments;
+};
+
+extern const CServer NoServer;
+
+typedef QVector CServers;
+
+struct CProfile
+{
+ CProfile()
+ {
+ id = -1;
+ desktopShortcut = false;
+ menuShortcut = false;
+ }
+
+ int id;
+ QString account;
+ QString name;
+ QString server;
+ QString executable;
+ QString arguments;
+ QString comments;
+ bool desktopShortcut;
+ bool menuShortcut;
+};
+
+extern const CProfile NoProfile;
+
+typedef QVector CProfiles;
+
+/**
+ * Config file management and other stuff related to location of files/directories.
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CConfigFile : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum InstallationStep
+ {
+ DisplayNoServerError,
+ ShowWizard,
+ DownloadData,
+ ExtractDownloadedData,
+ DownloadClient,
+ ExtractDownloadedClient,
+ CopyServerFiles,
+ CopyProfileFiles,
+ CleanFiles,
+ ExtractBnpClient,
+ CreateProfile,
+ CreateShortcuts,
+ Done
+ };
+
+ CConfigFile(QObject *parent = NULL);
+ virtual ~CConfigFile();
+
+ bool load();
+ bool load(const QString &filename);
+ bool save() const;
+
+ static CConfigFile* getInstance();
+
+ CServers getServers() const { return m_servers; }
+ void setServers(const CServers &servers) { m_servers = servers; }
+
+ int getServersCount() const;
+ const CServer& getServer(int i = -1) const;
+ const CServer& getServer(const QString &id) const;
+
+ CProfiles getProfiles() const { return m_profiles; }
+ void setProfiles(const CProfiles &profiles) { m_profiles = profiles; }
+
+ int getProfilesCount() const;
+ CProfile getProfile(int i = -1) const;
+ void setProfile(int i, const CProfile &profile);
+ int addProfile(const CProfile &profile);
+ void removeProfile(int i);
+
+ int getDefaultServer() const;
+ int getDefaultProfile() const;
+
+ bool isRyzomInstallerConfigured() const;
+
+ QString getInstallationDirectory() const;
+ void setInstallationDirectory(const QString &directory);
+
+ QString getSrcServerDirectory() const;
+ void setSrcServerDirectory(const QString &directory);
+
+ QString getProfileDirectory() const;
+ QString getSrcProfileDirectory() const;
+
+ static bool has64bitsOS();
+
+ // default directories
+ static QString getCurrentDirectory();
+ static QString getParentDirectory();
+ static QString getApplicationDirectory();
+ static QString getOldInstallationDirectory();
+ static QString getNewInstallationDirectory();
+
+ bool isRyzomInstalledIn(const QString &directory) const;
+ bool areRyzomDataInstalledIn(const QString &directory) const;
+ bool isRyzomClientInstalledIn(const QString &directory) const;
+ bool foundTemporaryFiles(const QString &directory) const;
+ bool shouldCreateDesktopShortcut() const;
+
+ // installation choices
+ bool use64BitsClient() const;
+ void setUse64BitsClient(bool on);
+
+ QString expandVariables(const QString &str);
+
+ QString getClientArch() const;
+
+ QString getClientFullPath() const;
+
+ QString getSrcServerClientBNPFullPath() const;
+
+ InstallationStep getNextStep() const;
+
+ bool createDefaultProfile();
+ bool createDefaultShortcuts();
+
+private:
+ int m_defaultServer;
+ int m_defaultProfile;
+
+ CServers m_servers;
+ CProfiles m_profiles;
+
+ QString m_installationDirectory;
+ QString m_srcDirectory;
+ bool m_use64BitsClient;
+ QString m_language;
+
+ QString m_defaultConfigPath;
+ QString m_configPath;
+
+ static CConfigFile *s_instance;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp b/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp
new file mode 100644
index 000000000..f129416ad
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/downloader.cpp
@@ -0,0 +1,394 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "downloader.h"
+
+#include "nel/misc/system_info.h"
+#include "nel/misc/path.h"
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+CDownloader::CDownloader(QObject *parent):QObject(parent), m_manager(NULL), m_reply(NULL), m_timer(NULL),
+ m_offset(0), m_size(0), m_supportsAcceptRanges(false), m_supportsContentRange(false),
+ m_downloadAfterHead(false), m_aborted(false), m_file(NULL)
+{
+ m_manager = new QNetworkAccessManager(this);
+ m_timer = new QTimer(this);
+
+ connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
+}
+
+CDownloader::~CDownloader()
+{
+ stopTimer();
+ closeFile();
+}
+
+bool CDownloader::getHtmlPageContent(const QString &url)
+{
+ if (url.isEmpty()) return false;
+
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::UserAgentHeader, "Ryzom Installer/1.0");
+
+ QNetworkReply *reply = m_manager->get(request);
+
+ connect(reply, SIGNAL(finished()), SLOT(onHtmlPageFinished()));
+
+ return true;
+}
+
+bool CDownloader::prepareFile(const QString &url, const QString &fullPath)
+{
+ if (url.isEmpty()) return false;
+
+ m_downloadAfterHead = false;
+
+ emit downloadPrepare();
+
+ m_fullPath = fullPath;
+ m_url = url;
+
+ getFileHead();
+
+ return true;
+}
+
+bool CDownloader::getFile()
+{
+ if (m_fullPath.isEmpty() || m_url.isEmpty())
+ {
+ qDebug() << "You forget to call prepareFile before";
+
+ return false;
+ }
+
+ m_downloadAfterHead = true;
+
+ getFileHead();
+
+ return true;
+}
+
+bool CDownloader::stop()
+{
+ if (!m_reply) return false;
+
+ m_reply->abort();
+
+ return true;
+}
+
+void CDownloader::startTimer()
+{
+ stopTimer();
+
+ m_timer->setInterval(5000);
+ m_timer->setSingleShot(true);
+ m_timer->start();
+}
+
+void CDownloader::stopTimer()
+{
+ if (m_timer->isActive()) m_timer->stop();
+}
+
+bool CDownloader::openFile()
+{
+ closeFile();
+
+ m_file = new QFile(m_fullPath);
+
+ if (m_file->open(QFile::Append)) return true;
+
+ closeFile();
+
+ return false;
+}
+
+void CDownloader::closeFile()
+{
+ if (m_file)
+ {
+ m_file->close();
+
+ delete m_file;
+ m_file = NULL;
+ }
+}
+
+void CDownloader::getFileHead()
+{
+ if (m_supportsAcceptRanges)
+ {
+ QFileInfo fileInfo(m_fullPath);
+
+ if (fileInfo.exists())
+ {
+ m_offset = fileInfo.size();
+ }
+ else
+ {
+ m_offset = 0;
+ }
+
+ // continue if offset less than size
+ if (m_offset >= m_size)
+ {
+ if (checkDownloadedFile())
+ {
+ // file is already downloaded
+ emit downloadSuccess(m_size);
+ }
+ else
+ {
+ // or has wrong size
+ emit downloadFail(tr("File (%1B) is larger than expected (%2B)").arg(m_offset).arg(m_size));
+ }
+
+ return;
+ }
+ }
+
+ QNetworkRequest request(m_url);
+ request.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0");
+
+ if (m_supportsAcceptRanges)
+ {
+ request.setRawHeader("Range", QString("bytes=%1-").arg(m_offset).toLatin1());
+ }
+
+ m_reply = m_manager->head(request);
+
+ connect(m_reply, SIGNAL(finished()), SLOT(onHeadFinished()));
+ connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onError(QNetworkReply::NetworkError)));
+
+ startTimer();
+}
+
+void CDownloader::downloadFile()
+{
+ qint64 freeSpace = NLMISC::CSystemInfo::availableHDSpace(m_fullPath.toUtf8().constData());
+
+ if (freeSpace < m_size - m_offset)
+ {
+ // we have not enough free disk space to continue download
+ emit downloadFail(tr("You only have %1 bytes left on device, but %2 bytes are required.").arg(freeSpace).arg(m_size - m_offset));
+ return;
+ }
+
+ if (!openFile())
+ {
+ emit downloadFail(tr("Unable to write file"));
+ return;
+ }
+
+ QNetworkRequest request(m_url);
+ request.setHeader(QNetworkRequest::UserAgentHeader, "Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.17");
+
+ if (supportsResume())
+ {
+ request.setRawHeader("Range", QString("bytes=%1-%2").arg(m_offset).arg(m_size-1).toLatin1());
+ }
+
+ m_reply = m_manager->get(request);
+
+ connect(m_reply, SIGNAL(finished()), SLOT(onDownloadFinished()));
+ connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onError(QNetworkReply::NetworkError)));
+ connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64)));
+ connect(m_reply, SIGNAL(readyRead()), SLOT(onDownloadRead()));
+
+ emit downloadStart();
+
+ startTimer();
+}
+
+bool CDownloader::checkDownloadedFile()
+{
+ QFileInfo file(m_fullPath);
+
+ return file.size() == m_size && file.lastModified().toUTC() == m_lastModified;
+}
+
+void CDownloader::onTimeout()
+{
+ qDebug() << "Timeout";
+
+ emit downloadFail(tr("Timeout"));
+}
+
+void CDownloader::onHtmlPageFinished()
+{
+ QNetworkReply *reply = qobject_cast(sender());
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ QString html = QString::fromUtf8(reply->readAll());
+
+ reply->deleteLater();
+
+ emit htmlPageContent(html);
+}
+
+void CDownloader::onHeadFinished()
+{
+ stopTimer();
+
+ int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ QString redirection = m_reply->header(QNetworkRequest::LocationHeader).toString();
+
+ m_size = m_reply->header(QNetworkRequest::ContentLengthHeader).toInt();
+ m_lastModified = m_reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toUTC();
+
+ QString acceptRanges = QString::fromLatin1(m_reply->rawHeader("Accept-Ranges"));
+ QString contentRange = QString::fromLatin1(m_reply->rawHeader("Content-Range"));
+
+ m_reply->deleteLater();
+ m_reply = NULL;
+
+ // redirection
+ if (status == 302)
+ {
+ if (redirection.isEmpty())
+ {
+ emit downloadFail(tr("Redirection URL is not defined"));
+ return;
+ }
+
+ // redirection on another server, recheck resume
+ m_supportsAcceptRanges = false;
+ m_supportsContentRange = false;
+
+ m_referer = m_url;
+
+ // update real URL
+ m_url = redirection;
+
+ getFileHead();
+
+ return;
+ }
+
+ // we requested without range
+ else if (status == 200)
+ {
+ // update size
+ emit downloadInit(0, m_size);
+
+ if (!m_supportsAcceptRanges && acceptRanges == "bytes")
+ {
+ // server supports resume, part 1
+ m_supportsAcceptRanges = true;
+
+ // request range
+ getFileHead();
+ return;
+ }
+
+ // server doesn't support resume or
+ // we requested range, but server always returns 200
+ // download from the beginning
+ }
+
+ // we requested with a range
+ else if (status == 206)
+ {
+ // server supports resume
+ QRegExp regexp("^bytes ([0-9]+)-([0-9]+)/([0-9]+)$");
+
+ if (m_supportsAcceptRanges && regexp.exactMatch(contentRange))
+ {
+ m_supportsContentRange = true;
+ m_offset = regexp.cap(1).toLongLong();
+
+ // when resuming, Content-Length is the size of missing parts to download
+ m_size = regexp.cap(3).toLongLong();
+
+ // update offset and size
+ emit downloadInit(m_offset, m_size);
+ }
+ else
+ {
+ qDebug() << "Unable to parse";
+ }
+ }
+
+ // other status
+ else
+ {
+ emit downloadFail(tr("Wrong status code: %1").arg(status));
+ return;
+ }
+
+ if (m_downloadAfterHead)
+ {
+ if (checkDownloadedFile())
+ {
+ qDebug() << "same date and size";
+ }
+ else
+ {
+ downloadFile();
+ }
+ }
+}
+
+void CDownloader::onDownloadFinished()
+{
+ m_reply->deleteLater();
+ m_reply = NULL;
+
+ closeFile();
+
+ if (m_aborted)
+ {
+ m_aborted = false;
+ emit downloadStop();
+ }
+ else
+ {
+ bool ok = NLMISC::CFile::setFileModificationDate(m_fullPath.toUtf8().constData(), m_lastModified.toTime_t());
+
+ emit downloadSuccess(m_size);
+ }
+}
+
+void CDownloader::onError(QNetworkReply::NetworkError error)
+{
+ if (error == QNetworkReply::OperationCanceledError)
+ {
+ m_aborted = true;
+ }
+ else
+ {
+ emit downloadFail(tr("Network error: %1").arg(error));
+ }
+}
+
+void CDownloader::onDownloadProgress(qint64 current, qint64 total)
+{
+ stopTimer();
+
+ emit downloadProgress(m_offset + current);
+}
+
+void CDownloader::onDownloadRead()
+{
+ if (m_file) m_file->write(m_reply->readAll());
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/downloader.h b/code/ryzom/tools/client/ryzom_installer/src/downloader.h
new file mode 100644
index 000000000..7d9090700
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/downloader.h
@@ -0,0 +1,110 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef DOWNLOADER_H
+#define DOWNLOADER_H
+
+/**
+ * Files downloader, please note that only one file can be downloaded at once.
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CDownloader : public QObject
+{
+ Q_OBJECT
+
+public:
+ CDownloader(QObject *parent);
+ virtual ~CDownloader();
+
+ bool getHtmlPageContent(const QString &url);
+
+ bool prepareFile(const QString &url, const QString &fullPath);
+ bool getFile();
+ bool stop();
+
+ bool supportsResume() const { return m_supportsAcceptRanges && m_supportsContentRange; }
+
+ bool isDownloading() const { return m_file != NULL; }
+
+signals:
+ // emitted when requesting real URL
+ void downloadPrepare();
+
+ // emitted when we got the initial (local) and total (remote) size of file
+ void downloadInit(qint64 current, qint64 total);
+
+ // emitted when we begin to download
+ void downloadStart();
+
+ // emitted when the download stopped
+ void downloadStop();
+
+ // emittd when downloading
+ void downloadProgress(qint64 current);
+
+ // emitted when the whole file is downloaded
+ void downloadSuccess(qint64 total);
+
+ // emitted when an error occurs
+ void downloadFail(const QString &error);
+
+ void htmlPageContent(const QString &html);
+
+private slots:
+ void onTimeout();
+ void onHtmlPageFinished();
+ void onHeadFinished();
+ void onDownloadFinished();
+ void onError(QNetworkReply::NetworkError error);
+ void onDownloadProgress(qint64 current, qint64 total);
+ void onDownloadRead();
+
+protected:
+ void startTimer();
+ void stopTimer();
+
+ bool openFile();
+ void closeFile();
+
+ void getFileHead();
+ void downloadFile();
+
+ bool checkDownloadedFile();
+
+ QNetworkAccessManager *m_manager;
+ QNetworkReply *m_reply;
+ QTimer *m_timer;
+
+ QString m_url;
+ QString m_referer;
+ QString m_fullPath;
+
+ qint64 m_offset;
+ qint64 m_size;
+ QDateTime m_lastModified;
+
+ bool m_supportsAcceptRanges;
+ bool m_supportsContentRange;
+
+ bool m_downloadAfterHead;
+ bool m_aborted;
+
+ QFile *m_file;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/main.cpp b/code/ryzom/tools/client/ryzom_installer/src/main.cpp
new file mode 100644
index 000000000..d4ee9926e
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/main.cpp
@@ -0,0 +1,108 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "mainwindow.h"
+#include "configfile.h"
+#include "wizarddialog.h"
+
+#include "nel/misc/path.h"
+#include "nel/misc/ucstring.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef QT_STATICPLUGIN
+
+#include
+
+#if defined(Q_OS_WIN32)
+ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
+#elif defined(Q_OS_MAC)
+ Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
+#elif defined(Q_OS_UNIX)
+ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
+ Q_IMPORT_PLUGIN(QXcbGlxIntegrationPlugin)
+#endif
+
+ Q_IMPORT_PLUGIN(QICOPlugin)
+
+#endif
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+int main(int argc, char *argv[])
+{
+#if defined(_MSC_VER) && defined(_DEBUG)
+ _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+
+ NLMISC::CApplicationContext appContext;
+
+ QApplication app(argc, argv);
+
+ QApplication::setApplicationName("Ryzom");
+ QApplication::setApplicationVersion(RYZOM_VERSION);
+ QApplication::setWindowIcon(QIcon(":/icons/ryzom.ico"));
+
+ QLocale locale = QLocale::system();
+
+ // load application translations
+ QTranslator localTranslator;
+ if (localTranslator.load(locale, "ryzom_installer", "_", "translations"))
+ {
+ QApplication::installTranslator(&localTranslator);
+ }
+
+ // load Qt default translations
+ QTranslator qtTranslator;
+ if (qtTranslator.load(locale, "qt", "_", "translations"))
+ {
+ QApplication::installTranslator(&qtTranslator);
+ }
+
+ // instanciate ConfigFile
+ CConfigFile config;
+ CConfigFile::InstallationStep step = config.load() ? config.getNextStep():CConfigFile::DisplayNoServerError;
+
+ if (step == CConfigFile::DisplayNoServerError)
+ {
+ QMessageBox::critical(NULL, QApplication::tr("Error"), QApplication::tr("Unable to find installer.ini"));
+ return 1;
+ }
+
+ bool displayMainWindow = true;
+
+ if (step == CConfigFile::ShowWizard)
+ {
+ CWizardDialog dialog;
+
+ if (!dialog.exec()) displayMainWindow = false;
+ }
+
+ if (displayMainWindow)
+ {
+ CMainWindow mainWindow;
+ mainWindow.show();
+
+ return QApplication::exec();
+ }
+
+ return 0;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp
new file mode 100644
index 000000000..f8d6b6ed6
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp
@@ -0,0 +1,448 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "mainwindow.h"
+#include "downloader.h"
+#include "archive.h"
+#include "wizarddialog.h"
+#include "profilesdialog.h"
+#include "configfile.h"
+#include "config.h"
+#include "profilesmodel.h"
+
+#include "seven_zip.h"
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+#include
+#include
+#endif
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+CMainWindow::CMainWindow():QMainWindow(), m_archive(NULL), m_statusLabel(NULL)
+{
+ setupUi(this);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button = new QWinTaskbarButton(this);
+#endif
+
+ connect(resumeButton, SIGNAL(clicked()), SLOT(onResumeClicked()));
+ connect(stopButton, SIGNAL(clicked()), SLOT(onStopClicked()));
+
+ // downloader
+ m_downloader = new CDownloader(this);
+
+ connect(m_downloader, SIGNAL(downloadPrepare()), SLOT(onDownloadPrepare()));
+ connect(m_downloader, SIGNAL(downloadInit(qint64, qint64)), SLOT(onDownloadInit(qint64, qint64)));
+ connect(m_downloader, SIGNAL(downloadStart()), SLOT(onDownloadStart()));
+ connect(m_downloader, SIGNAL(downloadStop()), SLOT(onDownloadStop()));
+ connect(m_downloader, SIGNAL(downloadProgress(qint64)), SLOT(onDownloadProgress(qint64)));
+ connect(m_downloader, SIGNAL(downloadSuccess(qint64)), SLOT(onDownloadSuccess(qint64)));
+ connect(m_downloader, SIGNAL(downloadFail(QString)), SLOT(onDownloadFail(QString)));
+ connect(m_downloader, SIGNAL(htmlPageContent(QString)), SLOT(onHtmlPageContent(QString)));
+
+ // archive
+ m_archive = new CArchive(this);
+
+ connect(m_archive, SIGNAL(extractPrepare()), SLOT(onExtractPrepare()));
+ connect(m_archive, SIGNAL(extractInit(qint64, qint64)), SLOT(onExtractInit(qint64, qint64)));
+ connect(m_archive, SIGNAL(extractStart()), SLOT(onExtractStart()));
+ connect(m_archive, SIGNAL(extractStop()), SLOT(onExtractStop()));
+ connect(m_archive, SIGNAL(extractProgress(qint64, QString)), SLOT(onExtractProgress(qint64, QString)));
+ connect(m_archive, SIGNAL(extractSuccess(qint64)), SLOT(onExtractSuccess(qint64)));
+ connect(m_archive, SIGNAL(extractFail(QString)), SLOT(onExtractFail(QString)));
+ connect(m_archive, SIGNAL(done()), SLOT(onDone()));
+
+ connect(actionProfiles, SIGNAL(triggered()), SLOT(onProfiles()));
+
+ connect(playButton, SIGNAL(clicked()), SLOT(onPlayClicked()));
+ connect(configureButton, SIGNAL(clicked()), SLOT(onConfigureClicked()));
+
+ connect(actionAboutQt, SIGNAL(triggered()), SLOT(onAboutQt()));
+ connect(actionAbout, SIGNAL(triggered()), SLOT(onAbout()));
+
+ m_statusLabel = new QLabel();
+
+ statusBar()->addWidget(m_statusLabel);
+
+// setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+}
+
+CMainWindow::~CMainWindow()
+{
+}
+
+void CMainWindow::processNextStep()
+{
+ CConfigFile *config = CConfigFile::getInstance();
+
+ // default server
+ const CServer &server = config->getServer();
+
+ // default profile
+ const CProfile &configuration = config->getProfile();
+
+ switch(CConfigFile::getInstance()->getNextStep())
+ {
+ case CConfigFile::DisplayNoServerError:
+ break;
+
+ case CConfigFile::ShowWizard:
+ break;
+
+ case CConfigFile::DownloadData:
+ displayProgressBar();
+ m_downloader->prepareFile(config->expandVariables(server.dataDownloadUrl), config->getInstallationDirectory() + "/" + config->expandVariables(server.dataDownloadFilename) + ".part");
+ break;
+
+ case CConfigFile::ExtractDownloadedData:
+ displayProgressBar();
+ break;
+
+ case CConfigFile::DownloadClient:
+ displayProgressBar();
+ m_downloader->prepareFile(config->expandVariables(server.clientDownloadUrl), config->getInstallationDirectory() + "/" + config->expandVariables(server.clientDownloadFilename) + ".part");
+ break;
+
+ case CConfigFile::ExtractDownloadedClient:
+ displayProgressBar();
+ // TODO
+ break;
+
+ case CConfigFile::CopyServerFiles:
+ displayProgressBar();
+ m_archive->copyServerFiles(config->getSrcServerDirectory(), config->getInstallationDirectory() + "/" + server.id);
+ break;
+
+ case CConfigFile::CopyProfileFiles:
+ displayProgressBar();
+ m_archive->copyProfileFiles(config->getSrcProfileDirectory(), config->getProfileDirectory() + "/0");
+ break;
+
+ case CConfigFile::ExtractBnpClient:
+ displayProgressBar();
+ m_archive->extract(config->getSrcServerClientBNPFullPath(), config->getInstallationDirectory() + "/" + server.id);
+ break;
+
+ case CConfigFile::CleanFiles:
+ hideProgressBar();
+ m_archive->cleanServerFiles(config->getInstallationDirectory() + "/" + server.id);
+ break;
+
+ case CConfigFile::CreateProfile:
+ hideProgressBar();
+ config->createDefaultProfile();
+ onDone();
+ break;
+
+ case CConfigFile::CreateShortcuts:
+ hideProgressBar();
+ config->createDefaultShortcuts();
+ onDone();
+ break;
+
+ default:
+ // cases already managed in main.cpp
+ displayConfigurationsChoices();
+ break;
+ }
+
+ m_downloader->getHtmlPageContent(config->expandVariables(server.displayUrl));
+}
+
+void CMainWindow::displayProgressBar()
+{
+ downloadFrame->setVisible(true);
+ configurationFrame->setVisible(false);
+
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::hideProgressBar()
+{
+ downloadFrame->setVisible(false);
+ configurationFrame->setVisible(false);
+
+ resumeButton->setVisible(false);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::displayConfigurationsChoices()
+{
+ downloadFrame->setVisible(false);
+ configurationFrame->setVisible(true);
+
+ profilesComboBox->setModel(new CProfilesModel(this));
+}
+
+void CMainWindow::showEvent(QShowEvent *e)
+{
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->setWindow(windowHandle());
+#endif
+
+ e->accept();
+
+ processNextStep();
+}
+
+void CMainWindow::closeEvent(QCloseEvent *e)
+{
+ hide();
+
+ e->accept();
+}
+
+void CMainWindow::onResumeClicked()
+{
+ m_downloader->getFile();
+}
+
+void CMainWindow::onStopClicked()
+{
+ if (m_downloader->isDownloading())
+ {
+ if (!m_downloader->supportsResume())
+ {
+ QMessageBox::StandardButton res = QMessageBox::question(this, tr("Confirmation"), tr("Warning, this server doesn't support resume! If you stop download now, you won't be able to resume it later.\nAre you sure to abort download?"));
+
+ if (res != QMessageBox::Yes) return;
+ }
+
+ m_downloader->stop();
+ }
+ else
+ {
+ m_archive->stop();
+ }
+}
+
+void CMainWindow::onPlayClicked()
+{
+ int profileIndex = profilesComboBox->currentIndex();
+
+ if (profileIndex < 0) return;
+
+ CProfile profile = CConfigFile::getInstance()->getProfile(profileIndex);
+
+ if (profile.executable.isEmpty()) return;
+
+ QStringList arguments;
+ arguments << "-p";
+ arguments << QString::number(profileIndex);
+ arguments << profile.arguments;
+
+ bool started = QProcess::startDetached(profile.executable, arguments);
+}
+
+void CMainWindow::onConfigureClicked()
+{
+}
+
+void CMainWindow::onProfiles()
+{
+ CProfilesDialog dialog;
+
+ dialog.exec();
+}
+
+void CMainWindow::onAbout()
+{
+ QString br("
");
+
+ QMessageBox::about(this,
+ tr("About %1").arg("Ryzom Installer"),
+ QString("Ryzom Installer") + QApplication::applicationVersion() + br +
+ tr("Program to install, download and manage Ryzom configurations.") +
+ br+br+
+ tr("Author: %1").arg("Cedric 'Kervala' OCHS") + br +
+ tr("Copyright: %1").arg(COPYRIGHT) + br +
+ tr("Support: %1").arg("Ryzom Core on Bitbucket"));
+}
+
+void CMainWindow::onAboutQt()
+{
+ QMessageBox::aboutQt(this);
+}
+
+void CMainWindow::onDownloadPrepare()
+{
+ progressBar->setFormat(tr("%p% (%v/%m KiB)"));
+
+ progressBar->setMinimum(0);
+ progressBar->setMaximum(0);
+ progressBar->setValue(0);
+
+ resumeButton->setVisible(false);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::onDownloadInit(qint64 current, qint64 total)
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+
+ progressBar->setMinimum(0);
+ progressBar->setMaximum(total / 1024);
+ progressBar->setValue(current / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->setMinimum(0);
+ m_button->progress()->setMaximum(total / 1024);
+ m_button->progress()->setValue(current / 1024);
+#endif
+}
+
+void CMainWindow::onDownloadStart()
+{
+ resumeButton->setVisible(false);
+ stopButton->setVisible(true);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->show();
+#endif
+}
+
+void CMainWindow::onDownloadStop()
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->hide();
+#endif
+}
+
+void CMainWindow::onDownloadProgress(qint64 current)
+{
+ progressBar->setValue(current / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->setValue(current / 1024);
+#endif
+}
+
+void CMainWindow::onDownloadSuccess(qint64 total)
+{
+ progressBar->setValue(total / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->hide();
+#endif
+
+ resumeButton->setVisible(false);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::onDownloadFail(const QString &error)
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::onHtmlPageContent(const QString &html)
+{
+ htmlTextEdit->setHtml(html);
+}
+
+void CMainWindow::onExtractPrepare()
+{
+ progressBar->setFormat("%p%");
+
+ progressBar->setMinimum(0);
+ progressBar->setMaximum(0);
+ progressBar->setValue(0);
+
+ resumeButton->setVisible(false);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::onExtractInit(qint64 current, qint64 total)
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+
+ progressBar->setMinimum(0);
+ progressBar->setMaximum(total / 1024);
+ progressBar->setValue(current / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->setMinimum(0);
+ m_button->progress()->setMaximum(total / 1024);
+ m_button->progress()->setValue(current / 1024);
+#endif
+}
+
+void CMainWindow::onExtractStart()
+{
+ resumeButton->setVisible(false);
+ stopButton->setVisible(true);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->show();
+#endif
+}
+
+void CMainWindow::onExtractStop()
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->hide();
+#endif
+}
+
+void CMainWindow::onExtractProgress(qint64 current, const QString &filename)
+{
+ m_statusLabel->setText(tr("Extracting %1...").arg(filename));
+
+ progressBar->setValue(current / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->setValue(current / 1024);
+#endif
+}
+
+void CMainWindow::onExtractSuccess(qint64 total)
+{
+ m_statusLabel->setText(tr("Extraction done"));
+
+ progressBar->setValue(total / 1024);
+
+#if defined(Q_OS_WIN32) && defined(QT_WINEXTRAS_LIB)
+ m_button->progress()->hide();
+#endif
+
+ resumeButton->setVisible(false);
+ stopButton->setVisible(false);
+}
+
+void CMainWindow::onDone()
+{
+ processNextStep();
+}
+
+void CMainWindow::onExtractFail(const QString &error)
+{
+ resumeButton->setVisible(true);
+ stopButton->setVisible(false);
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h
new file mode 100644
index 000000000..aa2be5659
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.h
@@ -0,0 +1,89 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "ui_mainwindow.h"
+
+class QWinTaskbarButton;
+class CDownloader;
+class CArchive;
+
+/**
+ * Main window
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CMainWindow : public QMainWindow, public Ui::MainWindow
+{
+ Q_OBJECT
+
+public:
+ CMainWindow();
+ virtual ~CMainWindow();
+
+public slots:
+ void onResumeClicked();
+ void onStopClicked();
+
+ void onPlayClicked();
+ void onConfigureClicked();
+
+ void onProfiles();
+ void onAbout();
+ void onAboutQt();
+
+ void onDownloadPrepare();
+ void onDownloadInit(qint64 current, qint64 total);
+ void onDownloadStart();
+ void onDownloadStop();
+ void onDownloadProgress(qint64 current);
+ void onDownloadSuccess(qint64 total);
+ void onDownloadFail(const QString &error);
+
+ void onHtmlPageContent(const QString &html);
+
+ void onExtractPrepare();
+ void onExtractInit(qint64 current, qint64 total);
+ void onExtractStart();
+ void onExtractStop();
+ void onExtractProgress(qint64 current, const QString &filename);
+ void onExtractSuccess(qint64 total);
+ void onExtractFail(const QString &error);
+
+ void onDone();
+
+protected:
+ void showEvent(QShowEvent *e);
+ void closeEvent(QCloseEvent *e);
+
+ void processNextStep();
+
+ void displayProgressBar();
+ void hideProgressBar();
+
+ void displayConfigurationsChoices();
+
+ QWinTaskbarButton *m_button;
+ CDownloader *m_downloader;
+ CArchive *m_archive;
+
+ QLabel *m_statusLabel;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp
new file mode 100644
index 000000000..ff42f12ec
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.cpp
@@ -0,0 +1,207 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "profilesdialog.h"
+#include "profilesmodel.h"
+#include "serversmodel.h"
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+CProfilesDialog::CProfilesDialog():QDialog(), m_currentProfileIndex(-1)
+{
+ setupUi(this);
+
+ connect(addButton, SIGNAL(clicked()), SLOT(onAddProfile()));
+ connect(deleteButton, SIGNAL(clicked()), SLOT(onDeleteProfile()));
+ connect(profilesListView, SIGNAL(clicked(QModelIndex)), SLOT(onProfileClicked(QModelIndex)));
+ connect(executableBrowseButton, SIGNAL(clicked()), SLOT(onExecutableBrowseClicked()));
+
+ m_model = new CProfilesModel(this);
+ m_serversModel = new CServersModel(this);
+
+ profilesListView->setModel(m_model);
+ serverComboBox->setModel(m_serversModel);
+
+ int index = m_model->getIndexFromProfileID(CConfigFile::getInstance()->getDefaultProfile());
+
+ profilesListView->setCurrentIndex(m_model->index(index, 0));
+ displayProfile(index);
+}
+
+CProfilesDialog::~CProfilesDialog()
+{
+}
+
+void CProfilesDialog::accept()
+{
+ saveProfile(m_currentProfileIndex);
+
+ m_model->save();
+
+ QDialog::accept();
+}
+
+void CProfilesDialog::onAddProfile()
+{
+}
+
+void CProfilesDialog::onDeleteProfile()
+{
+ QMessageBox::StandardButton res = QMessageBox::question(this, tr("Confirmation"), tr("You're going to delete a profile, files won't be deleted and you'll have to do that manually.\nAre you sure to delete this profile?"));
+
+ if (res != QMessageBox::Yes) return;
+
+ QModelIndex index = profilesListView->currentIndex();
+
+ deleteProfile(index.row());
+}
+
+void CProfilesDialog::onProfileClicked(const QModelIndex &index)
+{
+ qDebug() << "clicked on" << index;
+
+ displayProfile(index.row());
+}
+
+void CProfilesDialog::displayProfile(int index)
+{
+ bool enabled = index > -1;
+
+ profileIdLabel->setEnabled(enabled);
+ accountEdit->setEnabled(enabled);
+ nameEdit->setEnabled(enabled);
+ serverComboBox->setEnabled(enabled);
+ argumentsEdit->setEnabled(enabled);
+ commentsEdit->setEnabled(enabled);
+
+ if (index < 0) return;
+
+ saveProfile(m_currentProfileIndex);
+
+ const CProfile &profile = m_model->getProfiles()[index];
+
+ // update all widgets with content of profile
+ profileIdLabel->setText(QString::number(profile.id));
+ accountEdit->setText(profile.account);
+ nameEdit->setText(profile.name);
+ serverComboBox->setCurrentIndex(m_serversModel->getIndexFromServerID(profile.server));
+ executablePathLabel->setText(QFileInfo(profile.executable).fileName());
+ argumentsEdit->setText(profile.arguments);
+ commentsEdit->setPlainText(profile.comments);
+ directoryPathLabel->setText(CConfigFile::getInstance()->getProfileDirectory());
+ desktopShortcutCheckBox->setChecked(profile.desktopShortcut);
+ menuShortcutCheckBox->setChecked(profile.menuShortcut);
+
+ updateExecutableVersion(index);
+
+ m_currentProfileIndex = index;
+}
+
+void CProfilesDialog::saveProfile(int index)
+{
+ if (index < 0) return;
+
+ CProfile &profile = m_model->getProfiles()[index];
+
+ profile.account = accountEdit->text();
+ profile.name = nameEdit->text();
+ profile.server = m_serversModel->getServerIDFromIndex(serverComboBox->currentIndex());
+ profile.arguments = argumentsEdit->text();
+ profile.comments = commentsEdit->toPlainText();
+ profile.desktopShortcut = desktopShortcutCheckBox->isChecked();
+ profile.menuShortcut = menuShortcutCheckBox->isChecked();
+}
+
+void CProfilesDialog::deleteProfile(int index)
+{
+ if (index < 0) return;
+
+ m_model->removeRow(index);
+}
+
+void CProfilesDialog::addProfile()
+{
+ // TODO: browse all folders in AppData/Roaming/Ryzom
+}
+
+void CProfilesDialog::updateExecutableVersion(int index)
+{
+ if (index < 0) return;
+
+ const CProfile &profile = m_model->getProfiles()[index];
+
+ QString executable = profile.executable;
+
+ // file empty, use default one
+ if (executable.isEmpty())
+ {
+ executable = CConfigFile::getInstance()->getInstallationDirectory() + "/" + profile.server + "/";
+
+#if defined(Q_OS_WIN32)
+ executable += "ryzom_client_r.exe";
+#elif defined(Q_OS_APPLE)
+ executable += "Ryzom.app/Contents/MacOS/Ryzom";
+#else
+ executable += "ryzom_client";
+#endif
+ }
+
+ // file doesn't exist
+ if (!QFile::exists(executable)) return;
+
+ // launch executable with --version argument
+ QProcess process;
+ process.setProcessChannelMode(QProcess::MergedChannels);
+ process.start(executable, QStringList() << "--version", QIODevice::ReadWrite);
+
+ if (!process.waitForStarted()) return;
+
+ QByteArray data;
+
+ // read all output
+ while (process.waitForReadyRead()) data.append(process.readAll());
+
+ // convert output to string
+ QString versionString = QString::fromUtf8(data);
+
+ // parse version from output
+ QRegExp reg("([A-Za-z0-1_.]+) ((DEV|FV) ([0-9.]+))");
+
+ if (reg.indexIn(versionString) > -1)
+ {
+ executableVersionLabel->setText(reg.cap(2));
+ }
+}
+
+void CProfilesDialog::onExecutableBrowseClicked()
+{
+ if (m_currentProfileIndex < 0) return;
+
+ CProfile &profile = m_model->getProfiles()[m_currentProfileIndex];
+
+ QString file = QFileDialog::getOpenFileName(this, tr("Please choose Ryzom client executable to launch"), profile.executable, tr("Executables (*.exe)"));
+
+ if (file.isEmpty()) return;
+
+ profile.executable = file;
+
+ executablePathLabel->setText(QFileInfo(profile.executable).fileName());
+
+ updateExecutableVersion(m_currentProfileIndex);
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h
new file mode 100644
index 000000000..231ab554a
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/profilesdialog.h
@@ -0,0 +1,62 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef PROFILESDIALOG_H
+#define PROFILESDIALOG_H
+
+#include "ui_profiles.h"
+
+class CProfilesModel;
+class CServersModel;
+
+/**
+ * Dialog displayed when editing existing profiles.
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CProfilesDialog : public QDialog, public Ui::ProfilesDialog
+{
+ Q_OBJECT
+
+public:
+ CProfilesDialog();
+ virtual ~CProfilesDialog();
+
+private slots:
+ void accept();
+
+ void onAddProfile();
+ void onDeleteProfile();
+ void onProfileClicked(const QModelIndex &index);
+
+ void displayProfile(int index);
+ void saveProfile(int index);
+ void deleteProfile(int index);
+ void addProfile();
+
+ void updateExecutableVersion(int index);
+
+ void onExecutableBrowseClicked();
+
+private:
+ CProfilesModel *m_model;
+ CServersModel *m_serversModel;
+
+ int m_currentProfileIndex;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp
new file mode 100644
index 000000000..64f15c5d5
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.cpp
@@ -0,0 +1,66 @@
+#include "profilesmodel.h"
+
+CProfilesModel::CProfilesModel(QObject *parent):QAbstractListModel(parent)
+{
+ m_profiles = CConfigFile::getInstance()->getProfiles();
+}
+
+CProfilesModel::CProfilesModel(const CProfiles &profiles, QObject *parent):QAbstractListModel(parent), m_profiles(profiles)
+{
+}
+
+CProfilesModel::~CProfilesModel()
+{
+}
+
+int CProfilesModel::rowCount(const QModelIndex &parent) const
+{
+ return m_profiles.size();
+}
+
+QVariant CProfilesModel::data(const QModelIndex &index, int role) const
+{
+ if (role != Qt::DisplayRole) return QVariant();
+
+ const CProfile &profile = m_profiles.at(index.row());
+
+ return QString("%1 (#%2)").arg(profile.name).arg(profile.id);
+}
+
+bool CProfilesModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row < 0) return false;
+
+ beginRemoveRows(parent, row, row + count - 1);
+
+ m_profiles.removeAt(row);
+
+ endRemoveRows();
+
+ return true;
+}
+
+bool CProfilesModel::save() const
+{
+ CConfigFile::getInstance()->setProfiles(m_profiles);
+ CConfigFile::getInstance()->save();
+
+ return true;
+}
+
+int CProfilesModel::getIndexFromProfileID(int profileId) const
+{
+ for(int i = 0; i < m_profiles.size(); ++i)
+ {
+ if (m_profiles[i].id == profileId) return i;
+ }
+
+ return -1;
+}
+
+int CProfilesModel::getProfileIDFromIndex(int index) const
+{
+ if (index < 0 || index >= m_profiles.size()) return -1;
+
+ return m_profiles[index].id;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h
new file mode 100644
index 000000000..ea02f618c
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/profilesmodel.h
@@ -0,0 +1,35 @@
+#ifndef PROFILESMODEL_H
+#define PROFILESMODEL_H
+
+#include "configfile.h"
+
+/**
+ * Profiles model
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CProfilesModel : public QAbstractListModel
+{
+public:
+ CProfilesModel(QObject *parent);
+ CProfilesModel(const CProfiles &profiles, QObject *parent);
+ virtual ~CProfilesModel();
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex());
+
+ CProfiles& getProfiles() { return m_profiles; }
+
+ bool save() const;
+
+ int getIndexFromProfileID(int profileId) const;
+ int getProfileIDFromIndex(int index) const;
+
+private:
+ CProfiles m_profiles;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp b/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp
new file mode 100644
index 000000000..7372a0e82
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/qzip.cpp
@@ -0,0 +1,1260 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "stdpch.h"
+
+#include "qzipreader.h"
+#include "qzipwriter.h"
+
+#include
+
+#if defined(Q_OS_WIN)
+# undef S_IFREG
+# define S_IFREG 0100000
+# ifndef S_IFDIR
+# define S_IFDIR 0040000
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x) & S_IFDIR) > 0
+# endif
+# ifndef S_ISREG
+# define S_ISREG(x) ((x) & 0170000) == S_IFREG
+# endif
+# define S_IFLNK 020000
+# define S_ISLNK(x) ((x) & S_IFLNK) > 0
+# ifndef S_IRUSR
+# define S_IRUSR 0400
+# endif
+# ifndef S_IWUSR
+# define S_IWUSR 0200
+# endif
+# ifndef S_IXUSR
+# define S_IXUSR 0100
+# endif
+# define S_IRGRP 0040
+# define S_IWGRP 0020
+# define S_IXGRP 0010
+# define S_IROTH 0004
+# define S_IWOTH 0002
+# define S_IXOTH 0001
+#else
+# include
+#endif
+
+#if 0
+#define ZDEBUG qDebug
+#else
+#define ZDEBUG if (0) qDebug
+#endif
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+QT_BEGIN_NAMESPACE
+
+static inline uint readUInt(const uchar *data)
+{
+ return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
+}
+
+static inline ushort readUShort(const uchar *data)
+{
+ return (data[0]) + (data[1]<<8);
+}
+
+static inline void writeUInt(uchar *data, uint i)
+{
+ data[0] = i & 0xff;
+ data[1] = (i>>8) & 0xff;
+ data[2] = (i>>16) & 0xff;
+ data[3] = (i>>24) & 0xff;
+}
+
+static inline void writeUShort(uchar *data, ushort i)
+{
+ data[0] = i & 0xff;
+ data[1] = (i>>8) & 0xff;
+}
+
+static inline void copyUInt(uchar *dest, const uchar *src)
+{
+ dest[0] = src[0];
+ dest[1] = src[1];
+ dest[2] = src[2];
+ dest[3] = src[3];
+}
+
+static inline void copyUShort(uchar *dest, const uchar *src)
+{
+ dest[0] = src[0];
+ dest[1] = src[1];
+}
+
+static void writeMSDosDate(uchar *dest, const QDateTime& dt)
+{
+ if (dt.isValid()) {
+ quint16 time =
+ (dt.time().hour() << 11) // 5 bit hour
+ | (dt.time().minute() << 5) // 6 bit minute
+ | (dt.time().second() >> 1); // 5 bit double seconds
+
+ dest[0] = time & 0xff;
+ dest[1] = time >> 8;
+
+ quint16 date =
+ ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
+ | (dt.date().month() << 5) // 4 bit month
+ | (dt.date().day()); // 5 bit day
+
+ dest[2] = char(date);
+ dest[3] = char(date >> 8);
+ } else {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ }
+}
+
+static quint32 permissionsToMode(QFile::Permissions perms)
+{
+ quint32 mode = 0;
+ if (perms & QFile::ReadOwner)
+ mode |= S_IRUSR;
+ if (perms & QFile::WriteOwner)
+ mode |= S_IWUSR;
+ if (perms & QFile::ExeOwner)
+ mode |= S_IXUSR;
+ if (perms & QFile::ReadUser)
+ mode |= S_IRUSR;
+ if (perms & QFile::WriteUser)
+ mode |= S_IWUSR;
+ if (perms & QFile::ExeUser)
+ mode |= S_IXUSR;
+ if (perms & QFile::ReadGroup)
+ mode |= S_IRGRP;
+ if (perms & QFile::WriteGroup)
+ mode |= S_IWGRP;
+ if (perms & QFile::ExeGroup)
+ mode |= S_IXGRP;
+ if (perms & QFile::ReadOther)
+ mode |= S_IROTH;
+ if (perms & QFile::WriteOther)
+ mode |= S_IWOTH;
+ if (perms & QFile::ExeOther)
+ mode |= S_IXOTH;
+ return mode;
+}
+
+static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+ if ((uLong)stream.avail_in != sourceLen)
+ return Z_BUF_ERROR;
+
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen)
+ return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+
+ err = inflateInit2(&stream, -MAX_WBITS);
+ if (err != Z_OK)
+ return err;
+
+ err = inflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ inflateEnd(&stream);
+ if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
+ return Z_DATA_ERROR;
+ return err;
+ }
+ *destLen = stream.total_out;
+
+ err = inflateEnd(&stream);
+ return err;
+}
+
+static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
+ if (err != Z_OK) return err;
+
+ err = deflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ deflateEnd(&stream);
+ return err == Z_OK ? Z_BUF_ERROR : err;
+ }
+ *destLen = stream.total_out;
+
+ err = deflateEnd(&stream);
+ return err;
+}
+
+static QFile::Permissions modeToPermissions(quint32 mode)
+{
+ QFile::Permissions ret;
+ if (mode & S_IRUSR)
+ ret |= QFile::ReadOwner;
+ if (mode & S_IWUSR)
+ ret |= QFile::WriteOwner;
+ if (mode & S_IXUSR)
+ ret |= QFile::ExeOwner;
+ if (mode & S_IRUSR)
+ ret |= QFile::ReadUser;
+ if (mode & S_IWUSR)
+ ret |= QFile::WriteUser;
+ if (mode & S_IXUSR)
+ ret |= QFile::ExeUser;
+ if (mode & S_IRGRP)
+ ret |= QFile::ReadGroup;
+ if (mode & S_IWGRP)
+ ret |= QFile::WriteGroup;
+ if (mode & S_IXGRP)
+ ret |= QFile::ExeGroup;
+ if (mode & S_IROTH)
+ ret |= QFile::ReadOther;
+ if (mode & S_IWOTH)
+ ret |= QFile::WriteOther;
+ if (mode & S_IXOTH)
+ ret |= QFile::ExeOther;
+ return ret;
+}
+
+static QDateTime readMSDosDate(const uchar *src)
+{
+ uint dosDate = readUInt(src);
+ quint64 uDate;
+ uDate = (quint64)(dosDate >> 16);
+ uint tm_mday = (uDate & 0x1f);
+ uint tm_mon = ((uDate & 0x1E0) >> 5);
+ uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980);
+ uint tm_hour = ((dosDate & 0xF800) >> 11);
+ uint tm_min = ((dosDate & 0x7E0) >> 5);
+ uint tm_sec = ((dosDate & 0x1f) << 1);
+
+ return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec));
+}
+
+struct LocalFileHeader
+{
+ uchar signature[4]; // 0x04034b50
+ uchar version_needed[2];
+ uchar general_purpose_bits[2];
+ uchar compression_method[2];
+ uchar last_mod_file[4];
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+ uchar file_name_length[2];
+ uchar extra_field_length[2];
+};
+
+struct DataDescriptor
+{
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+};
+
+struct CentralFileHeader
+{
+ uchar signature[4]; // 0x02014b50
+ uchar version_made[2];
+ uchar version_needed[2];
+ uchar general_purpose_bits[2];
+ uchar compression_method[2];
+ uchar last_mod_file[4];
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+ uchar file_name_length[2];
+ uchar extra_field_length[2];
+ uchar file_comment_length[2];
+ uchar disk_start[2];
+ uchar internal_file_attributes[2];
+ uchar external_file_attributes[4];
+ uchar offset_local_header[4];
+ LocalFileHeader toLocalHeader() const;
+};
+
+struct EndOfDirectory
+{
+ uchar signature[4]; // 0x06054b50
+ uchar this_disk[2];
+ uchar start_of_directory_disk[2];
+ uchar num_dir_entries_this_disk[2];
+ uchar num_dir_entries[2];
+ uchar directory_size[4];
+ uchar dir_start_offset[4];
+ uchar comment_length[2];
+};
+
+struct FileHeader
+{
+ CentralFileHeader h;
+ QByteArray file_name;
+ QByteArray extra_field;
+ QByteArray file_comment;
+};
+
+QZipReader::FileInfo::FileInfo()
+ : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
+{
+}
+
+QZipReader::FileInfo::~FileInfo()
+{
+}
+
+QZipReader::FileInfo::FileInfo(const FileInfo &other)
+{
+ operator=(other);
+}
+
+QZipReader::FileInfo& QZipReader::FileInfo::operator=(const FileInfo &other)
+{
+ filePath = other.filePath;
+ isDir = other.isDir;
+ isFile = other.isFile;
+ isSymLink = other.isSymLink;
+ permissions = other.permissions;
+ crc32 = other.crc32;
+ size = other.size;
+ lastModified = other.lastModified;
+ return *this;
+}
+
+bool QZipReader::FileInfo::isValid() const
+{
+ return isDir || isFile || isSymLink;
+}
+
+class QZipPrivate
+{
+public:
+ QZipPrivate(QIODevice *device, bool ownDev)
+ : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
+ {
+ }
+
+ ~QZipPrivate()
+ {
+ if (ownDevice)
+ delete device;
+ }
+
+ void fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const;
+
+ QIODevice *device;
+ bool ownDevice;
+ bool dirtyFileTree;
+ QList fileHeaders;
+ QByteArray comment;
+ uint start_of_directory;
+};
+
+void QZipPrivate::fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const
+{
+ FileHeader header = fileHeaders.at(index);
+ fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
+ const quint32 mode = (qFromLittleEndian(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
+ fileInfo.isDir = S_ISDIR(mode);
+ fileInfo.isFile = S_ISREG(mode);
+ fileInfo.isSymLink = S_ISLNK(mode);
+ fileInfo.permissions = modeToPermissions(mode);
+ fileInfo.crc32 = readUInt(header.h.crc_32);
+ fileInfo.size = readUInt(header.h.uncompressed_size);
+ fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
+}
+
+class QZipReaderPrivate : public QZipPrivate
+{
+public:
+ QZipReaderPrivate(QIODevice *device, bool ownDev)
+ : QZipPrivate(device, ownDev), status(QZipReader::NoError)
+ {
+ }
+
+ void scanFiles();
+
+ QZipReader::Status status;
+};
+
+class QZipWriterPrivate : public QZipPrivate
+{
+public:
+ QZipWriterPrivate(QIODevice *device, bool ownDev)
+ : QZipPrivate(device, ownDev),
+ status(QZipWriter::NoError),
+ permissions(QFile::ReadOwner | QFile::WriteOwner),
+ compressionPolicy(QZipWriter::AlwaysCompress)
+ {
+ }
+
+ QZipWriter::Status status;
+ QFile::Permissions permissions;
+ QZipWriter::CompressionPolicy compressionPolicy;
+
+ enum EntryType { Directory, File, Symlink };
+
+ void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
+};
+
+LocalFileHeader CentralFileHeader::toLocalHeader() const
+{
+ LocalFileHeader h;
+ writeUInt(h.signature, 0x04034b50);
+ copyUShort(h.version_needed, version_needed);
+ copyUShort(h.general_purpose_bits, general_purpose_bits);
+ copyUShort(h.compression_method, compression_method);
+ copyUInt(h.last_mod_file, last_mod_file);
+ copyUInt(h.crc_32, crc_32);
+ copyUInt(h.compressed_size, compressed_size);
+ copyUInt(h.uncompressed_size, uncompressed_size);
+ copyUShort(h.file_name_length, file_name_length);
+ copyUShort(h.extra_field_length, extra_field_length);
+ return h;
+}
+
+void QZipReaderPrivate::scanFiles()
+{
+ if (!dirtyFileTree)
+ return;
+
+ if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
+ status = QZipReader::FileOpenError;
+ return;
+ }
+
+ if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
+ status = QZipReader::FileReadError;
+ return;
+ }
+
+ dirtyFileTree = false;
+ uchar tmp[4];
+ device->read((char *)tmp, 4);
+ if (readUInt(tmp) != 0x04034b50) {
+ qWarning() << "QZip: not a zip file!";
+ return;
+ }
+
+ // find EndOfDirectory header
+ int i = 0;
+ int start_of_directory = -1;
+ int num_dir_entries = 0;
+ EndOfDirectory eod;
+ while (start_of_directory == -1) {
+ int pos = device->size() - (int)sizeof(EndOfDirectory) - i;
+ if (pos < 0 || i > 65535) {
+ qWarning() << "QZip: EndOfDirectory not found";
+ return;
+ }
+
+ device->seek(pos);
+ device->read((char *)&eod, sizeof(EndOfDirectory));
+ if (readUInt(eod.signature) == 0x06054b50)
+ break;
+ ++i;
+ }
+
+ // have the eod
+ start_of_directory = readUInt(eod.dir_start_offset);
+ num_dir_entries = readUShort(eod.num_dir_entries);
+ ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
+ int comment_length = readUShort(eod.comment_length);
+ if (comment_length != i)
+ qWarning() << "QZip: failed to parse zip file.";
+ comment = device->read(qMin(comment_length, i));
+
+
+ device->seek(start_of_directory);
+ for (i = 0; i < num_dir_entries; ++i) {
+ FileHeader header;
+ int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
+ if (read < (int)sizeof(CentralFileHeader)) {
+ qWarning() << "QZip: Failed to read complete header, index may be incomplete";
+ break;
+ }
+ if (readUInt(header.h.signature) != 0x02014b50) {
+ qWarning() << "QZip: invalid header signature, index may be incomplete";
+ break;
+ }
+
+ int l = readUShort(header.h.file_name_length);
+ header.file_name = device->read(l);
+ if (header.file_name.length() != l) {
+ qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
+ break;
+ }
+ l = readUShort(header.h.extra_field_length);
+ header.extra_field = device->read(l);
+ if (header.extra_field.length() != l) {
+ qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
+ break;
+ }
+ l = readUShort(header.h.file_comment_length);
+ header.file_comment = device->read(l);
+ if (header.file_comment.length() != l) {
+ qWarning() << "QZip: Failed to read read file comment, index may be incomplete";
+ break;
+ }
+
+ ZDEBUG("found file '%s'", header.file_name.data());
+ fileHeaders.append(header);
+ }
+}
+
+void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
+{
+#ifndef NDEBUG
+ static const char *entryTypes[] = {
+ "directory",
+ "file ",
+ "symlink " };
+ ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? (" -> " + contents).constData() : "");
+#endif
+
+ if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) {
+ status = QZipWriter::FileOpenError;
+ return;
+ }
+ device->seek(start_of_directory);
+
+ // don't compress small files
+ QZipWriter::CompressionPolicy compression = compressionPolicy;
+ if (compressionPolicy == QZipWriter::AutoCompress) {
+ if (contents.length() < 64)
+ compression = QZipWriter::NeverCompress;
+ else
+ compression = QZipWriter::AlwaysCompress;
+ }
+
+ FileHeader header;
+ memset(&header.h, 0, sizeof(CentralFileHeader));
+ writeUInt(header.h.signature, 0x02014b50);
+
+ writeUShort(header.h.version_needed, 0x14);
+ writeUInt(header.h.uncompressed_size, contents.length());
+ writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
+ QByteArray data = contents;
+ if (compression == QZipWriter::AlwaysCompress) {
+ writeUShort(header.h.compression_method, 8);
+
+ ulong len = contents.length();
+ // shamelessly copied form zlib
+ len += (len >> 12) + (len >> 14) + 11;
+ int res;
+ do {
+ data.resize(len);
+ res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length());
+
+ switch (res) {
+ case Z_OK:
+ data.resize(len);
+ break;
+ case Z_MEM_ERROR:
+ qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
+ data.resize(0);
+ break;
+ case Z_BUF_ERROR:
+ len *= 2;
+ break;
+ }
+ } while (res == Z_BUF_ERROR);
+ }
+// TODO add a check if data.length() > contents.length(). Then try to store the original and revert the compression method to be uncompressed
+ writeUInt(header.h.compressed_size, data.length());
+ uint crc_32 = ::crc32(0, 0, 0);
+ crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
+ writeUInt(header.h.crc_32, crc_32);
+
+ header.file_name = fileName.toLocal8Bit();
+ if (header.file_name.size() > 0xffff) {
+ qWarning("QZip: Filename too long, chopping it to 65535 characters");
+ header.file_name = header.file_name.left(0xffff);
+ }
+ writeUShort(header.h.file_name_length, header.file_name.length());
+ //h.extra_field_length[2];
+
+ writeUShort(header.h.version_made, 3 << 8);
+ //uchar internal_file_attributes[2];
+ //uchar external_file_attributes[4];
+ quint32 mode = permissionsToMode(permissions);
+ switch (type) {
+ case File: mode |= S_IFREG; break;
+ case Directory: mode |= S_IFDIR; break;
+ case Symlink: mode |= S_IFLNK; break;
+ }
+ writeUInt(header.h.external_file_attributes, mode << 16);
+ writeUInt(header.h.offset_local_header, start_of_directory);
+
+
+ fileHeaders.append(header);
+
+ LocalFileHeader h = header.h.toLocalHeader();
+ device->write((const char *)&h, sizeof(LocalFileHeader));
+ device->write(header.file_name);
+ device->write(data);
+ start_of_directory = device->pos();
+ dirtyFileTree = true;
+}
+
+////////////////////////////// Reader
+
+/*!
+ \class QZipReader::FileInfo
+ \internal
+ Represents one entry in the zip table of contents.
+*/
+
+/*!
+ \variable FileInfo::filePath
+ The full filepath inside the archive.
+*/
+
+/*!
+ \variable FileInfo::isDir
+ A boolean type indicating if the entry is a directory.
+*/
+
+/*!
+ \variable FileInfo::isFile
+ A boolean type, if it is one this entry is a file.
+*/
+
+/*!
+ \variable FileInfo::isSymLink
+ A boolean type, if it is one this entry is symbolic link.
+*/
+
+/*!
+ \variable FileInfo::permissions
+ A list of flags for the permissions of this entry.
+*/
+
+/*!
+ \variable FileInfo::crc32
+ The calculated checksum as a crc32 type.
+*/
+
+/*!
+ \variable FileInfo::size
+ The total size of the unpacked content.
+*/
+
+/*!
+ \variable FileInfo::d
+ \internal
+ private pointer.
+*/
+
+/*!
+ \class QZipReader
+ \internal
+ \since 4.5
+
+ \brief the QZipReader class provides a way to inspect the contents of a zip
+ archive and extract individual files from it.
+
+ QZipReader can be used to read a zip archive either from a file or from any
+ device. An in-memory QBuffer for instance. The reader can be used to read
+ which files are in the archive using fileInfoList() and entryInfoAt() but
+ also to extract individual files using fileData() or even to extract all
+ files in the archive using extractAll()
+*/
+
+/*!
+ Create a new zip archive that operates on the \a fileName. The file will be
+ opened with the \a mode.
+*/
+QZipReader::QZipReader(const QString &archive, QIODevice::OpenMode mode)
+{
+ QScopedPointer f(new QFile(archive));
+ f->open(mode);
+ QZipReader::Status status;
+ if (f->error() == QFile::NoError)
+ status = NoError;
+ else {
+ if (f->error() == QFile::ReadError)
+ status = FileReadError;
+ else if (f->error() == QFile::OpenError)
+ status = FileOpenError;
+ else if (f->error() == QFile::PermissionsError)
+ status = FilePermissionsError;
+ else
+ status = FileError;
+ }
+
+ d = new QZipReaderPrivate(f.data(), /*ownDevice=*/true);
+ f.take();
+ d->status = status;
+}
+
+/*!
+ Create a new zip archive that operates on the archive found in \a device.
+ You have to open the device previous to calling the constructor and only a
+ device that is readable will be scanned for zip filecontent.
+ */
+QZipReader::QZipReader(QIODevice *device)
+ : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
+{
+ Q_ASSERT(device);
+}
+
+/*!
+ Desctructor
+*/
+QZipReader::~QZipReader()
+{
+ close();
+ delete d;
+}
+
+/*!
+ Returns device used for reading zip archive.
+*/
+QIODevice* QZipReader::device() const
+{
+ return d->device;
+}
+
+/*!
+ Returns true if the user can read the file; otherwise returns false.
+*/
+bool QZipReader::isReadable() const
+{
+ return d->device->isReadable();
+}
+
+/*!
+ Returns true if the file exists; otherwise returns false.
+*/
+bool QZipReader::exists() const
+{
+ QFile *f = qobject_cast (d->device);
+ if (f == 0)
+ return true;
+ return f->exists();
+}
+
+/*!
+ Returns the list of files the archive contains.
+*/
+QList QZipReader::fileInfoList() const
+{
+ d->scanFiles();
+ QList files;
+ for (int i = 0; i < d->fileHeaders.size(); ++i) {
+ QZipReader::FileInfo fi;
+ d->fillFileInfo(i, fi);
+ files.append(fi);
+ }
+ return files;
+
+}
+
+/*!
+ Return the number of items in the zip archive.
+*/
+int QZipReader::count() const
+{
+ d->scanFiles();
+ return d->fileHeaders.count();
+}
+
+/*!
+ Returns a FileInfo of an entry in the zipfile.
+ The \a index is the index into the directory listing of the zipfile.
+ Returns an invalid FileInfo if \a index is out of boundaries.
+
+ \sa fileInfoList()
+*/
+QZipReader::FileInfo QZipReader::entryInfoAt(int index) const
+{
+ d->scanFiles();
+ QZipReader::FileInfo fi;
+ if (index >= 0 && index < d->fileHeaders.count())
+ d->fillFileInfo(index, fi);
+ return fi;
+}
+
+/*!
+ Fetch the file contents from the zip archive and return the uncompressed bytes.
+*/
+QByteArray QZipReader::fileData(const QString &fileName) const
+{
+ d->scanFiles();
+ int i;
+ for (i = 0; i < d->fileHeaders.size(); ++i) {
+ if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
+ break;
+ }
+ if (i == d->fileHeaders.size())
+ return QByteArray();
+
+ FileHeader header = d->fileHeaders.at(i);
+
+ int compressed_size = readUInt(header.h.compressed_size);
+ int uncompressed_size = readUInt(header.h.uncompressed_size);
+ int start = readUInt(header.h.offset_local_header);
+ //qDebug("uncompressing file %d: local header at %d", i, start);
+
+ d->device->seek(start);
+ LocalFileHeader lh;
+ d->device->read((char *)&lh, sizeof(LocalFileHeader));
+ uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
+ d->device->seek(d->device->pos() + skip);
+
+ int compression_method = readUShort(lh.compression_method);
+ //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
+
+ //qDebug("file at %lld", d->device->pos());
+ QByteArray compressed = d->device->read(compressed_size);
+ if (compression_method == 0) {
+ // no compression
+ compressed.truncate(uncompressed_size);
+ return compressed;
+ } else if (compression_method == 8) {
+ // Deflate
+ //qDebug("compressed=%d", compressed.size());
+ compressed.truncate(compressed_size);
+ QByteArray baunzip;
+ ulong len = qMax(uncompressed_size, 1);
+ int res;
+ do {
+ baunzip.resize(len);
+ res = inflate((uchar*)baunzip.data(), &len,
+ (uchar*)compressed.constData(), compressed_size);
+
+ switch (res) {
+ case Z_OK:
+ if ((int)len != baunzip.size())
+ baunzip.resize(len);
+ break;
+ case Z_MEM_ERROR:
+ qWarning("QZip: Z_MEM_ERROR: Not enough memory");
+ break;
+ case Z_BUF_ERROR:
+ len *= 2;
+ break;
+ case Z_DATA_ERROR:
+ qWarning("QZip: Z_DATA_ERROR: Input data is corrupted");
+ break;
+ }
+ } while (res == Z_BUF_ERROR);
+ return baunzip;
+ }
+ qWarning() << "QZip: Unknown compression method";
+ return QByteArray();
+}
+
+/*!
+ Extracts the full contents of the zip file into \a destinationDir on
+ the local filesystem.
+ In case writing or linking a file fails, the extraction will be aborted.
+*/
+bool QZipReader::extractAll(const QString &destinationDir) const
+{
+ QDir baseDir(destinationDir);
+
+ // create directories first
+ QList allFiles = fileInfoList();
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isDir) {
+ if (!baseDir.mkpath(fi.filePath))
+ return false;
+ if (!QFile::setPermissions(absPath, fi.permissions))
+ return false;
+ }
+ }
+
+ // set up symlinks
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isSymLink) {
+ QString destination = QFile::decodeName(fileData(fi.filePath));
+ if (destination.isEmpty())
+ return false;
+ QFileInfo linkFi(absPath);
+ if (!QFile::exists(linkFi.absolutePath()))
+ QDir::root().mkpath(linkFi.absolutePath());
+ if (!QFile::link(destination, absPath))
+ return false;
+ /* cannot change permission of links
+ if (!QFile::setPermissions(absPath, fi.permissions))
+ return false;
+ */
+ }
+ }
+
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isFile) {
+ QFile f(absPath);
+ if (!f.open(QIODevice::WriteOnly))
+ return false;
+ f.write(fileData(fi.filePath));
+ f.setPermissions(fi.permissions);
+ f.close();
+ }
+ }
+
+ return true;
+}
+
+/*!
+ \enum QZipReader::Status
+
+ The following status values are possible:
+
+ \value NoError No error occurred.
+ \value FileReadError An error occurred when reading from the file.
+ \value FileOpenError The file could not be opened.
+ \value FilePermissionsError The file could not be accessed.
+ \value FileError Another file error occurred.
+*/
+
+/*!
+ Returns a status code indicating the first error that was met by QZipReader,
+ or QZipReader::NoError if no error occurred.
+*/
+QZipReader::Status QZipReader::status() const
+{
+ return d->status;
+}
+
+/*!
+ Close the zip file.
+*/
+void QZipReader::close()
+{
+ d->device->close();
+}
+
+////////////////////////////// Writer
+
+/*!
+ \class QZipWriter
+ \internal
+ \since 4.5
+
+ \brief the QZipWriter class provides a way to create a new zip archive.
+
+ QZipWriter can be used to create a zip archive containing any number of files
+ and directories. The files in the archive will be compressed in a way that is
+ compatible with common zip reader applications.
+*/
+
+
+/*!
+ Create a new zip archive that operates on the \a archive filename. The file will
+ be opened with the \a mode.
+ \sa isValid()
+*/
+QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode)
+{
+ QScopedPointer f(new QFile(fileName));
+ f->open(mode);
+ QZipWriter::Status status;
+ if (f->error() == QFile::NoError)
+ status = QZipWriter::NoError;
+ else {
+ if (f->error() == QFile::WriteError)
+ status = QZipWriter::FileWriteError;
+ else if (f->error() == QFile::OpenError)
+ status = QZipWriter::FileOpenError;
+ else if (f->error() == QFile::PermissionsError)
+ status = QZipWriter::FilePermissionsError;
+ else
+ status = QZipWriter::FileError;
+ }
+
+ d = new QZipWriterPrivate(f.data(), /*ownDevice=*/true);
+ f.take();
+ d->status = status;
+}
+
+/*!
+ Create a new zip archive that operates on the archive found in \a device.
+ You have to open the device previous to calling the constructor and
+ only a device that is readable will be scanned for zip filecontent.
+ */
+QZipWriter::QZipWriter(QIODevice *device)
+ : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
+{
+ Q_ASSERT(device);
+}
+
+QZipWriter::~QZipWriter()
+{
+ close();
+ delete d;
+}
+
+/*!
+ Returns device used for writing zip archive.
+*/
+QIODevice* QZipWriter::device() const
+{
+ return d->device;
+}
+
+/*!
+ Returns true if the user can write to the archive; otherwise returns false.
+*/
+bool QZipWriter::isWritable() const
+{
+ return d->device->isWritable();
+}
+
+/*!
+ Returns true if the file exists; otherwise returns false.
+*/
+bool QZipWriter::exists() const
+{
+ QFile *f = qobject_cast (d->device);
+ if (f == 0)
+ return true;
+ return f->exists();
+}
+
+/*!
+ \enum QZipWriter::Status
+
+ The following status values are possible:
+
+ \value NoError No error occurred.
+ \value FileWriteError An error occurred when writing to the device.
+ \value FileOpenError The file could not be opened.
+ \value FilePermissionsError The file could not be accessed.
+ \value FileError Another file error occurred.
+*/
+
+/*!
+ Returns a status code indicating the first error that was met by QZipWriter,
+ or QZipWriter::NoError if no error occurred.
+*/
+QZipWriter::Status QZipWriter::status() const
+{
+ return d->status;
+}
+
+/*!
+ \enum QZipWriter::CompressionPolicy
+
+ \value AlwaysCompress A file that is added is compressed.
+ \value NeverCompress A file that is added will be stored without changes.
+ \value AutoCompress A file that is added will be compressed only if that will give a smaller file.
+*/
+
+/*!
+ Sets the policy for compressing newly added files to the new \a policy.
+
+ \note the default policy is AlwaysCompress
+
+ \sa compressionPolicy()
+ \sa addFile()
+*/
+void QZipWriter::setCompressionPolicy(CompressionPolicy policy)
+{
+ d->compressionPolicy = policy;
+}
+
+/*!
+ Returns the currently set compression policy.
+ \sa setCompressionPolicy()
+ \sa addFile()
+*/
+QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const
+{
+ return d->compressionPolicy;
+}
+
+/*!
+ Sets the permissions that will be used for newly added files.
+
+ \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
+
+ \sa creationPermissions()
+ \sa addFile()
+*/
+void QZipWriter::setCreationPermissions(QFile::Permissions permissions)
+{
+ d->permissions = permissions;
+}
+
+/*!
+ Returns the currently set creation permissions.
+
+ \sa setCreationPermissions()
+ \sa addFile()
+*/
+QFile::Permissions QZipWriter::creationPermissions() const
+{
+ return d->permissions;
+}
+
+/*!
+ Add a file to the archive with \a data as the file contents.
+ The file will be stored in the archive using the \a fileName which
+ includes the full path in the archive.
+
+ The new file will get the file permissions based on the current
+ creationPermissions and it will be compressed using the zip compression
+ based on the current compression policy.
+
+ \sa setCreationPermissions()
+ \sa setCompressionPolicy()
+*/
+void QZipWriter::addFile(const QString &fileName, const QByteArray &data)
+{
+ d->addEntry(QZipWriterPrivate::File, fileName, data);
+}
+
+/*!
+ Add a file to the archive with \a device as the source of the contents.
+ The contents returned from QIODevice::readAll() will be used as the
+ filedata.
+ The file will be stored in the archive using the \a fileName which
+ includes the full path in the archive.
+*/
+void QZipWriter::addFile(const QString &fileName, QIODevice *device)
+{
+ Q_ASSERT(device);
+ QIODevice::OpenMode mode = device->openMode();
+ bool opened = false;
+ if ((mode & QIODevice::ReadOnly) == 0) {
+ opened = true;
+ if (! device->open(QIODevice::ReadOnly)) {
+ d->status = FileOpenError;
+ return;
+ }
+ }
+ d->addEntry(QZipWriterPrivate::File, fileName, device->readAll());
+ if (opened)
+ device->close();
+}
+
+/*!
+ Create a new directory in the archive with the specified \a dirName and
+ the \a permissions;
+*/
+void QZipWriter::addDirectory(const QString &dirName)
+{
+ QString name = dirName;
+ // separator is mandatory
+ if (!name.endsWith(QDir::separator()))
+ name.append(QDir::separator());
+ d->addEntry(QZipWriterPrivate::Directory, name, QByteArray());
+}
+
+/*!
+ Create a new symbolic link in the archive with the specified \a dirName
+ and the \a permissions;
+ A symbolic link contains the destination (relative) path and name.
+*/
+void QZipWriter::addSymLink(const QString &fileName, const QString &destination)
+{
+ d->addEntry(QZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination));
+}
+
+/*!
+ Closes the zip file.
+*/
+void QZipWriter::close()
+{
+ if (!(d->device->openMode() & QIODevice::WriteOnly)) {
+ d->device->close();
+ return;
+ }
+
+ //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
+ d->device->seek(d->start_of_directory);
+ // write new directory
+ for (int i = 0; i < d->fileHeaders.size(); ++i) {
+ const FileHeader &header = d->fileHeaders.at(i);
+ d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
+ d->device->write(header.file_name);
+ d->device->write(header.extra_field);
+ d->device->write(header.file_comment);
+ }
+ int dir_size = d->device->pos() - d->start_of_directory;
+ // write end of directory
+ EndOfDirectory eod;
+ memset(&eod, 0, sizeof(EndOfDirectory));
+ writeUInt(eod.signature, 0x06054b50);
+ //uchar this_disk[2];
+ //uchar start_of_directory_disk[2];
+ writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
+ writeUShort(eod.num_dir_entries, d->fileHeaders.size());
+ writeUInt(eod.directory_size, dir_size);
+ writeUInt(eod.dir_start_offset, d->start_of_directory);
+ writeUShort(eod.comment_length, d->comment.length());
+
+ d->device->write((const char *)&eod, sizeof(EndOfDirectory));
+ d->device->write(d->comment);
+ d->device->close();
+}
+
+QT_END_NAMESPACE
diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h b/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h
new file mode 100644
index 000000000..720846ae2
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/qzipreader.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QZIPREADER_H
+#define QZIPREADER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the QZipReader class. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QZipReaderPrivate;
+
+class QZipReader
+{
+public:
+ QZipReader(const QString &fileName, QIODevice::OpenMode mode = QIODevice::ReadOnly );
+
+ explicit QZipReader(QIODevice *device);
+ ~QZipReader();
+
+ QIODevice* device() const;
+
+ bool isReadable() const;
+ bool exists() const;
+
+ struct FileInfo
+ {
+ FileInfo();
+ FileInfo(const FileInfo &other);
+ ~FileInfo();
+ FileInfo &operator=(const FileInfo &other);
+ bool isValid() const;
+ QString filePath;
+ uint isDir : 1;
+ uint isFile : 1;
+ uint isSymLink : 1;
+ QFile::Permissions permissions;
+ uint crc32;
+ qint64 size;
+ QDateTime lastModified;
+ void *d;
+ };
+
+ QList fileInfoList() const;
+ int count() const;
+
+ FileInfo entryInfoAt(int index) const;
+ QByteArray fileData(const QString &fileName) const;
+ bool extractAll(const QString &destinationDir) const;
+
+ enum Status {
+ NoError,
+ FileReadError,
+ FileOpenError,
+ FilePermissionsError,
+ FileError
+ };
+
+ Status status() const;
+
+ void close();
+
+private:
+ QZipReaderPrivate *d;
+ Q_DISABLE_COPY(QZipReader)
+};
+
+QT_END_NAMESPACE
+
+#endif // QZIPREADER_H
diff --git a/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h b/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h
new file mode 100644
index 000000000..a92595479
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/qzipwriter.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QZIPWRITER_H
+#define QZIPWRITER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the QZipWriter class. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include
+#include
+
+QT_BEGIN_NAMESPACE
+
+class QZipWriterPrivate;
+
+
+class QZipWriter
+{
+public:
+ QZipWriter(const QString &fileName, QIODevice::OpenMode mode = (QIODevice::WriteOnly | QIODevice::Truncate) );
+
+ explicit QZipWriter(QIODevice *device);
+ ~QZipWriter();
+
+ QIODevice* device() const;
+
+ bool isWritable() const;
+ bool exists() const;
+
+ enum Status {
+ NoError,
+ FileWriteError,
+ FileOpenError,
+ FilePermissionsError,
+ FileError
+ };
+
+ Status status() const;
+
+ enum CompressionPolicy {
+ AlwaysCompress,
+ NeverCompress,
+ AutoCompress
+ };
+
+ void setCompressionPolicy(CompressionPolicy policy);
+ CompressionPolicy compressionPolicy() const;
+
+ void setCreationPermissions(QFile::Permissions permissions);
+ QFile::Permissions creationPermissions() const;
+
+ void addFile(const QString &fileName, const QByteArray &data);
+
+ void addFile(const QString &fileName, QIODevice *device);
+
+ void addDirectory(const QString &dirName);
+
+ void addSymLink(const QString &fileName, const QString &destination);
+
+ void close();
+private:
+ QZipWriterPrivate *d;
+ Q_DISABLE_COPY(QZipWriter)
+};
+
+QT_END_NAMESPACE
+
+#endif // QZIPWRITER_H
diff --git a/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp
new file mode 100644
index 000000000..7cda521de
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.cpp
@@ -0,0 +1,82 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "serversmodel.h"
+
+CServersModel::CServersModel(QObject *parent):QAbstractListModel(parent)
+{
+ m_servers = CConfigFile::getInstance()->getServers();
+}
+
+CServersModel::CServersModel(const CServers &servers, QObject *parent):QAbstractListModel(parent), m_servers(servers)
+{
+}
+
+CServersModel::~CServersModel()
+{
+}
+
+int CServersModel::rowCount(const QModelIndex &parent) const
+{
+ return m_servers.size();
+}
+
+QVariant CServersModel::data(const QModelIndex &index, int role) const
+{
+ if (role != Qt::DisplayRole) return QVariant();
+
+ const CServer &server = m_servers.at(index.row());
+
+ return server.name;
+}
+
+bool CServersModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row < 0) return false;
+
+ beginRemoveRows(parent, row, row + count - 1);
+
+ m_servers.removeAt(row);
+
+ endRemoveRows();
+
+ return true;
+}
+
+bool CServersModel::save() const
+{
+ CConfigFile::getInstance()->setServers(m_servers);
+
+ return true;
+}
+
+int CServersModel::getIndexFromServerID(const QString &serverId) const
+{
+ for(int i = 0; i < m_servers.size(); ++i)
+ {
+ if (m_servers[i].id == serverId) return i;
+ }
+
+ return -1;
+}
+
+QString CServersModel::getServerIDFromIndex(int index) const
+{
+ if (index < 0 || index >= m_servers.size()) return "";
+
+ return m_servers[index].id;
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h
new file mode 100644
index 000000000..27f5daed3
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/serversmodel.h
@@ -0,0 +1,51 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef SERVERSMODEL_H
+#define SERVERSMODEL_H
+
+#include "configfile.h"
+
+/**
+ * Servers model
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CServersModel : public QAbstractListModel
+{
+public:
+ CServersModel(QObject *parent);
+ CServersModel(const CServers &servers, QObject *parent);
+ virtual ~CServersModel();
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex());
+
+ CServers& getServers() { return m_servers; }
+
+ bool save() const;
+
+ int getIndexFromServerID(const QString &serverId) const;
+ QString getServerIDFromIndex(int index) const;
+
+private:
+ CServers m_servers;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp
new file mode 100644
index 000000000..a919bc2c7
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.cpp
@@ -0,0 +1,38 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "settingsdialog.h"
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+CSettingsDialog::CSettingsDialog():QDialog()
+{
+ setupUi(this);
+}
+
+CSettingsDialog::~CSettingsDialog()
+{
+}
+
+void CSettingsDialog::accept()
+{
+ // TODO: add save code
+
+ QDialog::accept();
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h
new file mode 100644
index 000000000..f8e3144f7
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/settingsdialog.h
@@ -0,0 +1,40 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+#include "ui_settings.h"
+
+/**
+ * Settings dialog
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CSettingsDialog : public QDialog, public Ui::SettingsDialog
+{
+ Q_OBJECT
+
+public:
+ CSettingsDialog();
+ virtual ~CSettingsDialog();
+
+private slots:
+ void accept();
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp b/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp
new file mode 100644
index 000000000..a3d45576c
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/stdpch.cpp
@@ -0,0 +1,17 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
diff --git a/code/ryzom/tools/client/ryzom_installer/src/stdpch.h b/code/ryzom/tools/client/ryzom_installer/src/stdpch.h
new file mode 100644
index 000000000..5d64b580f
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/stdpch.h
@@ -0,0 +1,65 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef STDPCH_H
+#define STDPCH_H
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+ #define _CRTDBG_MAP_ALLOC
+ #include
+ #include
+ #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
+ #undef realloc
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef _DEBUG
+#define QT_NO_DEBUG_OUTPUT
+#endif
+
+#include
+
+#ifdef Q_COMPILER_RVALUE_REFS
+#undef Q_COMPILER_RVALUE_REFS
+#endif
+
+#include
+#include
+#include
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
+#define USE_QT5
+#endif
+
+#ifdef USE_QT5
+#include
+#include
+#endif
+
+#include
+
+#include
+#include
+
+#endif
+
diff --git a/code/ryzom/tools/client/ryzom_installer/src/utils.cpp b/code/ryzom/tools/client/ryzom_installer/src/utils.cpp
new file mode 100644
index 000000000..b5a85e3b0
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/utils.cpp
@@ -0,0 +1,185 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "utils.h"
+
+QString qFromUtf8(const std::string &str)
+{
+ return QString::fromUtf8(str.c_str());
+}
+
+std::string qToUtf8(const QString &str)
+{
+ return str.toUtf8().constData();
+}
+
+QString qFromUtf16(const ucstring &str)
+{
+ return QString::fromUtf16(str.c_str());
+}
+
+ucstring qToUtf16(const QString &str)
+{
+ return ucstring::makeFromUtf8(qToUtf8(str));
+}
+
+QString qFromWide(const wchar_t *str)
+{
+ return QString::fromUtf16((ushort*)str);
+}
+
+wchar_t* qToWide(const QString &str)
+{
+ return (wchar_t*)str.utf16();
+}
+
+#ifdef Q_OS_WIN32
+
+// CreateLink - Uses the Shell's IShellLink and IPersistFile interfaces
+// to create and store a shortcut to the specified object.
+//
+// Returns the result of calling the member functions of the interfaces.
+//
+// Parameters:
+// lpszPathObj - Address of a buffer that contains the path of the object,
+// including the file name.
+// lpszPathLink - Address of a buffer that contains the path where the
+// Shell link is to be stored, including the file name.
+// lpszDesc - Address of a buffer that contains a description of the
+// Shell link, stored in the Comment field of the link
+// properties.
+
+HRESULT CreateLink(const QString &pathObj, const QString &pathLink, const QString &desc)
+{
+ IShellLinkW* psl;
+
+ // Get a pointer to the IShellLink interface. It is assumed that CoInitialize
+ // has already been called.
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ // Set the path to the shortcut target and add the description.
+ psl->SetPath(qToWide(pathObj));
+ psl->SetDescription(qToWide(desc));
+ psl->SetArguments(L"--profil ");
+ psl->SetWorkingDirectory(L"");
+
+ // Query IShellLink for the IPersistFile interface, used for saving the
+ // shortcut in persistent storage.
+ hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
+
+ if (SUCCEEDED(hres))
+ {
+ // Add code here to check return value from MultiByteWideChar
+ // for success.
+
+ // Save the link by calling IPersistFile::Save.
+ hres = ppf->Save(qToWide(pathLink), TRUE);
+ ppf->Release();
+ }
+ psl->Release();
+ }
+ return hres;
+}
+
+// ResolveIt - Uses the Shell's IShellLink and IPersistFile interfaces
+// to retrieve the path and description from an existing shortcut.
+//
+// Returns the result of calling the member functions of the interfaces.
+//
+// Parameters:
+// hwnd - A handle to the parent window. The Shell uses this window to
+// display a dialog box if it needs to prompt the user for more
+// information while resolving the link.
+// lpszLinkFile - Address of a buffer that contains the path of the link,
+// including the file name.
+// lpszPath - Address of a buffer that receives the path of the link
+// target, including the file name.
+// lpszDesc - Address of a buffer that receives the description of the
+// Shell link, stored in the Comment field of the link
+// properties.
+
+HRESULT ResolveIt(HWND hwnd, const QString &linkFile, QString &path)
+{
+ IShellLinkW* psl;
+ WIN32_FIND_DATAW wfd;
+
+ path.clear(); // Assume failure
+
+ // Get a pointer to the IShellLink interface. It is assumed that CoInitialize
+ // has already been called.
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ // Get a pointer to the IPersistFile interface.
+ hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf);
+
+ if (SUCCEEDED(hres))
+ {
+ // Add code here to check return value from MultiByteWideChar
+ // for success.
+
+ // Load the shortcut.
+ hres = ppf->Load(qToWide(linkFile), STGM_READ);
+
+ if (SUCCEEDED(hres))
+ {
+ // Resolve the link.
+ hres = psl->Resolve(hwnd, 0);
+
+ if (SUCCEEDED(hres))
+ {
+ WCHAR szGotPath[MAX_PATH];
+
+ // Get the path to the link target.
+ hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATAW*)&wfd, SLGP_SHORTPATH);
+
+ if (SUCCEEDED(hres))
+ {
+ WCHAR szDescription[MAX_PATH];
+
+ // Get the description of the target.
+ hres = psl->GetDescription(szDescription, MAX_PATH);
+
+ if (SUCCEEDED(hres))
+ {
+ // Handle success
+ path = qFromWide(szGotPath);
+ }
+ else
+ {
+ }
+ }
+ }
+ }
+
+ // Release the pointer to the IPersistFile interface.
+ ppf->Release();
+ }
+
+ // Release the pointer to the IShellLink interface.
+ psl->Release();
+ }
+
+ return hres;
+}
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/utils.h b/code/ryzom/tools/client/ryzom_installer/src/utils.h
new file mode 100644
index 000000000..179adae4d
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/utils.h
@@ -0,0 +1,47 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include
+
+#include
+
+/**
+ * Utils functions
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+
+// Convert a UTF-8 string to QString
+QString qFromUtf8(const std::string &str);
+
+// Convert a QString to UTF-8 string
+std::string qToUtf8(const QString &str);
+
+// Convert a UTF-16 string to QString
+QString qFromUtf16(const ucstring &str);
+
+// Convert a QString to UTF-16 string
+ucstring qToUtf16(const QString &str);
+
+QString qFromWide(const wchar_t *str);
+
+wchar_t* qToWide(const QString &str);
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp
new file mode 100644
index 000000000..0444487cd
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.cpp
@@ -0,0 +1,221 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "wizarddialog.h"
+#include "configfile.h"
+
+#include "nel/misc/system_info.h"
+#include "nel/misc/common.h"
+
+#ifdef DEBUG_NEW
+ #define new DEBUG_NEW
+#endif
+
+QString qBytesToHumanReadable(qint64 bytes)
+{
+ static std::vector units;
+
+ if (units.empty())
+ {
+ units.push_back(QObject::tr("B").toUtf8().constData());
+ units.push_back(QObject::tr("KiB").toUtf8().constData());
+ units.push_back(QObject::tr("MiB").toUtf8().constData());
+ units.push_back(QObject::tr("GiB").toUtf8().constData());
+ units.push_back(QObject::tr("TiB").toUtf8().constData());
+ units.push_back(QObject::tr("PiB").toUtf8().constData());
+ }
+
+ return QString::fromUtf8(NLMISC::bytesToHumanReadable(bytes).c_str());
+}
+
+CWizardDialog::CWizardDialog():QDialog()
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ setupUi(this);
+
+ currentDirectoryRadioButton->setVisible(false);
+ oldDirectoryRadioButton->setVisible(false);
+
+ // enable download radio button by default
+ internetRadioButton->setChecked(true);
+
+ // if launched from current directory, it means we just patched files
+ m_currentDirectory = CConfigFile::getInstance()->getCurrentDirectory();
+
+ if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory))
+ {
+ // current directory is a child of Ryzom root directory
+ m_currentDirectory = CConfigFile::getInstance()->getParentDirectory();
+
+ if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory))
+ {
+ // Ryzom is in the same directory as Ryzom Installer
+ m_currentDirectory = CConfigFile::getInstance()->getApplicationDirectory();
+
+ if (!CConfigFile::getInstance()->isRyzomInstalledIn(m_currentDirectory))
+ {
+ m_currentDirectory.clear();
+ }
+ }
+ }
+
+ // display found directory
+ if (!m_currentDirectory.isEmpty())
+ {
+ currentDirectoryRadioButton->setText(tr("Current directory: %1").arg(m_currentDirectory));
+ currentDirectoryRadioButton->setVisible(true);
+ currentDirectoryRadioButton->setChecked(true);
+ }
+
+ m_oldDirectory = CConfigFile::getInstance()->getOldInstallationDirectory();
+
+ // found a previous installation
+ if (CConfigFile::getInstance()->areRyzomDataInstalledIn(m_oldDirectory))
+ {
+ oldDirectoryRadioButton->setText(tr("Old installation: %1").arg(m_oldDirectory));
+ oldDirectoryRadioButton->setVisible(true);
+
+ if (m_currentDirectory.isEmpty()) oldDirectoryRadioButton->setChecked(true);
+ }
+
+ updateAnotherLocationText();
+
+ m_dstDirectory = CConfigFile::getNewInstallationDirectory();
+
+ updateDestinationText();
+
+ // check whether OS architecture is 32 or 64 bits
+ // TODO: 64 bits client only supported under Vista+
+ if (CConfigFile::has64bitsOS())
+ {
+ clientArchGroupBox->setVisible(true);
+ clientArch64RadioButton->setChecked(true);
+ }
+ else
+ {
+ clientArchGroupBox->setVisible(false);
+ clientArch32RadioButton->setChecked(true);
+ }
+
+ const CServer &server = CConfigFile::getInstance()->getServer();
+
+ internetRadioButton->setText(tr("Internet (%1 to download)").arg(qBytesToHumanReadable(server.dataCompressedSize)));
+ destinationGroupBox->setTitle(tr("Files will be installed to (requires %1):").arg(qBytesToHumanReadable(server.dataUncompressedSize)));
+
+ connect(anotherLocationBrowseButton, SIGNAL(clicked()), SLOT(onAnotherLocationBrowseButtonClicked()));
+ connect(destinationBrowseButton, SIGNAL(clicked()), SLOT(onDestinationBrowseButtonClicked()));
+
+ // TODO: if found a folder with initial data, get its total size and check if at least that size is free on the disk
+
+ // by default, advanced parameters are hidden
+ onShowAdvancedParameters(Qt::Unchecked);
+
+ connect(advancedCheckBox, SIGNAL(stateChanged(int)), SLOT(onShowAdvancedParameters(int)));
+}
+
+CWizardDialog::~CWizardDialog()
+{
+}
+
+void CWizardDialog::onShowAdvancedParameters(int state)
+{
+ advancedFrame->setVisible(state != Qt::Unchecked);
+
+ adjustSize();
+}
+
+void CWizardDialog::onAnotherLocationBrowseButtonClicked()
+{
+ QString directory;
+
+ for(;;)
+ {
+ directory = QFileDialog::getExistingDirectory(this, tr("Please choose directory where is installed Ryzom"));
+
+ if (directory.isEmpty()) return;
+
+ if (CConfigFile::getInstance()->isRyzomInstalledIn(directory)) break;
+
+ QMessageBox::StandardButton res = QMessageBox::warning(this, tr("Unable to find Ryzom"), tr("Unable to find Ryzom in selected directory. Please choose another one or cancel."));
+ }
+
+ m_anotherDirectory = directory;
+
+ // if we browse to a Ryzom installation, we want to use it
+ anotherLocationRadioButton->setChecked(true);
+
+ updateAnotherLocationText();
+}
+
+void CWizardDialog::onDestinationBrowseButtonClicked()
+{
+ QString directory = QFileDialog::getExistingDirectory(this, tr("Please choose directory where to install Ryzom"));
+
+ if (directory.isEmpty()) return;
+
+ m_dstDirectory = directory;
+
+ updateDestinationText();
+}
+
+void CWizardDialog::updateAnotherLocationText()
+{
+ anotherLocationRadioButton->setText(tr("Another location: %1").arg(m_anotherDirectory.isEmpty() ? tr("Undefined"):m_anotherDirectory));
+}
+
+void CWizardDialog::updateDestinationText()
+{
+ destinationLabel->setText(m_dstDirectory);
+}
+
+void CWizardDialog::accept()
+{
+ // check free disk space
+ qint64 freeSpace = NLMISC::CSystemInfo::availableHDSpace(m_dstDirectory.toUtf8().constData());
+
+ const CServer &server = CConfigFile::getInstance()->getServer();
+
+ if (freeSpace < server.dataUncompressedSize)
+ {
+ QMessageBox::StandardButton res = QMessageBox::warning(this, tr("Not enough free disk space"), tr("You don't have enough free space on this disk, please make more space or choose a directory on another disk."));
+ return;
+ }
+
+ if (currentDirectoryRadioButton->isChecked())
+ {
+ CConfigFile::getInstance()->setSrcServerDirectory(m_currentDirectory);
+ }
+ else if (oldDirectoryRadioButton->isChecked())
+ {
+ CConfigFile::getInstance()->setSrcServerDirectory(m_oldDirectory);
+ }
+ else if (anotherLocationRadioButton->isChecked())
+ {
+ CConfigFile::getInstance()->setSrcServerDirectory(m_anotherDirectory);
+ }
+ else
+ {
+ CConfigFile::getInstance()->setSrcServerDirectory("");
+ }
+
+ CConfigFile::getInstance()->setInstallationDirectory(m_dstDirectory);
+ CConfigFile::getInstance()->setUse64BitsClient(clientArch64RadioButton->isChecked());
+ CConfigFile::getInstance()->save();
+
+ QDialog::accept();
+}
diff --git a/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h
new file mode 100644
index 000000000..8ac146184
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/src/wizarddialog.h
@@ -0,0 +1,53 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef WIZARDDIALOG_H
+#define WIZARDDIALOG_H
+
+#include "ui_wizard.h"
+
+/**
+ * Wizard displayed at first launch, that asks user to choose source and destination directories.
+ *
+ * \author Cedric 'Kervala' OCHS
+ * \date 2016
+ */
+class CWizardDialog : public QDialog, public Ui::WizardDialog
+{
+ Q_OBJECT
+
+public:
+ CWizardDialog();
+ virtual ~CWizardDialog();
+
+private slots:
+ void onShowAdvancedParameters(int state);
+ void onAnotherLocationBrowseButtonClicked();
+ void onDestinationBrowseButtonClicked();
+
+ void accept();
+
+private:
+ void updateAnotherLocationText();
+ void updateDestinationText();
+
+ QString m_currentDirectory;
+ QString m_oldDirectory;
+ QString m_anotherDirectory;
+ QString m_dstDirectory;
+};
+
+#endif
diff --git a/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui b/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui
new file mode 100644
index 000000000..644a3a2c2
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/ui/mainwindow.ui
@@ -0,0 +1,212 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 627
+ 539
+
+
+
+ Ryzom Installer
+
+
+
+ -
+
+
-
+
+
+
+
+
+ :/images/background.png
+
+
+
+ -
+
+
+ true
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html>
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 0
+
+
+ 0
+
+
+ Qt::AlignCenter
+
+
+ %p%
+
+
+
+ -
+
+
+ Resume
+
+
+
+ -
+
+
+ Stop
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+ Atys
+
+
+
+
+ -
+
+
+ Play
+
+
+
+ -
+
+
+ Configure
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ About Qt
+
+
+
+
+ About...
+
+
+
+
+ &Profiles
+
+
+
+
+ &Directories
+
+
+
+
+ &Quit
+
+
+
+
+
+
+
+
diff --git a/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui b/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui
new file mode 100644
index 000000000..1e3e42768
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/ui/profiles.ui
@@ -0,0 +1,305 @@
+
+
+ ProfilesDialog
+
+
+
+ 0
+ 0
+ 583
+ 368
+
+
+
+ Dialog
+
+
+ -
+
+
-
+
+
-
+
+
+ List of profiles:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Delete
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Profile:
+
+
+
+ -
+
+
+ 0
+
+
+
+ -
+
+
+ Account:
+
+
+
+ -
+
+
+ -
+
+
+ Name:
+
+
+
+ -
+
+
+ -
+
+
+ Server:
+
+
+
+ -
+
+
-
+
+ Atys
+
+
+ -
+
+ Yubo
+
+
+
+
+ -
+
+
+ Executable:
+
+
+
+ -
+
+
-
+
+
+ ryzom_client_r.exe
+
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ Client version:
+
+
+
+ -
+
+
+ FV 3.0.0
+
+
+
+ -
+
+
+ Arguments:
+
+
+
+ -
+
+
+ -
+
+
+ Comments:
+
+
+
+ -
+
+
+
+ 0
+ 1
+
+
+
+
+ -
+
+
+ Directory:
+
+
+
+ -
+
+
-
+
+
+ ~/.ryzom/0
+
+
+
+ -
+
+
+ Open
+
+
+
+
+
+ -
+
+
+ Create shortcuts:
+
+
+
+ -
+
+
-
+
+
+ Desktop
+
+
+
+ -
+
+
+ Start Menu
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ profilesListView
+ addButton
+ deleteButton
+ accountEdit
+ nameEdit
+ serverComboBox
+ executableBrowseButton
+ argumentsEdit
+ commentsEdit
+ directoryButton
+ desktopShortcutCheckBox
+ menuShortcutCheckBox
+
+
+
+
+ buttonBox
+ accepted()
+ ProfilesDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ ProfilesDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/code/ryzom/tools/client/ryzom_installer/ui/settings.ui b/code/ryzom/tools/client/ryzom_installer/ui/settings.ui
new file mode 100644
index 000000000..1ab8f32af
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/ui/settings.ui
@@ -0,0 +1,113 @@
+
+
+ SettingsDialog
+
+
+
+ 0
+ 0
+ 400
+ 150
+
+
+
+ Dialog
+
+
+ -
+
+
-
+
+
+ Language
+
+
+
+ -
+
+
+ -
+
+
+ Base location of Ryzom files: %1
+
+
+
+ -
+
+
+ Browse...
+
+
+
+ -
+
+
+ Location of source Ryzom files: %1
+
+
+
+ -
+
+
+ Browse...
+
+
+
+ -
+
+
+ Use 64 bits client
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ SettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ SettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui b/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui
new file mode 100644
index 000000000..b7b6475e0
--- /dev/null
+++ b/code/ryzom/tools/client/ryzom_installer/ui/wizard.ui
@@ -0,0 +1,260 @@
+
+
+ WizardDialog
+
+
+ Qt::ApplicationModal
+
+
+
+ 0
+ 0
+ 402
+ 464
+
+
+
+ Ryzom Installer
+
+
+ true
+
+
+
+ QLayout::SetFixedSize
+
+ -
+
+
+ Welcome to Ryzom Installer!
+
+This program will allow you to download, install, migrate, configure or manage Ryzom on your computer.
+
+Just follow the different steps and make your choice between the different propositions.
+
+
+ Qt::AlignJustify|Qt::AlignTop
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Show advanced parameters (expert)
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 6
+
+
+ 0
+
+
+ 9
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Files will be installed from:
+
+
+
-
+
+
+ Current directory
+
+
+ true
+
+
+
+ -
+
+
+ Old installation: %1
+
+
+ true
+
+
+
+ -
+
+
+ 6
+
+
-
+
+
+ Another location: %1
+
+
+ true
+
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ Internet (%1 GiB to download)
+
+
+
+
+
+
+ -
+
+
+ Files will be installed to (requires 10 GiB):
+
+
+
-
+
+
+ c:\
+
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+
+ -
+
+
+ Do you prefer to use a 64 or 32 bits client?
+
+
+
-
+
+
+ 64 bits (recommended)
+
+
+ true
+
+
+
+ -
+
+
+ 32 bits
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ currentDirectoryRadioButton
+ oldDirectoryRadioButton
+ anotherLocationRadioButton
+ anotherLocationBrowseButton
+ internetRadioButton
+ destinationBrowseButton
+ clientArch64RadioButton
+ clientArch32RadioButton
+
+
+
+
+ buttonBox
+ accepted()
+ WizardDialog
+ accept()
+
+
+ 227
+ 406
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ WizardDialog
+ reject()
+
+
+ 295
+ 412
+
+
+ 286
+ 274
+
+
+
+
+