Explorar el Código

Mod from GitHub (#785)

* Make new dialog for checking updates
* update on startup
* Implemented auto-update
Nordsoft91 hace 3 años
padre
commit
5862c192b0

+ 10 - 2
config/schemas/settings.json

@@ -363,12 +363,12 @@
 			"type" : "object",
 			"type" : "object",
 			"default": {},
 			"default": {},
 			"additionalProperties" : false,
 			"additionalProperties" : false,
-			"required" : [ "repositoryURL", "enableInstalledMods", "autoCheckRepositories" ],
+			"required" : [ "repositoryURL", "enableInstalledMods", "autoCheckRepositories", "updateOnStartup", "updateConfigUrl" ],
 			"properties" : {
 			"properties" : {
 				"repositoryURL" : {
 				"repositoryURL" : {
 					"type" : "array",
 					"type" : "array",
 					"default" : [
 					"default" : [
-						"http://download.vcmi.eu/mods/repository/repository.json"
+						"https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/github.json"
 					],
 					],
 					"items" : {
 					"items" : {
 						"type" : "string"
 						"type" : "string"
@@ -381,6 +381,14 @@
 				"autoCheckRepositories" : {
 				"autoCheckRepositories" : {
 					"type" : "boolean",
 					"type" : "boolean",
 					"default" : true
 					"default" : true
+				},
+				"updateOnStartup" : {
+					"type" : "boolean",
+					"default" : true
+				},
+				"updateConfigUrl" : {
+					"type" : "string",
+					"default" : "https://raw.githubusercontent.com/vcmi/vcmi-updates/master/vcmi-updates.json"
 				}
 				}
 			}
 			}
 		}
 		}

+ 3 - 0
launcher/CMakeLists.txt

@@ -32,6 +32,7 @@ set(launcher_SRCS
 		mainwindow_moc.cpp
 		mainwindow_moc.cpp
 		launcherdirs.cpp
 		launcherdirs.cpp
 		jsonutils.cpp
 		jsonutils.cpp
+		updatedialog_moc.cpp
 )
 )
 
 
 set(launcher_HEADERS
 set(launcher_HEADERS
@@ -41,6 +42,7 @@ set(launcher_HEADERS
 		mainwindow_moc.h
 		mainwindow_moc.h
 		launcherdirs.h
 		launcherdirs.h
 		jsonutils.h
 		jsonutils.h
+		updatedialog_moc.h
 )
 )
 
 
 set(launcher_FORMS
 set(launcher_FORMS
@@ -48,6 +50,7 @@ set(launcher_FORMS
 		modManager/imageviewer_moc.ui
 		modManager/imageviewer_moc.ui
 		settingsView/csettingsview_moc.ui
 		settingsView/csettingsview_moc.ui
 		mainwindow_moc.ui
 		mainwindow_moc.ui
+		updatedialog_moc.ui
 )
 )
 
 
 assign_source_group(${launcher_SRCS} ${launcher_HEADERS} VCMI_launcher.rc)
 assign_source_group(${launcher_SRCS} ${launcher_HEADERS} VCMI_launcher.rc)

+ 5 - 0
launcher/mainwindow_moc.cpp

@@ -19,6 +19,8 @@
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 
 
+#include "updatedialog_moc.h"
+
 void MainWindow::load()
 void MainWindow::load()
 {
 {
 	// Set current working dir to executable folder.
 	// Set current working dir to executable folder.
@@ -80,6 +82,9 @@ MainWindow::MainWindow(QWidget * parent)
 
 
 	connect(ui->tabSelectList, SIGNAL(currentRowChanged(int)),
 	connect(ui->tabSelectList, SIGNAL(currentRowChanged(int)),
 		ui->tabListWidget, SLOT(setCurrentIndex(int)));
 		ui->tabListWidget, SLOT(setCurrentIndex(int)));
+
+	if(settings["launcher"]["updateOnStartup"].Bool())
+		UpdateDialog::showUpdateDialog(false);
 }
 }
 
 
 MainWindow::~MainWindow()
 MainWindow::~MainWindow()

+ 18 - 0
launcher/modManager/cdownloadmanager_moc.cpp

@@ -20,6 +20,7 @@ CDownloadManager::CDownloadManager()
 
 
 void CDownloadManager::downloadFile(const QUrl & url, const QString & file)
 void CDownloadManager::downloadFile(const QUrl & url, const QString & file)
 {
 {
+	filename = file;
 	QNetworkRequest request(url);
 	QNetworkRequest request(url);
 	FileEntry entry;
 	FileEntry entry;
 	entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file));
 	entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file));
@@ -61,6 +62,23 @@ CDownloadManager::FileEntry & CDownloadManager::getEntry(QNetworkReply * reply)
 void CDownloadManager::downloadFinished(QNetworkReply * reply)
 void CDownloadManager::downloadFinished(QNetworkReply * reply)
 {
 {
 	FileEntry & file = getEntry(reply);
 	FileEntry & file = getEntry(reply);
+	
+	QVariant possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+	QUrl qurl = possibleRedirectUrl.toUrl();
+	
+	if(possibleRedirectUrl.isValid())
+	{
+		for(int i = 0; i< currentDownloads.size(); ++i)
+		{
+			if(currentDownloads[i].file == file.file)
+			{
+				currentDownloads.removeAt(i);
+				break;
+			}
+		}
+		downloadFile(qurl, filename);
+		return;
+	}
 
 
 	if(file.reply->error())
 	if(file.reply->error())
 	{
 	{

+ 2 - 0
launcher/modManager/cdownloadmanager_moc.h

@@ -35,6 +35,8 @@ class CDownloadManager : public QObject
 	};
 	};
 
 
 	QStringList encounteredErrors;
 	QStringList encounteredErrors;
+	
+	QString filename;
 
 
 	QNetworkAccessManager manager;
 	QNetworkAccessManager manager;
 
 

+ 25 - 13
launcher/modManager/cmodmanager.cpp

@@ -24,21 +24,21 @@ static QString detectModArchive(QString path, QString modName)
 
 
 	QString modDirName;
 	QString modDirName;
 
 
-	for(auto file : files)
+	for(int folderLevel : {0, 1}) //search in subfolder if there is no mod.json in the root
 	{
 	{
-		QString filename = QString::fromUtf8(file.c_str());
-		if(filename.toLower().startsWith(modName))
+		for(auto file : files)
 		{
 		{
-			// archive must contain mod.json file
-			if(filename.toLower() == modName + "/mod.json")
-				modDirName = filename.section('/', 0, 0);
-		}
-		else // all files must be in <modname> directory
-		{
-			return "";
+			QString filename = QString::fromUtf8(file.c_str());
+			modDirName = filename.section('/', 0, folderLevel);
+			
+			if(filename == modDirName + "/mod.json")
+			{
+				return modDirName;
+			}
 		}
 		}
 	}
 	}
-	return modDirName;
+	
+	return "";
 }
 }
 
 
 CModManager::CModManager(CModList * modList)
 CModManager::CModManager(CModList * modList)
@@ -259,7 +259,18 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 		return addError(modname, "Failed to extract mod data");
 		return addError(modname, "Failed to extract mod data");
 	}
 	}
 
 
-	QVariantMap json = JsonUtils::JsonFromFile(destDir + modDirName + "/mod.json").toMap();
+	//rename folder and fix the path
+	QDir extractedDir(destDir + modDirName);
+	auto rc = QFile::rename(destDir + modDirName, destDir + modname);
+	if (rc)
+		extractedDir.setPath(destDir + modname);
+	
+	//there are possible excessive files - remove them
+	QString upperLevel = modDirName.section('/', 0, 0);
+	if(upperLevel != modDirName)
+		removeModDir(destDir + upperLevel);
+
+	QVariantMap json = JsonUtils::JsonFromFile(destDir + modname + "/mod.json").toMap();
 
 
 	localMods.insert(modname, json);
 	localMods.insert(modname, json);
 	modList->setLocalModList(localMods);
 	modList->setLocalModList(localMods);
@@ -280,8 +291,9 @@ bool CModManager::doUninstallMod(QString modname)
 	if(!localMods.contains(modname))
 	if(!localMods.contains(modname))
 		return addError(modname, "Data with this mod was not found");
 		return addError(modname, "Data with this mod was not found");
 
 
+	QDir modFullDir(modDir);
 	if(!removeModDir(modDir))
 	if(!removeModDir(modDir))
-		return addError(modname, "Failed to delete mod data");
+		return addError(modname, "Mod is located in protected directory, plase remove it manually:\n" + modFullDir.absolutePath());
 
 
 	localMods.remove(modname);
 	localMods.remove(modname);
 	modList->setLocalModList(localMods);
 	modList->setLocalModList(localMods);

+ 8 - 0
launcher/settingsView/csettingsview_moc.cpp

@@ -11,6 +11,8 @@
 #include "csettingsview_moc.h"
 #include "csettingsview_moc.h"
 #include "ui_csettingsview_moc.h"
 #include "ui_csettingsview_moc.h"
 
 
+#include "../updatedialog_moc.h"
+
 #include <QFileInfo>
 #include <QFileInfo>
 #include <QGuiApplication>
 #include <QGuiApplication>
 
 
@@ -232,3 +234,9 @@ void CSettingsView::on_comboBoxAutoSave_currentIndexChanged(int index)
 	Settings node = settings.write["general"]["saveFrequency"];
 	Settings node = settings.write["general"]["saveFrequency"];
 	node->Integer() = index;
 	node->Integer() = index;
 }
 }
+
+void CSettingsView::on_updatesButton_clicked()
+{
+	UpdateDialog::showUpdateDialog(true);
+}
+

+ 2 - 0
launcher/settingsView/csettingsview_moc.h

@@ -63,6 +63,8 @@ private slots:
 
 
 	void on_comboBoxAutoSave_currentIndexChanged(int index);
 	void on_comboBoxAutoSave_currentIndexChanged(int index);
 
 
+	void on_updatesButton_clicked();
+
 private:
 private:
 	Ui::CSettingsView * ui;
 	Ui::CSettingsView * ui;
 };
 };

+ 306 - 306
launcher/settingsView/csettingsview_moc.ui

@@ -6,8 +6,8 @@
    <rect>
    <rect>
     <x>0</x>
     <x>0</x>
     <y>0</y>
     <y>0</y>
-    <width>738</width>
-    <height>471</height>
+    <width>779</width>
+    <height>619</height>
    </rect>
    </rect>
   </property>
   </property>
   <property name="windowTitle">
   <property name="windowTitle">
@@ -26,40 +26,15 @@
    <property name="bottomMargin">
    <property name="bottomMargin">
     <number>0</number>
     <number>0</number>
    </property>
    </property>
-   <item row="3" column="6">
-    <widget class="QLabel" name="labelTempDir">
-     <property name="text">
-      <string>Log files directory</string>
-     </property>
-    </widget>
-   </item>
-   <item row="17" column="6">
-    <widget class="QComboBox" name="comboBoxAutoCheck">
-     <property name="currentIndex">
-      <number>1</number>
-     </property>
-     <item>
-      <property name="text">
-       <string>Off</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>On</string>
-      </property>
-     </item>
-    </widget>
-   </item>
-   <item row="0" column="6" colspan="4">
-    <widget class="QLabel" name="labelDataDirs">
+   <item row="8" column="1" colspan="4">
+    <widget class="QLabel" name="labelAIMovingOnTheMap">
      <property name="font">
      <property name="font">
       <font>
       <font>
-       <weight>75</weight>
        <bold>true</bold>
        <bold>true</bold>
       </font>
       </font>
      </property>
      </property>
      <property name="text">
      <property name="text">
-      <string>Data Directories</string>
+      <string>AI on the map</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
@@ -67,7 +42,6 @@
     <widget class="QLabel" name="labelVideo">
     <widget class="QLabel" name="labelVideo">
      <property name="font">
      <property name="font">
       <font>
       <font>
-       <weight>75</weight>
        <bold>true</bold>
        <bold>true</bold>
       </font>
       </font>
      </property>
      </property>
@@ -76,123 +50,69 @@
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="1" column="1">
-    <widget class="QLabel" name="labelResolution">
+   <item row="3" column="1">
+    <widget class="QLabel" name="labelShowIntro">
      <property name="text">
      <property name="text">
-      <string>Resolution</string>
+      <string>Show intro</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="3" column="9">
-    <widget class="QPushButton" name="openTempDir">
-     <property name="text">
-      <string>Open</string>
+   <item row="9" column="7" colspan="3">
+    <widget class="QSpinBox" name="spinBoxNetworkPort">
+     <property name="minimum">
+      <number>1024</number>
      </property>
      </property>
-    </widget>
-   </item>
-   <item row="11" column="4">
-    <widget class="QComboBox" name="comboBoxNeutralAI">
-     <item>
-      <property name="text">
-       <string>BattleAI</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>StupidAI</string>
-      </property>
-     </item>
-    </widget>
-   </item>
-   <item row="2" column="6">
-    <widget class="QLabel" name="labelUserDataDir">
-     <property name="text">
-      <string>User data directory</string>
+     <property name="maximum">
+      <number>65535</number>
+     </property>
+     <property name="value">
+      <number>3030</number>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="14" column="1">
-    <widget class="QLabel" name="labelEnemyAI">
+   <item row="1" column="7">
+    <widget class="QLineEdit" name="lineEditGameDir">
      <property name="minimumSize">
      <property name="minimumSize">
       <size>
       <size>
-       <width>0</width>
-       <height>22</height>
+       <width>150</width>
+       <height>0</height>
       </size>
       </size>
      </property>
      </property>
      <property name="text">
      <property name="text">
-      <string>Enemy AI</string>
-     </property>
-    </widget>
-   </item>
-   <item row="17" column="1" colspan="4">
-    <widget class="QLabel" name="labelAutoCheck">
-     <property name="text">
-      <string>Check repositories on startup</string>
+      <string>/usr/share/vcmi</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="4" column="1">
-    <widget class="QLabel" name="labelDisplayIndex">
+   <item row="10" column="6">
+    <widget class="QLabel" name="labelEncoding">
      <property name="text">
      <property name="text">
-      <string>Display index</string>
+      <string>Heroes III character set</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="3" column="1">
-    <widget class="QLabel" name="labelShowIntro">
+   <item row="5" column="1">
+    <widget class="QCheckBox" name="checkBoxFullScreen">
      <property name="text">
      <property name="text">
-      <string>Show intro</string>
+      <string>Real fullscreen mode</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="8" column="6" colspan="4">
-    <widget class="QLabel" name="labelGeneral">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
+   <item row="1" column="1">
+    <widget class="QLabel" name="labelResolution">
      <property name="text">
      <property name="text">
-      <string>General</string>
+      <string>Resolution</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="4" column="4">
-    <widget class="QComboBox" name="comboBoxDisplayIndex">
-     <item>
-      <property name="text">
-       <string>0</string>
-      </property>
-     </item>
-    </widget>
-   </item>
-   <item row="1" column="8">
-    <widget class="QPushButton" name="changeGameDataDir">
-     <property name="enabled">
-      <bool>false</bool>
-     </property>
+   <item row="2" column="1">
+    <widget class="QLabel" name="labelFullScreen">
      <property name="text">
      <property name="text">
-      <string>Change</string>
+      <string>Fullscreen</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="9" column="4">
-    <widget class="QComboBox" name="comboBoxPlayerAI">
-     <item>
-      <property name="text">
-       <string>VCAI</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>Nullkiller</string>
-      </property>
-     </item>
-    </widget>
-   </item>
-   <item row="3" column="7" colspan="2">
-    <widget class="QLineEdit" name="lineEditTempDir">
+   <item row="2" column="7" colspan="2">
+    <widget class="QLineEdit" name="lineEditUserDataDir">
      <property name="enabled">
      <property name="enabled">
       <bool>false</bool>
       <bool>false</bool>
      </property>
      </property>
@@ -210,36 +130,32 @@
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="18" column="1" colspan="4">
-    <widget class="QLabel" name="labelRepositories">
+   <item row="2" column="4">
+    <widget class="QComboBox" name="comboBoxFullScreen">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <item>
+      <property name="text">
+       <string>Off</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>On</string>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="8" column="6" colspan="4">
+    <widget class="QLabel" name="labelGeneral">
      <property name="font">
      <property name="font">
       <font>
       <font>
-       <weight>75</weight>
        <bold>true</bold>
        <bold>true</bold>
       </font>
       </font>
      </property>
      </property>
      <property name="text">
      <property name="text">
-      <string>Repositories</string>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="9">
-    <widget class="QPushButton" name="openGameDataDir">
-     <property name="text">
-      <string>Open</string>
-     </property>
-    </widget>
-   </item>
-   <item row="9" column="7" colspan="3">
-    <widget class="QSpinBox" name="spinBoxNetworkPort">
-     <property name="minimum">
-      <number>1024</number>
-     </property>
-     <property name="maximum">
-      <number>65535</number>
-     </property>
-     <property name="value">
-      <number>3030</number>
+      <string>General</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
@@ -253,26 +169,13 @@
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="2" column="9">
-    <widget class="QPushButton" name="openUserDataDir">
+   <item row="1" column="9">
+    <widget class="QPushButton" name="openGameDataDir">
      <property name="text">
      <property name="text">
       <string>Open</string>
       <string>Open</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="13" column="1">
-    <widget class="QLabel" name="labelFriendlyAI">
-     <property name="minimumSize">
-      <size>
-       <width>0</width>
-       <height>22</height>
-      </size>
-     </property>
-     <property name="text">
-      <string>Friendly AI</string>
-     </property>
-    </widget>
-   </item>
    <item row="9" column="1">
    <item row="9" column="1">
     <widget class="QLabel" name="labelPlayerAI">
     <widget class="QLabel" name="labelPlayerAI">
      <property name="text">
      <property name="text">
@@ -280,152 +183,152 @@
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="14" column="4">
-    <widget class="QComboBox" name="comboBoxEnemyAI">
-     <property name="editable">
-      <bool>false</bool>
+   <item row="18" column="1" colspan="4">
+    <widget class="QLabel" name="labelRepositories">
+     <property name="font">
+      <font>
+       <bold>true</bold>
+      </font>
      </property>
      </property>
-     <property name="currentText">
-      <string>BattleAI</string>
+     <property name="text">
+      <string>Repositories</string>
      </property>
      </property>
+    </widget>
+   </item>
+   <item row="9" column="4">
+    <widget class="QComboBox" name="comboBoxPlayerAI">
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>BattleAI</string>
+       <string>VCAI</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>StupidAI</string>
+       <string>Nullkiller</string>
       </property>
       </property>
      </item>
      </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="2" column="1">
-    <widget class="QLabel" name="labelFullScreen">
+   <item row="4" column="1">
+    <widget class="QLabel" name="labelDisplayIndex">
      <property name="text">
      <property name="text">
-      <string>Fullscreen</string>
+      <string>Display index</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="7" column="1" colspan="4">
-    <spacer name="spacerSections">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeType">
-      <enum>QSizePolicy::Fixed</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>56</width>
-       <height>8</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item row="1" column="7">
-    <widget class="QLineEdit" name="lineEditGameDir">
-     <property name="minimumSize">
-      <size>
-       <width>150</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="text">
-      <string>/usr/share/vcmi</string>
+   <item row="3" column="4">
+    <widget class="QComboBox" name="comboBoxShowIntro">
+     <property name="currentIndex">
+      <number>1</number>
      </property>
      </property>
+     <item>
+      <property name="text">
+       <string>Off</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>On</string>
+      </property>
+     </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="1" column="6">
-    <widget class="QLabel" name="labelGameDir">
-     <property name="text">
-      <string>Extra data directory</string>
-     </property>
+   <item row="11" column="4">
+    <widget class="QComboBox" name="comboBoxNeutralAI">
+     <item>
+      <property name="text">
+       <string>BattleAI</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>StupidAI</string>
+      </property>
+     </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="2" column="7" colspan="2">
-    <widget class="QLineEdit" name="lineEditUserDataDir">
-     <property name="enabled">
+   <item row="13" column="4">
+    <widget class="QComboBox" name="comboBoxFriendlyAI">
+     <property name="editable">
       <bool>false</bool>
       <bool>false</bool>
      </property>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>150</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="text">
-      <string>/home/user/.vcmi</string>
-     </property>
-     <property name="readOnly">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="4">
-    <widget class="QComboBox" name="comboBoxFullScreen">
-     <property name="currentIndex">
-      <number>0</number>
+     <property name="currentText">
+      <string>BattleAI</string>
      </property>
      </property>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Off</string>
+       <string>BattleAI</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>On</string>
+       <string>StupidAI</string>
       </property>
       </property>
      </item>
      </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="8" column="1" colspan="4">
-    <widget class="QLabel" name="labelAIMovingOnTheMap">
+   <item row="11" column="5">
+    <spacer name="spacerColumns">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>8</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="3" column="9">
+    <widget class="QPushButton" name="openTempDir">
+     <property name="text">
+      <string>Open</string>
+     </property>
+    </widget>
+   </item>
+   <item row="16" column="1" colspan="4">
+    <widget class="QLabel" name="LauncherSettings">
      <property name="font">
      <property name="font">
       <font>
       <font>
-       <weight>75</weight>
        <bold>true</bold>
        <bold>true</bold>
       </font>
       </font>
      </property>
      </property>
      <property name="text">
      <property name="text">
-      <string>AI on the map</string>
+      <string>Launcher Settings</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="3" column="4">
-    <widget class="QComboBox" name="comboBoxShowIntro">
-     <property name="currentIndex">
-      <number>1</number>
+   <item row="1" column="6">
+    <widget class="QLabel" name="labelGameDir">
+     <property name="text">
+      <string>Extra data directory</string>
      </property>
      </property>
-     <item>
-      <property name="text">
-       <string>Off</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>On</string>
-      </property>
-     </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="16" column="1" colspan="4">
-    <widget class="QLabel" name="LauncherSettings">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
+   <item row="14" column="1">
+    <widget class="QLabel" name="labelEnemyAI">
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>22</height>
+      </size>
      </property>
      </property>
      <property name="text">
      <property name="text">
-      <string>Launcher Settings</string>
+      <string>Enemy AI</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="9" column="6">
-    <widget class="QLabel" name="labelNetworkPort">
+   <item row="1" column="8">
+    <widget class="QPushButton" name="changeGameDataDir">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
      <property name="text">
      <property name="text">
-      <string>Network port</string>
+      <string>Change</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
@@ -449,7 +352,7 @@
        <string>1024x768</string>
        <string>1024x768</string>
       </property>
       </property>
      </item>
      </item>
-      <item>
+     <item>
       <property name="text">
       <property name="text">
        <string>1181x664</string>
        <string>1181x664</string>
       </property>
       </property>
@@ -506,8 +409,24 @@
      </item>
      </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="15" column="1" colspan="4">
-    <spacer name="spacerRepos">
+   <item row="11" column="1">
+    <widget class="QLabel" name="labelNeutralAI">
+     <property name="text">
+      <string>Neutral AI</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="4">
+    <widget class="QComboBox" name="comboBoxDisplayIndex">
+     <item>
+      <property name="text">
+       <string>0</string>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="7" column="1" colspan="4">
+    <spacer name="spacerSections">
      <property name="orientation">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
       <enum>Qt::Vertical</enum>
      </property>
      </property>
@@ -516,71 +435,105 @@
      </property>
      </property>
      <property name="sizeHint" stdset="0">
      <property name="sizeHint" stdset="0">
       <size>
       <size>
-       <width>20</width>
+       <width>56</width>
        <height>8</height>
        <height>8</height>
       </size>
       </size>
      </property>
      </property>
     </spacer>
     </spacer>
    </item>
    </item>
-   <item row="13" column="4">
-    <widget class="QComboBox" name="comboBoxFriendlyAI">
-     <property name="editable">
+   <item row="3" column="7" colspan="2">
+    <widget class="QLineEdit" name="lineEditTempDir">
+     <property name="enabled">
       <bool>false</bool>
       <bool>false</bool>
      </property>
      </property>
-     <property name="currentText">
-      <string>BattleAI</string>
+     <property name="minimumSize">
+      <size>
+       <width>150</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>/home/user/.vcmi</string>
+     </property>
+     <property name="readOnly">
+      <bool>true</bool>
      </property>
      </property>
-     <item>
-      <property name="text">
-       <string>BattleAI</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>StupidAI</string>
-      </property>
-     </item>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="10" column="7" colspan="3">
-    <widget class="QComboBox" name="comboBoxEncoding">
+   <item row="17" column="6">
+    <widget class="QComboBox" name="comboBoxAutoCheck">
+     <property name="currentIndex">
+      <number>1</number>
+     </property>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Central European (Windows 1250)</string>
+       <string>Off</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Cyrillic script (Windows 1251)</string>
+       <string>On</string>
       </property>
       </property>
      </item>
      </item>
+    </widget>
+   </item>
+   <item row="11" column="7">
+    <widget class="QComboBox" name="comboBoxAutoSave">
+     <property name="currentIndex">
+      <number>1</number>
+     </property>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Western European (Windows 1252)</string>
+       <string>Off</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Simplified Chinese (GBK)</string>
+       <string>On</string>
       </property>
       </property>
      </item>
      </item>
+    </widget>
+   </item>
+   <item row="14" column="4">
+    <widget class="QComboBox" name="comboBoxEnemyAI">
+     <property name="editable">
+      <bool>false</bool>
+     </property>
+     <property name="currentText">
+      <string>BattleAI</string>
+     </property>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Simplified Chinese (GB2312)</string>
+       <string>BattleAI</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Korean (Windows 949)</string>
+       <string>StupidAI</string>
       </property>
       </property>
      </item>
      </item>
     </widget>
     </widget>
    </item>
    </item>
+   <item row="15" column="1" colspan="4">
+    <spacer name="spacerRepos">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>8</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
    <item row="10" column="1">
    <item row="10" column="1">
     <widget class="QLabel" name="labelAIInTheBattlefield">
     <widget class="QLabel" name="labelAIInTheBattlefield">
      <property name="font">
      <property name="font">
       <font>
       <font>
-       <weight>75</weight>
        <italic>true</italic>
        <italic>true</italic>
        <bold>true</bold>
        <bold>true</bold>
        <underline>false</underline>
        <underline>false</underline>
@@ -591,65 +544,112 @@
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="11" column="5">
-    <spacer name="spacerColumns">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="sizeType">
-      <enum>QSizePolicy::Fixed</enum>
+   <item row="17" column="1" colspan="4">
+    <widget class="QLabel" name="labelAutoCheck">
+     <property name="text">
+      <string>Check repositories on startup</string>
      </property>
      </property>
-     <property name="sizeHint" stdset="0">
+    </widget>
+   </item>
+   <item row="13" column="1">
+    <widget class="QLabel" name="labelFriendlyAI">
+     <property name="minimumSize">
       <size>
       <size>
-       <width>8</width>
-       <height>20</height>
+       <width>0</width>
+       <height>22</height>
       </size>
       </size>
      </property>
      </property>
-    </spacer>
+     <property name="text">
+      <string>Friendly AI</string>
+     </property>
+    </widget>
    </item>
    </item>
-   <item row="10" column="6">
-    <widget class="QLabel" name="labelEncoding">
+   <item row="3" column="6">
+    <widget class="QLabel" name="labelTempDir">
      <property name="text">
      <property name="text">
-      <string>Heroes III character set</string>
+      <string>Log files directory</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="11" column="1">
-    <widget class="QLabel" name="labelNeutralAI">
+   <item row="0" column="6" colspan="4">
+    <widget class="QLabel" name="labelDataDirs">
+     <property name="font">
+      <font>
+       <bold>true</bold>
+      </font>
+     </property>
      <property name="text">
      <property name="text">
-      <string>Neutral AI</string>
+      <string>Data Directories</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="5" column="1">
-    <widget class="QCheckBox" name="checkBoxFullScreen">
+   <item row="2" column="6">
+    <widget class="QLabel" name="labelUserDataDir">
      <property name="text">
      <property name="text">
-      <string>Real fullscreen mode</string>
+      <string>User data directory</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="11" column="6">
-    <widget class="QLabel" name="labelAutoSave">
+   <item row="2" column="9">
+    <widget class="QPushButton" name="openUserDataDir">
      <property name="text">
      <property name="text">
-      <string>Autosave</string>
+      <string>Open</string>
      </property>
      </property>
     </widget>
     </widget>
    </item>
    </item>
-   <item row="11" column="7">
-    <widget class="QComboBox" name="comboBoxAutoSave">
-     <property name="currentIndex">
-      <number>1</number>
+   <item row="9" column="6">
+    <widget class="QLabel" name="labelNetworkPort">
+     <property name="text">
+      <string>Network port</string>
      </property>
      </property>
+    </widget>
+   </item>
+   <item row="10" column="7" colspan="3">
+    <widget class="QComboBox" name="comboBoxEncoding">
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>Off</string>
+       <string>Central European (Windows 1250)</string>
       </property>
       </property>
      </item>
      </item>
      <item>
      <item>
       <property name="text">
       <property name="text">
-       <string>On</string>
+       <string>Cyrillic script (Windows 1251)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Western European (Windows 1252)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Simplified Chinese (GBK)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Simplified Chinese (GB2312)</string>
       </property>
       </property>
      </item>
      </item>
+     <item>
+      <property name="text">
+       <string>Korean (Windows 949)</string>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="11" column="6">
+    <widget class="QLabel" name="labelAutoSave">
+     <property name="text">
+      <string>Autosave</string>
+     </property>
+    </widget>
+   </item>
+   <item row="17" column="7">
+    <widget class="QPushButton" name="updatesButton">
+     <property name="text">
+      <string>Check for updates</string>
+     </property>
     </widget>
     </widget>
    </item>
    </item>
   </layout>
   </layout>

+ 147 - 0
launcher/updatedialog_moc.cpp

@@ -0,0 +1,147 @@
+/*
+ * updatedialog_moc.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "updatedialog_moc.h"
+#include "ui_updatedialog_moc.h"
+
+#include "../lib/CConfigHandler.h"
+#include "../lib/GameConstants.h"
+
+#include <QNetworkReply>
+#include <QNetworkRequest>
+
+UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent):
+	QDialog(parent),
+	ui(new Ui::UpdateDialog),
+	calledManually(calledManually)
+{
+	ui->setupUi(this);
+	
+	if(calledManually)
+	{
+		setWindowModality(Qt::ApplicationModal);
+		show();
+	}
+	
+	connect(ui->closeButton, SIGNAL(clicked()), this, SLOT(close()));
+	
+	if(settings["launcher"]["updateOnStartup"].Bool())
+		ui->checkOnStartup->setCheckState(Qt::CheckState::Checked);
+	
+	currentVersion = GameConstants::VCMI_VERSION;
+	
+	setWindowTitle(QString::fromStdString(currentVersion));
+	
+#ifdef VCMI_WINDOWS
+	platformParameter = "windows";
+#elif defined(VCMI_MAC)
+	platformParameter = "macos";
+#elif defined(VCMI_IOS)
+	platformParameter = "ios";
+#elif defined(VCMI_ANDROID)
+	platformParameter = "android";
+#elif defined(VCMI_UNIX)
+	platformParameter = "linux";
+#endif
+	
+	QString url = QString::fromStdString(settings["launcher"]["updateConfigUrl"].String());
+		
+	QNetworkReply *response = networkManager.get(QNetworkRequest(QUrl(url)));
+	
+	connect(response, &QNetworkReply::finished, [&, response]{
+		response->deleteLater();
+		
+		if(response->error() != QNetworkReply::NoError)
+		{
+			ui->versionLabel->setStyleSheet("QLabel { background-color : red; color : black; }");
+			ui->versionLabel->setText("Network error");
+			ui->plainTextEdit->setPlainText(response->errorString());
+			return;
+		}
+		
+		auto byteArray = response->readAll();
+		JsonNode node(byteArray.constData(), byteArray.size());
+		loadFromJson(node);
+	});
+}
+
+UpdateDialog::~UpdateDialog()
+{
+	delete ui;
+}
+
+void UpdateDialog::showUpdateDialog(bool isManually)
+{
+	UpdateDialog * dialog = new UpdateDialog(isManually);
+	
+	dialog->setAttribute(Qt::WA_DeleteOnClose);
+}
+
+void UpdateDialog::on_checkOnStartup_stateChanged(int state)
+{
+	Settings node = settings.write["launcher"]["updateOnStartup"];
+	node->Bool() = ui->checkOnStartup->isChecked();
+}
+
+void UpdateDialog::loadFromJson(const JsonNode & node)
+{
+	if(node.getType() != JsonNode::JsonType::DATA_STRUCT ||
+	   node["updateType"].getType() != JsonNode::JsonType::DATA_STRING ||
+	   node["version"].getType() != JsonNode::JsonType::DATA_STRING ||
+	   node["changeLog"].getType() != JsonNode::JsonType::DATA_STRING ||
+	   node.getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional
+	{
+		ui->plainTextEdit->setPlainText("Cannot read JSON from url or incorrect JSON data");
+		return;
+	}
+	
+	//check whether update is needed
+	bool isFutureVersion = true;
+	std::string newVersion = node["version"].String();
+	for(auto & prevVersion : node["history"].Vector())
+	{
+		if(prevVersion.String() == currentVersion)
+			isFutureVersion = false;
+	}
+		
+	if(isFutureVersion || currentVersion == newVersion)
+	{
+		if(!calledManually)
+			close();
+		
+		return;
+	}
+	
+	if(!calledManually)
+	{
+		setWindowModality(Qt::ApplicationModal);
+		show();
+	}
+	
+	const auto updateType = node["updateType"].String();
+	
+	QString bgColor;
+	if(updateType == "minor")
+		bgColor = "gray";
+	else if(updateType == "major")
+		bgColor = "orange";
+	else if(updateType == "critical")
+		bgColor = "red";
+	
+	ui->versionLabel->setStyleSheet(QString("QLabel { background-color : %1; color : black; }").arg(bgColor));
+	ui->versionLabel->setText(QString::fromStdString(newVersion));
+	ui->plainTextEdit->setPlainText(QString::fromStdString(node["changeLog"].String()));
+	
+	QString downloadLink = QString::fromStdString(node["downloadLinks"]["other"].String());
+	if(node["downloadLinks"][platformParameter].getType() == JsonNode::JsonType::DATA_STRING)
+		downloadLink = QString::fromStdString(node["downloadLinks"][platformParameter].String());
+	
+	ui->downloadLink->setText(QString{"<a href=\"%1\">Download page</a>"}.arg(downloadLink));
+}

+ 44 - 0
launcher/updatedialog_moc.h

@@ -0,0 +1,44 @@
+/*
+ * updatedialog_moc.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+#include <QDialog>
+#include <QNetworkAccessManager>
+
+class JsonNode;
+
+namespace Ui {
+class UpdateDialog;
+}
+
+class UpdateDialog : public QDialog
+{
+	Q_OBJECT
+
+public:
+	explicit UpdateDialog(bool calledManually, QWidget *parent = nullptr);
+	~UpdateDialog();
+	
+	static void showUpdateDialog(bool isManually);
+
+private slots:
+    void on_checkOnStartup_stateChanged(int state);
+
+private:
+	Ui::UpdateDialog *ui;
+	
+	std::string currentVersion;
+	std::string platformParameter = "other";
+	
+	QNetworkAccessManager networkManager;
+	
+	bool calledManually;
+	
+	void loadFromJson(const JsonNode & node);
+};

+ 128 - 0
launcher/updatedialog_moc.ui

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UpdateDialog</class>
+ <widget class="QDialog" name="UpdateDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>371</width>
+    <height>247</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>371</width>
+    <height>247</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="leftMargin">
+    <number>12</number>
+   </property>
+   <property name="topMargin">
+    <number>12</number>
+   </property>
+   <property name="rightMargin">
+    <number>12</number>
+   </property>
+   <property name="bottomMargin">
+    <number>12</number>
+   </property>
+   <item row="0" column="0" colspan="5">
+    <widget class="QLabel" name="versionLabel">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>48</height>
+      </size>
+     </property>
+     <property name="font">
+      <font>
+       <pointsize>18</pointsize>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="autoFillBackground">
+      <bool>false</bool>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">QLabel {background-color: green; color : black} </string>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <property name="text">
+      <string>You have latest version</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0" colspan="5">
+    <widget class="QPlainTextEdit" name="plainTextEdit">
+     <property name="readOnly">
+      <bool>true</bool>
+     </property>
+     <property name="plainText">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="3" colspan="2">
+    <widget class="QPushButton" name="closeButton">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string>Close</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="2">
+    <widget class="QCheckBox" name="checkOnStartup">
+     <property name="text">
+      <string>Check updates on startup</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="downloadLink">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="openExternalLinks">
+      <bool>true</bool>
+     </property>
+     <property name="textInteractionFlags">
+      <set>Qt::TextBrowserInteraction</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>