上个月时候,zwwooooo同鞋联系我让帮写一个铁血联盟中需要用到的Edt<->Txt转换器,我前前后后大约三个星期完成 到v0.9,能完成基本的功能(但似乎存在一些未知Bug,-_-#)。由于我之后很长一段时间得找工作,所以不能继续维护这个小工具,现在把他的代码开放,如果有高 人能看到这个小工具,有兴趣的话可以继续维护下去,(zwwooooo,真的很不好意思……)。 一些关于程序说明,zwwooooo原文,更详细可以联系zwwooooo本人:
edt(后缀) 是游戏对话文件,游戏里有很多种,但都是有规律的,我把一些规律和流程说一下
一、从 edt 提取出对话部分文本,然后转换并输出为 ansi 码文本,用附件的 000.edt 为例说明
-
把 000.edt 文件用16进制方式打开,对话从"0"处开始,结束标志是2个16进制字节:00 00
-
000.edt 里面每个/每段对话/说明相隔 640 个字节,即"0"开始为第一段,直到16进制双字节出现"00 00"结束,然后第2段话在第 640 字节处开始,也是以"00 00"表示结束,后面的以此类推。 (因为有几种edt文件,每种edt文件的对话间隔不同,所以这个"间隔"最好设个参数,可以在转换前自定义输入。)
-
然后把提取出的16进制数全部按UTF-16的双字节(或者转为对应的十进制) -1 处理,如:0~1字节处的双字节值为02 1E(1E为高位,02为地位 —-哈哈,这个我好想说多余了),那么进行 -1 处理后得到:01 1E。然后把所有 -1 处理好的数据转换为 ansi 码(注:不转也可以 ,直接用UTF-16无bom格式做文本文件—-不过这样不太方便编辑),那么文本就出来了
每段话提取并转换后按 000.txt 一样显示,方便修改
- 对话中的一些特殊字符说明(都是双字节16进制),有些提取转换时需要特殊处理的(不能直接 -1 处理):
1)就是刚才说的"00 00",它是每个对话的结束标志。
2)“20 00”:UTF-16编码’空格’的编码,这个是保持不变的,不用 -1 处理。
3)“B3 00” 和 “B4 00”:这两个是游戏专用的特殊控制符,用来控制文本高亮和居中,这个也要 -1 处理,-1 处理后为:B2 00 和 B3 00,但因为ansi码里面没有这个字符,所以要用ansi码里面的2个不常用的字符表示,例如"㈡" “㈢”。
这样就能修改对话了/提取出的英文翻译成中文。
二、把 txt 转换回 edt(直接修改原 edt 文件或者新建一个edt文件,对话部分以外部分用"00 00"填充),当然最好两个方式都行
在v0.9中,我已经可以在程序中对参数进行配置,对系统的blockSize和标识符进行修改。一个已知缺陷是在Edt->Txt的处理行数时候可能出现问题。
程序是用Python写的,不要觉得很难,我也是在用Python完成Hello World之后第三天开始写这个程序,只要有C/Java/C#这些语言一些基础,就可以理解这些程序了。
# coding=utf-8
import struct
import os
import ConfigParser
configParser=ConfigParser.ConfigParser()
CONFIGNAME = ‘config.cfg’
blockSize = 640 #size per block 640/480
isDebug = False #isDebug mode True/False
sysEnd = ‘x00x00’ #end of a sentence, e.g. a space x00x00
startPosition = 00 #start posion
PASS_SYMBOL = ‘x20x00’ #the list of word not decode
SPECIAL_1 = ‘xB3x00’ #the word of B300
SPECIAL_2 = ‘xB4x00’ #the word of B400
sysEndList = {‘00’: ‘x00’, ‘01’: ‘x01’, ‘02’: ‘x02’, ‘03’: ‘x03’, ‘04’: ‘x04’, ‘05’: ‘x05’, ‘06’: ‘x06’, ‘07’: ‘x07’, ‘08’: ‘x08’, ‘09’: ‘x09’, ‘0a’: ‘x0a’, ‘0b’: ‘x0b’, ‘0c’: ‘x0c’, ‘0d’: ‘x0d’, ‘0e’: ‘x0e’, ‘0f’: ‘x0f’, ‘10’: ‘x10’, ‘11’: ‘x11’, ‘12’: ‘x12’, ‘13’: ‘x13’, ‘14’: ‘x14’, ‘15’: ‘x15’, ‘16’: ‘x16’, ‘17’: ‘x17’, ‘18’: ‘x18’, ‘19’: ‘x19’, ‘1a’: ‘x1a’, ‘1b’: ‘x1b’, ‘1c’: ‘x1c’, ‘1d’: ‘x1d’, ‘1e’: ‘x1e’, ‘1f’: ‘x1f’, ‘20’: ‘x20’, ‘21’: ‘x21’, ‘22’: ‘x22’, ‘23’: ‘x23’, ‘24’: ‘x24’, ‘25’: ‘x25’, ‘26’: ‘x26’, ‘27’: ‘x27’, ‘28’: ‘x28’, ‘29’: ‘x29’, ‘2a’: ‘x2a’, ‘2b’: ‘x2b’, ‘2c’: ‘x2c’, ‘2d’: ‘x2d’, ‘2e’: ‘x2e’, ‘2f’: ‘x2f’, ‘30’: ‘x30’, ‘31’: ‘x31’, ‘32’: ‘x32’, ‘33’: ‘x33’, ‘34’: ‘x34’, ‘35’: ‘x35’, ‘36’: ‘x36’, ‘37’: ‘x37’, ‘38’: ‘x38’, ‘39’: ‘x39’, ‘3a’: ‘x3a’, ‘3b’: ‘x3b’, ‘3c’: ‘x3c’, ‘3d’: ‘x3d’, ‘3e’: ‘x3e’, ‘3f’: ‘x3f’, ‘40’: ‘x40’, ‘41’: ‘x41’, ‘42’: ‘x42’, ‘43’: ‘x43’, ‘44’: ‘x44’, ‘45’: ‘x45’, ‘46’: ‘x46’, ‘47’: ‘x47’, ‘48’: ‘x48’, ‘49’: ‘x49’, ‘4a’: ‘x4a’, ‘4b’: ‘x4b’, ‘4c’: ‘x4c’, ‘4d’: ‘x4d’, ‘4e’: ‘x4e’, ‘4f’: ‘x4f’, ‘50’: ‘x50’, ‘51’: ‘x51’, ‘52’: ‘x52’, ‘53’: ‘x53’, ‘54’: ‘x54’, ‘55’: ‘x55’, ‘56’: ‘x56’, ‘57’: ‘x57’, ‘58’: ‘x58’, ‘59’: ‘x59’, ‘5a’: ‘x5a’, ‘5b’: ‘x5b’, ‘5c’: ‘x5c’, ‘5d’: ‘x5d’, ‘5e’: ‘x5e’, ‘5f’: ‘x5f’, ‘60’: ‘x60’, ‘61’: ‘x61’, ‘62’: ‘x62’, ‘63’: ‘x63’, ‘64’: ‘x64’, ‘65’: ‘x65’, ‘66’: ‘x66’, ‘67’: ‘x67’, ‘68’: ‘x68’, ‘69’: ‘x69’, ‘6a’: ‘x6a’, ‘6b’: ‘x6b’, ‘6c’: ‘x6c’, ‘6d’: ‘x6d’, ‘6e’: ‘x6e’, ‘6f’: ‘x6f’, ‘70’: ‘x70’, ‘71’: ‘x71’, ‘72’: ‘x72’, ‘73’: ‘x73’, ‘74’: ‘x74’, ‘75’: ‘x75’, ‘76’: ‘x76’, ‘77’: ‘x77’, ‘78’: ‘x78’, ‘79’: ‘x79’, ‘7a’: ‘x7a’, ‘7b’: ‘x7b’, ‘7c’: ‘x7c’, ‘7d’: ‘x7d’, ‘7e’: ‘x7e’, ‘7f’: ‘x7f’, ‘80’: ‘x80’, ‘81’: ‘x81’, ‘82’: ‘x82’, ‘83’: ‘x83’, ‘84’: ‘x84’, ‘85’: ‘x85’, ‘86’: ‘x86’, ‘87’: ‘x87’, ‘88’: ‘x88’, ‘89’: ‘x89’, ‘8a’: ‘x8a’, ‘8b’: ‘x8b’, ‘8c’: ‘x8c’, ‘8d’: ‘x8d’, ‘8e’: ‘x8e’, ‘8f’: ‘x8f’, ‘90’: ‘x90’, ‘91’: ‘x91’, ‘92’: ‘x92’, ‘93’: ‘x93’, ‘94’: ‘x94’, ‘95’: ‘x95’, ‘96’: ‘x96’, ‘97’: ‘x97’, ‘98’: ‘x98’, ‘99’: ‘x99’, ‘9a’: ‘x9a’, ‘9b’: ‘x9b’, ‘9c’: ‘x9c’, ‘9d’: ‘x9d’, ‘9e’: ‘x9e’, ‘9f’: ‘x9f’, ‘a0’: ‘xa0’, ‘a1’: ‘xa1’, ‘a2’: ‘xa2’, ‘a3’: ‘xa3’, ‘a4’: ‘xa4’, ‘a5’: ‘xa5’, ‘a6’: ‘xa6’, ‘a7’: ‘xa7’, ‘a8’: ‘xa8’, ‘a9’: ‘xa9’, ‘aa’: ‘xaa’, ‘ab’: ‘xab’, ‘ac’: ‘xac’, ‘ad’: ‘xad’, ‘ae’: ‘xae’, ‘af’: ‘xaf’, ‘b0’: ‘xb0’, ‘b1’: ‘xb1’, ‘b2’: ‘xb2’, ‘b3’: ‘xb3’, ‘b4’: ‘xb4’, ‘b5’: ‘xb5’, ‘b6’: ‘xb6’, ‘b7’: ‘xb7’, ‘b8’: ‘xb8’, ‘b9’: ‘xb9’, ‘ba’: ‘xba’, ‘bb’: ‘xbb’, ‘bc’: ‘xbc’, ‘bd’: ‘xbd’, ‘be’: ‘xbe’, ‘bf’: ‘xbf’, ‘c0’: ‘xc0’, ‘c1’: ‘xc1’, ‘c2’: ‘xc2’, ‘c3’: ‘xc3’, ‘c4’: ‘xc4’, ‘c5’: ‘xc5’, ‘c6’: ‘xc6’, ‘c7’: ‘xc7’, ‘c8’: ‘xc8’, ‘c9’: ‘xc9’, ‘ca’: ‘xca’, ‘cb’: ‘xcb’, ‘cc’: ‘xcc’, ‘cd’: ‘xcd’, ‘ce’: ‘xce’, ‘cf’: ‘xcf’, ‘d0’: ‘xd0’, ‘d1’: ‘xd1’, ‘d2’: ‘xd2’, ‘d3’: ‘xd3’, ‘d4’: ‘xd4’, ‘d5’: ‘xd5’, ‘d6’: ‘xd6’, ‘d7’: ‘xd7’, ‘d8’: ‘xd8’, ‘d9’: ‘xd9’, ‘da’: ‘xda’, ‘db’: ‘xdb’, ‘dc’: ‘xdc’, ‘dd’: ‘xdd’, ‘de’: ‘xde’, ‘df’: ‘xdf’, ’e0’: ‘xe0’, ’e1’: ‘xe1’, ’e2’: ‘xe2’, ’e3’: ‘xe3’, ’e4’: ‘xe4’, ’e5’: ‘xe5’, ’e6’: ‘xe6’, ’e7’: ‘xe7’, ’e8’: ‘xe8’, ’e9’: ‘xe9’, ’ea’: ‘xea’, ’eb’: ‘xeb’, ’ec’: ‘xec’, ’ed’: ‘xed’, ’ee’: ‘xee’, ’ef’: ‘xef’, ‘f0’: ‘xf0’, ‘f1’: ‘xf1’, ‘f2’: ‘xf2’, ‘f3’: ‘xf3’, ‘f4’: ‘xf4’, ‘f5’: ‘xf5’, ‘f6’: ‘xf6’, ‘f7’: ‘xf7’, ‘f8’: ‘xf8’, ‘f9’: ‘xf9’, ‘fa’: ‘xfa’, ‘fb’: ‘xfb’, ‘fc’: ‘xfc’, ‘fd’: ‘xfd’, ‘fe’: ‘xfe’, ‘ff’: ‘xff’, }
def welcome():
print u’*******************************************************************’
print u’*************************** ***************************’
print u’EdtTxtConvert’
print u’*************************** ***************************’
print u’*******************************************************************’
print u’Any problem can connect me with my web site.’
print u’ powered by alswl '
print u’ http://dddspace.cn’
print u’ 09-10-01 v0.9 '
def getFiles(type):
‘get the files of path’
global dirPath
dirPath = raw_input(u’Input the files path:’)
while not os.path.isdir(dirPath):
print u’%s is not a path’ %(dirPath)
dirPath = raw_input(u’Input the files path:’)
#os.chdir(dirPath)
files = os.listdir(dirPath)
fileList = list()
for file in files:
if os.path.splitext(file)[1].lower() == type:
fileList.append(file)
return fileList
def myInit():
‘init the argu’
try:
global blockSize, isDebug, sysEnd, startPosition
configParser.read(CONFIGNAME)
blockSize = configParser.getint(’edt_encode_config’, ‘block_size’)
isDebug = configParser.getboolean(’edt_encode_config’, ‘debug’)
sysEnd = configParser.get(’edt_encode_config’, ‘sys_end’)
startPosition = configParser.getint(’edt_encode_config’, ‘start_position’)
viewConfig()
except Exception, e:
print u’configure file %s error!Exiting!’ %CONFIGNAME
raw_input(u’Press Enter to exit’)
sys.exit()
def viewConfig():
print u’Current configure:’
print u’Block size:%s’ %blockSize
print u’Start Position:%s’ %startPosition
print u’Debug mode:%s’ %isDebug
print u’End Symbol:%s’ %toHex(sysEnd)
def setConfig():
global blockSize, isDebug, sysEnd, startPosition
configParser.read(CONFIGNAME)
try:
blockSize = int(raw_input(‘set block size:’))
startPosition = int(raw_input(‘set start position:’))
isDebug = bool(raw_input(‘set is debug mode(empty is False):’))
sysEndInput = raw_input(‘set symbol of end(eg.0000)’)
sysEnd = sysEndList[sysEndInput[:2]] + sysEndList[sysEndInput[2:]]
configParser.set(’edt_encode_config’, ‘block_size’, blockSize)
configParser.set(’edt_encode_config’, ‘debug’, str(isDebug))
configParser.set(’edt_encode_config’, ‘sys_end’, sysEnd)
configParser.set(’edt_encode_config’, ‘start_position’, startPosition)
with open(CONFIGNAME, ‘wb’) as configFile:
configParser.write(configFile)
except Exception, e:
print ‘Error set’
raw_input(‘Press Enter to continue’)
def toHex(s):
‘format string to hex’
lst = []
for ch in s:
hv = hex(ord(ch)).replace(‘0x’, ‘’)
if len(hv) == 1:
hv = ‘0’+hv
lst.append(hv)
return reduce(lambda x,y:x+y, lst)
def getBlock(block640):
‘get the useful string from block of 640 bytes’
for i in range(0, len(block640), 2):
if block640[i : i+2] == sysEnd:
return block640[:i]
def decode(text):
‘-1 operate’
list = '’
for i in range(0, len(text), 2):
try:
rawStr = text[i: i+2]
if rawStr == PASS_SYMBOL :
list += rawStr
elif rawStr == SPECIAL_1:
list += ‘x21x32’
elif rawStr == SPECIAL_2:
list += ‘x22x32’
else:
a, b = struct.unpack(‘BB’, rawStr)
a -= 1
list += struct.pack(‘BB’, a, b)
except Exception, e:
tt = 0
return list
def encode(text):
‘+1 operate’
list = '’
for char in text:
try:
if char == ‘x21x32’:
list += SPECIAL_1
elif char == ‘x22x32’:
list += SPECIAL_2
else:
a, b = struct.unpack(‘BB’, char.encode(‘utf-16’)[2:])
a += 1
list += struct.pack(‘BB’, a, b)
except Exception, e:
tt = 0
list += ‘x00x00’
return list
def read(file):
‘read the block of file(use blockSize)’
return file.read(blockSize)
def edtProcess(edtFile):
’edt->txt process’
fedt = open(dirPath + ‘' + edtFile, ‘rb’)
ftxt = open(dirPath + ‘' + edtFile[0:-3] + ’txt’, ‘w’)
global i
i = 0
try:
fedt.seek(startPosition)
while True:
block640 = read(fedt)
if not block640:
break
if i == 640 and isDebug == True:
nonono = 0
block = getBlock(block640)
blockStr = decode(block)
if blockStr != ‘’:
lineNum = u’%d:n’ %(i)
lineStr = blockStr.decode(‘utf-16’) + ’nn’
if isDebug:
print lineNum, ’line number:%s’ %(hex(i/16))
print lineStr,
ftxt.write(lineNum.encode(‘gbk’))
ftxt.write(lineStr.encode(‘gbk’))
i += blockSize
print u’%s -> %s Convert successful’ %(fedt.name, ftxt.name)
except Exception, e:
print u’%s -> %s Convert Errorn%s’ %(fedt.name, ftxt.name, e)
finally:
fedt.close()
ftxt.close()
def txtProcess(txtFile):
’txt->edt process’
ftxt = open(dirPath + ‘' + txtFile, ‘r’)
fedt = open(dirPath + ‘' + txtFile[0:-3] + ’edt’, ‘wb’)
global i
i = 0
try:
fedt.seek(startPosition)
i = 0
blockStr = '’
for eachLine in ftxt:
if eachLine != ’n’ and i % 3 != 0:
blockStr += eachLine
if (i+2) % 3 == 0:
if isDebug:
print blockStr.decode(‘gbk’),
block = encode(blockStr[:-1].decode(‘gbk’))
fedt.write(block)
fedt.seek(640 - len(block), os.SEEK_CUR)
blockStr = '’
i += 1
print u’%s -> %s Convert successful’ %(ftxt.name, fedt.name)
except Exception, e:
print u’%s -> %s Convert Errorn%s’ %(ftxt.name, fedt.name, e)
finally:
ftxt.close()
fedt.close()
if name == ‘main’:
welcome()
myInit()
while True:
print ’nMenu’
print ‘1.edt->txtn2.txt->edtn3.view confign4.set confign0.exit’
mode = raw_input(u’Please choose:’)
if mode == ‘1’:
fileList = getFiles(’.edt’)
if fileList != None:
print u’fiels ready to convert:%sn’ %fileList
raw_input(u’Press Enter to convert’)
for edtFile in fileList:
edtProcess(edtFile)
else:
print ‘There is no edt files!’
raw_input(‘Press Enter to continue’)
elif mode == ‘2’:
fileList = getFiles(’.txt’)
if fileList != None:
print u’fiels ready to convert:%sn’ %fileList
raw_input(u’Press Enter to convert’)
for txtFile in fileList:
txtProcess(txtFile)
else:
print ‘There is no txt files!’
raw_input(‘Press Enter to continue’)
elif mode == ‘3’:
print '’
viewConfig()
raw_input(‘Press Enter to continue’)
elif mode == ‘4’:
print '’
setConfig()
raw_input(‘Press Enter to continue’)
elif mode == ‘0’:
break
else:
print ‘Error: Input error!!n’
raw_input(‘Press Enter to continue’)
点击这里下载源代码(edtTxtConvert.py+config.cfg+reade me.txt,需要Python2.6以上),如果需要exe版本的程序,可以向我索取,也可以使用py2exe自行编译。
原文链接: 铁血联盟EdtTxt转换器v0.9 | Log4D
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹