def __init__(self):
     self.leeCommon = LeeCommon()
     self.leeFileIO = None
     self.translateDefaultDBPath = None
     self.translateMap = {}
     self.reSrcPathPattern = None
     self.reDstPathPattern = None
Exemple #2
0
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.stagingFiles = []
        self.patchesFiles = []
        self.backupFiles = []

        self.forceRemoveDirs = [
            'AI', 'AI_sakray', '_tmpEmblem', 'memo', 'Replay', 'SaveData',
            'Navigationdata', 'System'
        ]
        return
Exemple #3
0
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.leeParser = LeeStructParser()
        self.textureDirs = ['data/texture']
        self.modelDirs = ['data/model']
        self.spriteDirs = ['data/sprite']

        self.reportInfo = []
        self.reportStartTime = 0  # 用于记录当前检测项目的启动时间
        self.reportFileCount = 0  # 记录本次检测项目中, 丢失了资源的文件数量
        self.reportMissCount = 0  # 记录本次检测项目中, 累计丢失的资源文件数量
Exemple #4
0
def main():
    '''
    LeeClientAgent 的主入口函数
    '''
    # 显示欢迎信息
    LeeCommon().welcome()

    # 验证程序所在位置是否正确
    LeeCommon().verifyAgentLocation()

    # 进入主菜单
    LeeMenu().entrance()

    # Windows 上用户按一下再终止
    LeeCommon().pauseScreen()
Exemple #5
0
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.itemInfoDict = {}

        self.singleItemFormat = \
'''	[%s] = {
		unidentifiedDisplayName = "%s",
		unidentifiedResourceName = "%s",
		unidentifiedDescriptionName = {
%s
		},
		identifiedDisplayName = "%s",
		identifiedResourceName = "%s",
		identifiedDescriptionName = {
%s
		},
		slotCount = %s,
		ClassNum = %s
	}%s'''.replace('\n', '\r\n').replace('\r\r', '\r')

        self.itemInfoFormat = \
'''tbl = {
%s
}

main = function()
	for ItemID,DESC in pairs(tbl) do
		result, msg = AddItem(ItemID, DESC.unidentifiedDisplayName, DESC.unidentifiedResourceName, DESC.identifiedDisplayName, DESC.identifiedResourceName, DESC.slotCount, DESC.ClassNum)
		if not result then
			return false, msg
		end
		for k,v in pairs(DESC.unidentifiedDescriptionName) do
			result, msg = AddItemUnidentifiedDesc(ItemID, v)
			if not result then
				return false, msg
			end
		end
		for k,v in pairs(DESC.identifiedDescriptionName) do
			result, msg = AddItemIdentifiedDesc(ItemID, v)
			if not result then
				return false, msg
			end
		end
	end
	return true, "good"
end
'''.replace('\n', '\r\n').replace('\r\r', '\r')
Exemple #6
0
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.skillinfolistDict = {}
        self.SKID = []
        self.JOBID = []

        self.singleSkillinfoFormat = \
'''	[SKID.%s] = {
		"%s",
		SkillName = "%s",
		MaxLv = %s,
		SpAmount = { %s },
		bSeperateLv = %s,
		AttackRange = { %s }%s
	}%s'''.replace('\n', '\r\n').replace('\r\r', '\r')

        self._neeskillListFormat = ',\r\n\t\t_NeedSkillList = {\r\n%s\r\n\t\t}'
        self.neeskillListFormat = ',\r\n\t\tNeedSkillList = {\r\n%s\r\n\t\t}'
        self.jobDepentFormat = '\t\t\t[JOBID.%s] = {\r\n%s\r\n\t\t\t}%s'

        self.skillScaleListFormat = ',\r\n\t\tSkillScale = {\r\n%s\r\n\t\t}'
        self.skillScaleItemFormat = '\t\t\t[%s] = { x = %s, y = %s }%s'
        self.skillinfoListFormat = 'SKILL_INFO_LIST = {\r\n%s\r\n}\r\n'
Exemple #7
0
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.towninfoDict = {}

        self.singleMapFormat = '\t%s = {\r\n%s\r\n\t}%s'
        self.singleInfoFormat = '\t\t{ name = "%s", X = %d, Y = %d, TYPE = %d }%s'

        self.townInfoFormat = \
'''mapNPCInfoTable = {
%s
}

main = function()
	for mapName, info in pairs(mapNPCInfoTable) do
		for k, v in pairs(info) do
			result, msg = AddTownInfo(mapName, v.name, v.X, v.Y, v.TYPE)
			if not result == true then
				return false, msg
			end
		end
	end
	return true, "good"
end
'''.replace('\n', '\r\n').replace('\r\r', '\r')
Exemple #8
0
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.revertDefaultDBPath = None
     self.revertFiles = []
Exemple #9
0
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.sourceDirectory = None
     self.outputDirectory = None
     self.grfCLFilepath = None
     self.baseDirectory = None
Exemple #10
0
class LeeBaseRevert:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.revertDefaultDBPath = None
        self.revertFiles = []

    def clearRevert(self):
        self.revertFiles.clear()

    def loadRevert(self, revertDatabaseLoadpath=None):
        if revertDatabaseLoadpath is None:
            revertDatabaseLoadpath = self.revertDefaultDBPath
        revertDatabaseLoadpath = '%s/%s' % (self.leeCommon.utility(
            withmark=False), revertDatabaseLoadpath)
        if not self.leeCommon.isFileExists(revertDatabaseLoadpath):
            return False

        self.revertFiles.clear()
        revertContent = json.load(
            open(revertDatabaseLoadpath, 'r', encoding='utf-8'))
        self.revertFiles = revertContent['files']

        # 标准化处理一下反斜杠, 以便在跨平台备份恢复时可以顺利找到文件
        self.revertFiles = [
            item.replace('\\', os.path.sep) for item in self.revertFiles
        ]
        return True

    def rememberRevert(self, filepath):
        patchesDir = self.leeCommon.patches()
        self.revertFiles.append(
            os.path.relpath(filepath, patchesDir).replace('/', '\\'))

    def saveRevert(self, revertDatabaseSavepath=None):
        if revertDatabaseSavepath is None:
            revertDatabaseSavepath = self.revertDefaultDBPath
        revertDatabaseSavepath = '%s/%s' % (self.leeCommon.utility(
            withmark=False), revertDatabaseSavepath)
        if self.leeCommon.isFileExists(revertDatabaseSavepath):
            os.remove(revertDatabaseSavepath)
        os.makedirs(os.path.dirname(revertDatabaseSavepath), exist_ok=True)

        # 标准化处理一下反斜杠, 以便在跨平台备份恢复时可以顺利找到文件
        self.revertFiles = [
            item.replace('/', '\\') for item in self.revertFiles
        ]

        json.dump({'files': self.revertFiles},
                  open(revertDatabaseSavepath, 'w', encoding='utf-8'),
                  indent=4,
                  ensure_ascii=False)

    def canRevert(self, revertDatabaseLoadpath=None):
        if revertDatabaseLoadpath is None:
            revertDatabaseLoadpath = self.revertDefaultDBPath
        self.loadRevert(revertDatabaseLoadpath)
        return len(self.revertFiles) > 0

    def doRevert(self, revertDatabaseLoadpath=None):
        if revertDatabaseLoadpath is None:
            revertDatabaseLoadpath = self.revertDefaultDBPath
        self.loadRevert(revertDatabaseLoadpath)

        patchesDir = self.leeCommon.patches()

        for relpath in self.revertFiles:
            # replace('\\', os.path.sep) 标准化处理一下反斜杠
            fullpath = os.path.abspath(
                '%s/%s' % (patchesDir, relpath.replace('\\', os.path.sep)))
            if self.leeCommon.isFileExists(fullpath):
                os.remove(fullpath)

        if self.leeCommon.isFileExists(revertDatabaseLoadpath):
            os.remove(revertDatabaseLoadpath)

        self.leeCommon.removeEmptyDirectorys(patchesDir)
Exemple #11
0
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.configureFilepath = './LeeClientAgent.yml'
     self.configureData = None
     self.load()
Exemple #12
0
class LeeLua:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.sourceDirectory = None
        self.outputDirectory = None
        self.grfCLFilepath = None
        self.baseDirectory = None

    def __getOutputFilePath(self, sourcePath):
        relpath = os.path.relpath(sourcePath, self.sourceDirectory)
        outputpath = os.path.abspath(
            '%s%s%s' % (self.outputDirectory, os.path.sep, relpath))
        return outputpath

    def isTrulyLubFile(self, filepath):
        rawfile = open(filepath, 'rb')
        magicHeader = rawfile.read(6)
        rawfile.close()

        return (magicHeader[0] == 0x1B and magicHeader[1] == 0x4C
                and magicHeader[2] == 0x75 and magicHeader[3] == 0x61
                and magicHeader[4] == 0x51 and magicHeader[5] == 0x00)

    def __replaceFunctionStruct(self, line):
        matches = re.match(r'^(\w*?)\s=\sfunction\((.*?)\)(.*?)$', line)
        if not matches:
            return line
        return 'function {funname}({params})'.format(funname=matches.group(1),
                                                     params=matches.group(2))

    def __removeFunctionNote(self, line):
        if line.startswith('-- Function #'):
            return None
        return line

    def __lubAmendmentsByFile(self, filename, content):

        # 移除 GRF Editor Decompiler 标记
        markRemoveIndex = []
        for index, line in enumerate(content):
            if re.match('^-- Using GRF Editor Decompiler.*?$', line):
                content[index] = None
                markRemoveIndex.append(index + 1)
            if index in markRemoveIndex:
                content[index] = None

        # 若文件的末尾没有空行的话, 补加一个空行
        if not content:
            content.append('')
        if str(content[-1]).strip() != '' and not str(
                content[-1]).endswith('\n'):
            content.append('')

        # if filename.lower() == 'kaframovemapservicelist.lub':
        #     pass

        content = [x for x in content if x is not None]
        return content

    def lubAmendments(self, srcfilepath, dstfilepath):
        encoding = self.leeCommon.getEncodingByFile(srcfilepath)
        encoding = 'latin1' if encoding is None else encoding

        try:
            luafile = open(srcfilepath, 'r', encoding=encoding, newline='')
            content = luafile.readlines()
            luafile.close()

            # 按行进行处理
            content = [
                x.replace('\r\n', '\n').replace('\n', '') for x in content
            ]
            for index, line in enumerate(content):
                line = self.__replaceFunctionStruct(line)
                line = self.__removeFunctionNote(line)
                content[index] = line
            content = [x for x in content if x is not None]

            # 按文件进行处理
            content = self.__lubAmendmentsByFile(os.path.basename(srcfilepath),
                                                 content)

            savefile = open(dstfilepath, 'w', encoding=encoding, newline='')
            savefile.write('\r\n'.join(content))
            savefile.close()
            return True
        except Exception as _err:
            print('对 lub 文件进行处理时发生错误: %s' % srcfilepath)
            raise

    def amendmentsDir(self, lubSourceDirectory, lubOutputDirectory):
        self.sourceDirectory = lubSourceDirectory
        self.outputDirectory = lubOutputDirectory
        self.baseDirectory = os.path.dirname(self.sourceDirectory)

        for dirpath, _dirnames, filenames in os.walk(lubSourceDirectory):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                destpath = self.__getOutputFilePath(fullpath)
                os.makedirs(os.path.dirname(destpath), exist_ok=True)

                if fullpath.lower().endswith(
                        '.lub') and not self.isTrulyLubFile(fullpath):
                    self.lubAmendments(fullpath, destpath)
                    print('整理完毕: ' +
                          os.path.relpath(fullpath, self.baseDirectory))
                else:
                    shutil.copyfile(fullpath, destpath)
                    print('已经复制: ' +
                          os.path.relpath(fullpath, self.baseDirectory))

    def getLubEncoding(self, filepath):
        if not self.isTrulyLubFile(filepath):
            return True, self.leeCommon.getEncodingByFile(filepath)
        else:
            return False, None

    def decodeFile(self, lubSourcePath, lubOutputPath):
        grfCLProc = subprocess.Popen(
            '%s %s' %
            (self.grfCLFilepath, '-breakOnExceptions true -lub "%s" "%s"' %
             (lubSourcePath, lubOutputPath)),
            stdout=subprocess.PIPE,
            cwd=os.path.dirname(self.grfCLFilepath))
        grfCLProc.wait()

        # 确认结果并输出提示信息表示反编译结束
        if grfCLProc.returncode == 0 and self.leeCommon.isFileExists(
                lubOutputPath):
            print('已输出到: ' +
                  os.path.relpath(lubOutputPath, self.baseDirectory))
            self.lubAmendments(lubOutputPath, lubOutputPath)
            return True

        print('进行反编译时发生错误: ' +
              os.path.relpath(lubSourcePath, self.baseDirectory))
        return False

    def decodeDir(self, lubSourceDirectory, lubOutputDirectory):
        # 记录到成员变量里面
        self.sourceDirectory = lubSourceDirectory
        self.outputDirectory = lubOutputDirectory
        self.baseDirectory = os.path.dirname(self.sourceDirectory)

        # 确认操作系统平台
        if platform.system() != 'Windows':
            self.leeCommon.exitWithMessage('很抱歉, 此功能目前只能在 Windows 平台上运行.')

        # 确认 GrfCL 所需要的 .net framework 已安装
        if not self.leeCommon.isDotNetFrameworkInstalled('v3.5'):
            print('您必须先安装微软的 .NET Framework v3.5 框架.')
            self.leeCommon.exitWithMessage(
                '下载地址: https://www.microsoft.com/zh-CN/download/details.aspx?id=21'
            )

        # 确认 GrfCL 文件存在
        scriptDir = self.leeCommon.utility(withmark=False)
        self.grfCLFilepath = ('%s/Bin/GrfCL/GrfCL.exe' % scriptDir).replace(
            '/', os.path.sep)
        if not self.leeCommon.isFileExists(self.grfCLFilepath):
            self.leeCommon.exitWithMessage(
                '反编译 lub 文件所需的 GrfCL.exe 程序不存在, 无法执行反编译.')

        for dirpath, _dirnames, filenames in os.walk(lubSourceDirectory):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                destpath = self.__getOutputFilePath(fullpath)
                os.makedirs(os.path.dirname(destpath), exist_ok=True)

                print('')
                if fullpath.lower().endswith('.lub') and self.isTrulyLubFile(
                        fullpath):
                    print('需反编译: ' +
                          os.path.relpath(fullpath, self.baseDirectory))
                    if not self.decodeFile(fullpath, destpath):
                        print('失败复制: ' +
                              os.path.relpath(fullpath, self.baseDirectory))
                        shutil.copyfile(fullpath, destpath)
                else:
                    print('直接复制: ' +
                          os.path.relpath(fullpath, self.baseDirectory))
                    shutil.copyfile(fullpath, destpath)
Exemple #13
0
class LeeButtonRender:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.btnConfigure = {}
        self.fontPathMap = {}
        pygame.font.init()

    def autoCrop(self, image, backgroundColor=None):
        '''Intelligent automatic image cropping.
            This functions removes the usless "white" space around an image.
            If the image has an alpha (tranparency) channel, it will be used
            to choose what to crop.
            Otherwise, this function will try to find the most popular color
            on the edges of the image and consider this color "whitespace".
            (You can override this color with the backgroundColor parameter)
            Input:
                image (a PIL Image object): The image to crop.
                backgroundColor (3 integers tuple): eg. (0,0,255)
                    The color to consider "background to crop".
                    If the image is transparent, this parameters will be ignored.
                    If the image is not transparent and this parameter is not
                    provided, it will be automatically calculated.
            Output:
                a PIL Image object : The cropped image.
        '''
        def mostPopularEdgeColor(image):
            ''' Compute who's the most popular color on the edges of an image.
                (left,right,top,bottom)
                Input:
                    image: a PIL Image object
                Ouput:
                    The most popular color (A tuple of integers (R,G,B))
            '''
            im = image
            if im.mode != 'RGB':
                im = image.convert('RGB')
            # Get pixels from the edges of the image:
            width, height = im.size
            left = im.crop((0, 1, 1, height - 1))
            right = im.crop((width - 1, 1, width, height - 1))
            top = im.crop((0, 0, width, 1))
            bottom = im.crop((0, height - 1, width, height))
            pixels = left.tostring() + right.tostring() + top.tostring(
            ) + bottom.tostring()
            # Compute who's the most popular RGB triplet
            counts = {}
            for i in range(0, len(pixels), 3):
                RGB = pixels[i] + pixels[i + 1] + pixels[i + 2]
                if RGB in counts:
                    counts[RGB] += 1
                else:
                    counts[RGB] = 1
            # Get the colour which is the most popular:
            mostPopularColor = sorted([(count, rgba)
                                       for (rgba, count) in counts.items()],
                                      reverse=True)[0][1]
            return ord(mostPopularColor[0]), ord(mostPopularColor[1]), ord(
                mostPopularColor[2])

        bbox = None
        # If the image has an alpha (tranparency) layer, we use it to crop the image.
        # Otherwise, we look at the pixels around the image (top, left, bottom and right)
        # and use the most used color as the color to crop.
        # --- For transparent images -----------------------------------------------
        if 'A' in image.getbands(
        ):  # If the image has a transparency layer, use it.
            # This works for all modes which have transparency layer
            bbox = image.split()[list(image.getbands()).index('A')].getbbox()
        # --- For non-transparent images -------------------------------------------
        elif image.mode == 'RGB':
            if not backgroundColor:
                backgroundColor = mostPopularEdgeColor(image)
            # Crop a non-transparent image.
            # .getbbox() always crops the black color.
            # So we need to substract the "background" color from our image.
            bg = Image.new('RGB', image.size, backgroundColor)
            diff = ImageChops.difference(
                image, bg)  # Substract background color from image
            bbox = diff.getbbox(
            )  # Try to find the real bounding box of the image.
        else:
            raise NotImplementedError(
                "Sorry, this function is not implemented yet for images in mode '%s'."
                % image.mode)
        if bbox:
            image = image.crop(bbox)
        return image

    def createButtonImage(self, tplName, btnState, btnText, btnWidth):
        self.btnConfigure = self.__loadButtonConfigure(tplName)
        globalOffsetX = int(
            self.__getButtonConfigureValue(btnState,
                                           'globalOffset').split(',')[0])
        globalOffsetY = int(
            self.__getButtonConfigureValue(btnState,
                                           'globalOffset').split(',')[1])

        imgButton = self.createButtonBackgroundImage(tplName, btnState,
                                                     btnWidth)
        imgText = self.createTextImage(btnText, btnState)

        imgButtonWidth, imgButtonHeight = imgButton.size
        imgTextWidth, imgTextHeight = imgText.size

        pasteRect = (((imgButtonWidth - imgTextWidth) // 2) + globalOffsetX,
                     ((imgButtonHeight - imgTextHeight) // 2) + globalOffsetY,
                     (((imgButtonWidth - imgTextWidth) // 2) + imgTextWidth) +
                     globalOffsetX, (((imgButtonHeight - imgTextHeight) // 2) +
                                     imgTextHeight) + globalOffsetY)

        imgButton = imgButton.convert('RGB')
        imgButton.paste(imgText, pasteRect, mask=imgText)

        return imgButton

    def createButtonBmpFile(self, tplName, btnState, btnText, btnWidth,
                            savePath):
        btnImage = self.createButtonImage(tplName, btnState, btnText, btnWidth)
        btnImage.save(os.path.abspath(savePath), 'bmp')
        return True

    def createButtonBackgroundImage(self, tplName, btnState, btnWidth):
        pathLeftPiece = self.getButtonTemplatePath(tplName, btnState, 'left')
        pathMidPiece = self.getButtonTemplatePath(tplName, btnState, 'mid')
        pathRightPiece = self.getButtonTemplatePath(tplName, btnState, 'right')

        imgLeftPiece = Image.open(pathLeftPiece)
        imgMidPiece = Image.open(pathMidPiece)
        imgRightPiece = Image.open(pathRightPiece)

        imgLeftWidth, imgLeftHeight = imgLeftPiece.size
        imgMidWidth, imgMidHeight = imgMidPiece.size
        imgRightWidth, imgRightHeight = imgRightPiece.size

        if not imgLeftHeight == imgMidHeight == imgRightHeight:
            print('左中右三张切图文件的高度必须完全匹配')

        # 解析来开始拼接按钮的背景图片
        # 建立一个 btnWidth x imgLeftHeight 的图片对象, 底色用 RO 的透明色 FF40FF
        imgButton = Image.new('RGBA', (btnWidth, imgLeftHeight), '#FF40FF')

        # 将中间的图片填满除了左右两侧之外的中央的区域
        midSpace = btnWidth - imgLeftWidth - imgRightWidth
        repeatTime = 0
        while midSpace > 0:
            left = imgLeftWidth + (imgMidWidth * repeatTime)
            imgButton.paste(imgMidPiece,
                            (left, 0, left + imgMidWidth, imgMidHeight))
            repeatTime = repeatTime + 1
            midSpace = midSpace - imgMidWidth

        # 将左侧的图片填充到按钮背景的最左侧
        imgButton.paste(imgLeftPiece, (0, 0, imgLeftWidth, imgLeftHeight))

        # 将右侧的图片填充到按钮背景的最右侧
        imgButton.paste(
            imgRightPiece,
            (btnWidth - imgRightWidth, 0, btnWidth, imgRightHeight))

        # 尝试进行一些资源文件的释放, 但这里不会去释放 imgButton
        imgLeftPiece.close()
        imgMidPiece.close()
        imgRightPiece.close()

        return imgButton

    def createTextImage(self, btnText, btnState):
        fontName, fontSize = self.__getButtonFontInfomation()
        fontPath = self.getFontPath(fontName)
        foreFontColor = self.leeCommon.strHexToRgb(
            self.__getButtonConfigureValue(btnState, 'fontColor'))
        shadowFontColor = self.leeCommon.strHexToRgb(
            self.__getButtonConfigureValue(btnState, 'shadowColor'))
        shadowFontAlpha = int(
            self.__getButtonConfigureValue(btnState, 'shadowAlpha'))

        # 根据不同的阴影类型, 进行渲染
        if self.__getButtonConfigureValue(btnState, 'shadowMode') == 'offset':

            # 进行前端文字的渲染
            pygameForeFont = pygame.font.Font(fontPath, fontSize)
            pygameForeText = pygameForeFont.render(btnText, True,
                                                   foreFontColor)
            pyForeTextStor = pygame.image.tostring(pygameForeText, 'RGBA',
                                                   False)
            imgForeText = Image.frombytes('RGBA', pygameForeText.get_size(),
                                          pyForeTextStor)
            imgForeText = self.autoCrop(imgForeText)

            # 进行阴影字体的渲染
            pygameBackFont = pygame.font.Font(fontPath, fontSize)
            pygameBackText = pygameBackFont.render(btnText, True,
                                                   shadowFontColor)
            pyBackTextStor = pygame.image.tostring(pygameBackText, 'RGBA',
                                                   False)
            imgBackText = Image.frombytes('RGBA', pygameBackText.get_size(),
                                          pyBackTextStor)
            imgBackText = self.autoCrop(imgBackText)

            # 对阴影字体应用指定透明度
            _red, _green, _blue, alpha = imgBackText.split()
            alpha = alpha.point(lambda i: i > 0 and
                                (255 / 100) * shadowFontAlpha)
            imgBackText.putalpha(alpha)

            # 文字的阴影的偏移叠加处理过程
            shadowOffsetX = int(
                self.__getButtonConfigureValue(btnState,
                                               'shadowOffset').split(',')[0])
            shadowOffsetY = int(
                self.__getButtonConfigureValue(btnState,
                                               'shadowOffset').split(',')[1])
            boardWidth = imgForeText.size[0] + abs(shadowOffsetX)
            boardHeight = imgForeText.size[1] + abs(shadowOffsetY)
            foreOffsetX = 0 if self.leeCommon.isPositive(
                shadowOffsetX) else abs(shadowOffsetX)
            foreOffsetY = 0 if self.leeCommon.isPositive(
                shadowOffsetY) else abs(shadowOffsetY)
            shadowOffsetX = 0 if not self.leeCommon.isPositive(
                shadowOffsetX) else shadowOffsetX
            shadowOffsetY = 0 if not self.leeCommon.isPositive(
                shadowOffsetY) else shadowOffsetY

            imgMergeText = Image.new('RGBA', (boardWidth, boardHeight),
                                     (0, 0, 0, 0))
            imgMergeText.paste(imgBackText, (shadowOffsetX, shadowOffsetY))
            imgMergeText.paste(imgForeText, (foreOffsetX, foreOffsetY),
                               mask=imgForeText)

            return imgMergeText

        elif self.__getButtonConfigureValue(btnState,
                                            'shadowMode') == 'outline':

            outFont = pygame.font.Font(fontPath, fontSize + 2)
            imgSurface = pygame.Surface(outFont.size(btnText), pygame.SRCALPHA)
            innerFont = pygame.font.Font(fontPath, fontSize)
            outline = innerFont.render(btnText, 1, shadowFontColor)

            w, h = imgSurface.get_size()
            ww, hh = outline.get_size()
            cx = w / 2 - ww / 2
            cy = h / 2 - hh / 2

            for x in range(-1, 2):
                for y in range(-1, 2):
                    imgSurface.blit(outline, (x + cx, y + cy))

            imgSurface.blit(innerFont.render(btnText, 1, foreFontColor),
                            (cx, cy))
            imgSurfaceTextStor = pygame.image.tostring(imgSurface, 'RGBA',
                                                       False)
            imgFinalText = Image.frombytes('RGBA', imgSurface.get_size(),
                                           imgSurfaceTextStor)
            imgFinalText = self.autoCrop(imgFinalText)

            return imgFinalText

    def getButtonTemplatePath(self, tplName, btnState, piece):
        return os.path.abspath(
            '%s/Resources/Texture/Button/Style_%s/%s_%s.png' %
            (self.leeCommon.utility(withmark=False), tplName, btnState, piece))

    def getFontPath(self, fontFilename):
        if fontFilename in self.fontPathMap:
            return self.fontPathMap[fontFilename]

        fontOriginPath = os.path.abspath(
            '%s/Resources/Fonts/%s' %
            (self.leeCommon.utility(withmark=False), fontFilename))

        # 把字体文件复制到系统临时目录, 以便确保路径没有任何中文
        fontTempPath = tempfile.mktemp(prefix='leefont_', suffix='.ttc')
        shutil.copyfile(fontOriginPath, fontTempPath)

        # 记住临时字体文件的路径避免重复复制
        self.fontPathMap[fontFilename] = fontTempPath
        return fontTempPath

    def getImageSizeByFilepath(self, filepath):
        img = Image.open(filepath)
        imgsize = img.size
        img.close()
        return imgsize

    def __loadButtonConfigure(self, tplName):
        configurePath = (
            '%s/Resources/Texture/Button/Style_%s/configure.json' %
            (self.leeCommon.utility(withmark=False), tplName))
        return (json.load(open(configurePath, 'r'))
                if self.leeCommon.isFileExists(configurePath) else None)

    def __getButtonFontInfomation(self):
        if self.btnConfigure:
            return self.btnConfigure['fontName'], int(
                self.btnConfigure['fontSize'])
        else:
            self.leeCommon.exitWithMessage(
                '__getButtonFontInfomation: 无法加载字体的配置信息')
            return None, None

    def __getButtonConfigureValue(self, btnState, attrib):
        if self.btnConfigure:
            return self.btnConfigure[btnState][attrib]
        else:
            self.leeCommon.exitWithMessage(
                '__getButtonConfigureValue: 无法加载字体的配置信息')
            return None, None
Exemple #14
0
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.btnConfigure = {}
     self.fontPathMap = {}
     pygame.font.init()
Exemple #15
0
class LeeVerifier:
    '''
    此操作类用于验证客户端的文件是否完整
    '''
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.leeParser = LeeStructParser()
        self.textureDirs = ['data/texture']
        self.modelDirs = ['data/model']
        self.spriteDirs = ['data/sprite']

        self.reportInfo = []
        self.reportStartTime = 0  # 用于记录当前检测项目的启动时间
        self.reportFileCount = 0  # 记录本次检测项目中, 丢失了资源的文件数量
        self.reportMissCount = 0  # 记录本次检测项目中, 累计丢失的资源文件数量

    def __verifyGnd(self, gndfilepath, priorityDataDir=None):
        result, texturePathList = self.leeParser.parseGndFile(gndfilepath)
        if not result:
            return None, None

        missTexturePathList = []
        existsTexturePathList = []
        leeClientDir = self.leeCommon.client(withmark=False)

        for texturePath in texturePathList:
            if priorityDataDir:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/texture/%s' %
                    (leeClientDir, priorityDataDir, texturePath))
                # print(fullpath)
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    continue
            for textureDir in self.textureDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s' % (leeClientDir, textureDir, texturePath))
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    break
            else:
                # print(texturePath)
                missTexturePathList.append(fullpath)

        return existsTexturePathList, missTexturePathList

    def __verifyRsw(self, rswfilepath, priorityDataDir=None):
        result, modelPathList = self.leeParser.parseRswFile(rswfilepath)
        if not result:
            return None, None

        missModelPathList = []
        existsModelPathList = []
        leeClientDir = self.leeCommon.client(withmark=False)

        for modelPath in modelPathList:
            if priorityDataDir:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/model/%s' %
                    (leeClientDir, priorityDataDir, modelPath))
                # print(fullpath)
                if self.leeCommon.isFileExists(fullpath):
                    existsModelPathList.append(fullpath)
                    continue
            for modelDir in self.modelDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s' % (leeClientDir, modelDir, modelPath))
                if self.leeCommon.isFileExists(fullpath):
                    # print('existsModelPathList: %s' % modelPath)
                    existsModelPathList.append(fullpath)
                    break
            else:
                # print('missModelPathList: %s' % modelPath)
                missModelPathList.append(fullpath)

        return existsModelPathList, missModelPathList

    def __verifyRsm(self, rsmfilepath, priorityDataDir=None):
        result, texturePathList = self.leeParser.parseRsmFile(rsmfilepath)
        if not result:
            return None, None

        missTexturePathList = []
        existsTexturePathList = []
        leeClientDir = self.leeCommon.client(withmark=False)

        for texturePath in texturePathList:
            if priorityDataDir:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/texture/%s' %
                    (leeClientDir, priorityDataDir, texturePath))
                # print(fullpath)
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    continue
            for textureDir in self.textureDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s' % (leeClientDir, textureDir, texturePath))
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    break
            else:
                # print(texturePath)
                missTexturePathList.append(fullpath)

        return existsTexturePathList, missTexturePathList

    def __verifyStr(self, strfilepath, priorityDataDir=None):
        result, texturePathList = self.leeParser.parseStrFile(strfilepath)
        if not result:
            return None, None
        texturePathList = list(
            set(texturePathList))  # 文件名消重(str解析出来重复的图档文件名太多)

        missTexturePathList = []
        existsTexturePathList = []

        leeClientDir = self.leeCommon.client(withmark=False)
        leeClientCommonDataDir = '%s/data' % leeClientDir
        isPatchStrFile = leeClientCommonDataDir.lower(
        ) not in strfilepath.lower()

        dataPostion = strfilepath.lower().rfind('data/')
        strfileDirBaseonData = os.path.dirname(strfilepath[dataPostion:])

        if priorityDataDir is not None and priorityDataDir.lower().endswith(
                '/data'):
            priorityDataDir = priorityDataDir[:-len('/data')]

        for texturePath in texturePathList:
            if isPatchStrFile and priorityDataDir is not None:
                # leeClientDir + priorityDataDir + strfileDirBaseonData + 文件名
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s/%s' % (leeClientDir, priorityDataDir,
                                     strfileDirBaseonData, texturePath))
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    continue

                # leeClientDir + priorityDataDir + self.textureDirs + '/effect' + 文件名
                isFound = False
                for textureDir in self.textureDirs:
                    fullpath = self.leeCommon.normpath(
                        '%s/%s/%s/effect/%s' % (leeClientDir, priorityDataDir,
                                                textureDir, texturePath))
                    if self.leeCommon.isFileExists(fullpath):
                        existsTexturePathList.append(fullpath)
                        isFound = True
                        break
                if isFound:
                    continue

            # leeClientDir + strfileDirBaseonData + 文件名
            fullpath = self.leeCommon.normpath(
                '%s/%s/%s' % (leeClientDir, strfileDirBaseonData, texturePath))
            if self.leeCommon.isFileExists(fullpath):
                existsTexturePathList.append(fullpath)
                continue

            # leeClientDir + self.textureDirs + '/effect'
            isFound = False
            for textureDir in self.textureDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/effect/%s' %
                    (leeClientDir, textureDir, texturePath))
                if self.leeCommon.isFileExists(fullpath):
                    existsTexturePathList.append(fullpath)
                    isFound = True
                    break
            if not isFound:
                missTexturePathList.append(fullpath)

        return existsTexturePathList, missTexturePathList

    def __verifyIteminfo(self, iteminfofilepath, priorityDataDir=None):
        result, texturePathList, spritePathList = self.leeParser.parseIteminfo(
            iteminfofilepath)
        if not result:
            return None, None

        leeClientDir = self.leeCommon.client(withmark=False)

        missTexturePathList = []
        for texturePath in texturePathList:
            if priorityDataDir:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/texture/%s' %
                    (leeClientDir, priorityDataDir, texturePath))
                # print(fullpath)
                if self.leeCommon.isFileExists(fullpath):
                    continue
            for textureDir in self.textureDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s' % (leeClientDir, textureDir, texturePath))
                if self.leeCommon.isFileExists(fullpath):
                    break
            else:
                # print('missTexturePathList: %s' % texturePath)
                missTexturePathList.append(fullpath)

        missSpritePathList = []
        for spritePath in spritePathList:
            if priorityDataDir:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/sprite/%s' %
                    (leeClientDir, priorityDataDir, spritePath))
                # print(fullpath)
                if self.leeCommon.isFileExists(fullpath):
                    continue
            for spriteDir in self.spriteDirs:
                fullpath = self.leeCommon.normpath(
                    '%s/%s/%s' % (leeClientDir, spriteDir, spritePath))
                if self.leeCommon.isFileExists(fullpath):
                    break
            else:
                # print('missSpritePathList: %s' % spritePath)
                missSpritePathList.append(fullpath)

        return missTexturePathList, missSpritePathList

    def __resetReport(self):
        self.reportInfo.clear()

    def __appendReportMessage(self, mesType, message):
        if mesType.lower() == 'header':
            self.reportInfo.append(
                '=============================================')
            self.reportInfo.append(message)
            self.reportInfo.append(
                '=============================================')
            # 启动一个计时器和一个 __appendReportData 计数器
            self.reportFileCount = self.reportMissCount = 0
            self.reportStartTime = timeit.default_timer()
        elif mesType.lower() == 'footer':
            # 总结耗时以及写入一些统计信息
            spendTime = timeit.default_timer() - self.reportStartTime
            resourceInfo = '非常好, 此项目无任何文件缺失!' if self.reportFileCount == 0 else '有 %d 个文件共计缺失 %d 个资源' % (
                self.reportFileCount, self.reportMissCount)
            self.reportInfo.append('%s / 耗时: %.2f 秒' %
                                   (resourceInfo, spendTime))
            self.reportInfo.append(
                '=============================================')
            self.reportInfo.append('')
            self.reportInfo.append('')

    def __appendReportData(self, sourceFile, missFilesList):
        leeClientDir = self.leeCommon.client()
        relSourceFile = os.path.relpath(sourceFile, leeClientDir)

        missFileCount = 0
        for fileslist in missFilesList:
            if not fileslist or not fileslist['files']:
                continue
            missFileCount = missFileCount + len(fileslist['files'])

        if not missFileCount:
            return

        self.reportInfo.append('>>> %s (缺失 %d 个文件)' %
                               (relSourceFile, missFileCount))

        for fileslist in missFilesList:
            if not fileslist:
                continue
            for missFile in fileslist['files']:
                self.reportInfo.append(
                    '    缺失%s: %s' % (fileslist['name'],
                                      os.path.relpath(missFile, leeClientDir)))

        self.reportInfo.append('')
        self.reportFileCount = self.reportFileCount + 1
        self.reportMissCount = self.reportMissCount + missFileCount

    def __saveReport(self):
        reportTime = time.strftime("%Y%m%d_%H%M%S", time.localtime())
        savePath = '%s/Reports/VerifyRpt_%s.txt' % (self.leeCommon.utility(
            withmark=False), reportTime)
        savePath = self.leeCommon.normpath(savePath)
        os.makedirs(os.path.dirname(savePath), exist_ok=True)

        rptfile = open(savePath, 'w+', encoding='utf-8', newline='')
        rptfile.write('\r\n'.join(self.reportInfo))

        print('校验结果已保存到 : %s' %
              os.path.relpath(savePath, self.leeCommon.client()))

    def __getFilesInfo(self,
                       glob_or_re,
                       reWalkDir,
                       pattern,
                       baseDir_or_reGroupID,
                       baseDir_append=None):
        filesinfo = []

        if glob_or_re == 'glob':
            for filepath in glob.glob(pattern):
                datadir = baseDir_or_reGroupID
                if baseDir_append:
                    datadir = datadir + baseDir_append
                filesinfo.append({'datadir': datadir, 'filepath': filepath})
        elif glob_or_re == 're':
            pattern = self.leeCommon.normPattern(pattern)
            for dirpath, _dirnames, filenames in os.walk(reWalkDir):
                for filename in filenames:
                    fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                    matches = re.match(pattern, fullpath, re.I)
                    if not matches:
                        continue

                    datadir = None
                    if baseDir_or_reGroupID is not None:
                        datadir = matches.group(baseDir_or_reGroupID)
                    if datadir and baseDir_append:
                        datadir = datadir + baseDir_append

                    filesinfo.append({
                        'datadir': datadir,
                        'filepath': fullpath
                    })
        else:
            self.leeCommon.exitWithMessage('指定的 glob_or_re 的值无效, 程序终止')

        return filesinfo

    def __subVerifier(self,
                      filesinfo,
                      parsefunc,
                      subject,
                      returnPathListIndex=None,
                      reportMissInfo=None):

        if filesinfo is None:
            return None

        self.__appendReportMessage('header',
                                   '%s - 共 %d 个' % (subject, len(filesinfo)))
        print('正在%s - 共 %d 个' % (subject, len(filesinfo)))

        if filesinfo and not isinstance(filesinfo[0], dict):
            restructFilesinfo = []
            for filepath in filesinfo:
                restructFilesinfo.append({
                    'filepath': filepath,
                    'datadir': None
                })
            filesinfo = restructFilesinfo

        for fileinfo in filesinfo:
            filepath = fileinfo['filepath']
            datadir = fileinfo['datadir']
            parsefuncResult = parsefunc(filepath, datadir)

            if not reportMissInfo:
                continue

            needReportFilelist = []
            for missinfo in reportMissInfo:
                needReportFilelist.append({
                    'name':
                    missinfo['name'],
                    'files':
                    parsefuncResult[missinfo['resultIndex']]
                })

            self.__appendReportData(filepath, needReportFilelist)
        self.__appendReportMessage('footer', '')

        if returnPathListIndex is None:
            return None
        return parsefuncResult[returnPathListIndex]

    def __globalResourceVerifier(self):
        leeClientDir = self.leeCommon.client(withmark=False)

        # 校验公用目录中地图文件所需的图档文件
        # =====================================================================

        # 校验地图的 gnd 文件(纹理层)

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='glob',
            reWalkDir=None,
            pattern='%s/data/*.gnd' % leeClientDir,
            baseDir_or_reGroupID=None,
            baseDir_append=None),
                           parsefunc=self.__verifyGnd,
                           subject='校验通用资源目录中的 gnd 文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '地表贴图',
                               'resultIndex': 1
                           }])

        # 校验地图的 rsw 文件(模型层)

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='glob',
            reWalkDir=None,
            pattern='%s/data/*.rsw' % leeClientDir,
            baseDir_or_reGroupID=None,
            baseDir_append=None),
                           parsefunc=self.__verifyRsw,
                           subject='校验通用资源目录中的 rsw 文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': 'RSM模型文件',
                               'resultIndex': 1
                           }])

        # 校验地图中 rsm 模型文件的贴图

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir='%s/data' % leeClientDir,
            pattern=r'^.*?/data/.*?\.(rsm)',
            baseDir_or_reGroupID=None,
            baseDir_append=None),
                           parsefunc=self.__verifyRsm,
                           subject='校验通用资源目录中的 rsm 模型的贴图文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': 'RSM模型文件的贴图文件',
                               'resultIndex': 1
                           }])

        # 校验公用目录中动画效果索引文件 str 中所需的贴图
        # =====================================================================

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir='%s/data' % leeClientDir,
            pattern=r'^.*?/data/.*?\.(str)',
            baseDir_or_reGroupID=None,
            baseDir_append=None),
                           parsefunc=self.__verifyStr,
                           subject='校验通用资源目录中 str 文档所需的贴图文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '动画索引贴图文件',
                               'resultIndex': 1
                           }])

        # 校验各个补丁目录中 Iteminfo 文件中所需的贴图
        # =====================================================================

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir='%s/System' % leeClientDir,
            pattern=r'^.*?/iteminfo.*?\.(lua|lub)',
            baseDir_or_reGroupID=None,
            baseDir_append=None),
                           parsefunc=self.__verifyIteminfo,
                           subject='校验通用资源目录中的 iteminfo 道具描述文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '道具图片',
                               'resultIndex': 0
                           }, {
                               'name': '掉落和拖动时的图档',
                               'resultIndex': 1
                           }])

    def __patchesResourceVerifier(self):
        patchesDir = self.leeCommon.patches()

        # 校验各个补丁目录中地图文件所需的图档文件
        # =====================================================================

        # 校验地图的 gnd 文件(纹理层)

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir=patchesDir,
            pattern=
            r'^.*?/(Utility/Patches/.*?/Resource/Original/data)/.*?\.(gnd)',
            baseDir_or_reGroupID=1,
            baseDir_append=None),
                           parsefunc=self.__verifyGnd,
                           subject='校验各补丁目录中的 gnd 文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '地表贴图',
                               'resultIndex': 1
                           }])

        # 校验地图的 rsw 文件(模型层)

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir=patchesDir,
            pattern=
            r'^.*?/(Utility/Patches/.*?/Resource/Original/data)/.*?\.(rsw)',
            baseDir_or_reGroupID=1,
            baseDir_append=None),
                           parsefunc=self.__verifyRsw,
                           subject='校验各补丁目录中的 rsw 文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': 'RSM模型文件',
                               'resultIndex': 1
                           }])

        # 校验地图中 rsm 模型文件的贴图

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir=patchesDir,
            pattern=
            r'^.*?/(Utility/Patches/.*?/Resource/Original/data)/.*?\.(rsm)',
            baseDir_or_reGroupID=1,
            baseDir_append=None),
                           parsefunc=self.__verifyRsm,
                           subject='校验各补丁目录中的 rsm 模型的贴图文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': 'RSM模型文件的贴图文件',
                               'resultIndex': 1
                           }])

        # 校验各个补丁目录中动画效果索引文件 str 中所需的贴图
        # =====================================================================

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir=patchesDir,
            pattern=
            r'^.*?/(Utility/Patches/.*?/Resource/Original/data)/.*?\.(str)',
            baseDir_or_reGroupID=1,
            baseDir_append=None),
                           parsefunc=self.__verifyStr,
                           subject='校验各补丁目录中 str 文档所需的贴图文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '动画索引贴图文件',
                               'resultIndex': 1
                           }])

        # 校验各个补丁目录中 Iteminfo 文件中所需的贴图
        # =====================================================================

        self.__subVerifier(filesinfo=self.__getFilesInfo(
            glob_or_re='re',
            reWalkDir=patchesDir,
            pattern=r'^.*?/(Utility/Patches/.*?/Resource/Original)/System/' +
            r'iteminfo.*?\.(lua|lub)',
            baseDir_or_reGroupID=1,
            baseDir_append='/data'),
                           parsefunc=self.__verifyIteminfo,
                           subject='校验各补丁目录中的 iteminfo 道具描述文件',
                           returnPathListIndex=None,
                           reportMissInfo=[{
                               'name': '道具图片',
                               'resultIndex': 0
                           }, {
                               'name': '掉落和拖动时的图档',
                               'resultIndex': 1
                           }])

    def runVerifier(self):
        self.__resetReport()
        self.__globalResourceVerifier()
        self.__patchesResourceVerifier()
        self.__saveReport()
Exemple #16
0
class LeeGrf:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.patchManager = LeePatchManager()

    def isGrfExists(self):
        leeClientDir = self.leeCommon.client(withmark=False)
        grfFiles = glob.glob('%s/*.grf' % leeClientDir)
        return len(grfFiles) > 0

    def makeGrf(self, dataDirpath, grfOutputPath):
        # 确认操作系统平台
        if platform.system() != 'Windows':
            self.leeCommon.exitWithMessage('很抱歉, 此功能目前只能在 Windows 平台上运行.')

        # 确认 GrfCL 所需要的 .net framework 已安装
        if not self.leeCommon.isDotNetFrameworkInstalled('v3.5'):
            print('您必须先安装微软的 .NET Framework v3.5 框架.')
            self.leeCommon.exitWithMessage(
                '下载地址: https://www.microsoft.com/zh-CN/download/details.aspx?id=21'
            )

        # 确认已经切换到了需要的客户端版本
        if not self.patchManager.canRevert():
            self.leeCommon.exitWithMessage(
                '请先将 LeeClient 切换到某个客户端版本, 以便制作出来的 grf 文件内容完整.')

        # 确认有足够的磁盘剩余空间进行压缩
        currentDriver = self.leeCommon.utility()[0]
        currentFreeSpace = self.leeCommon.getDiskFreeSpace(currentDriver)
        if currentFreeSpace <= 1024 * 1024 * 1024 * 3:
            self.leeCommon.exitWithMessage('磁盘 %s: 的空间不足 3GB, 请清理磁盘释放更多空间.' %
                                           currentDriver)

        # 确认 GrfCL 文件存在
        scriptDir = self.leeCommon.utility(withmark=False)
        grfCLFilepath = ('%s/Bin/GrfCL/GrfCL.exe' % scriptDir).replace(
            '/', os.path.sep)
        if not self.leeCommon.isFileExists(grfCLFilepath):
            self.leeCommon.exitWithMessage(
                '制作 grf 文件所需的 GrfCL.exe 程序不存在, 无法执行压缩.')

        # data.grf 文件若存在则进行覆盖确认
        if self.leeCommon.isFileExists(grfOutputPath):
            lines = [
                '发现客户端目录中已存在 %s 文件,' % os.path.basename(grfOutputPath),
                '若继续将会先删除此文件, 为避免文件被误删, 请您进行确认.'
            ]
            title = '文件覆盖提示'
            prompt = '是否删除文件并继续?'
            if not self.leeCommon.confirm(lines, title, prompt, None, None,
                                          None):
                self.leeCommon.exitWithMessage('由于您放弃继续, 程序已自动终止.')
            os.remove(grfOutputPath)

        # 执行压缩工作(同步等待)
        grfCLProc = subprocess.Popen(
            '%s %s' %
            (grfCLFilepath, '-breakOnExceptions true -makeGrf "%s" "%s"' %
             (os.path.relpath(grfOutputPath, os.path.dirname(grfCLFilepath)),
              os.path.relpath(dataDirpath, os.path.dirname(grfCLFilepath)))),
            stdout=sys.stdout,
            cwd=os.path.dirname(grfCLFilepath))
        grfCLProc.wait()

        # 确认结果并输出提示信息表示压缩结束
        if grfCLProc.returncode == 0 and self.leeCommon.isFileExists(
                grfOutputPath):
            print('已经将 data 目录压缩为 data.grf 并存放到根目录.')
            print('')
            self.leeCommon.printSmallCutLine()
            print('')
        else:
            self.leeCommon.exitWithMessage('进行压缩工作的时候发生错误, 请发 Issue 进行反馈.')
Exemple #17
0
 def __init__(self):
     self.patchManager = LeePatchManager()
     self.buttonTranslator = LeeButtonTranslator()
     self.leeVerifier = LeeVerifier()
     self.leeCommon = LeeCommon()
Exemple #18
0
class LeeStructParser:
    '''
    这个操作类不对外开放, 仅在本文件中被 LeeVerifier 使用
    它负责将各种不同文件格式中所需要的图档路径信息提取出来, 并以数组方式返回
    '''
    def __init__(self):
        self.leeCommon = LeeCommon()

    def __bytesToString(self, bytesArray, targetCode='gbk'):
        '''
        将以 0 结尾的 bytes 数组转成字符串对象

        Args:
            bytesArray: 以 0 结尾的 bytes 数组
            targetCode: 转换成功之后的字符串编码, 默认用 gbk 编码

        Returns:
            转换后的字符串
        '''
        zeroEndBytes = 0
        for x in range(len(bytesArray)):
            zeroEndBytes = x
            if bytesArray[zeroEndBytes] == 0:
                break
        asciiString = bytesArray.decode('latin1')[:zeroEndBytes]
        return asciiString.encode('latin1').decode(targetCode)

    def __readFormatVersionInfo(self, f):
        '''
        根据打开的文件对象获取其版本信息
        直接读取接下来的 2 个字节来获得版本信息, 要求指针紧跟在 Magic Bytes 后面

        Args:
            f: 已经打开的文件对象 (RSW 或 RSM 文件)

        Returns:
            此函数包括三个返回值, 需要使用多个变量来接收函数返回的内容

            Major: 主版本号
            Minor: 子版本号
            Version: 2 个字节连在一起的版本号数值
        '''
        Major, Minor = struct.unpack("2b", f.read(struct.calcsize("2b")))
        f.seek(-2, 1)
        Version = struct.unpack("1h", f.read(struct.calcsize("1h")))[0]
        # ShowDebug('Major %d | Minor %d | Version %d' % (Major, Minor, Version))
        return Major, Minor, Version

    def parseGndFile(self, gndfilepath):
        '''
        读取 GND 文件, 并获取它所有相关的贴图地址
        读取到的贴图地址相对于 data/texture/ 目录, 如: data/texture/BACKSIDE.BMP

        Returns:
            此函数包括两个返回值, 需要使用多个变量来接收函数返回的内容

            result: Boolean 执行成功与否
            texturePathList: List 保存着贴图文件路径的数组, 相对于 data/texture/ 目录
        '''
        if not self.leeCommon.isFileExists(gndfilepath):
            print('读取 Gnd 文件失败, 文件不存在: %s' % gndfilepath)
            return False, None

        try:
            gndfile = open(gndfilepath, "rb")
            gndfile.seek(len(b'GRGN\x01\x07'))  # Magic Bytes

            _Width, _Height, _Ratio, TextureCount, TextureSize = struct.unpack(
                "5I", gndfile.read(struct.calcsize("5I")))
            # ShowDebug("parseGnd_GetTexturePathList: Width %d | Height %d | Ratio %d | TextureCount %d | TextureSize %d" % (_Width, _Height, _Ratio, TextureCount, TextureSize))

            texturePathList = []
            for _i in range(TextureCount):
                fmt = "%ds" % TextureSize
                TexturePath = struct.unpack(fmt,
                                            gndfile.read(
                                                struct.calcsize(fmt)))[0]
                texturePathList.append(self.__bytesToString(TexturePath))
                # print(self.__bytesToString(TexturePath))

            gndfile.close()
        except Exception as _err:
            print('处理 gnd 文件时出错了: %s' % gndfilepath)
            raise

        return True, texturePathList

    def parseRswFile(self, rswfilepath):
        '''
        读取 RSW 文件, 并获取它所有相关的 RSM 模型地址
        读取到的模型地址相对于 data/model/ 目录, 如: data/model/malaya/龋荐唱公03.rsm

        Args:
            rswfilepath: RSW 文件的路径

        Returns:
            此函数包括两个返回值, 需要使用多个变量来接收函数返回的内容

            result: Boolean 执行成功与否
            modelPathList: List 保存着 Rsm 模型文件路径的数组, 相对于 data/model/ 目录
        '''
        if not self.leeCommon.isFileExists(rswfilepath):
            print('读取 Rsw 文件失败, 文件不存在: %s' % rswfilepath)
            return False, None

        rswfile = open(rswfilepath, 'rb')
        rswfile.seek(len(b'GRSW'))  # Magic Bytes
        fmtMajor, fmtMinor, _Version = self.__readFormatVersionInfo(rswfile)

        def isCompatible(major, minor):
            return fmtMajor > major or (fmtMajor == major
                                        and fmtMinor >= minor)

        _IniFilename = rswfile.read(struct.calcsize("40s"))
        _GndFilename = rswfile.read(struct.calcsize("40s"))
        _GatFilename = '' if not isCompatible(1, 4) else rswfile.read(
            struct.calcsize("40s"))
        _ScrFilename = rswfile.read(struct.calcsize("40s"))

        # ==================== WaterData ====================

        _Level = 0.0
        _Type = 0
        _WaveHeight = 0.2
        _WaveSpeed = 2.0
        _WavePitch = 50.0
        _AnimSpeed = 3

        if isCompatible(1, 3):
            _Level = struct.unpack("1f",
                                   rswfile.read(struct.calcsize("1f")))[0]

        if isCompatible(1, 8):
            _Type, _WaveHeight, _WaveSpeed, _WavePitch = struct.unpack(
                "1I3f", rswfile.read(struct.calcsize("1I3f")))

        if isCompatible(1, 9):
            _AnimSpeed = struct.unpack("1I",
                                       rswfile.read(struct.calcsize("1I")))[0]

        # ShowInfo('WaterData: Level %f | Type %d | WaveHeight %f | WaveSpeed %f | WavePitch %f | AnimSpeed %d' %
        #     (_Level, _Type, _WaveHeight, _WaveSpeed, _WavePitch, _AnimSpeed))

        # ==================== LightData ====================

        _Longitude = 45
        _Latitude = 45
        _DiffuseColor = [1.0, 1.0, 1.0]
        _AmbientColor = [0.3, 0.3, 0.3]
        _Opacity = 1.0

        if isCompatible(1, 5):
            _Longitude, _Latitude = struct.unpack(
                "2I", rswfile.read(struct.calcsize("2I")))
            _DiffuseColor[0], _DiffuseColor[1], _DiffuseColor[
                2] = struct.unpack("3f", rswfile.read(struct.calcsize("3f")))
            _AmbientColor[0], _AmbientColor[1], _AmbientColor[
                2] = struct.unpack("3f", rswfile.read(struct.calcsize("3f")))

        if isCompatible(1, 7):
            _Opacity = struct.unpack("1f",
                                     rswfile.read(struct.calcsize("1f")))[0]

        # ShowInfo('LightData: Longitude %d | Latitude %d | Opacity %f' % (_Longitude, _Latitude, _Opacity))
        # ShowInfo('LightData: DiffuseColorRed %f | DiffuseColorGreen %f | DiffuseColorBlue %f' % (_DiffuseColor[0], _DiffuseColor[1], _DiffuseColor[2]))
        # ShowInfo('LightData: AmbientColorRed %f | AmbientColorGreen %f | AmbientColorBlue %f' % (_AmbientColor[0], _AmbientColor[1], _AmbientColor[2]))

        # ==================== GroundData ====================

        _Top = -500
        _Bottom = 500
        _Left = -500
        _Right = 500

        if isCompatible(1, 6):
            _Top, _Bottom, _Left, _Right = struct.unpack(
                "4I", rswfile.read(struct.calcsize("4I")))

        # ShowInfo('GroundData: Top %d | Bottom %d | Left %d | Right %d' % (_Top, _Bottom, _Left, _Right))

        # ==================== MapObject ====================

        objectCount = struct.unpack("1I",
                                    rswfile.read(struct.calcsize("1I")))[0]

        modelPathList = []
        for _i in range(objectCount):
            objectType = struct.unpack("1I",
                                       rswfile.read(struct.calcsize("1I")))[0]

            if objectType == 1:  # Model - 关注会加载的 RSM 模型
                if isCompatible(1, 3):
                    _ModelName = self.__bytesToString(
                        struct.unpack("40s",
                                      rswfile.read(struct.calcsize("40s")))[0])
                    _AnimationType = struct.unpack(
                        "1I", rswfile.read(struct.calcsize("1I")))[0]
                    _AnimationSpeed = struct.unpack(
                        "1f", rswfile.read(struct.calcsize("1f")))[0]
                    _BlockType = struct.unpack(
                        "1I", rswfile.read(struct.calcsize("1I")))[0]

                modelFilename = self.__bytesToString(
                    struct.unpack("80s",
                                  rswfile.read(struct.calcsize("80s")))[0])
                _ModelNodeName = self.__bytesToString(
                    struct.unpack("80s",
                                  rswfile.read(struct.calcsize("80s")))[0])

                modelPathList.append(modelFilename)
                # ShowInfo("[RSM Model] Path = %s" % modelFilename)

        rswfile.close()
        return True, modelPathList

    def parseRsmFile(self, rsmfilepath):
        '''
        读取 RSM 文件, 并获取它所有相关的贴图地址
        读取到的贴图地址相对于 data/texture/ 目录, 如: data/texture/eclage/ecl_obj15.bmp

        Args:
            rsmfilepath: RSM 文件的路径

        Returns:
            此函数包括两个返回值, 需要使用多个变量来接收函数返回的内容

            result: Boolean 执行成功与否
            texturePathList: List 保存着贴图文件路径的数组, 相对于 data/texture/ 目录
        '''
        def isCompatible(major, minor):
            return (fmtMajor > major
                    or (fmtMajor == major and fmtMinor >= minor))

        try:
            if not self.leeCommon.isFileExists(rsmfilepath):
                print("读取 Rsm 文件失败, 文件不存在: %s" % rsmfilepath)
                return False, None

            rsmfile = open(rsmfilepath, "rb")
            rsmfile.seek(len(b'GRSM'))  # Magic Bytes
            fmtMajor, fmtMinor, _Version = self.__readFormatVersionInfo(
                rsmfile)

            _AnimationLength, _ShadeType = struct.unpack(
                "2I", rsmfile.read(struct.calcsize("2I")))
            _Alpha = 0 if not isCompatible(1, 4) else struct.unpack(
                "1b", rsmfile.read(struct.calcsize("1b")))[0]
            _unknow = struct.unpack("16s",
                                    rsmfile.read(struct.calcsize("16s")))[0]
            textureCount = struct.unpack("1I",
                                         rsmfile.read(
                                             struct.calcsize("1I")))[0]

            texturePathList = []
            for _i in range(textureCount):
                texturePath = self.__bytesToString(
                    struct.unpack("40s",
                                  rsmfile.read(struct.calcsize("40s")))[0])
                texturePathList.append(texturePath)

            rsmfile.close()
        except Exception as _err:
            print('处理 rsm 文件时出错了: %s' % rsmfilepath)
            raise

        return True, texturePathList

    def parseStrFile(self, strfilepath):
        '''
        读取 STR 文件, 并获取它所有相关的贴图地址
        读取到的贴图地址相对于 STR 文件所在目录, 如: data/texture/effect/magnus/ff.bmp

        Args:
            strfilepath: STR 文件的路径

        Returns:
            此函数包括两个返回值, 需要使用多个变量来接收函数返回的内容

            result: Boolean 执行成功与否
            texturePathList: List 保存着贴图文件路径的数组, 相对于 STR 文件所在目录
        '''
        if not self.leeCommon.isFileExists(strfilepath):
            print("读取 Str 文件失败, 文件不存在: %s" % strfilepath)
            return False, None

        strfile = open(strfilepath, "rb")
        strfile.seek(len(b'STRM'))  # Magic Bytes
        _Version, _FPS, _frameCount, layerCount, _Reserved = struct.unpack(
            "4I16s", strfile.read(struct.calcsize("4I16s")))

        texturePathList = []
        for _i in range(layerCount):
            textureCount = struct.unpack("1I",
                                         strfile.read(
                                             struct.calcsize("1I")))[0]
            for _k in range(textureCount):
                textureName = self.__bytesToString(
                    struct.unpack("128s",
                                  strfile.read(struct.calcsize("128s")))[0])
                texturePathList.append(textureName)

            keyFrameCount = struct.unpack("1I",
                                          strfile.read(
                                              struct.calcsize("1I")))[0]
            for _k in range(keyFrameCount):
                struct.unpack("2I19f1I6f3I",
                              strfile.read(struct.calcsize("2I19f1I6f3I")))

        strfile.close()
        return True, texturePathList

    def parseIteminfo(self, iteminfofilepath):
        '''
        读取 iteminfo 文件, 并获取它所有相关的贴图和 ACT&SPR 图档地址

        Args:
            strfilepath: iteminfo 文件的路径

        Returns:
            此函数包括三个返回值, 需要使用多个变量来接收函数返回的内容

            result: Boolean 执行成功与否
            texturePathList: List 保存着贴图文件路径的数组, 相对于 data/texture/ 目录
            spritePathList: List 保存着 ACT&SPR 图档路径的数组, 相对于 data/sprite/ 目录
        '''
        if not self.leeCommon.isFileExists(iteminfofilepath):
            print("读取 Iteminfo 文件失败, 文件不存在: %s" % iteminfofilepath)
            return False, None, None

        itemLua = LeeIteminfoLua()
        itemLua.load(iteminfofilepath)

        texturePathList = []
        spritePathList = []

        for itemID in itemLua.items():
            unidentifiedResourceName = itemLua.getItemAttribute(
                itemID, 'unidentifiedResourceName')
            if unidentifiedResourceName.strip() != '':
                texturePathList.append('蜡历牢磐其捞胶/collection/%s.bmp' %
                                       unidentifiedResourceName)
                texturePathList.append('蜡历牢磐其捞胶/item/%s.bmp' %
                                       unidentifiedResourceName)
                spritePathList.append('酒捞袍/%s.spr' % unidentifiedResourceName)
                spritePathList.append('酒捞袍/%s.act' % unidentifiedResourceName)

            identifiedResourceName = itemLua.getItemAttribute(
                itemID, 'identifiedResourceName')
            if identifiedResourceName.strip() != '':
                texturePathList.append('蜡历牢磐其捞胶/collection/%s.bmp' %
                                       identifiedResourceName)
                texturePathList.append('蜡历牢磐其捞胶/item/%s.bmp' %
                                       identifiedResourceName)
                spritePathList.append('酒捞袍/%s.spr' % identifiedResourceName)
                spritePathList.append('酒捞袍/%s.act' % identifiedResourceName)

        texturePathList = list(set(texturePathList))
        spritePathList = list(set(spritePathList))

        return True, texturePathList, spritePathList
Exemple #19
0
class LeeMenu:
    def __init__(self):
        self.patchManager = LeePatchManager()
        self.buttonTranslator = LeeButtonTranslator()
        self.leeVerifier = LeeVerifier()
        self.leeCommon = LeeCommon()

    def menufuncResetWorkshop(self):
        '''
        重置 LeeClient 客户端环境
        为接下来切换其他版本的客户端做好准备
        '''
        try:
            print('正在重置按钮汉化文件 ...')
            self.buttonTranslator.doRevert('AllVersions')

            print('正在重置其他客户端资源 ...')
            self.patchManager.doRevertPatch()

            leeClientDir = self.leeCommon.client(withmark=False)
            if self.leeCommon.isFileExists('%s/data.grf' % leeClientDir):
                os.remove('%s/data.grf' % leeClientDir)

            # 移除各个 Patches 版本目录中的 Temporary 目录
            clientList = self.leeCommon.getRagexeClientList(
                self.leeCommon.patches())
            for x in clientList:
                temporaryDir = self.leeCommon.special(x, 'temporary')
                self.leeCommon.removeDirectory(temporaryDir)

            print('正在删除空目录 ...')
            self.leeCommon.removeEmptyDirectorys(leeClientDir)

            print('已成功重置 LeeClient 客户端环境')
        except Exception as _err:
            print('很抱歉, 重置 LeeClient 客户端环境的过程中发生了意外, 请检查结果')
            raise

    def menufuncSwitchWorkshop(self, clientver):
        '''
        重置工作区, 并切换 LeeClient 到指定的客户端版本
        '''
        if self.patchManager.canRevert():
            self.leeCommon.confirm([
                '在切换版本之前, 需要将 LeeClient 客户端恢复到干净状态',
                '请将自己添加的额外重要文件移出 LeeClient 目录, 避免被程序误删'
            ],
                                   title='切换主程序版本到 %s' % clientver,
                                   prompt='是否立刻执行重置操作?',
                                   inject=self,
                                   cancelExec='inject.menuitemExitAgent()',
                                   evalcmd='inject.menufuncResetWorkshop()')
            print(
                '----------------------------------------------------------------'
            )

        # 先执行与此版本相关的汉化工作
        print('正在汉化 iteminfo ...')
        LeeIteminfoTranslator().doTranslate(clientver)

        print('正在汉化 towninfo ...')
        LeeTowninfoTranslator().doTranslate(clientver)

        print('正在汉化 skillinfolist ...')
        LeeSkillinfolistTranslator().doTranslate(clientver)

        print('正在汉化 skilldescript ...')
        LeeSkilldescriptTranslator().doTranslate(clientver)

        print('正在汉化 客户端按钮 ...')
        LeeButtonTranslator().doTranslate(clientver)

        # 将对应的资源覆盖到 LeeClient 主目录
        print('正在切换版本, 请耐心等待...')
        if not self.patchManager.doApplyPatch(clientver):
            print('很抱歉, 切换仙境传说的主程序到 %s 版本的时发生错误, 请检查结果' % clientver)
        else:
            print('已切换仙境传说的主程序到 %s 版本\r\n' % clientver)

    def menufuncPackageSourceToZipfile(self, packageSourceDirname):
        '''
        将指定的打包源压缩成一个 ZIP 文件
        '''
        leeClientParantDir = self.leeCommon.client('..', withmark=False)
        packageSourceDirpath = '%s/%s' % (leeClientParantDir,
                                          packageSourceDirname)

        zipFilename = LeePublisher().getZipFilename(packageSourceDirpath)
        if not LeePublisher().makeZip(packageSourceDirpath, zipFilename):
            print('很抱歉, 压缩 ZIP 文件时发生错误, 请检查结果')
        else:
            print('已压缩为 ZIP 文件: %s\r\n' % (zipFilename))

    def menufuncPackageSourceToSetup(self, packageSourceDirname):
        '''
        将指定的打包源制作成一个 Setup 安装程序
        '''
        leeClientParantDir = self.leeCommon.client('..', withmark=False)
        packageSourceDirpath = '%s/%s' % (leeClientParantDir,
                                          packageSourceDirname)
        outputDirpath = './Output/%s' % packageSourceDirname

        if not LeePublisher().makeSetup(packageSourceDirpath, outputDirpath):
            print('很抱歉, 制作 Setup 安装程序时发生错误, 请检查结果')
        else:
            print('\r\n已制作完毕的 Setup 安装程序存放在: %s 目录中, 请确认.\r\n' %
                  (os.path.abspath(outputDirpath)))

    def menufuncUpdateButtonTranslateDB(self):
        '''
        根据目前各个客户端的 Resource/Original 目录中的最新文件
        来更新目前正在使用的按钮汉化数据库文件
        '''
        print('正在读取数据库...')
        self.buttonTranslator.load()
        print('正在根据目前 Patches 的内容升级数据库...')
        self.buttonTranslator.update()
        print('正在保存数据库...')
        self.buttonTranslator.save()
        print('更新操作已经完成, 请确认文件的变更内容...\r\n')

    def menufuncClientResourceVerifier(self):
        '''
        对客户端进行资源完整性校验
        '''
        self.leeVerifier.runVerifier()
        print('客户端文件完整性校验已结束\r\n')

    def menufuncBatchDecompileLub(self, lubSourceDirectory):
        '''
        将某个目录下的 lub 文件批量反编译
        需要让用户来选择这个目录的所在位置, 而不是一个固定位置
        '''
        print('您指定的目录为: %s' % lubSourceDirectory)

        if not self.leeCommon.isDirectoryExists(lubSourceDirectory):
            self.leeCommon.exitWithMessage('很抱歉, 你指定的目录不存在, 程序终止')

        if lubSourceDirectory.endswith('/') or lubSourceDirectory.endswith(
                '\\'):
            lubSourceDirectory = lubSourceDirectory[:-1]

        lubOutputDirectory = '%s%s%s' % (
            os.path.dirname(lubSourceDirectory), os.path.sep,
            os.path.basename(lubSourceDirectory) + '_output')
        print('计划的输出目录: %s' % lubOutputDirectory)

        if self.leeCommon.isDirectoryExists(lubOutputDirectory):
            self.leeCommon.exitWithMessage('发现输出目录已经存在, 请先手动删除后重试..')

        print('')
        LeeLua().decodeDir(lubSourceDirectory, lubOutputDirectory)

    def menufuncBatchAmendmentsLub(self):
        '''
        将 Patches 目录下的 lub 文件批量进行整理
        '''
        patchesDir = self.leeCommon.patches()

        for dirpath, _dirnames, filenames in os.walk(patchesDir):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                if not filename.lower().endswith('.lub'):
                    continue
                if LeeLua().isTrulyLubFile(fullpath):
                    continue
                if not LeeLua().lubAmendments(fullpath, fullpath):
                    self.leeCommon.exitWithMessage('整理 %s 时发生错误, 请确认.' %
                                                   fullpath)
                else:
                    print('已整理: %s' % os.path.relpath(fullpath, patchesDir))

    def menufuncDetectLubCompiled(self):
        '''
        扫描整个 Patches 目录下的 lub 文件
        检测他们的是否已经被反编译, 并将没有被反编译的 lub 文件列出
        '''
        patchesDir = self.leeCommon.patches()

        print('正在扫描, 可能会花几分钟时间, 请耐心等待...')
        print('')
        work_start = timeit.default_timer()

        for dirpath, _dirnames, filenames in os.walk(patchesDir):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                if not filename.lower().endswith('.lub'):
                    continue

                if LeeLua().isTrulyLubFile(fullpath):
                    print('尚未反编译 - %s' %
                          (os.path.relpath(fullpath, patchesDir)))

        work_elapsed = (timeit.default_timer() - work_start)

        print('扫描并检测完毕, 耗时: %0.2f 秒' % work_elapsed)
        print('')

    def menufuncDetectNonAnsiLub(self):
        '''
        扫描整个 Patches 目录下的 lub 文件
        检测他们的文件编码, 并列出非 ANSI 编码的文件
        '''
        patchesDir = self.leeCommon.patches()
        allowANSI = [
            'ASCII', 'EUC-KR', 'LATIN1', 'GBK', 'GB2312', 'CP949',
            'ISO-8859-1', 'WINDOWS-1252'
        ]

        print('正在扫描, 可能会花几分钟时间, 请耐心等待...')
        print('')
        work_start = timeit.default_timer()

        for dirpath, _dirnames, filenames in os.walk(patchesDir):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                if not filename.lower().endswith('.lub'):
                    continue

                result, encoding = LeeLua().getLubEncoding(fullpath)
                if result and encoding not in allowANSI:
                    print('%s - %s' %
                          (encoding, os.path.relpath(fullpath, patchesDir)))

        work_elapsed = (timeit.default_timer() - work_start)

        print('扫描并检测完毕, 耗时: %0.2f 秒' % work_elapsed)
        print('')

    def menuitemUpdateButtonTranslateDB(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 汉化管理 - 更新按钮汉化数据库”时执行
        '''
        self.leeCommon.confirm(
            [
                '已汉化的内容将会被自动继承, 请不用担心',
                '涉及的数据库文件为: Resources/Databases/ButtonTranslate.json'
            ],
            title='更新客户端按钮的翻译数据库',
            prompt='是否执行更新操作?',
            inject=self,
            cancelExec='inject.menuitemExitAgent()',
            evalcmd='inject.menufuncUpdateButtonTranslateDB()')

    def menuitemStatisticsTranslateCoverage(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 汉化管理 - 汉化覆盖率统计”时执行
        '''
        self.leeCommon.exitWithMessage('此功能目前还在规划中, 待实现...')

    def menuitemClientResourceVerifier(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 资源管理 - 校验客户端资源完整性”时执行
        '''
        self.leeCommon.confirm(
            ['此过程可以协助排除可能的一些图档丢失情况.', '不过由于需要对客户端的大量文件进行判断, 时间可能会比较长.'],
            title='对客户端资源进行完整性校验',
            prompt='是否确认执行?',
            inject=self,
            cancelExec='inject.menuitemExitAgent()',
            evalcmd='inject.menufuncClientResourceVerifier()')

    def menuitemBatchDecompileLub(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 脚本管理 - 批量反编译某个目录中的 lub 文件”时执行
        '''
        self.leeCommon.input(
            [
                '您不能指望反编译后的 lua 文件可被 RO 客户端无错运行.',
                '反编译 lua 的函数时, 转换后的结果常出问题(语法错误), 需手动修正.', '',
                '请填写一个包含 lub 文件的 luafiles514 目录的完整路径.',
                '程序只转换后缀为 lub 的文件, 并将反编译后的文件保存到平级目录下.', '',
                '比如你填写的路径是: C:\\luafiles 那么输出会在 C:\\luafiles_output',
                '程序会将无需反编译的文件, 也一起复制到输出目录(保持目录结构).'
            ],
            title='批量反编译指定目录下的 lub 文件',
            prompt='请填写目录路径',
            inject=self,
            evalcmd='inject.menufuncBatchDecompileLub(user_input)')

    def menuitemBatchAmendmentsLub(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 脚本管理 - 批量整理某个目录中的 lub 文件”时执行
        '''
        self.leeCommon.confirm([
            '此操作会将 Patches 目录中的 lub 文件全部找出',
            '然后移除 lub 文件中的一些多余的注释, 并纠正一些格式错误等等.',
            '',
            '程序只转换后缀为 lub 的文件, 并将处理后的文件直接进行替换.',
            '注意: 由于会直接进行替换, 所以请您一定先自己做好备份! 避免意外.',
        ],
                               title='批量整理所有 lub 文件的内容',
                               prompt='是否立刻执行整理操作?',
                               inject=self,
                               cancelExec='inject.menuitemExitAgent()',
                               evalcmd='inject.menufuncBatchAmendmentsLub()')

    def menuitemDetectLubCompiled(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 脚本管理 - 扫描并列出所有未被反编译的 lub 文件”时执行
        '''
        self.leeCommon.confirm([
            '此操作会将 Patches 目录中的 lub 文件全部找出',
            '然后判断其是否已经被反编译, 并将没有被反编译的 lub 全部列出.', '',
            '注意: 为了提高效率, 我们只对文件后缀为 lub 的文件进行判断.'
        ],
                               title='扫描并列出所有未被反编译的 lub 文件',
                               prompt='是否立刻执行扫描操作?',
                               inject=self,
                               cancelExec='inject.menuitemExitAgent()',
                               evalcmd='inject.menufuncDetectLubCompiled()')

    def menuitemDetectNonAnsiLub(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作 -> 脚本管理 - 扫描并列出所有非 ANSI 编码的 lub 文件”时执行
        '''
        self.leeCommon.confirm([
            '此操作会将 Patches 目录中的 lub 文件全部找出',
            '然后探测其文件编码, 将所有非 ANSI 类型编码的文件都列出来.', '',
            '注意: GBK 和 EUC-KR 都属于 ANSI 类型编码, 而 UTF8 则不是.',
            '这里的 lub 文件实际上是 lua 的明文脚本文件.'
        ],
                               title='扫描并列出所有非 ANSI 编码的 lub 文件',
                               prompt='是否立刻执行扫描操作?',
                               inject=self,
                               cancelExec='inject.menuitemExitAgent()',
                               evalcmd='inject.menufuncDetectNonAnsiLub()')

    def menuitemBuildSourceUseGRF(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程 -> 将当前客户端状态导出成打包源 ->
        将 data 目录压缩成 GRF (推荐)”时执行
        '''
        LeePublisher().makeSource(True)

    def menuitemBuildSourceDontUseGRF(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程 -> 将当前客户端状态导出成打包源 ->
        不压缩 data 目录中的文件, 保持零散小文件状态 (不推荐)”时执行
        '''
        LeePublisher().makeSource(False)

    def menuitemConfrimDataFolderType(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程 -> 将当前客户端状态导出成打包源”时执行
        '''
        self.leeCommon.menu([[
            '将 data 目录压缩成 GRF (推荐, 仅 Windows 支持)',
            'inject.menuitemBuildSourceUseGRF()'
        ],
                             [
                                 '不压缩 data 目录中的文件, 保持零散小文件状态 (不推荐)',
                                 'inject.menuitemBuildSourceDontUseGRF()'
                             ]],
                            title='生成打包源时, 您希望如何处理 data 目录:',
                            inject=self)

    def menuitemPackageSourceToZipfile(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程 -> 选择一个打包源, 压缩成 ZIP 包”时执行
        '''
        packageSourceDirnameList = LeePublisher().getPackageSourceList(
            self.leeCommon.client('..'))
        if packageSourceDirnameList is None:
            self.leeCommon.exitWithMessage('很抱歉, 无法获取打包源列表, 程序终止')

        if not packageSourceDirnameList:
            self.leeCommon.exitWithMessage('没有发现任何可用的打包源, 请先生成一个吧')

        self.leeCommon.menu(
            [[x, 'inject.menufuncPackageSourceToZipfile(\'%s\')' % x]
             for x in packageSourceDirnameList],
            title='将指定的打包源压缩成 ZIP 文件',
            prompt='请选择你想压缩的打包源目录',
            inject=self,
            cancelExec='inject.entrance()',
            withCancel=True)

    def menuitemPackageSourceToSetup(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程 -> 选择一个打包源, 制作游戏安装程序”时执行
        '''
        packageSourceDirnameList = LeePublisher().getPackageSourceList(
            self.leeCommon.client('..'))
        if packageSourceDirnameList is None:
            self.leeCommon.exitWithMessage('很抱歉, 无法获取打包源列表, 程序终止')

        if not packageSourceDirnameList:
            self.leeCommon.exitWithMessage('没有发现任何可用的打包源, 请先生成一个吧')

        self.leeCommon.menu(
            [[x, 'inject.menufuncPackageSourceToSetup(\'%s\')' % x]
             for x in packageSourceDirnameList],
            title='将指定的打包源制作成安装程序',
            prompt='请选择你想制作的打包源目录',
            inject=self,
            cancelExec='inject.entrance()',
            withCancel=True)

    def menuitemSwitchWorkshop(self):
        '''
        菜单处理函数
        当选择“切换客户端到指定版本”时执行
        '''
        clientList = self.leeCommon.getRagexeClientList(
            self.leeCommon.patches())
        if clientList is None:
            self.leeCommon.exitWithMessage('很抱歉, 无法获取客户端版本列表, 程序终止')

        menus = [[
            '切换到 %s 版本' % x,
            'inject.menufuncSwitchWorkshop(\'%s\')' % x
        ] for x in clientList]
        menus.insert(0, ['将客户端重置回干净状态', 'inject.menuitemResetWorkshop()'])

        self.leeCommon.menu(items=menus,
                            title='切换客户端到指定版本, 以便与服务端配套工作:',
                            inject=self,
                            cancelExec='inject.entrance()',
                            withCancel=True)

    def menuitemResetWorkshop(self):
        '''
        菜单处理函数
        当选择“切换客户端到指定版本 -> 将客户端重置回干净状态”时执行
        '''
        if self.patchManager.canRevert():
            self.leeCommon.confirm([
                '此操作可以将 LeeClient 客户端恢复到干净状态',
                '请将自己添加的额外重要文件移出 LeeClient 目录, 避免被程序误删.', '',
                '提醒: 此操作不会删除 Utility/Import 目录下的文件, 请放心.'
            ],
                                   title='将客户端重置回干净状态',
                                   prompt='是否立刻执行重置操作?',
                                   inject=self,
                                   cancelExec='inject.menuitemExitAgent()',
                                   evalcmd='inject.menufuncResetWorkshop()')
        else:
            self.leeCommon.exitWithMessage('您的客户端环境看起来十分干净, 不需要再进行清理了.')

    def menuitemMaintenance(self):
        '''
        菜单处理函数
        当选择“进行一些开发者维护工作”时执行
        '''
        self.leeCommon.menu(
            [['汉化管理 - 更新按钮汉化数据库', 'inject.menuitemUpdateButtonTranslateDB()'],
             [
                 '汉化管理 - 汉化覆盖率统计(计划实现)',
                 'inject.menuitemStatisticsTranslateCoverage()'
             ],
             ['资源管理 - 校验客户端资源完整性', 'inject.menuitemClientResourceVerifier()'],
             [
                 '脚本管理 - 批量反编译某个目录中的 lub 文件',
                 'inject.menuitemBatchDecompileLub()'
             ],
             [
                 '脚本管理 - 批量整理某个目录中的 lub 文件',
                 'inject.menuitemBatchAmendmentsLub()'
             ],
             [
                 '脚本管理 - 扫描并列出所有未被反编译的 lub 文件',
                 'inject.menuitemDetectLubCompiled()'
             ],
             [
                 '脚本管理 - 扫描并列出所有非 ANSI 编码的 lub 文件',
                 'inject.menuitemDetectNonAnsiLub()'
             ]],
            title='以下是一些开发者维护工作, 请选择您需要的操作:',
            inject=self)

    def menuitemMakePackageOrSetup(self):
        '''
        菜单处理函数
        当选择“生成 / 打包 / 制作客户端安装程序”时执行
        '''
        self.leeCommon.menu(
            [['将当前客户端状态导出成打包源', 'inject.menuitemConfrimDataFolderType()'],
             ['选择一个打包源, 压缩成 ZIP 包', 'inject.menuitemPackageSourceToZipfile()'],
             [
                 '选择一个打包源, 制作游戏安装程序 (仅 Windows 平台支持)',
                 'inject.menuitemPackageSourceToSetup()'
             ]],
            title='生成 / 打包 / 制作客户端安装程序, 请选择您需要的操作:',
            inject=self)

    def menuitemExitAgent(self):
        '''
        菜单处理函数
        当选择“退出程序”时执行
        '''
        self.leeCommon.exitWithMessage('感谢您的使用, 再见')

    def entrance(self):
        '''
        菜单处理函数
        这里是主菜单的入口处
        '''
        self.leeCommon.menu(
            [['切换客户端到指定版本', 'inject.menuitemSwitchWorkshop()'],
             ['生成 / 打包 / 制作客户端安装程序', 'inject.menuitemMakePackageOrSetup()'],
             ['进行一些开发者维护工作', 'inject.menuitemMaintenance()'],
             ['退出程序', 'inject.menuitemExitAgent()']],
            title='您好, 欢迎使用 LeeClientAgent 来管理您的客户端!',
            inject=self)
Exemple #20
0
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.patchManager = LeePatchManager()
Exemple #21
0
class LeeSkillinfolistLua:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.skillinfolistDict = {}
        self.SKID = []
        self.JOBID = []

        self.singleSkillinfoFormat = \
'''	[SKID.%s] = {
		"%s",
		SkillName = "%s",
		MaxLv = %s,
		SpAmount = { %s },
		bSeperateLv = %s,
		AttackRange = { %s }%s
	}%s'''.replace('\n', '\r\n').replace('\r\r', '\r')

        self._neeskillListFormat = ',\r\n\t\t_NeedSkillList = {\r\n%s\r\n\t\t}'
        self.neeskillListFormat = ',\r\n\t\tNeedSkillList = {\r\n%s\r\n\t\t}'
        self.jobDepentFormat = '\t\t\t[JOBID.%s] = {\r\n%s\r\n\t\t\t}%s'

        self.skillScaleListFormat = ',\r\n\t\tSkillScale = {\r\n%s\r\n\t\t}'
        self.skillScaleItemFormat = '\t\t\t[%s] = { x = %s, y = %s }%s'
        self.skillinfoListFormat = 'SKILL_INFO_LIST = {\r\n%s\r\n}\r\n'

    def createLuaTable(self, luaContent, regex, pos, tableName):
        matches = re.finditer(regex, luaContent,
                              re.MULTILINE | re.IGNORECASE | re.DOTALL)

        tables = []
        for match in matches:
            tables.append(match.group(pos))

        tables = set(tables)
        setattr(self, tableName, list(tables))

        contentList = []
        for num, item in enumerate(tables):
            contentList.append('%s = %s' % (item, num + 1))

        return '%s = { %s }' % (tableName, ', '.join(contentList))

    def getSkillConstant(self, skillFakeID):
        return self.SKID[skillFakeID - 1]

    def getJobIdConstant(self, jobFakeID):
        return self.JOBID[jobFakeID - 1]

    def load(self, filepath):
        self.skillinfolistDict.clear()

        luafile = open(filepath, 'r', encoding='latin1')
        content = luafile.read()
        luafile.close()

        # 读取并构建假设的 SKID 常量列表
        SKIDTableContent = self.createLuaTable(
            content, r"[\[|{]\s*SKID\.(.*?)\s*[,|}|\]]", 1, 'SKID')

        # 读取并构建假设的 JOBID 常量列表
        JOBIDTableContent = self.createLuaTable(
            content, r"[\[|{]\s*JOBID\.(.*?)\s*[,|}|\]]", 1, 'JOBID')

        # 读取上面构建的两个常量列表
        lua = LuaRuntime(unpack_returned_tuples=True)
        lua.execute(SKIDTableContent)
        lua.execute(JOBIDTableContent)

        # 然后正式载入 skillinfolist 接下来进行处理
        lua.execute(content)

        # 再将信息提取到内存中存储起来
        g = lua.globals()

        dummyClass = LeeSkillinfoSingleItem(Constant='',
                                            SkillName='',
                                            MaxLv=0,
                                            Type='',
                                            SpAmount=[],
                                            bSeperateLv=False,
                                            AttackRange=[],
                                            _NeedSkillList=[],
                                            NeedSkillList={},
                                            SkillScale=[])

        for skillFakeID in list(g.SKILL_INFO_LIST):
            dummyClass.SpAmount.clear()
            dummyClass.AttackRange.clear()
            dummyClass._NeedSkillList.clear()
            dummyClass.NeedSkillList.clear()
            dummyClass.SkillScale.clear()

            skillItemLuaObject = g.SKILL_INFO_LIST[skillFakeID]
            skillConstant = self.getSkillConstant(skillFakeID)

            for attributeName in list(skillItemLuaObject):
                if attributeName == 1:
                    dummyClass.Constant = skillItemLuaObject[attributeName]
                elif attributeName in ['SkillName']:
                    dummyClass.SkillName = skillItemLuaObject[attributeName]
                elif attributeName in ['SpAmount', 'AttackRange']:
                    strValueList = []
                    for key in list(skillItemLuaObject[attributeName]):
                        strValueList.append(
                            str(skillItemLuaObject[attributeName][key]))
                    setattr(dummyClass, attributeName, strValueList)
                elif attributeName in ['_NeedSkillList']:
                    for needID in list(skillItemLuaObject[attributeName]):
                        needItem = skillItemLuaObject[attributeName][needID]
                        needSkillItem = LeeNeedSkillItem(
                            Constant=self.getSkillConstant(needItem[1]),
                            SkillFakeID=needItem[1],
                            RequireLv=needItem[2])
                        dummyClass._NeedSkillList.append(needSkillItem)
                elif attributeName in ['SkillScale']:
                    for scaleLevel in list(skillItemLuaObject[attributeName]):
                        scaleInfoItems = skillItemLuaObject[attributeName][
                            scaleLevel]
                        scaleInfo = LeeSkillScaleItem(lv=scaleLevel,
                                                      x=scaleInfoItems['x'],
                                                      y=scaleInfoItems['y'])
                        dummyClass.SkillScale.append(scaleInfo)
                elif attributeName in ['NeedSkillList']:
                    for jobID in list(skillItemLuaObject[attributeName]):
                        jobConstant = self.getJobIdConstant(jobID)
                        jobItems = skillItemLuaObject[attributeName][jobID]
                        jobNeedSkillList = []
                        for needID in list(jobItems):
                            needItem = jobItems[needID]
                            needSkillItem = LeeNeedSkillItem(
                                Constant=self.getSkillConstant(needItem[1]),
                                SkillFakeID=needItem[1],
                                RequireLv=needItem[2])
                            jobNeedSkillList.append(needSkillItem)
                        dummyClass.NeedSkillList[
                            jobConstant] = jobNeedSkillList
                elif hasattr(dummyClass, attributeName):
                    setattr(dummyClass, attributeName,
                            skillItemLuaObject[attributeName])
                else:
                    self.leeCommon.exitWithMessage(
                        '技能 %s 存在未知的属性 "%s", 请进行处理' %
                        (self.getSkillConstant(skillFakeID), attributeName))

            self.skillinfolistDict[skillConstant] = copy.deepcopy(dummyClass)

    def save(self, savepath):
        fullSkillinfolistText = []

        for skillConstant in self.skillinfolistDict:
            _needSkillContent = []
            for needskill in self.skillinfolistDict[
                    skillConstant]._NeedSkillList:
                _needSkillContent.append(
                    '\t\t\t{ SKID.%s, %s }%s' %
                    (needskill.Constant, needskill.RequireLv,
                     self.leeCommon.isLastReturn(
                         self.skillinfolistDict[skillConstant]._NeedSkillList,
                         needskill, '', ',')))
            _needSkillListText = '' if len(self.skillinfolistDict[skillConstant]._NeedSkillList) <= 0 else \
            self._neeskillListFormat % (
                '\r\n'.join(_needSkillContent)
            )

            ###

            needSkillContent = []
            for jobConstant in self.skillinfolistDict[
                    skillConstant].NeedSkillList:
                jobNeedSkillText = []
                for needskill in self.skillinfolistDict[
                        skillConstant].NeedSkillList[jobConstant]:
                    jobNeedSkillText.append(
                        '\t\t\t\t{ SKID.%s, %s }%s' %
                        (needskill.Constant, needskill.RequireLv,
                         self.leeCommon.isLastReturn(
                             self.skillinfolistDict[skillConstant].
                             NeedSkillList[jobConstant], needskill, '', ',')))
                jobDependText = self.jobDepentFormat % (
                    jobConstant, '\r\n'.join(jobNeedSkillText),
                    self.leeCommon.isLastReturn(
                        self.skillinfolistDict[skillConstant].NeedSkillList,
                        jobConstant, '', ','))
                needSkillContent.append(jobDependText)

            needSkillListText = '' if len(self.skillinfolistDict[skillConstant].NeedSkillList) <= 0 else \
            self.neeskillListFormat % (
                '\r\n'.join(needSkillContent)
            )

            ###

            skillScaleContent = []
            for scaleInfo in self.skillinfolistDict[skillConstant].SkillScale:
                skillScaleItemText = self.skillScaleItemFormat % (
                    scaleInfo.lv, scaleInfo.x, scaleInfo.y,
                    self.leeCommon.isLastReturn(
                        self.skillinfolistDict[skillConstant].SkillScale,
                        scaleInfo, '', ','))
                skillScaleContent.append(skillScaleItemText)

            skillScaleText = '' if len(self.skillinfolistDict[skillConstant].SkillScale) <= 0 else \
            self.skillScaleListFormat % (
                '\r\n'.join(skillScaleContent)
            )

            ###

            singleItemText = self.singleSkillinfoFormat % (
                self.skillinfolistDict[skillConstant].Constant,
                self.skillinfolistDict[skillConstant].Constant,
                self.skillinfolistDict[skillConstant].SkillName,
                self.skillinfolistDict[skillConstant].MaxLv, ', '.join(
                    self.skillinfolistDict[skillConstant].SpAmount),
                str(self.skillinfolistDict[skillConstant].bSeperateLv).lower(),
                ', '.join(self.skillinfolistDict[skillConstant].AttackRange),
                _needSkillListText + needSkillListText + skillScaleText,
                self.leeCommon.isLastReturn(self.skillinfolistDict,
                                            skillConstant, '', ','))
            singleItemText = singleItemText.replace('{  }', '{ }')
            fullSkillinfolistText.append(singleItemText)

        luaContent = self.skillinfoListFormat % (
            '\r\n'.join(fullSkillinfolistText))

        fullSavePath = os.path.abspath(savepath)
        os.makedirs(os.path.dirname(fullSavePath), exist_ok=True)
        luafile = open(fullSavePath, 'w', encoding='latin1', newline='')
        luafile.write(luaContent.replace('\r\r', '\r'))
        luafile.close()

    def clear(self):
        self.skillinfolistDict.clear()

    def items(self):
        return self.skillinfolistDict

    def getSkillinfo(self, skillConstant):
        return None if skillConstant not in self.skillinfolistDict else self.skillinfolistDict[
            skillConstant]

    def getItemAttribute(self, skillConstant, attribname, dstEncode='gbk'):
        try:
            skilldata = self.getSkillinfo(skillConstant)
            if skilldata == None: return None
            value = getattr(skilldata, attribname, None)
            if value == None: return None
            return value.encode('latin1').decode(dstEncode,
                                                 errors='backslashreplace')
        except:
            print('getItemAttribute: 处理 %s 的 %s 字段时出问题, 内容为: \r\n%s',
                  (skillConstant, attribname, value))
            raise

    def setItemAttribute(self,
                         skillConstant,
                         attribname,
                         value,
                         srcEncode='gbk'):
        try:
            skilldata = self.getSkillinfo(skillConstant)
            if skilldata == None: return False
            value = value.encode(srcEncode).decode('latin1')
            return setattr(self.skillinfolistDict[skillConstant], attribname,
                           value)
        except:
            print('setItemAttribute: 处理 %d 的 %s 字段时出问题, 内容为: \r\n%s',
                  (skillConstant, attribname, value))
            raise
Exemple #22
0
class LeePatchManager:
    '''
    用于管理补丁文件列表的操作类
    '''
    class SourceFileNotFoundError(FileNotFoundError):
        pass

    def __init__(self):
        self.leeCommon = LeeCommon()
        self.stagingFiles = []
        self.patchesFiles = []
        self.backupFiles = []

        self.forceRemoveDirs = [
            'AI', 'AI_sakray', '_tmpEmblem', 'memo', 'Replay', 'SaveData',
            'Navigationdata', 'System'
        ]
        return

    def __getSessionPath(self):
        '''
        获取最后一次应用补丁时,路径信息数据库的存储路径
        '''
        revertDir = self.leeCommon.resources('Databases/RevertData')
        os.makedirs(revertDir, exist_ok=True)
        sessionInfoFile = os.path.abspath('%s/LeeClientRevert.json' %
                                          revertDir)
        return sessionInfoFile

    def __createSession(self):
        self.stagingFiles.clear()
        self.backupFiles.clear()
        self.patchesFiles.clear()

    def __pathrestore(self, dictobj):
        '''
        用于处理 dictobj 字典对象中存储的路径
        把反斜杠转换回当前系统的路径分隔符
        '''
        dictobj['src'] = dictobj['src'].replace('\\', os.path.sep)
        dictobj['dst'] = dictobj['dst'].replace('\\', os.path.sep)
        return dictobj

    def __pathnorm(self, dictobj):
        '''
        用于处理 dictobj 字典对象中存储的路径
        把斜杠转换回统一的反斜杠
        '''
        dictobj['src'] = dictobj['src'].replace('/', '\\')
        dictobj['dst'] = dictobj['dst'].replace('/', '\\')
        return dictobj

    def __loadSession(self):
        sessionInfoFile = self.__getSessionPath()
        if os.path.exists(sessionInfoFile) and os.path.isfile(sessionInfoFile):
            self.backupFiles.clear()
            self.patchesFiles.clear()

            patchesInfo = json.load(
                open(sessionInfoFile, 'r', encoding='utf-8'))
            self.backupFiles = patchesInfo['backuplist']
            self.patchesFiles = patchesInfo['patchlist']

            # 标准化处理一下反斜杠, 以便在跨平台备份恢复时可以顺利找到文件
            self.backupFiles = [
                filepath.replace('\\', os.path.sep)
                for filepath in self.backupFiles
            ]
            self.patchesFiles = [
                self.__pathrestore(item) for item in self.patchesFiles
            ]

            return True
        return False

    def __copyDirectory(self, srcDirectory):
        scriptDir = self.leeCommon.utility()
        useless_files = ['thumbs.db', '.ds_store', '.gitignore', '.gitkeep']

        # 复制文件,且记录所有可能会应用的目的地址
        for dirpath, _dirnames, filenames in os.walk(srcDirectory):
            for filename in filenames:
                if filename.lower() in useless_files:
                    continue
                file_path = os.path.join(dirpath, filename)
                srcrel_path = os.path.relpath(file_path, scriptDir)
                dstrel_path = os.path.relpath(file_path, srcDirectory)
                self.stagingFiles.append({
                    'src': srcrel_path,
                    'dst': dstrel_path
                })

    def __commitSession(self):
        scriptDir = self.leeCommon.utility(withmark=False)
        leeClientDir = self.leeCommon.client(withmark=False)
        backupDir = self.leeCommon.patches('Backup')

        try:
            # 确保来源文件都存在
            for item in self.stagingFiles:
                src_path = os.path.abspath('%s/%s' % (scriptDir, item['src']))
                if not (os.path.exists(src_path) and os.path.isfile(src_path)):
                    raise self.SourceFileNotFoundError(
                        "Can't found source patch file : %s" % src_path)

            # 备份所有可能会被覆盖的文件,并记录备份成功的文件
            for item in self.stagingFiles:
                dst_path = os.path.abspath('%s/%s' %
                                           (leeClientDir, item['dst']))
                dst_dirs = os.path.dirname(dst_path)

                try:
                    # 目的地文件已经存在,需要备份避免误删
                    if os.path.exists(dst_path) and os.path.isfile(dst_path):
                        backup_path = os.path.abspath('%s/%s' %
                                                      (backupDir, item['dst']))
                        backup_dirs = os.path.dirname(backup_path)
                        os.makedirs(backup_dirs, exist_ok=True)
                        shutil.copyfile(dst_path, backup_path)
                        self.backupFiles.append(item['dst'])
                except BaseException as err:
                    print('文件备份失败 : %s' % dst_path)
                    raise err

            # 执行文件复制工作,并记录复制成功的文件
            for item in self.stagingFiles:
                src_path = os.path.abspath('%s/%s' % (scriptDir, item['src']))
                dst_path = os.path.abspath('%s/%s' %
                                           (leeClientDir, item['dst']))
                dst_dirs = os.path.dirname(dst_path)

                os.makedirs(dst_dirs, exist_ok=True)
                if os.path.exists(dst_path):
                    os.remove(dst_path)
                shutil.copyfile(src_path, dst_path)
                # print('复制 %s 到 %s' % (src_path, dst_path))
                self.patchesFiles.append(item)

        except BaseException as err:
            # 根据 self.backupFiles 和 self.patchesFiles 记录的信息回滚后报错
            print('_commitSession 失败, 正在回滚...')
            if self.doRevertPatch(loadSession=False):
                print('回滚成功, 请检查目前的文件状态是否正确')
            else:
                print('很抱歉, 回滚失败了, 请检查目前的文件状态')
            return False

        # 记录备份和成功替换的文件信息
        sessionInfoFile = self.__getSessionPath()
        if os.path.exists(sessionInfoFile):
            os.remove(sessionInfoFile)

        # 标准化处理一下反斜杠, 以便在跨平台备份恢复时可以顺利找到文件
        self.backupFiles = [
            filepath.replace('/', '\\') for filepath in self.backupFiles
        ]
        self.patchesFiles = [
            self.__pathnorm(item) for item in self.patchesFiles
        ]

        json.dump(
            {
                'patchtime': time.strftime('%Y-%m-%d %H:%M:%S',
                                           time.localtime()),
                'backuplist': self.backupFiles,
                'patchlist': self.patchesFiles
            },
            open(sessionInfoFile, 'w', encoding='utf-8'),
            ensure_ascii=False,
            indent=4)

        return True

    def canRevert(self):
        self.__loadSession()
        leeClientDir = self.leeCommon.client()

        restoreFileInfo = []

        for folder in self.forceRemoveDirs:
            for dirpath, _dirnames, filenames in os.walk(
                    os.path.join(leeClientDir, folder)):
                for filename in filenames:
                    fullpath = os.path.join(dirpath, filename)
                    restoreFileInfo.append(
                        [1, os.path.relpath(fullpath, leeClientDir)])

        for item in self.patchesFiles:
            path = item['dst']
            restoreFileInfo.append([1, path])

        return len(restoreFileInfo) > 0

    def doRevertPatch(self, loadSession=True):
        if loadSession:
            self.__loadSession()

        leeClientDir = self.leeCommon.client(withmark=False)
        backupDir = self.leeCommon.patches('Backup')
        sessionInfoFile = self.__getSessionPath()

        # 删除之前应用的文件
        for item in self.patchesFiles:
            abspath = os.path.abspath('%s/%s' % (leeClientDir, item['dst']))
            if os.path.exists(abspath) and os.path.isfile(abspath):
                # print('即将删除 : %s' % abspath)
                os.remove(abspath)

        # 删除一些需要强制移除的目录
        for folder in self.forceRemoveDirs:
            for dirpath, _dirnames, filenames in os.walk(
                    self.leeCommon.client(folder)):
                for filename in filenames:
                    fullpath = os.path.join(dirpath, filename)
                    if os.path.exists(fullpath) and os.path.isfile(fullpath):
                        # print('强制移除 : %s' % fullpath)
                        os.remove(fullpath)

        # 还原之前备份的文件列表
        for item in self.backupFiles:
            backup_path = os.path.abspath('%s/%s' % (backupDir, item))
            source_path = os.path.abspath('%s/%s' % (leeClientDir, item))
            shutil.copyfile(backup_path, source_path)

        if os.path.exists(backupDir) and os.path.isdir(backupDir):
            shutil.rmtree(backupDir)

        if os.path.exists(sessionInfoFile) and os.path.isfile(sessionInfoFile):
            os.remove(sessionInfoFile)

        self.leeCommon.removeEmptyDirectorys(leeClientDir)
        return True

    def doApplyPatch(self, clientver):
        '''
        应用特定版本的补丁
        '''
        clientList = self.leeCommon.getRagexeClientList(
            self.leeCommon.patches())

        if not clientver in clientList:
            self.leeCommon.exitWithMessage('您期望切换的版本号 %s 是无效的' % clientver)

        beforeDir = self.leeCommon.special(None, 'patches_before')
        ragexeDir = self.leeCommon.special(clientver, 'build')
        clientOriginDir = self.leeCommon.special(clientver, 'origin')
        clientTranslatedDir = self.leeCommon.special(clientver, 'translated')
        clientTemporaryDir = self.leeCommon.special(clientver, 'temporary')
        afterDir = self.leeCommon.special(None, 'patches_after')

        importBeforeDir = self.leeCommon.special(None, 'import_before')
        importClientDir = self.leeCommon.special(clientver, 'import_version')
        importAftertDir = self.leeCommon.special(None, 'import_after')

        # 确认对应的资源目录在是存在的
        if not self.leeCommon.isDirectoryExists(beforeDir):
            self.leeCommon.exitWithMessage('无法找到 BeforePatches 的 Base 目录: %s' %
                                           beforeDir)
        if not self.leeCommon.isDirectoryExists(ragexeDir):
            self.leeCommon.exitWithMessage('无法找到 %s 版本的 Ragexe 目录: %s' %
                                           (clientver, ragexeDir))
        if not self.leeCommon.isDirectoryExists(clientOriginDir):
            self.leeCommon.exitWithMessage('无法找到 %s 版本的 Original 目录: %s' %
                                           (clientver, clientOriginDir))
        if not self.leeCommon.isDirectoryExists(clientTranslatedDir):
            self.leeCommon.exitWithMessage('无法找到 %s 版本的 Translated 目录: %s' %
                                           (clientver, clientTranslatedDir))
        if not self.leeCommon.isDirectoryExists(afterDir):
            self.leeCommon.exitWithMessage('无法找到 AfterPatches 的 Base 目录: %s' %
                                           afterDir)

        if not self.leeCommon.isDirectoryExists(importBeforeDir):
            self.leeCommon.exitWithMessage(
                '无法找到 BeforePatches 的 Import 目录: %s' % importBeforeDir)
        if not self.leeCommon.isDirectoryExists(importClientDir):
            self.leeCommon.exitWithMessage('无法找到 %s 版本的 Import 目录: %s' %
                                           (clientver, importClientDir))
        if not self.leeCommon.isDirectoryExists(importAftertDir):
            self.leeCommon.exitWithMessage(
                '无法找到 AfterPatches 的 Import 目录: %s' % importAftertDir)

        # 创建一个事务并执行复制工作, 最后提交事务
        self.__createSession()
        self.__copyDirectory(beforeDir)
        self.__copyDirectory(importBeforeDir)  # Import
        self.__copyDirectory(ragexeDir)
        self.__copyDirectory(clientOriginDir)
        self.__copyDirectory(clientTemporaryDir)
        self.__copyDirectory(clientTranslatedDir)
        self.__copyDirectory(importClientDir)  # Import
        self.__copyDirectory(afterDir)
        self.__copyDirectory(importAftertDir)  # Import

        if not self.__commitSession():
            print('应用特定版本的补丁过程中发生错误, 终止...')
            return False

        return True
Exemple #23
0
class LeeIteminfoLua:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.itemInfoDict = {}

        self.singleItemFormat = \
'''	[%s] = {
		unidentifiedDisplayName = "%s",
		unidentifiedResourceName = "%s",
		unidentifiedDescriptionName = {
%s
		},
		identifiedDisplayName = "%s",
		identifiedResourceName = "%s",
		identifiedDescriptionName = {
%s
		},
		slotCount = %s,
		ClassNum = %s
	}%s'''.replace('\n', '\r\n').replace('\r\r', '\r')

        self.itemInfoFormat = \
'''tbl = {
%s
}

main = function()
	for ItemID,DESC in pairs(tbl) do
		result, msg = AddItem(ItemID, DESC.unidentifiedDisplayName, DESC.unidentifiedResourceName, DESC.identifiedDisplayName, DESC.identifiedResourceName, DESC.slotCount, DESC.ClassNum)
		if not result then
			return false, msg
		end
		for k,v in pairs(DESC.unidentifiedDescriptionName) do
			result, msg = AddItemUnidentifiedDesc(ItemID, v)
			if not result then
				return false, msg
			end
		end
		for k,v in pairs(DESC.identifiedDescriptionName) do
			result, msg = AddItemIdentifiedDesc(ItemID, v)
			if not result then
				return false, msg
			end
		end
	end
	return true, "good"
end
'''.replace('\n', '\r\n').replace('\r\r', '\r')

    def __normdesc(self, desc):
        descLines = []
        for lineNo in desc:
            descLines.append(desc[lineNo])
        return None if not descLines else '\r\n'.join(descLines)

    def __quotedesc(self, descLines):
        if descLines is None:
            return ''
        descLines = descLines.replace('\r\n', '\n').split('\n')
        for index, line in enumerate(descLines):
            line = line.replace('"', r'\"').replace(r'\\', '\\')
            descLines[index] = '\t\t\t"%s"%s' % (line, ',' if (index + 1) < len(descLines) else '')
        return '' if not descLines else '\r\n'.join(descLines)

    def load(self, filepath):
        self.itemInfoDict.clear()

        try:
            luafile = open(filepath, 'r', encoding = 'latin1')
            content = luafile.read()
            luafile.close()

            lua = LuaRuntime(unpack_returned_tuples=True)
            lua.execute(content)
            g = lua.globals()
        except Exception as _err:
            print('解析文件时发生了错误: %s' % filepath)
            raise

        for itemID in list(g.tbl):
            try:
                singleItem = LeeIteminfoSingleItem(
                    itemID = itemID,
                    unidentifiedDisplayName = g.tbl[itemID]['unidentifiedDisplayName'],
                    unidentifiedResourceName = g.tbl[itemID]['unidentifiedResourceName'],
                    unidentifiedDescriptionName = self.__normdesc(
                        g.tbl[itemID]['unidentifiedDescriptionName']
                    ),
                    identifiedDisplayName = g.tbl[itemID]['identifiedDisplayName'],
                    identifiedResourceName = g.tbl[itemID]['identifiedResourceName'],
                    identifiedDescriptionName = self.__normdesc(
                        g.tbl[itemID]['identifiedDescriptionName']
                    ),
                    slotCount = g.tbl[itemID]['slotCount'],
                    ClassNum = g.tbl[itemID]['ClassNum'],
                )
                self.itemInfoDict[self.leeCommon.atoi(itemID)] = singleItem
            except Exception as _err:
                print('Error Item ID = %d' % itemID)
                raise

    def save(self, savepath):
        # 构建表格主体部分, 先定义一下格式部分
        fullItemText = []    # 保存每一个道具完整的文本段

        for itemID in sorted(self.itemInfoDict):
            singleItemText = self.singleItemFormat % (
                self.itemInfoDict[itemID].itemID,
                self.itemInfoDict[itemID].unidentifiedDisplayName,
                self.itemInfoDict[itemID].unidentifiedResourceName,
                self.__quotedesc(self.itemInfoDict[itemID].unidentifiedDescriptionName),
                self.itemInfoDict[itemID].identifiedDisplayName,
                self.itemInfoDict[itemID].identifiedResourceName,
                self.__quotedesc(self.itemInfoDict[itemID].identifiedDescriptionName),
                self.itemInfoDict[itemID].slotCount,
                self.itemInfoDict[itemID].ClassNum,
                self.leeCommon.isLastReturn(sorted(self.itemInfoDict), itemID, '', ',')
            )
            fullItemText.append(singleItemText)

        luaContent = self.itemInfoFormat % ('\r\n'.join(fullItemText))

        fullSavePath = os.path.abspath(savepath)
        os.makedirs(os.path.dirname(fullSavePath), exist_ok = True)
        luafile = open(fullSavePath, 'w', encoding = 'latin1', newline = '')
        luafile.write(luaContent.replace('\r\r', '\r'))
        luafile.close()

    def items(self):
        return self.itemInfoDict

    def getIteminfo(self, itemID):
        return None if itemID not in self.itemInfoDict else self.itemInfoDict[itemID]

    def getItemAttribute(self, itemID, attribname, dstEncode = 'gbk'):
        try:
            itemdata = self.getIteminfo(itemID)
            if itemdata is None:
                return None
            value = getattr(itemdata, attribname, None)
            if value is None:
                return None
            if isinstance(value, list):
                for index, val in enumerate(value):
                    value[index] = val.encode('latin1').decode(dstEncode)
                return value
            else:
                return value.encode('latin1').decode(dstEncode)
        except:
            print('getItemAttribute: 处理 %d 的 %s 字段时出问题, 内容为: \r\n%s', (itemID, attribname, value))
            raise

    def setItemAttribute(self, itemID, attribname, value, srcEncode = 'gbk'):
        try:
            itemdata = self.getIteminfo(itemID)
            if itemdata is None:
                return False
            if isinstance(value, list):
                for index, val in enumerate(value):
                    value[index] = val.encode(srcEncode).decode('latin1')
            else:
                value = value.encode(srcEncode).decode('latin1')
            return setattr(self.itemInfoDict[itemID], attribname, value)
        except:
            print('setItemAttribute: 处理 %d 的 %s 字段时出问题, 内容为: \r\n%s', (itemID, attribname, value))
            raise

    def clear(self):
        self.itemInfoDict.clear()
Exemple #24
0
 def __init__(self):
     self.leeCommon = LeeCommon()
Exemple #25
0
class LeeTowninfoLua:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.towninfoDict = {}

        self.singleMapFormat = '\t%s = {\r\n%s\r\n\t}%s'
        self.singleInfoFormat = '\t\t{ name = "%s", X = %d, Y = %d, TYPE = %d }%s'

        self.townInfoFormat = \
'''mapNPCInfoTable = {
%s
}

main = function()
	for mapName, info in pairs(mapNPCInfoTable) do
		for k, v in pairs(info) do
			result, msg = AddTownInfo(mapName, v.name, v.X, v.Y, v.TYPE)
			if not result == true then
				return false, msg
			end
		end
	end
	return true, "good"
end
'''.replace('\n', '\r\n').replace('\r\r', '\r')

    def load(self, filepath):
        self.towninfoDict.clear()

        luafile = open(filepath, 'r', encoding='latin1')
        content = luafile.read()
        luafile.close()

        lua = LuaRuntime(unpack_returned_tuples=True)
        lua.execute(content)
        g = lua.globals()

        for mapname in list(g.mapNPCInfoTable):
            self.towninfoDict[mapname] = []
            for tagid in list(g.mapNPCInfoTable[mapname]):
                singleItem = LeeTowninfoSingleItem(
                    tagname=g.mapNPCInfoTable[mapname][tagid]['name'],
                    gbkname=g.mapNPCInfoTable[mapname][tagid]['name'].encode(
                        'latin1').decode('gbk'),
                    x=g.mapNPCInfoTable[mapname][tagid]['X'],
                    y=g.mapNPCInfoTable[mapname][tagid]['Y'],
                    tagtype=g.mapNPCInfoTable[mapname][tagid]['TYPE'])
                self.towninfoDict[mapname].append(singleItem)

    def save(self, savepath):
        fullMapsText = []

        for mapname in sorted(self.towninfoDict):
            singleItemText = []
            for taginfo in self.towninfoDict[mapname]:
                infoText = self.singleInfoFormat % (
                    taginfo.tagname, taginfo.x, taginfo.y, taginfo.tagtype,
                    self.leeCommon.isLastReturn(self.towninfoDict[mapname],
                                                taginfo, '', ','))
                singleItemText.append(infoText)

            singleMapText = self.singleMapFormat % (
                mapname, '\r\n'.join(singleItemText),
                self.leeCommon.isLastReturn(sorted(self.towninfoDict), mapname,
                                            '', ','))
            fullMapsText.append(singleMapText)

        luaContent = self.townInfoFormat % ('\r\n'.join(fullMapsText))

        fullSavePath = os.path.abspath(savepath)
        os.makedirs(os.path.dirname(fullSavePath), exist_ok=True)
        luafile = open(fullSavePath, 'w', encoding='latin1', newline='')
        luafile.write(luaContent.replace('\r\r', '\r'))
        luafile.close()

    def clear(self):
        self.towninfoDict.clear()

    def replaceName(self, oldname, newname):
        oldname_latin1 = oldname.encode('gbk').decode('latin1')
        newname_latin1 = newname.encode('gbk').decode('latin1')

        for mapname in self.towninfoDict:
            for taginfo in self.towninfoDict[mapname]:
                if taginfo.tagname == oldname_latin1:
                    taginfo.tagname = newname_latin1
class LeeSkilldescriptLua:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.skilldescriptDict = {}
        self.SKID = []
        self.singleSkilldescriptFormat = '\t[SKID.%s] = {\r\n%s\r\n\t}%s'
        self.skillDescriptFormat = 'SKILL_DESCRIPT = {\r\n%s\r\n}\r\n'

    def createLuaTable(self, luaContent, regex, pos, tableName):
        matches = re.finditer(regex, luaContent,
                              re.MULTILINE | re.IGNORECASE | re.DOTALL)

        tables = []
        for match in matches:
            tables.append(match.group(pos))

        tables = set(tables)
        setattr(self, tableName, list(tables))

        contentList = []
        for num, item in enumerate(tables):
            contentList.append('%s = %s' % (item, num + 1))

        return '%s = { %s }' % (tableName, ', '.join(contentList))

    def getSkillConstant(self, skillFakeID):
        return self.SKID[skillFakeID - 1]

    def load(self, filepath):
        self.skilldescriptDict.clear()

        luafile = open(filepath, 'r', encoding='latin1')
        content = luafile.read()
        luafile.close()

        # 读取并构建假设的 SKID 常量列表
        SKIDTableContent = self.createLuaTable(
            content, r"[\[|{]\s*SKID\.(.*?)\s*[,|}|\]]", 1, 'SKID')

        # 读取上面构建的常量列表
        lua = LuaRuntime(unpack_returned_tuples=True)
        lua.execute(SKIDTableContent)

        # 然后正式载入 skillinfolist 接下来进行处理
        lua.execute(content)

        # 再将信息提取到内存中存储起来
        g = lua.globals()

        for skillFakeID in list(g.SKILL_DESCRIPT):
            skillDescriptLuaObject = g.SKILL_DESCRIPT[skillFakeID]
            skillConstant = self.getSkillConstant(skillFakeID)

            descriptLines = []
            for descriptLine in list(skillDescriptLuaObject):
                descriptLines.append(skillDescriptLuaObject[descriptLine])

            descriptSingleItem = LeeSkilldescriptSingleItem(
                Constant=skillConstant, Description='\r\n'.join(descriptLines))

            self.skilldescriptDict[skillConstant] = descriptSingleItem

    def save(self, savepath):
        fullSkilldescriptText = []

        for skillConstant in self.skilldescriptDict:
            skillDescriptLines = []
            skillDescriptText = self.skilldescriptDict[
                skillConstant].Description
            skillDescriptList = skillDescriptText.split('\r\n')
            for line in skillDescriptList:
                skillDescriptLines.append(
                    '\t\t"%s"%s' % (line,
                                    self.leeCommon.isLastReturn(
                                        skillDescriptList, line, '', ',')))

            singleSkilldescriptText = self.singleSkilldescriptFormat % (
                skillConstant, '\r\n'.join(skillDescriptLines),
                self.leeCommon.isLastReturn(self.skilldescriptDict,
                                            skillConstant, '', ','))

            fullSkilldescriptText.append(singleSkilldescriptText)

        luaContent = self.skillDescriptFormat % (
            '\r\n'.join(fullSkilldescriptText))

        fullSavePath = os.path.abspath(savepath)
        os.makedirs(os.path.dirname(fullSavePath), exist_ok=True)
        luafile = open(fullSavePath, 'w', encoding='latin1', newline='')
        luafile.write(luaContent.replace('\r\r', '\r'))
        luafile.close()

    def clear(self):
        self.skilldescriptDict.clear()

    def items(self):
        return self.skilldescriptDict

    def getSkilldescript(self, skillConstant):
        return None if skillConstant not in self.skilldescriptDict else self.skilldescriptDict[
            skillConstant]

    def getItemAttribute(self, skillConstant, attribname, dstEncode='gbk'):
        try:
            skilldata = self.getSkilldescript(skillConstant)
            if skilldata == None: return None
            value = getattr(skilldata, attribname, None)
            if value == None: return None
            if isinstance(value, list):
                for index, val in enumerate(value):
                    value[index] = val.encode('latin1').decode(
                        dstEncode, errors='backslashreplace')
                return value
            else:
                return value.encode('latin1').decode(dstEncode,
                                                     errors='backslashreplace')
        except:
            print('getItemAttribute: 处理 %s 的 %s 字段时出问题, 内容为: \r\n%s',
                  (skillConstant, attribname, value))
            raise

    def setItemAttribute(self,
                         skillConstant,
                         attribname,
                         value,
                         srcEncode='gbk'):
        try:
            skilldata = self.getSkilldescript(skillConstant)
            if skilldata == None: return False
            if isinstance(value, list):
                for index, val in enumerate(value):
                    value[index] = val.encode(srcEncode).decode('latin1')
            else:
                value = value.encode(srcEncode).decode('latin1')
            return setattr(self.skilldescriptDict[skillConstant], attribname,
                           value)
        except:
            print('setItemAttribute: 处理 %d 的 %s 字段时出问题, 内容为: \r\n%s',
                  (skillConstant, attribname, value))
            raise
 def __init__(self):
     self.leeCommon = LeeCommon()
     self.skilldescriptDict = {}
     self.SKID = []
     self.singleSkilldescriptFormat = '\t[SKID.%s] = {\r\n%s\r\n\t}%s'
     self.skillDescriptFormat = 'SKILL_DESCRIPT = {\r\n%s\r\n}\r\n'
class LeeBaseTranslator:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.leeFileIO = None
        self.translateDefaultDBPath = None
        self.translateMap = {}
        self.reSrcPathPattern = None
        self.reDstPathPattern = None

    def clear(self):
        self.translateMap.clear()

    def load(self, translateDBPath = None):
        self.clear()
        if translateDBPath is None:
            translateDBPath = self.translateDefaultDBPath
        translatePath = '%s/%s' % (self.leeCommon.utility(withmark=False), translateDBPath)
        if not self.leeCommon.isFileExists(translatePath):
            return False
        try:
            self.translateMap = json.load(open(translatePath, 'r', encoding = 'utf-8'))
            return True
        except FileNotFoundError as _err:
            print('很抱歉, 无法打开翻译数据库文件: %s' % translatePath)
            raise

    def save(self, translateDBPath = None):
        if translateDBPath is None:
            translateDBPath = self.translateDefaultDBPath
        savePath = self.leeCommon.utility(translateDBPath)
        json.dump(
            self.translateMap, open(savePath, 'w', encoding = 'utf-8', newline = '\n'),
            indent = 4, ensure_ascii = False
        )
        return True

    def doTranslate(self, specifiedClientVer = None):
        leeClientDir = self.leeCommon.client()
        patchesDir = self.leeCommon.patches()

        if self.reSrcPathPattern is None:
            return False
        if self.reDstPathPattern is None:
            return False

        sourceFilepathList = []
        for dirpath, _dirnames, filenames in os.walk(patchesDir):
            for filename in filenames:
                fullpath = os.path.normpath('%s/%s' % (dirpath, filename))
                if not re.match(self.reSrcPathPattern, fullpath, re.I):
                    continue
                sourceFilepathList.append(fullpath)

        self.load()

        for sourceFilepath in sourceFilepathList:
            if (specifiedClientVer is not None) and (specifiedClientVer not in sourceFilepath):
                continue
            print('正在汉化, 请稍候: %s' % os.path.relpath(sourceFilepath, leeClientDir))
            match = re.search(
                self.reDstPathPattern, sourceFilepath, re.MULTILINE | re.IGNORECASE | re.DOTALL
            )
            if match is None:
                self.leeCommon.exitWithMessage('无法确定翻译后的文件的存放位置, 程序终止')
            destinationPath = '%s/Translated/%s' % (match.group(1), match.group(2))
            self.translate(sourceFilepath, destinationPath)
            print('汉化完毕, 保存到: %s\r\n' % os.path.relpath(destinationPath, leeClientDir))

        return True

    def translate(self, srcFilepath, dstFilepath):
        pass
Exemple #29
0
class LeePublisher:
    def __init__(self):
        self.leeCommon = LeeCommon()
        self.leeConfig = LeeConfigure()
        self.patchManager = LeePatchManager()

    def removeOldGrf(self):
        leeClientDir = self.leeCommon.client(withmark=False)
        grfFiles = glob.glob('%s/*.grf' % leeClientDir)

        for filepath in grfFiles:
            os.remove(filepath)

    def ensureHasGRF(self):
        leeClientDir = self.leeCommon.client(withmark=False)

        if LeeGrf().isGrfExists():
            reUseExistsGrfFiles = self.leeCommon.confirm(
                [
                    '当前客户端目录已经存在了 Grf 文件', '请问是直接使用他们(y), 还是需要重新生成(n)?', '',
                    '注意: 若选重新生成(n), 那么目前的 Grf 文件将被立刻删除.'
                ],
                title='文件覆盖确认',
                prompt='是否继续使用这些 Grf 文件?',
                inject=self,
                cancelExec='inject.removeOldGrf()',
                evalcmd=None)

            if reUseExistsGrfFiles:
                return

        LeeGrf().makeGrf('%s/data' % leeClientDir,
                         '%s/data.grf' % leeClientDir)

        if not LeeGrf().isGrfExists():
            self.leeCommon.exitWithMessage(
                '请先将 data 目录打包为 Grf 文件, 以便提高文件系统的复制效率.')

    def makeSource(self, useGrf):
        '''
        将 LeeClient 的内容复制到打包源目录(并删除多余文件)
        '''
        leeClientDir = self.leeCommon.client(withmark=False)
        packageSourceCfg = self.leeConfig.get('PackageSource')

        # 判断是否已经切换到某个客户端版本
        if not self.patchManager.canRevert():
            self.leeCommon.exitWithMessage(
                '请先将 LeeClient 切换到某个客户端版本, 否则制作出来的 grf 内容不完整.')

        if useGrf:
            self.ensureHasGRF()

        # 判断磁盘的剩余空间是否足够
        currentDriver = self.leeCommon.utility()[0]
        currentFreeSpace = self.leeCommon.getDiskFreeSpace(currentDriver)
        if currentFreeSpace <= 1024 * 1024 * 1024 * 4:
            self.leeCommon.exitWithMessage('磁盘 %s: 的空间不足 4 GB, 请清理磁盘释放更多空间.' %
                                           currentDriver)

        # 生成一个 LeeClient 平级的发布目录
        nowTime = time.strftime("%Y%m%d_%H%M%S", time.localtime())
        releaseDirName = 'LeeClient_Release_%s' % nowTime
        releaseDirpath = self.leeCommon.client('../' + releaseDirName,
                                               withmark=False)

        # 先列出需要复制到打包源的文件列表
        filterDirectories = ['Utility', '.git', '.vscode']
        filterFiles = ['.gitignore', '.DS_Store']

        # 若使用 grf 文件的话, 则排除掉 data 目录
        if useGrf:
            filterDirectories.append('data')

        print('正在分析需要复制的文件, 请稍后...')
        copyFileList = []
        for dirpath, dirnames, filenames in os.walk(leeClientDir):
            for filename in filenames:
                fullpath = os.path.join(dirpath, filename)

                # 过滤一下不需要导出的目录 (大小写敏感)
                dirnames[:] = [
                    d for d in dirnames if d not in filterDirectories
                ]

                # 过滤一下不需要导出的文件 (不区分大小写)
                isBlocked = False
                for filterFile in filterFiles:
                    if filterFile.lower() in filename.lower():
                        isBlocked = True
                        break
                if isBlocked:
                    continue

                # 判断是否需要移除调试版的登录器主程序
                if filename.lower().endswith(
                        '.exe') and '_ReportError.exe' in filename:
                    if packageSourceCfg['AutoRemoveDebugClient']:
                        continue

                # 记录到 copyFileList 表示需要复制此文件到打包源
                copyFileList.append(fullpath)

        print('分析完毕, 共需复制 %d 个文件, 马上开始.' % len(copyFileList))

        # 确定游戏启动入口的相对路径
        srcClientName = packageSourceCfg['SourceClientName']
        pubClientName = packageSourceCfg['PublishClientName']

        if packageSourceCfg['SourceClientName'] == 'auto':
            for srcFilepath in copyFileList:
                if not srcFilepath.lower().endswith('.exe'):
                    continue
                filename = os.path.basename(srcFilepath)
                if '_patched.exe' in filename:
                    srcClientName = filename
                    break

        # 把文件拷贝到打包源
        # TODO: 最好能够显示文件的复制进度
        # http://zzq635.blog.163.com/blog/static/1952644862013125112025129/
        for srcFilepath in copyFileList:
            # 获取相对路径, 用于复制到目标文件时使用
            relFilepath = os.path.relpath(srcFilepath, leeClientDir)

            # 构建复制到的目标文件全路径
            dstFilepath = '%s/%s' % (releaseDirpath, relFilepath)

            # 对游戏的入口程序进行重命名操作
            bIsSourceClient = False
            if srcFilepath.lower().endswith('.exe'):
                if os.path.basename(srcFilepath) == srcClientName:
                    bIsSourceClient = True
                    dstFilepath = self.leeCommon.replaceBasename(
                        dstFilepath, pubClientName)

            print('正在复制: %s%s' % (relFilepath, ' (%s)' %
                                  pubClientName if bIsSourceClient else ''))
            os.makedirs(os.path.dirname(dstFilepath), exist_ok=True)
            shutil.copyfile(srcFilepath, dstFilepath)

        # 把最终发布源所在的目录当做参数返回值回传
        return releaseDirpath

    def getZipFilename(self, sourceDir):
        if sourceDir.endswith('\\') or sourceDir.endswith('/'):
            sourceDir = sourceDir[:-1]
        return '%s.zip' % sourceDir

    def getPackageSourceList(self, dirpath):
        '''
        根据指定的 dir 中枚举出子目录的名字 (即打包源的目录名)
        返回: Array 保存着每个子目录名称的数组
        '''
        dirlist = []
        osdirlist = os.listdir(dirpath)

        for dname in osdirlist:
            if not dname.lower().startswith('leeclient_release_'):
                continue
            if os.path.isdir(os.path.normpath(dirpath) + os.path.sep + dname):
                dirlist.append(dname)

        dirlist.sort()
        return dirlist

    def makeZip(self, sourceDir, zipSavePath):
        '''
        将打包源目录直接压缩成一个 zip 文件
        '''
        # https://blog.csdn.net/dou_being/article/details/81546172
        # https://blog.csdn.net/zhd199500423/article/details/80853405

        zipCfg = self.leeConfig.get('ZipConfigure')
        return LeeZipfile().zip(sourceDir, zipSavePath,
                                zipCfg['TopLevelDirName'])

    def makeSetup(self, sourceDir, setupOutputDir=None):
        '''
        将打包源目录直接制作成一个 Setup 安装程序
        '''
        if setupOutputDir is None:
            setupOutputDir = './Output'

        sourceDir = os.path.abspath(sourceDir)
        setupOutputDir = os.path.abspath(setupOutputDir)

        # 判断 InnoSetup 是否已经安装
        if not self.__isInnoSetupInstalled():
            # 若未安装则进行安装 (此处需要管理员权限)
            if self.__instInnoSetup():
                # 安装后将补丁文件复制到 InnoSetup 的安装目录中
                self.__applyInnoSetupLdrPatch()
            else:
                self.leeCommon.exitWithMessage('无法成功安装 Inno Setup, 请联系作者进行排查')

        # 再次进行环境检查, 确保一切就绪
        if not self.__checkInnoSetupStatus():
            self.leeCommon.exitWithMessage('本机的 Inno Setup 环境不正确, 无法继续进行工作.')

        # 读取目前的配置值, 请求用户确认后继续
        configure = self.__choiceConfigure()
        if configure is None:
            self.leeCommon.exitWithMessage('您没有确定用于生成 Setup 的配置, 无法继续进行工作.')

        # 若是第一次使用, 则需要帮用户生成一个 GUID 并嘱咐用户保存 GUID
        if str(configure['LeeAppId']).lower() == 'none':
            # 需要帮用户初始化一个 AppId 并告知用户记录好这个值
            print('发现配置 “%s” 的 LeeAppId 值为 None' % configure['LeeName'])
            print('您必须为其分配一个 GUID. 程序已为您自动生成了一个 GUID:')
            print('')
            print(str(uuid.uuid1()).upper())
            print('')
            print('请复制后粘贴到 LeeClientAgent.yml 文件中 SetupConfigure 节点的')
            print('对应选项里面替换掉 None 值, 然后再重试一次.')
            print('')
            self.leeCommon.pauseScreen()
            sys.exit(0)

        # 在 configure 中补充其他参数
        configure['LeePackageSourceDirpath'] = sourceDir
        configure['LeeOutputDir'] = setupOutputDir
        configure['LeeAppId'] = (r'{{%s}' % configure['LeeAppId']).upper()

        # 确认打包源中配置里填写的“主程序”和“游戏设置程序”都存在, 不在则中断
        leeAppExePath = os.path.abspath(
            '%s/%s' % (sourceDir, configure['LeeAppExeName']))
        leeGameSetupExePath = os.path.abspath(
            '%s/%s' % (sourceDir, configure['LeeGameSetupExeName']))
        if not self.leeCommon.isFileExists(leeAppExePath):
            self.leeCommon.exitWithMessage(
                'LeeAppExeName 指向的主程序 %s 不在打包源目录中: %s' %
                (configure['LeeAppExeName'], sourceDir))
        if not self.leeCommon.isFileExists(leeGameSetupExePath):
            self.leeCommon.exitWithMessage(
                'leeGameSetupExePath 指向的主程序 %s 不在打包源目录中: %s' %
                (configure['LeeGameSetupExeName'], sourceDir))

        # 读取脚本模板
        scriptTemplateContent = self.__readScriptTemplate()

        # 根据配置进行配置项的值替换
        scriptFinallyContent = self.__generateFinallyScript(
            scriptTemplateContent, configure)

        # 将最终的脚本保存到临时目录中
        scriptCachePath = self.__saveFinallyScriptToCache(scriptFinallyContent)

        # 调用 ISCC.exe 执行 Setup 的打包操作
        return self.__runInnoScript(scriptCachePath)

    def __isInnoSetupInstalled(self):
        '''
        判断 Inno Setup 是否已经安装到电脑中
        '''
        innoSetupDir = self.__getInnoSetupInstallPath()
        if innoSetupDir is None:
            return False
        return self.leeCommon.isFileExists('%sCompil32.exe' % innoSetupDir)

    def __getInnoSetupInstallPath(self):
        '''
        获取 Inno Setup 的安装目录, 末尾自动补斜杠
        '''
        try:
            if platform.system() != 'Windows':
                self.leeCommon.exitWithMessage(
                    '很抱歉, %s 此函数目前只能在 Windows 平台上运行.' %
                    sys._getframe().f_code.co_name)

            # 根据不同的平台切换注册表路径
            if platform.machine() == 'AMD64':
                innoSetup_key = winreg.OpenKey(
                    winreg.HKEY_LOCAL_MACHINE,
                    'SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Inno Setup 5_is1'
                )
            else:
                innoSetup_key = winreg.OpenKey(
                    winreg.HKEY_LOCAL_MACHINE,
                    'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Inno Setup 5_is1'
                )

            # 读取 Inno Setup 的安装目录
            installLocation, _value_type = winreg.QueryValueEx(
                innoSetup_key, 'InstallLocation')
            if not (installLocation.endswith('\\')
                    or installLocation.endswith('/')):
                installLocation = '%s%s' % (installLocation, os.path.sep)

            return installLocation
        except Exception as _err:
            return None

    def __applyInnoSetupLdrPatch(self):
        '''
        应用 SetupLdr.e32 补丁到 Inno Setup 的安装目录下 (要求管理员权限)
        '''
        # 此过程要求管理员权限, 看看如何检测一下
        if not self.leeCommon.isAdministrator():
            self.leeCommon.exitWithMessage('此操作要求程序“以管理员权限运行”, 请重试.')

        scriptDir = self.leeCommon.utility(withmark=False)
        innoSetupDir = self.__getInnoSetupInstallPath()

        if innoSetupDir is None:
            return False

        srcFilepath = ('%s/Bin/InnoSetup/Resources/Installer/SetupLdr.e32' %
                       scriptDir).replace('/', os.path.sep)
        dstFilepath = ('%sSetupLdr.e32' % innoSetupDir)
        bakFilepath = ('%sSetupLdr.e32.bak' % innoSetupDir)

        if (not self.leeCommon.isFileExists(bakFilepath)
            ) and self.leeCommon.isFileExists(dstFilepath):
            shutil.copyfile(dstFilepath, bakFilepath)

        if not self.leeCommon.isFileExists(srcFilepath):
            return False
        os.remove(dstFilepath)
        shutil.copyfile(srcFilepath, dstFilepath)

        return True

    def __checkInnoSetupStatus(self):
        '''
        检查 Inno Setup 的状态是否正常且符合要求
        '''
        innoSetupDir = self.__getInnoSetupInstallPath()
        if innoSetupDir is None:
            return False

        # Inno Setup 是否已经安装
        if not self.__isInnoSetupInstalled():
            return False

        # Inno Setup 的 ISCC.exe 是否存在
        if not self.leeCommon.isFileExists('%sISCC.exe' % innoSetupDir):
            return False

        # 是否已经安装了 SetupLdr.e32 补丁
        setupLdrMD5 = self.leeCommon.getMD5ForSmallFile('%sSetupLdr.e32' %
                                                        innoSetupDir)
        if setupLdrMD5 != '544dbcf30c8ccb55082709b095173f6c':
            return False

        return True

    def __instInnoSetup(self):
        '''
        安装 Inno Setup 并确保安装成功 (要求管理员权限)
        '''
        # 此过程要求管理员权限, 在此进行检查
        if not self.leeCommon.isAdministrator():
            self.leeCommon.exitWithMessage('此操作要求程序“以管理员权限运行”, 请重试.')

        # 先确认 Inno Setup 的安装程序是否存在
        scriptDir = self.leeCommon.utility(withmark=False)
        installerFilepath = (
            '%s/Bin/InnoSetup/Resources/Installer/innosetup-5.6.1-unicode.exe'
            % scriptDir).replace('/', os.path.sep)
        if not self.leeCommon.isFileExists(installerFilepath):
            return False

        # 执行静默安装过程
        setupProc = subprocess.Popen('%s /VERYSILENT' % installerFilepath,
                                     stdout=sys.stdout,
                                     cwd=os.path.dirname(installerFilepath))
        setupProc.wait()

        # 确认结果并输出提示信息表示压缩结束
        return setupProc.returncode == 0 and self.__isInnoSetupInstalled()

    def choiceExit(self):
        print('不选择任何一个配置的话, 无法继续工作, 请重试.')

    def __choiceConfigure(self):
        '''
        让用户选择一个生成 Setup 的配置
        '''
        # 读取现在的所有 setup 配置
        setupCfg = self.leeConfig.get('SetupConfigure')

        # 若只有一个配置, 则直接选中这个配置, 进入用户确认阶段
        if len(setupCfg) > 1:
            # 列出来让用户进行选择, 选中哪个就把配置读取出来返回
            menus = []
            for cfgKey, cfgValue in enumerate(setupCfg):
                menuItem = [cfgValue['LeeName'], None, cfgKey]
                menus.append(menuItem)

            configure = self.leeCommon.menu(items=menus,
                                            title='选择生成配置',
                                            prompt='请选择用于生成安装程序的配置',
                                            inject=self,
                                            cancelExec='inject.choiceExit()',
                                            withCancel=True,
                                            resultMap=setupCfg)
        else:
            configure = setupCfg[0]

        # 把配置内容列出来让用户进行最终确认
        lines = self.__getConfigureInfos(configure)
        title = '确认配置的各个选项值'
        prompt = '是否继续?'

        if not self.leeCommon.confirm(lines, title, prompt, None, None, None):
            self.leeCommon.exitWithMessage('感谢您的使用, 再见')

        return configure

    def __getConfigureInfos(self, configure, dontPrint=True):
        '''
        给定一个配置的字典对象, 把内容构建成可读样式, 必要的话打印出来
        '''
        configureInfos = [
            '配置名称(LeeName): %s' % configure['LeeName'],
            '安装包唯一编号(LeeAppId): %s' % configure['LeeAppId'],
            '游戏名称(LeeAppName): %s' % configure['LeeAppName'],
            '游戏主程序(LeeAppExeName): %s' % configure['LeeAppExeName'],
            '安装包版本号(LeeAppVersion): %s' % configure['LeeAppVersion'],
            '安装包发布者(LeeAppPublisher): %s' % configure['LeeAppPublisher'],
            '发布者官网(LeeAppURL): %s' % configure['LeeAppURL'],
            '开始菜单程序组名称(LeeDefaultGroupName): %s' %
            configure['LeeDefaultGroupName'],
            '设置程序在开始菜单的名称(LeeGameSetupName): %s' %
            configure['LeeGameSetupName'],
            '设置程序在安装目录中的实际文件名(LeeGameSetupExeName): %s' %
            configure['LeeGameSetupExeName'],
            '安装时的默认目录名(LeeDefaultDirName): %s' %
            configure['LeeDefaultDirName'],
            '最终输出的安装程序文件名(LeeOutputBaseFilename): %s' %
            configure['LeeOutputBaseFilename'],
            '----------------------------------------------------------------',
            '若想修改以上选项的值, 或者想对他们的作用有更详细的了解, 请编辑',
            'Utility 目录下的 LeeClientAgent.yml 配置文件.',
            '----------------------------------------------------------------'
        ]

        if not dontPrint:
            print('\r\n'.join(configureInfos))
        return configureInfos

    def __readScriptTemplate(self):
        '''
        获取 Inno Setup 的脚本模板并作为字符串返回
        '''
        scriptDir = self.leeCommon.utility(withmark=False)
        scriptTemplateFilepath = (
            '%s/Bin/InnoSetup/Resources/Scripts/Scripts_Template.iss' %
            scriptDir).replace('/', os.path.sep)

        if not self.leeCommon.isFileExists(scriptTemplateFilepath):
            return None
        return open(scriptTemplateFilepath, 'r', encoding='utf-8').read()

    def __generateFinallyScript(self, templateContent, configure):
        '''
        把配置套到模板中, 并将处理后的脚本内容返回
        '''
        finallyContent = templateContent

        for k, v in configure.items():
            if v is None:
                continue
            rePattern = '(#define %s ".*?")' % k
            searchResult = re.search(rePattern, finallyContent)
            if searchResult is None:
                continue
            finallyContent = finallyContent.replace(searchResult.group(0),
                                                    '#define %s "%s"' % (k, v))

        return finallyContent

    def __saveFinallyScriptToCache(self, finallyContent):
        '''
        将给定的最终脚本内容保存到一个临时目录中, 并返回脚本的全路径
        '''
        scriptDir = self.leeCommon.utility(withmark=False)
        scriptCacheDir = ('%s/Bin/InnoSetup/Cache/' % scriptDir).replace(
            '/', os.path.sep)
        os.makedirs(scriptCacheDir, exist_ok=True)

        contentHash = self.leeCommon.getMD5ForString(finallyContent)
        scriptCachePath = os.path.abspath('%s%s.iss' %
                                          (scriptCacheDir, contentHash))

        if self.leeCommon.isFileExists(scriptCachePath):
            os.remove(scriptCachePath)

        # 这里保存脚本文件的时候必须用 UTF8 with BOM (对应的 encoding 为 utf-8-sig)
        # 否则 Inno Setup 引擎将无法识别出这是 UTF8 编码, 从而改用 ANSI 去解读脚本文件
        # 最后导致的结果就是中文快捷方式的名称直接变成了乱码
        cfile = open(scriptCachePath, 'w+', encoding='utf-8-sig')
        cfile.write(finallyContent)
        cfile.close()

        return scriptCachePath

    def __runInnoScript(self, scriptPath):
        '''
        调用 ISCC.exe 进行安装程序的制作, 同步等待结束
        '''
        innoSetupDir = self.__getInnoSetupInstallPath()
        if innoSetupDir is None:
            return False

        isccPath = '%sISCC.exe' % innoSetupDir
        if not self.leeCommon.isFileExists(isccPath):
            return False

        isccProc = subprocess.Popen('%s "%s"' % (isccPath, scriptPath),
                                    stdout=sys.stdout,
                                    cwd=os.path.dirname(scriptPath))
        isccProc.wait()

        return isccProc.returncode == 0