402 lines
16 KiB
Python
402 lines
16 KiB
Python
import sys
|
|
import os
|
|
import time
|
|
import string
|
|
import bz2
|
|
import random
|
|
from direct.showbase.MessengerGlobal import *
|
|
from direct.showbase.DirectObject import DirectObject
|
|
from direct.showbase.EventManagerGlobal import *
|
|
from direct.task.TaskManagerGlobal import *
|
|
from direct.task.Task import Task
|
|
from direct.directnotify.DirectNotifyGlobal import *
|
|
from panda3d.core import *
|
|
from otp.launcher.LauncherBase import LauncherBase
|
|
from toontown.toonbase import TTLocalizer
|
|
|
|
class QuickLauncher(LauncherBase):
|
|
GameName = 'Toontown'
|
|
ArgCount = 3
|
|
LauncherPhases = [1,
|
|
2,
|
|
3,
|
|
3.5,
|
|
4,
|
|
5,
|
|
5.5,
|
|
6,
|
|
7,
|
|
8,
|
|
9,
|
|
10,
|
|
11,
|
|
12,
|
|
13]
|
|
TmpOverallMap = [0.01,
|
|
0.01,
|
|
0.23,
|
|
0.15,
|
|
0.12,
|
|
0.17,
|
|
0.08,
|
|
0.07,
|
|
0.05,
|
|
0.05,
|
|
0.017,
|
|
0.011,
|
|
0.01,
|
|
0.012,
|
|
0.01]
|
|
ForegroundSleepTime = 0.001
|
|
Localizer = TTLocalizer
|
|
DecompressMultifiles = True
|
|
launcherFileDbFilename = 'patcher.ver?%s' % random.randint(1, 1000000000)
|
|
CompressionExt = 'bz2'
|
|
PatchExt = 'pch'
|
|
|
|
def __init__(self):
|
|
print 'Running: ToontownQuickLauncher'
|
|
self.toontownBlueKey = 'TOONTOWN_BLUE'
|
|
self.launcherMessageKey = 'LAUNCHER_MESSAGE'
|
|
self.game1DoneKey = 'GAME1_DONE'
|
|
self.game2DoneKey = 'GAME2_DONE'
|
|
self.tutorialCompleteKey = 'TUTORIAL_DONE'
|
|
LauncherBase.__init__(self)
|
|
self.useTTSpecificLogin = config.GetBool('tt-specific-login', 0)
|
|
if self.useTTSpecificLogin:
|
|
self.toontownPlayTokenKey = 'LOGIN_TOKEN'
|
|
else:
|
|
self.toontownPlayTokenKey = 'PLAYTOKEN'
|
|
print 'useTTSpecificLogin=%s' % self.useTTSpecificLogin
|
|
self.contentDir = '/'
|
|
self.serverDbFileHash = HashVal()
|
|
self.launcherFileDbHash = HashVal()
|
|
self.DECREASE_BANDWIDTH = 0
|
|
self.httpChannel.setDownloadThrottle(0)
|
|
self.webAcctParams = 'WEB_ACCT_PARAMS'
|
|
self.parseWebAcctParams()
|
|
self.showPhase = -1
|
|
self.maybeStartGame()
|
|
self.mainLoop()
|
|
|
|
def addDownloadVersion(self, serverFilePath):
|
|
url = URLSpec(self.downloadServer)
|
|
origPath = url.getPath()
|
|
if origPath[-1] == '/':
|
|
url.setPath('%s%s' % (origPath, serverFilePath))
|
|
else:
|
|
url.setPath('%s/%s' % (origPath, serverFilePath))
|
|
return url
|
|
|
|
def downloadLauncherFileDbDone(self):
|
|
settings = {}
|
|
for line in self.ramfile.readlines():
|
|
if line.find('=') >= 0:
|
|
key, value = line.strip().split('=')
|
|
settings[key] = value
|
|
|
|
self.requiredInstallFiles = []
|
|
if sys.platform == 'win32':
|
|
fileList = settings['REQUIRED_INSTALL_FILES']
|
|
elif sys.platform == 'darwin':
|
|
fileList = settings['REQUIRED_INSTALL_FILES_OSX']
|
|
else:
|
|
self.notify.warning('Unknown sys.platform: %s' % sys.platform)
|
|
fileList = settings['REQUIRED_INSTALL_FILES']
|
|
for fileDesc in fileList.split():
|
|
fileName, flag = fileDesc.split(':')
|
|
directions = BitMask32(flag)
|
|
extract = directions.getBit(0)
|
|
required = directions.getBit(1)
|
|
optionalDownload = directions.getBit(2)
|
|
self.notify.info('fileName: %s, flag:=%s directions=%s, extract=%s required=%s optDownload=%s' % (fileName,
|
|
flag,
|
|
directions,
|
|
extract,
|
|
required,
|
|
optionalDownload))
|
|
if required:
|
|
self.requiredInstallFiles.append(fileName)
|
|
|
|
self.notify.info('requiredInstallFiles: %s' % self.requiredInstallFiles)
|
|
self.mfDetails = {}
|
|
for mfName in self.requiredInstallFiles:
|
|
currentVer = settings['FILE_%s.current' % mfName]
|
|
details = settings['FILE_%s.%s' % (mfName, currentVer)]
|
|
size, hash = details.split()
|
|
self.mfDetails[mfName] = (currentVer, int(size), hash)
|
|
self.notify.info('mfDetails[%s] = %s' % (mfName, self.mfDetails[mfName]))
|
|
|
|
self.resumeInstall()
|
|
|
|
def resumeMultifileDownload(self):
|
|
curVer, expectedSize, expectedMd5 = self.mfDetails[self.currentMfname]
|
|
localFilename = Filename(self.topDir, Filename('_%s.%s.%s' % (self.currentMfname, curVer, self.CompressionExt)))
|
|
serverFilename = '%s%s.%s.%s' % (self.contentDir,
|
|
self.currentMfname,
|
|
curVer,
|
|
self.CompressionExt)
|
|
if localFilename.exists():
|
|
fileSize = localFilename.getFileSize()
|
|
self.notify.info('Previous partial download exists for: %s size=%s' % (localFilename.cStr(), fileSize))
|
|
self.downloadMultifile(serverFilename, localFilename, self.currentMfname, self.downloadMultifileDone, 0, fileSize, self.downloadMultifileWriteToDisk)
|
|
else:
|
|
self.downloadMultifile(serverFilename, localFilename, self.currentMfname, self.downloadMultifileDone, 0, 0, self.downloadMultifileWriteToDisk)
|
|
|
|
def resumeInstall(self):
|
|
for self.currentPhaseIndex in xrange(len(self.LauncherPhases)):
|
|
self.currentPhase = self.LauncherPhases[self.currentPhaseIndex]
|
|
self.currentPhaseName = self.Localizer.LauncherPhaseNames[self.currentPhase]
|
|
self.currentMfname = 'phase_%s.mf' % self.currentPhase
|
|
if sys.platform == 'darwin' and (self.currentMfname == 'phase_1.mf' or self.currentMfname == 'phase_2.mf'):
|
|
self.currentMfname = 'phase_%sOSX.mf' % self.currentPhase
|
|
if self.currentMfname in self.requiredInstallFiles:
|
|
self.requiredInstallFiles.remove(self.currentMfname)
|
|
else:
|
|
self.notify.warning('avoiding crash ValueError: list.remove(x): x not in list')
|
|
curVer, expectedSize, expectedMd5 = self.mfDetails[self.currentMfname]
|
|
self.curPhaseFile = Filename(self.topDir, Filename(self.currentMfname))
|
|
self.notify.info('working on: %s' % self.curPhaseFile)
|
|
if self.curPhaseFile.exists():
|
|
self.notify.info('file exists')
|
|
fileSize = self.curPhaseFile.getFileSize()
|
|
clientMd5 = HashVal()
|
|
clientMd5.hashFile(self.curPhaseFile)
|
|
self.notify.info('clientMd5: %s expectedMd5: %s' % (clientMd5, expectedMd5))
|
|
self.notify.info('clientSize: %s expectedSize: %s' % (fileSize, expectedSize))
|
|
if fileSize == expectedSize and clientMd5.asHex() == expectedMd5:
|
|
self.notify.info('file is up to date')
|
|
self.finalizePhase()
|
|
continue
|
|
else:
|
|
self.notify.info('file is not valid')
|
|
self.resumeMultifileDownload()
|
|
return
|
|
else:
|
|
self.notify.info('file does not exist - start download')
|
|
self.resumeMultifileDownload()
|
|
return
|
|
|
|
if not self.requiredInstallFiles:
|
|
self.notify.info('ALL PHASES COMPLETE')
|
|
messenger.send('launcherAllPhasesComplete')
|
|
self.cleanup()
|
|
return
|
|
raise StandardError, 'Some phases not listed in LauncherPhases: %s' % self.requiredInstallFiles
|
|
|
|
def getDecompressMultifile(self, mfname):
|
|
if not self.DecompressMultifiles:
|
|
self.decompressMultifileDone()
|
|
elif 1:
|
|
self.notify.info('decompressMultifile: Decompressing multifile: ' + mfname)
|
|
curVer, expectedSize, expectedMd5 = self.mfDetails[self.currentMfname]
|
|
localFilename = Filename(self.topDir, Filename('_%s.%s.%s' % (mfname, curVer, self.CompressionExt)))
|
|
self.decompressMultifile(mfname, localFilename, self.decompressMultifileDone)
|
|
else:
|
|
self.notify.info('decompressMultifile: Multifile already decompressed: %s' % mfname)
|
|
self.decompressMultifileDone()
|
|
|
|
def decompressMultifile(self, mfname, localFilename, callback):
|
|
self.notify.info('decompressMultifile: request: ' + localFilename.cStr())
|
|
self.launcherMessage(self.Localizer.LauncherDecompressingFile % {'name': self.currentPhaseName,
|
|
'current': self.currentPhaseIndex,
|
|
'total': self.numPhases})
|
|
task = Task(self.decompressMultifileTask)
|
|
task.mfname = mfname
|
|
task.mfFilename = Filename(self.topDir, Filename('_' + task.mfname))
|
|
task.mfFile = open(task.mfFilename.toOsSpecific(), 'wb')
|
|
task.localFilename = localFilename
|
|
task.callback = callback
|
|
task.lastUpdate = 0
|
|
task.decompressor = bz2.BZ2File(localFilename.toOsSpecific(), 'rb')
|
|
taskMgr.add(task, 'launcher-decompressMultifile')
|
|
|
|
def decompressMultifileTask(self, task):
|
|
data = task.decompressor.read(8192)
|
|
if data:
|
|
task.mfFile.write(data)
|
|
now = self.getTime()
|
|
if now - task.lastUpdate >= self.UserUpdateDelay:
|
|
task.lastUpdate = now
|
|
curSize = task.mfFilename.getFileSize()
|
|
curVer, expectedSize, expectedMd5 = self.mfDetails[self.currentMfname]
|
|
progress = curSize / float(expectedSize)
|
|
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
|
|
'current': self.currentPhaseIndex,
|
|
'total': self.numPhases,
|
|
'percent': int(round(progress * 100))})
|
|
percentProgress = int(round(progress * self.decompressPercentage))
|
|
totalPercent = self.downloadPercentage + percentProgress
|
|
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
|
|
self.foregroundSleep()
|
|
return Task.cont
|
|
else:
|
|
task.mfFile.close()
|
|
task.decompressor.close()
|
|
unlinked = task.localFilename.unlink()
|
|
if not unlinked:
|
|
self.notify.warning('unlink failed on file: %s' % task.localFilename.cStr())
|
|
realMf = Filename(self.topDir, Filename(self.currentMfname))
|
|
renamed = task.mfFilename.renameTo(realMf)
|
|
if not renamed:
|
|
self.notify.warning('rename failed on file: %s' % task.mfFilename.cStr())
|
|
self.launcherMessage(self.Localizer.LauncherDecompressingPercent % {'name': self.currentPhaseName,
|
|
'current': self.currentPhaseIndex,
|
|
'total': self.numPhases,
|
|
'percent': 100})
|
|
totalPercent = self.downloadPercentage + self.decompressPercentage
|
|
self.setPercentPhaseComplete(self.currentPhase, totalPercent)
|
|
self.notify.info('decompressMultifileTask: Decompress multifile done: ' + task.localFilename.cStr())
|
|
if self.dldb:
|
|
self.dldb.setClientMultifileDecompressed(task.mfname)
|
|
del task.decompressor
|
|
task.callback()
|
|
del task.callback
|
|
return Task.done
|
|
|
|
def decompressMultifileDone(self):
|
|
self.finalizePhase()
|
|
self.notify.info('Done updating multifiles in phase: ' + `(self.currentPhase)`)
|
|
self.progressSoFar += int(round(self.phaseOverallMap[self.currentPhase] * 100))
|
|
self.notify.info('progress so far ' + `(self.progressSoFar)`)
|
|
messenger.send('phaseComplete-' + `(self.currentPhase)`)
|
|
self.resumeInstall()
|
|
|
|
def finalizePhase(self):
|
|
mfFilename = Filename(self.topDir, Filename(self.currentMfname))
|
|
self.MakeNTFSFilesGlobalWriteable(mfFilename)
|
|
vfs = VirtualFileSystem.getGlobalPtr()
|
|
vfs.mount(mfFilename, '.', VirtualFileSystem.MFReadOnly)
|
|
self.setPercentPhaseComplete(self.currentPhase, 100)
|
|
|
|
def getValue(self, key, default = None):
|
|
return os.environ.get(key, default)
|
|
|
|
def setValue(self, key, value):
|
|
os.environ[key] = str(value)
|
|
|
|
def getVerifyFiles(self):
|
|
return config.GetInt('launcher-verify', 0)
|
|
|
|
def getTestServerFlag(self):
|
|
return self.getValue('IS_TEST_SERVER', 0)
|
|
|
|
def getGameServer(self):
|
|
return self.getValue('GAME_SERVER', '')
|
|
|
|
def getLogFileName(self):
|
|
return 'toontown'
|
|
|
|
def parseWebAcctParams(self):
|
|
s = config.GetString('fake-web-acct-params', '')
|
|
if not s:
|
|
s = self.getRegistry(self.webAcctParams)
|
|
self.notify.info('webAcctParams = %s' % s)
|
|
self.setRegistry(self.webAcctParams, '')
|
|
l = s.split('&')
|
|
length = len(l)
|
|
dict = {}
|
|
for index in xrange(0, len(l)):
|
|
args = l[index].split('=')
|
|
if len(args) == 3:
|
|
name, value = args[-2:]
|
|
dict[name] = int(value)
|
|
elif len(args) == 2:
|
|
name, value = args
|
|
dict[name] = int(value)
|
|
|
|
if 'secretsNeedsParentPassword' in dict and 1:
|
|
self.secretNeedsParentPasswordKey = dict['secretsNeedsParentPassword']
|
|
self.notify.info('secretNeedsParentPassword = %d' % self.secretNeedsParentPasswordKey)
|
|
else:
|
|
self.notify.warning('no secretNeedsParentPassword token in webAcctParams')
|
|
|
|
if 'chatEligible' in dict:
|
|
self.chatEligibleKey = dict['chatEligible']
|
|
self.notify.info('chatEligibleKey = %d' % self.chatEligibleKey)
|
|
else:
|
|
self.notify.warning('no chatEligible token in webAcctParams')
|
|
|
|
def getBlue(self):
|
|
blue = self.getValue(self.toontownBlueKey)
|
|
self.setValue(self.toontownBlueKey, '')
|
|
if blue == 'NO BLUE':
|
|
blue = None
|
|
return blue
|
|
|
|
def getPlayToken(self):
|
|
playToken = self.getValue(self.toontownPlayTokenKey)
|
|
self.setValue(self.toontownPlayTokenKey, '')
|
|
if playToken == 'NO PLAYTOKEN':
|
|
playToken = None
|
|
return playToken
|
|
|
|
def setRegistry(self, name, value):
|
|
pass
|
|
|
|
def getRegistry(self, name, missingValue = None):
|
|
self.notify.info('getRegistry %s' % ((name, missingValue),))
|
|
self.notify.info('checking env' % os.environ)
|
|
if missingValue == None:
|
|
missingValue = ''
|
|
value = os.environ.get(name, missingValue)
|
|
try:
|
|
value = int(value)
|
|
except:
|
|
pass
|
|
|
|
return value
|
|
|
|
def getCDDownloadPath(self, origPath, serverFilePath):
|
|
return '%s/%s/CD_%d/%s' % (origPath,
|
|
self.ServerVersion,
|
|
self.fromCD,
|
|
serverFilePath)
|
|
|
|
def getDownloadPath(self, origPath, serverFilePath):
|
|
return '%s/%s' % (origPath, serverFilePath)
|
|
|
|
def hashIsValid(self, serverHash, hashStr):
|
|
return serverHash.setFromDec(hashStr)
|
|
|
|
def getAccountServer(self):
|
|
return self.getValue('ACCOUNT_SERVER', '')
|
|
|
|
def getGame2Done(self):
|
|
return True
|
|
|
|
def getNeedPwForSecretKey(self):
|
|
if self.useTTSpecificLogin:
|
|
self.notify.info('getNeedPwForSecretKey using tt-specific-login')
|
|
try:
|
|
if base.cr.chatChatCodeCreationRule == 'PARENT':
|
|
return True
|
|
else:
|
|
return False
|
|
except:
|
|
return True
|
|
|
|
else:
|
|
return self.secretNeedsParentPasswordKey
|
|
|
|
def getParentPasswordSet(self):
|
|
if self.useTTSpecificLogin:
|
|
self.notify.info('getParentPasswordSet using tt-specific-login')
|
|
try:
|
|
if base.cr.isPaid():
|
|
return True
|
|
else:
|
|
return False
|
|
except:
|
|
return False
|
|
|
|
else:
|
|
return self.chatEligibleKey
|
|
|
|
def canLeaveFirstIsland(self):
|
|
return self.getPhaseComplete(4)
|
|
|
|
def startGame(self):
|
|
self.newTaskManager()
|
|
eventMgr.restart()
|
|
from toontown.toonbase import ToontownStart
|