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 pandac.PandaModules 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 range(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 Exception('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: ' + repr((self.currentPhase))) self.progressSoFar += int(round(self.phaseOverallMap[self.currentPhase] * 100)) self.notify.info('progress so far ' + repr((self.progressSoFar))) messenger.send('phaseComplete-' + repr((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 range(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('self.VISTA = %s' % self.VISTA) 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