add AO2XP auto-updater

This commit is contained in:
Headshotnoby 2021-06-21 22:57:55 -04:00
parent 4c5095ec9b
commit 6bff2b2b21
6 changed files with 291 additions and 7 deletions

View File

@ -1,9 +1,11 @@
import sys, thread, time, os, platform, __builtin__ import subprocess, sys, thread, time, os, platform, __builtin__
from os.path import exists, abspath from os.path import exists, abspath
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
import audio as AUDIO import audio as AUDIO
import ini
__builtin__.audio = AUDIO __builtin__.audio = AUDIO
del AUDIO del AUDIO
@ -62,6 +64,7 @@ class gamewindow(QtGui.QMainWindow):
self.settingsgui.showSettings() self.settingsgui.showSettings()
if not debugmode: if not debugmode:
# Vanilla downloader
force_downloader = len(sys.argv) > 1 and sys.argv[1] == "download" force_downloader = len(sys.argv) > 1 and sys.argv[1] == "download"
if force_downloader or (not exists("base/background") and not exists("base/characters") and not exists("base/sounds") and not exists("base/evidence")): if force_downloader or (not exists("base/background") and not exists("base/characters") and not exists("base/sounds") and not exists("base/evidence")):
jm = QtGui.QMessageBox.information(None, "Warning", "You seem to be missing the included Attorney Online content.\nWould you like to download them automatically?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) jm = QtGui.QMessageBox.information(None, "Warning", "You seem to be missing the included Attorney Online content.\nWould you like to download them automatically?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
@ -72,7 +75,18 @@ if not debugmode:
else: else:
os._exit(-3) os._exit(-3)
import gameview, mainmenu, options, ini # AO2XP update checker
can_update = ini.read_ini_bool("AO2XP.ini", "General", "install updates", True)
force_update = "forceupdate" in sys.argv[1:]
if can_update or force_update:
import updater
code = updater.checkForUpdates(force_update)
if code == 0:
subprocess.Popen(["./AO2XPupdat"])
os._exit(0)
import gameview, mainmenu, options
audio.init() audio.init()
shit = gamewindow() shit = gamewindow()

View File

@ -1,3 +1,4 @@
pyinstaller AO2XP.spec pyinstaller AO2XP.spec
pyinstaller AO2XP_console.spec pyinstaller AO2XP_console.spec
pyinstaller install_update.spec
move .\dist\* . move .\dist\* .

30
install_update.py Normal file
View File

@ -0,0 +1,30 @@
import zipfile, tarfile, platform, os, time, shutil
ext = {
"Windows": "zip",
"Darwin": "zip",
"Linux": "gz"
}
def extractzip(): # Mac
archive = zipfile.ZipFile("update.zip")
if platform.system() == "Darwin": shutil.rmtree("AO2XP.app", ignore_errors=True) # delete the old app package.
archive.extractall() # extract the new version
def extractgz(): # Linux
archive = tarfile.open("update.tar.gz")
archive.extractall()
if os.path.exists("update." + ext[platform.system()]):
print "Waiting 3 seconds for AO2XP to close..."
time.sleep(3)
print "Extracting update." + ext[platform.system()] + "..."
globals()["extract" + ext[platform.system()]]() # call the extract function according to OS
print "Done!"
os.remove("update." + ext[platform.system()])
else:
print "This program will be automatically run by AO2XP to apply updates.\nYou do not need to run this yourself."
raw_input("Press enter to exit.\n")

34
install_update.spec Normal file
View File

@ -0,0 +1,34 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['install_update.py'],
pathex=['C:\\Users\\Public\\1.7.5 Cut Content Patch\\Client'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='AO2XPupdat',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
manifest=None,
console=True )

View File

@ -1,6 +1,7 @@
from ConfigParser import ConfigParser from ConfigParser import ConfigParser
from os.path import exists from os.path import exists
from os import listdir from os import listdir, _exit
import subprocess
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
@ -46,9 +47,9 @@ class Settings(QtGui.QDialog):
cancelbtn.clicked.connect(self.onCancelClicked) cancelbtn.clicked.connect(self.onCancelClicked)
separators = [] separators = []
for i in range(4): for i in range(5):
separator = QtGui.QFrame() separator = QtGui.QFrame()
separator.setFixedSize(separator.size().width(), 16) separator.setFixedSize(separator.size().width(), 12)
separators.append(separator) separators.append(separator)
###### General tab ###### ###### General tab ######
@ -93,6 +94,14 @@ class Settings(QtGui.QDialog):
currtheme_layout.addWidget(currtheme_label) currtheme_layout.addWidget(currtheme_label)
currtheme_layout.addWidget(self.currtheme) currtheme_layout.addWidget(self.currtheme)
update_layout = QtGui.QHBoxLayout()
self.check_updates = QtGui.QCheckBox("Check for AO2XP updates on startup")
self.check_updates_btn = QtGui.QPushButton()
self.check_updates_btn.setText("Check now...")
self.check_updates_btn.clicked.connect(self.onUpdateClicked)
update_layout.addWidget(self.check_updates)
update_layout.addWidget(self.check_updates_btn)
savechangeswarn = QtGui.QLabel() savechangeswarn = QtGui.QLabel()
savechangeswarn.setText("* Change takes effect upon restarting the client") savechangeswarn.setText("* Change takes effect upon restarting the client")
@ -106,6 +115,8 @@ class Settings(QtGui.QDialog):
general_layout.addLayout(allowdownload_layout) general_layout.addLayout(allowdownload_layout)
general_layout.addWidget(separators[2]) general_layout.addWidget(separators[2])
general_layout.addLayout(currtheme_layout) general_layout.addLayout(currtheme_layout)
general_layout.addWidget(separators[3])
general_layout.addLayout(update_layout)
general_layout.addWidget(savechangeswarn, 50, QtCore.Qt.AlignBottom) general_layout.addWidget(savechangeswarn, 50, QtCore.Qt.AlignBottom)
###### Audio tab ###### ###### Audio tab ######
@ -113,7 +124,7 @@ class Settings(QtGui.QDialog):
self.device_list = QtGui.QComboBox() self.device_list = QtGui.QComboBox()
audio_layout.setWidget(0, QtGui.QFormLayout.LabelRole, device_label) audio_layout.setWidget(0, QtGui.QFormLayout.LabelRole, device_label)
audio_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.device_list) audio_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.device_list)
audio_layout.setWidget(1, QtGui.QFormLayout.FieldRole, separators[3]) audio_layout.setWidget(1, QtGui.QFormLayout.FieldRole, separators[4])
volumelabel = QtGui.QLabel("Sound volume") volumelabel = QtGui.QLabel("Sound volume")
musiclabel = QtGui.QLabel("Music") musiclabel = QtGui.QLabel("Music")
@ -176,6 +187,7 @@ class Settings(QtGui.QDialog):
self.allowdownload_music.setChecked(ini.read_ini_bool(self.inifile, "General", "download music")) self.allowdownload_music.setChecked(ini.read_ini_bool(self.inifile, "General", "download music"))
self.allowdownload_evidence.setChecked(ini.read_ini_bool(self.inifile, "General", "download evidence")) self.allowdownload_evidence.setChecked(ini.read_ini_bool(self.inifile, "General", "download evidence"))
self.currtheme.setCurrentIndex(self.themes.index(ini.read_ini(self.inifile, "General", "theme", "default"))) self.currtheme.setCurrentIndex(self.themes.index(ini.read_ini(self.inifile, "General", "theme", "default")))
self.check_updates.setChecked(ini.read_ini_bool(self.inifile, "General", "install updates", True))
self.device_list.setCurrentIndex(ini.read_ini_int(self.inifile, "Audio", "device", audio.getcurrdevice())) self.device_list.setCurrentIndex(ini.read_ini_int(self.inifile, "Audio", "device", audio.getcurrdevice()))
self.musicslider.setValue(ini.read_ini_int(self.inifile, "Audio", "Music volume", 100)) self.musicslider.setValue(ini.read_ini_int(self.inifile, "Audio", "Music volume", 100))
@ -190,6 +202,7 @@ class Settings(QtGui.QDialog):
self.allowdownload_music.setChecked(True) self.allowdownload_music.setChecked(True)
self.allowdownload_evidence.setChecked(True) self.allowdownload_evidence.setChecked(True)
self.currtheme.setCurrentIndex(self.themes.index("default")) self.currtheme.setCurrentIndex(self.themes.index("default"))
self.check_updates.setChecked(True)
self.device_list.setCurrentIndex(audio.getcurrdevice()) self.device_list.setCurrentIndex(audio.getcurrdevice())
self.musicslider.setValue(100) self.musicslider.setValue(100)
@ -223,6 +236,7 @@ class Settings(QtGui.QDialog):
self.inifile.set("General", "download music", self.allowdownload_music.isChecked()) self.inifile.set("General", "download music", self.allowdownload_music.isChecked())
self.inifile.set("General", "download evidence", self.allowdownload_evidence.isChecked()) self.inifile.set("General", "download evidence", self.allowdownload_evidence.isChecked())
self.inifile.set("General", "theme", self.currtheme.currentText()) self.inifile.set("General", "theme", self.currtheme.currentText())
self.inifile.set("General", "install updates", self.check_updates.isChecked())
self.inifile.set("Audio", "device", self.device_list.currentIndex()) self.inifile.set("Audio", "device", self.device_list.currentIndex())
self.inifile.set("Audio", "Music volume", self.musicslider.value()) self.inifile.set("Audio", "Music volume", self.musicslider.value())
@ -238,3 +252,10 @@ class Settings(QtGui.QDialog):
def onCancelClicked(self): def onCancelClicked(self):
self.hide() self.hide()
def onUpdateClicked(self):
import updater
code = updater.checkForUpdates()
if code == 0:
subprocess.Popen(["./AO2XPupdat"])
_exit(0)

184
updater.py Normal file
View File

@ -0,0 +1,184 @@
import json, urllib, sys, requests, time, os, platform
from PyQt4 import QtGui, QtCore
from game_version import *
returncode = -1
assetfile = {
"Windows": "AO2XP-Windows.zip",
"Darwin": "AO2XP-macOS.zip",
"Linux": "AO2XP-Linux.tar.gz"
}
def checkForUpdates(force=False):
update_dialog = QtGui.QProgressDialog()
update_dialog.setWindowTitle("AO2XP updater")
update_dialog.setAutoClose(False)
update_dialog.setAutoReset(False)
update_dialog.setLabelText("Checking for client updates...")
update_dialog.resize(512, 96)
update_dialog.setMinimum(0)
update_dialog.setMaximum(0)
update_dialog.setValue(0)
thr = updateThread(update_dialog, force)
def setProgressValue(value):
update_dialog.setValue(value)
def setLabelText(msg):
update_dialog.setLabelText(msg)
def showMessageBox(icon, title, msg):
getattr(QtGui.QMessageBox, str(icon))(None, title, msg)
def updateAvailableCall(name, version, body):
updateWindow = updateAvailableWidget(name, version, body)
result = updateWindow.show()
thr.waitConfirm = 1 if result else -1
if not result:
update_dialog.close()
returncode = -2
else:
update_dialog.setMinimum(0)
update_dialog.setMaximum(100)
thr.progressValue.connect(setProgressValue)
thr.labelText.connect(setLabelText)
thr.showMessageBox.connect(showMessageBox)
thr.updateAvailable.connect(updateAvailableCall)
thr.finished.connect(update_dialog.close)
thr.start()
update_dialog.exec_()
if not update_dialog.wasCanceled() and thr.isRunning(): thr.wait()
print "update return code:", returncode
return returncode
class updateAvailableWidget(QtGui.QDialog):
def __init__(self, title, version, body):
super(self.__class__, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.layout.setAlignment(QtCore.Qt.AlignTop)
self.available = QtGui.QLabel(text="Changelog for update \"%s\":" % title)
self.changelog = QtGui.QTextEdit()
self.confirmtext = QtGui.QLabel(text="Do you want to download and install the update?")
self.yesnolayout = QtGui.QHBoxLayout()
self.yes = QtGui.QPushButton(text="Yes")
self.no = QtGui.QPushButton(text="No")
self.setModal(True)
self.setWindowTitle("Update %s available" % version)
self.changelog.setText(body)
self.changelog.setReadOnly(True)
self.yes.clicked.connect(self.confirm)
self.no.clicked.connect(self.cancel)
self.yesnolayout.addWidget(self.yes)
self.yesnolayout.addWidget(self.no)
self.layout.addWidget(self.available)
self.layout.addWidget(self.changelog)
self.layout.addWidget(self.confirmtext)
self.layout.addLayout(self.yesnolayout, QtCore.Qt.AlignBottom)
self.result = False
self.resize(640, self.sizeHint().height())
def confirm(self):
self.close()
self.result = True
def cancel(self):
self.close()
self.result = False
def show(self):
super(self.__class__, self).show()
self.exec_()
return self.result
class updateThread(QtCore.QThread):
progressValue = QtCore.pyqtSignal(int)
labelText = QtCore.pyqtSignal(str)
showMessageBox = QtCore.pyqtSignal(str, str, str)
updateAvailable = QtCore.pyqtSignal(str, str, str)
finished = QtCore.pyqtSignal()
waitConfirm = 0
def __init__(self, nouis, force):
super(self.__class__, self).__init__()
self.jm = nouis
self.force = force
def run(self):
global returncode
try:
manifest = json.load(urllib.urlopen("http://api.github.com/repos/headshot2017/AO2XP/releases"))
except:
self.showMessageBox.emit("critical", "Error", "Failed to check for updates.\nPlease check your internet connection.")
self.finished.emit()
return
if self.jm.wasCanceled():
return
if GAME_VERSION != manifest[0]["tag_name"] or self.force: # update available
self.updateAvailable.emit(manifest[0]["name"], manifest[0]["tag_name"], manifest[0]["body"])
self.waitConfirm = 0
while self.waitConfirm == 0: pass
if self.waitConfirm > 0:
resume_bytes = 0
filename = assetfile[platform.system()]
link = ""
for asset in manifest[0]["assets"]:
if asset["name"] == filename:
link = asset["browser_download_url"]
if not link:
self.showMessageBox.emit("critical", "Error", "Release %s is missing the '%s' file.\nCannot continue updating." % (manifest[0]["tag_name"], filename))
self.finished.emit()
return
updatezip = "update" + os.path.splitext(filename)[1]
if not os.path.exists(updatezip):
downloadfile = open(updatezip, "wb")
else:
existing_data = open(updatezip, "rb").read()
downloadfile = open(updatezip, "ab")
resume_bytes = len(existing_data)
print resume_bytes
del existing_data
self.labelText.emit("Downloading '%s' for update '%s'..." % (filename, manifest[0]["name"]))
dl = resume_bytes
speed = 0.0
start = time.clock()
calcspeed_time = time.time()
zip = requests.get(link, stream=True, headers={"Range": "bytes=%d-" % resume_bytes})
length = resume_bytes + int(zip.headers.get("content-length"))
for noby in zip.iter_content(chunk_size=4096):
if not self.jm.isVisible():
downloadfile.close()
return
downloadfile.write(noby)
dl += len(noby)
percent = 100 * dl / length
if percent != self.jm.value():
self.progressValue.emit(percent)
self.labelText.emit("Downloading '%s' for update '%s'... %.1f KB/s" % (filename, manifest[0]["name"], speed))
if (time.time() - calcspeed_time) >= 0.5:
calcspeed_time = time.time()
speed = ((dl-resume_bytes)/(time.clock() - start)) / 1024.
self.labelText.emit("Downloading '%s' for update '%s'... %.1f KB/s" % (filename, manifest[0]["name"], speed))
print "downloaded update"
downloadfile.close()
returncode = 0
else:
returncode = -2
self.finished.emit()