Installer.qml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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 string patchRepo: "https://notabug.org/Krock/dawn"
  18. property alias installedVersion: localVersionLoader.version
  19. property string availableVersion
  20. property string preDownloadVersion
  21. property bool isPreDownloading: false
  22. property bool quitAfterCancel: false
  23. property bool startupUpdateChecked: false
  24. anchors.fill: parent
  25. InstallerUi {
  26. id: ui
  27. anchors.fill: parent
  28. progressText: clientInstaller.progressText
  29. progress: clientInstaller.progressPercent / 100.0
  30. onBrowseClicked: {
  31. fileDialog.open()
  32. }
  33. onInstallClicked: {
  34. ui.uiState = ui.uiStateInstalling
  35. dependenciesInstaller.check()
  36. }
  37. onCancelConfirmed: {
  38. clientInstaller.cancel()
  39. ui.log(qsTr("Canceled"))
  40. ui.log(qsTr(""))
  41. revertUiState()
  42. if (quitAfterCancel) {
  43. quitAfterCancel = false
  44. window.close()
  45. }
  46. }
  47. onCancelRejected: {
  48. quitAfterCancel = false
  49. }
  50. onPreDownloadClicked: {
  51. isPreDownloading = true
  52. dependenciesInstaller.check()
  53. }
  54. onUpdateClicked: {
  55. ui.uiState = ui.uiStateUpdating
  56. dependenciesInstaller.check()
  57. }
  58. onRetryClicked: checkForUpdates()
  59. onHdiffPatchClicked: {
  60. ui.uiState = ui.uiStateUpdating
  61. clientInstaller.installHDiffPatches()
  62. }
  63. onRepatchClicked: clientInstaller.reapplyWinePatch()
  64. onStartClicked: {
  65. ui.uiState = ui.uiStateStarting
  66. clientLauncher.start()
  67. }
  68. onStopConfirmed: {
  69. ui.uiState = ui.uiStateStopping
  70. clientLauncher.stop()
  71. }
  72. onKillWineServerClicked: clientLauncher.killWineServer()
  73. onCheckSelfUpdateClicked: selfUpdate.check()
  74. onCheckIntegrityClicked: {
  75. ui.uiState = ui.uiStateChecking
  76. integrityCheck.checkAll()
  77. }
  78. onCheckStopClicked: {
  79. ui.log(qsTr("Integrity check canceled."))
  80. integrityCheck.stop()
  81. checkFinished()
  82. }
  83. }
  84. Settings {
  85. id: settings
  86. category: "game"
  87. property string gamePath: ""
  88. property alias isLogVisible: ui.isLogVisible
  89. property bool multiConnections: false
  90. property bool limitDownload: false
  91. property bool checkLauncherUpdatesAtStartup: true
  92. property string multiConnectionsValue: "4"
  93. property string limitDownloadValue: "1M"
  94. property bool hideBackground: false
  95. property bool firstRun: true
  96. }
  97. LocalVersionLoader {
  98. id: localVersionLoader
  99. iniFilePath: settings.gamePath + configIni
  100. }
  101. WebVersionLoader {
  102. id: webVersionLoader
  103. onJsonChanged: tryCheckVersion()
  104. onError: {
  105. ui.showLog()
  106. ui.uiState = ui.uiStateRetry
  107. ui.log(qsTr("Failed to check version info. Check your Internet connection and try again."))
  108. }
  109. }
  110. DependenciesInstaller {
  111. id: dependenciesInstaller
  112. anchors.fill: parent
  113. onDependenciesInstallCanceled: {
  114. isPreDownloading = false
  115. ui.uiState = ui.uiStateInstall
  116. }
  117. onDependenciesInstallError: {
  118. ui.showLog()
  119. ui.log(qsTr("Failed to install dependencies"))
  120. revertUiState()
  121. }
  122. onDependenciesInstalled: startInstallUpdate()
  123. }
  124. ClientInstaller {
  125. id: clientInstaller
  126. anchors.fill: parent
  127. onPredownloadFinished: {
  128. isPreDownloading = false
  129. ui.hasPreDownload = false
  130. }
  131. onInstallError: {
  132. ui.showLog()
  133. switch (ui.uiState) {
  134. case ui.uiStateInstalling:
  135. ui.uiState = ui.uiStateInstall
  136. ui.log(qsTr("Error installing client"))
  137. return
  138. case ui.uiStateUpdating:
  139. ui.log(qsTr("Error updating client"))
  140. break
  141. case ui.uiStatePatch:
  142. ui.log(qsTr("Error patching client"))
  143. break
  144. }
  145. ui.log(qsTr(""))
  146. revertUiState()
  147. }
  148. onInstallCanceled: {
  149. switch (ui.uiState) {
  150. case ui.uiStateInstalling:
  151. ui.log(qsTr("Installation canceled"))
  152. return
  153. case ui.uiStateUpdating:
  154. ui.log(qsTr("Update canceled"))
  155. break
  156. }
  157. ui.log(qsTr(""))
  158. revertUiState()
  159. }
  160. onInstallFinished: {
  161. switch (ui.uiState) {
  162. case ui.uiStateInstalling:
  163. ui.log(qsTr("Installation completed!"))
  164. break
  165. case ui.uiStateUpdating:
  166. ui.log(qsTr("Update completed!"))
  167. break
  168. }
  169. ui.log(qsTr(""))
  170. checkFinished()
  171. isPreDownloading = false
  172. }
  173. onInstallSelectLanguagesCanceled: {
  174. revertUiState()
  175. }
  176. }
  177. IntegrityCheck {
  178. id: integrityCheck
  179. onProgressChanged: {
  180. if (progress.total) {
  181. clientInstaller.progressText = qsTr("Checking file ")
  182. + progress.value + qsTr(" of ") + progress.total
  183. } else {
  184. clientInstaller.progressText = qsTr("Checking files...")
  185. }
  186. clientInstaller.progressPercent = progress.value * 100.0 / progress.total
  187. }
  188. onFinished: checkFinished()
  189. }
  190. ClientLauncher {
  191. id: clientLauncher
  192. onIsRunningChanged: {
  193. if (isRunning) {
  194. ui.uiState = ui.uiStateRunning
  195. } else {
  196. checkFinished()
  197. }
  198. }
  199. }
  200. SelfUpdate {
  201. id: selfUpdate
  202. anchors.fill: parent
  203. Component.onCompleted: {
  204. if (settings.checkLauncherUpdatesAtStartup && !startupUpdateChecked) {
  205. startupUpdateChecked = true
  206. selfUpdate.check()
  207. }
  208. }
  209. }
  210. FolderDialog {
  211. id: fileDialog
  212. title: qsTr(`Please choose 'Genshin Impact Game' directory`)
  213. folder: "file://" + (settings.gamePath || shortcuts.home)
  214. onAccepted: {
  215. let path = fileDialog.folder.toString().replace("file://", "")
  216. if (!path.endsWith('/')) {
  217. path += '/'
  218. }
  219. settings.gamePath = path
  220. ui.clearLog()
  221. if (!bootstrap()) {
  222. showInstallState()
  223. }
  224. }
  225. }
  226. function checkFinished() {
  227. ui.hasUpdate = !!availableVersion && availableVersion !== installedVersion
  228. if (!clientInstaller.isHdiffPatchInstalled()) {
  229. ui.log(qsTr("HDiff patches aren't installed. Press Update to install them..."))
  230. ui.uiState = ui.uiStateHdiffPatch
  231. } else if (!clientInstaller.isWinePatchInstalled()) {
  232. ui.uiState = ui.uiStatePatch
  233. } else {
  234. ui.uiState = ui.uiStateStart
  235. }
  236. }
  237. function tryCheckVersion() {
  238. if (!webVersionLoader.json) {
  239. return
  240. }
  241. let game = webVersionLoader.json.data.game
  242. availableVersion = game.latest.version
  243. ui.log(qsTr("Web Version:") + " " + availableVersion)
  244. ui.hasUpdate = availableVersion !== installedVersion
  245. if (!ui.hasUpdate) {
  246. ui.log(qsTr("The game is up to date!"))
  247. checkFinished()
  248. let existingPreDownloadedVersion = clientInstaller.getPreDownloadedVersion()
  249. let preDownload = webVersionLoader.json.data.pre_download_game
  250. let hasPreDownload = preDownload && preDownload.latest && preDownload.latest.version || false
  251. if (hasPreDownload) {
  252. let latestPreDownloadVersion = preDownload.latest.version
  253. if (latestPreDownloadVersion !== existingPreDownloadedVersion) {
  254. preDownloadVersion = preDownload.latest.version
  255. ui.log(qsTr("Pre-download available") + " " + latestPreDownloadVersion)
  256. } else {
  257. ui.log(qsTr("The version") + " " + existingPreDownloadedVersion + " " + qsTr("is pre-downloaded"))
  258. hasPreDownload = false
  259. }
  260. }
  261. ui.hasPreDownload = hasPreDownload
  262. return
  263. }
  264. ui.log(qsTr("Update available"))
  265. switch (ui.uiState) {
  266. case ui.uiStateInstalling:
  267. clientInstaller.start()
  268. break
  269. case ui.uiStateLoading:
  270. checkFinished()
  271. break
  272. }
  273. }
  274. function tryLoadGameSettings() {
  275. if (!settings.gamePath) {
  276. console.debug("settings.gamePath is not set")
  277. return false
  278. }
  279. ui.log(qsTr("Game path: ") + settings.gamePath)
  280. const gameConfigPath = settings.gamePath + configIni
  281. if (!FileIO.isFileExists(gameConfigPath)) {
  282. console.debug(`${gameConfigPath} doesn't exist`)
  283. return false
  284. }
  285. localVersionLoader.load()
  286. return true
  287. }
  288. function checkForUpdates() {
  289. ui.log(qsTr("Installed Version:") + " " + installedVersion)
  290. ui.log(qsTr("Checking for updates..."))
  291. ui.uiState = ui.uiStateLoading
  292. webVersionLoader.loadVersion()
  293. }
  294. function detectLocation() {
  295. let foundPath
  296. for (let location of possibleLocations) {
  297. let foundLocations = FileIO.matchByWildcard(homePath + "/" + location + "/" + configIni)
  298. var foundLocation = foundLocations[0]
  299. if (foundLocation) {
  300. foundPath = foundLocation.substr(0, foundLocation.length - configIni.length)
  301. break
  302. }
  303. }
  304. return foundPath
  305. }
  306. function bootstrap() {
  307. if (tryLoadGameSettings()) {
  308. if (!FileIO.isFileExists(settings.gamePath + "GenshinImpact.exe")) {
  309. ui.log(qsTr("⚠ Wrong directory set as game directory."))
  310. ui.log(qsTr("⚠ Please choose 'Genshin Imact Game' dir, not the 'Genshin Impact'!"))
  311. ui.uiState = ui.uiStateWrongDir
  312. return true
  313. }
  314. if (!localVersionLoader.version) {
  315. ui.log(qsTr("⚠ Can't read game_version from config.ini, update wouldn't be possible."))
  316. checkFinished()
  317. return true
  318. }
  319. checkForUpdates()
  320. integrityCheck.reload()
  321. return true
  322. }
  323. return false
  324. }
  325. function handleAppExit() {
  326. if (!clientInstaller.isActive) {
  327. return true
  328. }
  329. quitAfterCancel = true
  330. ui.confirmCancelInstall()
  331. return false
  332. }
  333. function revertUiState() {
  334. switch (ui.uiState) {
  335. case ui.uiStateInstalling:
  336. ui.uiState = ui.uiStateInstall
  337. break
  338. case ui.uiStateInstall:
  339. break
  340. default:
  341. checkFinished()
  342. }
  343. isPreDownloading = false
  344. }
  345. function startInstallUpdate() {
  346. if (!webVersionLoader.json && !webVersionLoader.isLoading) {
  347. webVersionLoader.loadVersion()
  348. return
  349. }
  350. clientInstaller.start()
  351. }
  352. function showInstallState() {
  353. // in case we have empty directory as game path
  354. ui.log(qsTr("Install to specified location or press 'Browse...' to browse for game dir"))
  355. ui.uiState = ui.uiStateInstall
  356. }
  357. Component.onCompleted: {
  358. ui.log(qsTr("Please refrain from sharing this project in public."))
  359. ui.log("")
  360. if (!settings.gamePath.endsWith('/')) {
  361. settings.gamePath += '/'
  362. }
  363. if (bootstrap()) {
  364. return
  365. }
  366. if (settings.gamePath) {
  367. showInstallState()
  368. return
  369. }
  370. let foundPath = detectLocation()
  371. if (foundPath) {
  372. ui.log(qsTr("Location detected:") + " " + foundPath)
  373. settings.gamePath = foundPath
  374. if (bootstrap()) {
  375. return
  376. }
  377. }
  378. if (!foundPath) {
  379. ui.log(qsTr("No previously installed game detected."))
  380. } else {
  381. ui.log(qsTr("Failed to load game config."))
  382. }
  383. ui.log(qsTr("Install to default location or press 'Browse...' to browse for game dir"))
  384. ui.log(qsTr("Location to install:"))
  385. ui.log(defaultLocation)
  386. settings.gamePath = defaultLocation
  387. ui.uiState = ui.uiStateInstall
  388. }
  389. }