Installer.qml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. import QtQuick 2.0
  2. import Qt.labs.settings 1.0
  3. import Qt.labs.platform 1.1
  4. import FileIO 1.0
  5. import "components"
  6. Item {
  7. id: installer
  8. readonly property string homePath: FileIO.userHomePath()
  9. readonly property string defaultGamePath: "drive_c/Program Files/Genshin Impact/Genshin Impact Game"
  10. readonly property string defaultLocation: `${homePath}/.wine/${defaultGamePath}`
  11. readonly property string configIni: "config.ini"
  12. readonly property string versionNotInstalled: "Not installed"
  13. readonly property var possibleLocations: [
  14. `.wine*/${defaultGamePath}`,
  15. `.steam/steam/steamapps/common/Proton */dist/share/default_pfx/${defaultGamePath}`
  16. ]
  17. property alias installedVersion: localVersionLoader.version
  18. property string availableVersion
  19. property string preDownloadVersion
  20. property bool isPreDownloading: false
  21. property bool quitAfterCancel: false
  22. anchors.fill: parent
  23. InstallerUi {
  24. id: ui
  25. anchors.fill: parent
  26. progressText: clientInstaller.progressText
  27. progress: clientInstaller.progressPercent / 100.0
  28. onBrowseClicked: {
  29. fileDialog.open()
  30. }
  31. onInstallClicked: {
  32. ui.uiState = ui.uiStateInstalling
  33. dependenciesInstaller.check()
  34. }
  35. onCancelConfirmed: {
  36. clientInstaller.cancel()
  37. ui.log(qsTr("Canceled"))
  38. ui.log(qsTr(""))
  39. revertUiState()
  40. if (quitAfterCancel) {
  41. quitAfterCancel = false
  42. window.close()
  43. }
  44. }
  45. onCancelRejected: {
  46. quitAfterCancel = false
  47. }
  48. onPreDownloadClicked: {
  49. isPreDownloading = true
  50. dependenciesInstaller.check()
  51. }
  52. onUpdateClicked: {
  53. ui.uiState = ui.uiStateUpdating
  54. dependenciesInstaller.check()
  55. }
  56. onRetryClicked: checkForUpdates()
  57. onRepatchClicked: clientInstaller.reapplyWinePatch()
  58. onStartClicked: {
  59. ui.uiState = ui.uiStateStarting
  60. clientLauncher.start()
  61. }
  62. onStopConfirmed: {
  63. ui.uiState = ui.uiStateStopping
  64. clientLauncher.stop()
  65. }
  66. onKillWineServerClicked: clientLauncher.killWineServer()
  67. onCheckSelfUpdateClicked: selfUpdate.check()
  68. }
  69. Settings {
  70. id: settings
  71. category: "game"
  72. property string gamePath: ""
  73. property alias isLogVisible: ui.isLogVisible
  74. property bool multiConnections: false
  75. property bool limitDownload: false
  76. property string limitDownloadValue: "1M"
  77. property bool hideBackground: false
  78. property bool firstRun: true
  79. }
  80. LocalVersionLoader {
  81. id: localVersionLoader
  82. iniFilePath: settings.gamePath + "/" + configIni
  83. }
  84. WebVersionLoader {
  85. id: webVersionLoader
  86. onJsonChanged: tryCheckVersion()
  87. onError: {
  88. ui.showLog()
  89. ui.uiState = ui.uiStateRetry
  90. ui.log(qsTr("Failed to check version info. Check your Internet connection and try again."))
  91. }
  92. }
  93. DependenciesInstaller {
  94. id: dependenciesInstaller
  95. anchors.fill: parent
  96. onDependenciesInstallCanceled: {
  97. isPreDownloading = false
  98. ui.uiState = ui.uiStateInstall
  99. }
  100. onDependenciesInstallError: {
  101. ui.showLog()
  102. ui.log(qsTr("Failed to install dependencies"))
  103. revertUiState()
  104. }
  105. onDependenciesInstalled: startInstallUpdate()
  106. }
  107. ClientInstaller {
  108. id: clientInstaller
  109. anchors.fill: parent
  110. onPredownloadFinished: {
  111. isPreDownloading = false
  112. ui.hasPreDownload = false
  113. }
  114. onInstallError: {
  115. ui.showLog()
  116. switch (ui.uiState) {
  117. case ui.uiStateInstalling:
  118. ui.uiState = ui.uiStateInstall
  119. ui.log(qsTr("Error installing client"))
  120. return
  121. case ui.uiStateUpdating:
  122. ui.log(qsTr("Error updating client"))
  123. break
  124. }
  125. ui.log(qsTr(""))
  126. revertUiState()
  127. }
  128. onInstallCanceled: {
  129. switch (ui.uiState) {
  130. case ui.uiStateInstalling:
  131. ui.log(qsTr("Installation canceled"))
  132. return
  133. case ui.uiStateUpdating:
  134. ui.log(qsTr("Update canceled"))
  135. break
  136. }
  137. ui.log(qsTr(""))
  138. revertUiState()
  139. }
  140. onInstallFinished: {
  141. switch (ui.uiState) {
  142. case ui.uiStateInstalling:
  143. ui.log(qsTr("Installation completed!"))
  144. break
  145. case ui.uiStateUpdating:
  146. ui.log(qsTr("Update completed!"))
  147. break
  148. }
  149. ui.log(qsTr(""))
  150. checkFinished()
  151. isPreDownloading = false
  152. }
  153. onInstallSelectLanguagesCanceled: {
  154. revertUiState()
  155. }
  156. }
  157. ClientLauncher {
  158. id: clientLauncher
  159. onIsRunningChanged: {
  160. if (isRunning) {
  161. ui.uiState = ui.uiStateRunning
  162. } else {
  163. checkFinished()
  164. }
  165. }
  166. }
  167. SelfUpdate {
  168. id: selfUpdate
  169. anchors.fill: parent
  170. }
  171. FolderDialog {
  172. id: fileDialog
  173. title: qsTr(`Please choose 'Genshin Impact Game' directory`)
  174. folder: "file://" + (settings.gamePath || shortcuts.home)
  175. onAccepted: {
  176. let path = fileDialog.folder.toString().replace("file://", "")
  177. settings.gamePath = path
  178. ui.clearLog()
  179. if (!bootstrap()) {
  180. showInstallState()
  181. }
  182. }
  183. }
  184. function checkFinished() {
  185. ui.hasUpdate = availableVersion !== installedVersion
  186. if (clientInstaller.isPatchInstalled()) {
  187. ui.uiState = ui.uiStateStart
  188. } else {
  189. ui.uiState = ui.uiStatePatch
  190. }
  191. }
  192. function tryCheckVersion() {
  193. if (!webVersionLoader.json) {
  194. return
  195. }
  196. let game = webVersionLoader.json.data.game
  197. availableVersion = game.latest.version
  198. ui.log(qsTr("Web Version:") + " " + availableVersion)
  199. ui.hasUpdate = availableVersion !== installedVersion
  200. if (!ui.hasUpdate) {
  201. ui.log(qsTr("The game is up to date!"))
  202. checkFinished()
  203. let existingPreDownloadedVersion = clientInstaller.getPreDownloadedVersion()
  204. let preDownload = webVersionLoader.json.data.pre_download_game
  205. let hasPreDownload = preDownload && preDownload.latest && preDownload.latest.version || false
  206. if (hasPreDownload) {
  207. let latestPreDownloadVersion = preDownload.latest.version
  208. if (latestPreDownloadVersion !== existingPreDownloadedVersion) {
  209. preDownloadVersion = preDownload.latest.version
  210. ui.log(qsTr("Pre-download available") + " " + latestPreDownloadVersion)
  211. } else {
  212. ui.log(qsTr("The version") + " " + existingPreDownloadedVersion + " " + qsTr("is pre-downloaded"))
  213. hasPreDownload = false
  214. }
  215. }
  216. ui.hasPreDownload = hasPreDownload
  217. return
  218. }
  219. ui.log(qsTr("Update available"))
  220. switch (ui.uiState) {
  221. case ui.uiStateInstalling:
  222. clientInstaller.start()
  223. break
  224. case ui.uiStateLoading:
  225. checkFinished()
  226. break
  227. }
  228. }
  229. function tryLoadGameSettings() {
  230. if (!settings.gamePath) {
  231. console.debug("settings.gamePath is not set")
  232. return false
  233. }
  234. ui.log(qsTr("Game path: ") + settings.gamePath)
  235. const gameConfigPath = settings.gamePath + "/" + configIni
  236. if (!FileIO.isFileExists(gameConfigPath)) {
  237. console.debug(`${gameConfigPath} doesn't exist`)
  238. return false
  239. }
  240. localVersionLoader.load()
  241. return true
  242. }
  243. function checkForUpdates() {
  244. ui.log(qsTr("Installed Version:") + " " + installedVersion)
  245. ui.log(qsTr("Checking for updates..."))
  246. ui.uiState = ui.uiStateLoading
  247. webVersionLoader.loadVersion()
  248. }
  249. function detectLocation() {
  250. let foundPath
  251. for (let location of possibleLocations) {
  252. let foundLocations = FileIO.matchByWildcard(homePath + "/" + location + "/" + configIni)
  253. var foundLocation = foundLocations[0]
  254. if (foundLocation) {
  255. foundPath = foundLocation.substr(0, foundLocation.length - configIni.length)
  256. break
  257. }
  258. }
  259. return foundPath
  260. }
  261. function bootstrap() {
  262. if (tryLoadGameSettings()) {
  263. if (!localVersionLoader.version) {
  264. ui.log(qsTr("⚠ Wrong directory set as game directory."))
  265. ui.log(qsTr("⚠ Please choose 'Genshin Imact Game' dir, not the 'Genshin Impact'!"))
  266. ui.uiState = ui.uiStateWrongDir
  267. return true
  268. }
  269. checkForUpdates()
  270. return true
  271. }
  272. return false
  273. }
  274. function handleAppExit() {
  275. if (!clientInstaller.isActive) {
  276. return true
  277. }
  278. quitAfterCancel = true
  279. ui.confirmCancelInstall()
  280. return false
  281. }
  282. function revertUiState() {
  283. switch (ui.uiState) {
  284. case ui.uiStateInstalling:
  285. ui.uiState = ui.uiStateInstall
  286. break
  287. case ui.uiStateInstall:
  288. break
  289. default:
  290. checkFinished()
  291. }
  292. isPreDownloading = false
  293. }
  294. function startInstallUpdate() {
  295. if (!webVersionLoader.json && !webVersionLoader.isLoading) {
  296. webVersionLoader.loadVersion()
  297. return
  298. }
  299. clientInstaller.start()
  300. }
  301. function showInstallState() {
  302. // in case we have empty directory as game path
  303. ui.log(qsTr("Install to specified location or press 'Browse...' to browse for game dir"))
  304. ui.uiState = ui.uiStateInstall
  305. }
  306. Component.onCompleted: {
  307. ui.log(qsTr("Please refrain from sharing this project in public."))
  308. ui.log("")
  309. if (bootstrap()) {
  310. return
  311. }
  312. if (settings.gamePath) {
  313. showInstallState()
  314. return
  315. }
  316. let foundPath = detectLocation()
  317. if (foundPath) {
  318. ui.log(qsTr("Location detected:") + " " + foundPath)
  319. settings.gamePath = foundPath
  320. if (bootstrap()) {
  321. return
  322. }
  323. }
  324. if (!foundPath) {
  325. ui.log(qsTr("No previously installed game detected."))
  326. } else {
  327. ui.log(qsTr("Failed to load game config."))
  328. }
  329. ui.log(qsTr("Install to default location or press 'Browse...' to browse for game dir"))
  330. ui.log(qsTr("Location to install:"))
  331. ui.log(defaultLocation)
  332. settings.gamePath = defaultLocation
  333. ui.uiState = ui.uiStateInstall
  334. }
  335. }