AI少女 捏人 存档修改 python 脚本

本案例,通过分析《AI少女》游戏的PNG图片捏人存档,解析了存档的数据格式,编写了便于修改数据的python脚本。 脚本过于BUG,可随意替换经验/性格/属性等,仅供学习研究数据结构及代码学习之用。

脚本内参考了多篇网页,无法一一列举,欢迎原作者投诉,以便添加引用文献。

脚本文件可以见 链接:https://pan.baidu.com/s/1DPxVOVoSuP5Nhuf7qjSEsA
提取码:jlz0

玩家或读者,需要复制下面的代码为一个python脚本文件,替换存档路径信息,运行即可生成原图片的作弊版,不伤原始文件,如果因为使用此代码而无法打开游戏,可以删掉作弊图片。

玩家也可修改自定义函数 do_it_yourself 变更属性。

#coding=gbk
#python 2.7
import struct,uuid,os,binascii
import collections
import pprint

debug = False
'''
仅支持修改:  
0. 文件头的uuid    自动修改uuid,以免重复
1. Parameter   89   9个元素字典 (可以无序)
    version   0.0.1
    sex       00男  01女   
        把女的图片改成男的,放到male文件夹,结果女的特征不见了,头发还在。变成男的了
    fullname  全名  字符串  支持UTF8编码
    personality  性格 整数值  取值范围:00~05
        编号  类型              对声音的影响        性格特点
        00  机械的无感情       无感                 感情淡薄,经常机械地行动。重视效率的类型。
        01  豁达温和           温柔                 如果是为了主人公的话就什么都愿做的温柔大姐姐类型
        02  意志坚定的自信家   职场女强人           不借助他人的力量,自己什么都能干的自信满满的类型。
        03  任性自私           任性女子             以蔑视的目光命令却无法独立生存的类型
        04  无精打采           蔫不拉几             无精打采怕麻烦的人。坐享其成的类型。
        05  积极开朗           积极活泼             不去想讨厌的事情,永远活力满满积极进取的类型。
    birthMonth   0A 整数
    birthDay     整数
    voiceRate   语速  浮点数     取值范围 -1.0~2.0
        界面显示   实际取值
        -10        -1.0
         -9        -0.09
         -5        -0.05
          0         0.0
          5         0.05
        100         1.0
        150         1.5
        200         2.0 
    hsWish  愿望 18个选0~3个,数组
        编号    类型
        00      温柔
        01      快乐
        02      小恶魔
        03      刺激
        04      礼貌
        05      禁忌
        06      稳重
        07      拜金
        08      知性
        09      信念
        0A      忠诚
        0B      持久
        0C      可怜
        0D      无邪气
        0E      不完全
        0F      理解力
        10      判断力
        11      治愈
    futanari 是否双性人  布尔值
        C2 False
        C3 True
2. GameInfo 字典的(可以无序)
DE0012  18个元素的字典
     1  version    0.0.0
     2  gameRegistration  布尔值   C3 True 载入时左上角有五角星  C2 False 没有 
        #如果此项为False,那么视为新开局,moodBound/flavorState/totalFlavor/desireDefVal/desireBuffVal会被清空
     3  tempBound   82 2个元素的字典       体温     
        体温为某个值 0.~100.,此处定义了三个区间的分界点,心情落在哪个区间则判定心情 蓝绿黄
                    lower  CA41A00000   20.0   天天温度正常: CA3F800000 1.0
                    upper  CA42A00000   80.0   天天温度正常: CA42C60000 99.0    
     4  moodBound   82 2个元素的字典       心情   
            心情为某个值 0~100,此处定义了三个区间的分界点,心情落在哪个区间则判定心情 蓝绿黄
                    lower  CA41900000  35.0   天天好心情: CA3F800000 1.0
                    upper  CA42600000  71.0   天天好心情: CA40000000 2.0 
     5  flavorState     
            88 8个元素的字典     整数值  可以7位数,但只能显示6位  例如:CE000F423F对应999999  CE0001869F对应99999
            键  值      说明
            00 女子力   洗澡、换衣服的频率,以及随时摆Pose的机会,魅力高的妹子经常会做一些好玩的小动作
            01 信任度   这个影响到妹子会不会接收你的建议,比如吃饭睡觉
            02 人性     指的是AI会不会自己睡觉,会不会自己吃饭喝水
            03 本能     是指该AI运用各种生活技能的熟练度,包括料理等级和收集材料的等级
            04 变态     高了容易发神经,比如裸奔。
            05 警惕     警戒影响偷袭成功率以及偷袭后后的妹子心情
            06 黑暗 (注意不按显示顺序!)  据说不要超过200,不然晕倒。为0的话,有些工具用不了。
            07 社交     是指该AI和其他AI的交流。
     6  totalFlavor  整数   总属性?  可以不等于各属性之和
            CD080D    2061 示例 
            CDFFFF    65535
     7  desireDefVal    某种内部参数,可能是出现各种行为的概率?
            DE0010 16个元素的字典  16个浮点数      0~51
            00 CA00000000
            01 CA00000000
            02 CA3F800000    CA424C0000
            03 CA40000000    CA40800000
            04 CA00000000
            05 CA00000000
            06 CA00000000
            07 CA41400000
            08 CA00000000
            09 CA00000000
            0A CA40800000    CA41400000
            0B CA41F00000
            0C CA40800000
            0D CA00000000
            0E CA40400000
            0F CA41300000
     8  desireBuffVal   日常行为?某种内部参数  0~18
            DE0010  16个元素的字典  16个CA值
            00 CA00000000
            01 CA00000000
            02 CA00000000
            03 CA41300000
            04 CA00000000
            05 CA00000000
            06 CA00000000
            07 CA00000000
            08 CA41A00000
            09 CA00000000
            0A CA41000000
            0B CA3F800000
            0C CA41A00000
            0D CA00000000
            0E CA40E00000
            0F CA40C00000
     9  phase  整数或索引号   取值范围 00~03   00 一颗心  03 四颗心
            03  
    10  normalSkill  普通技能
            85 5个元素字典,推荐技能
            00 04 巫女
            01 09 贤妻良母
            02 0E 不知疲倦
            03 18 淫魔诅咒
            04 22 心灵相通
        46个技能表: FF 无
            00  厨师        ★★  经常去做饭
            01  泡澡        ★★  经常泡澡
            02  爱好动物    ★★  与动物互动更容易
            03  睡美人      ★★  睡得好
            04  巫女        ★★★ 防止人体潮湿时体温下降,并使黑暗渡难以增加
            05  学龄前      ★★  在行动中,成长很少会比平时快
            06  轻松        ★★  心情轻松很容易
            07  礼尚往来    ★★  更多机会送礼物
            08  亲爱        ★★  随之而来的成功率有所提高
            09  贤妻良母    ★★★ 每次采取行动时,信任增加,最大动力增加10%
            0A  垂钓爱好者  ★★  喜欢钓鱼
            0B  易受伤      ★   有很多受伤案例
            0C  农民        ★★  更容易种田,作物加快生长
            0D  物质少女    ★★  更容易乞求一些东西
            0E  不知疲倦    ★★★ 不容易劳累,搜索起来更容易
            0F  暴食        ★★  更容易吃饭
            10  天气之子    ★★  下雨也要出去
            11  野性        ★★  使用家具的次数变少
            12  手工收集    ★★  易于手工收集
            13  兽王        ★★★ 容易抓猫
            14  污          ★★  顽皮的感觉可能会有所上升
            15  爱玩        ★★  更多的女孩去玩
            16  性欲旺盛    ★★  一个女孩经常来做爱
            17  好奇心      ★★  对调皮的对话表现出兴趣更容易
            18  淫魔诅咒    ★★★ 顽皮情绪的增加速度不会一直保持疲倦,反而会曾家,并且将邀请更多的人进行蚀刻(多人?)
            19  神经质      ★   情绪恶化
            1A  可疑        ★   顽皮谈话失败后,降低心情
            1B  防备        ★★  当提供建议时,很难听从
            1C  爱惜身体    ★★  当疲劳积累时,更容易先休息
            1D  忍耐        ★★★ 我很饿
            1E  寂寞        ★★  与英雄交谈变得更加容易
            1F  更好说话    ★★  与其他女孩聊天可以更轻松地相处
            20  好说话      ★   与其他女孩聊天变得更加容易
            21  行动派      ★★  最大动力提高10%
            22  心灵相通    ★★★ 打电话时,即使英雄很远,也可以响应
            23  懒惰        ★   这样可以轻松地休息一下
            24  洁癖        ★   如果不洗澡,心情会变差
            25  不幸        ★★  较容易拾取一级物品
            26  易感冒      ★★  容易感冒
            27  腹黑        ★★★ 与其他人交谈,其他人积累疲劳
            28  勇气        ★   较容易移动到较远的地方
            29  收藏        ★★  拾起物品更容易
            2A  地形掌握    ★   更容易挖矿
            2B  毅力坚决    ★★  难以成为异常状态的高温状态
            2C  欧皇        ★★★ 更容易拿到三级物品
            2D  体温超控    ★   饮料的温度控制效果减半
            2E  好运        ★★  更容易拿到二级物品
            2F  挖掘        ★★  用铲子收集更容易
            30  捕虫        ★★  更容易用虫网
            31  猎手        ★★★ 获取物品时变得更容易掌握技能
    11  hSkill  H技能
            85 5个元素字典,推荐技能
            00 03 敏感
            01 06 下体敏感
            02 0F 屁股
            03 14 受虐
            04 16 性欲
         18个技能表: FF   无
            00  胸部敏感    08  外射        10  电臀
            01  名器        09  M           11  性厌恶
            02  浪漫主义    0A  S           12  侍奉
            03  敏感        0B  欲火        13  女同2
            04  服务爱好    0C  女同1       14  受虐
            05  中出        0D  痴女        15  不满
            06  下体敏感    0E  小便处      16  性欲
            07  喜好下体    0F  屁股敏感    17  寂寞

    12  favoritePlace  对话时喜欢的地点?索引类型?
        00 
    13  lifestyle  生活习惯6种  索引类型  单个值
        00   ゲッター      刺客     导致贪婪的贪婪类型。对食物和物品的需求增加。
        01   ベイビー      婴儿     总是喜欢的类型。当他感到孤独时,他会给我礼物或礼物。
        02  ドライバー     螺丝刀    一种类型,其自身在不依赖他人的情况下起作用。容易收集食物和物品。
        03  コントローラー 控制器   一种不吸引人的类型。我经常休息或睡觉。
        04  シーカー       纸箱     这种类型立即引起关注,但很累。搬到另一个地方很容易,但是累了就开始做另一件事。
        05  アームチェア   扶手椅   永远是我的步伐类型。我喜欢享受我的世界,玩耍和洗个澡。
    14  morality  整数  道德?  此值高了是不是不会裸奔呢?
            31 似乎单字节
    15  motivation  整数 干劲?活动性?   抓鱼等的参数?
        F6   似乎是有符号整数? -10
    16  immoral  整数  淫荡程度   自嗨?
        0F    15
    17  isHAddTaii0  特殊H有关参数?   布尔型
        C3
    18  isHAddTaii1  特殊H有关参数?   布尔型
        C3
'''

'''
总共3个大数据区
    大数据区由长度标记+数据组成
    小数据区没有长度标记
        第1个数据区的长度标记为4字节,小端在前
        第2个数据区的长度标记为4字节,小端在前
        第3个数据区的长度标记为8字节,小端在前
        
第3个数据区,细分为 5个大数据区 + 3个小数据区 + 1个可选小数据区
    第1~3个大数据区  为 Custom      脸型/体型/发型
    第4~5个大数据区  为 Coordinate  服装/饰品
    3个小数据区分别为  Parameter   GameInfo  Status
    可选区为 KKEx
    
lstInfo 的信息记录了第3个数据区的起始位置及数据长度,(第3个数据区的总长度标记,不算在内)
'''

#待修改的属性
def do_it_yourself():
    ParameterDict = {
        'sex'        : 1, #女
        'fullname'   : '山村野女',
        'personality': 2, #0~5  意志坚定的自信家
        'voiceRate'  : 50, #-10~200
        'hsWish'     : [0,4,10],#温柔 礼貌 忠诚
        'futanari'   : False, #单性
        }
    GameInfo = {
        'gameRegistration':True, #如果此项为False,那么视为新开局,moodBound/flavorState/totalFlavor/desireDefVal/desireBuffVal会被清空
        'tempBound':{ #98%的范围体温正常
            'lower':1.0 ,  
            'upper':99.0},
        'moodBound':{ #98%的范围开心
            'lower':2.0 ,  
            'upper':8.0},
        'flavorState':{
            0:99999, #力量
            1:99999, #信任
            2:99999, #人性
            3:99999, #本能
            4:    0, #变态
            5:    0, #警惕
            6:    0, #黑暗
            7:99999, #社交
            },
        'desireDefVal':{#某种内部参数,可能是出现各种行为的概率?
             0: 88.0,
             1: 88.0,
             2: 88.0,
             3: 88.0,
             4: 88.0,
             5: 88.0,
             6: 88.0,
             7: 88.0,
             8: 88.0,
             9: 88.0,
            10: 88.0,
            11: 88.0,
            12: 88.0,
            13: 88.0,
            14: 88.0,
            15: 88.0,
            },
        'desireDefVal':{#某种内部参数,可能是出现各种行为的缓冲概率?
             0: 88.0,
             1: 88.0,
             2: 88.0,
             3: 88.0,
             4: 88.0,
             5: 88.0,
             6: 88.0,
             7: 88.0,
             8: 88.0,
             9: 88.0,
            10: 88.0,
            11: 88.0,
            12: 88.0,
            13: 88.0,
            14: 88.0,
            15: 88.0,
            },
        'phase': 3, #四颗心
        'normalSkill':{
            0: 4, #巫女
            1: 9, #贤妻良母
            2:14, #不知疲倦
            3:24, #淫魔诅咒
            4:34, #心灵相通
            },
        'hSkill':{
            0: 3, #敏感
            1: 6, #下体敏感
            2:15, #屁股
            3:20, #受虐
            4:22, #性欲
            },
        'favoritePlace':0,
        'lifestyle':2, #螺丝刀
        'morality':100,
        'motivation':100,
        'immoral':10,
        'isHAddTaii0':True,
        'isHAddTaii1':True,
        }
    return ParameterDict,GameInfo

#############################################################
def str2bin(instr):  #'high'-->'\x....'
    return binascii.unhexlify(binascii.hexlify(instr))
def hex2bin(instr):  #'64' --> '\x64'
    return binascii.unhexlify(instr)
def bin2str(instr):  #'\x64'--'64'
    return binascii.b2a_hex(instr)
def voiceRate_int2float(val): #-10~200  整数转为浮点数   仅用于计算 voiceRate
    assert type(val) is int
    assert -10 <= val <= 200
    if val == -10:
        return -1.0
    else:
        return val*0.01
    
#########
def pack_Parameter(origin,usrData):
    for key,value in usrData.items():
        if   key == 'fullname':
            origin[key] = value.decode('gbk')
        elif key == 'voiceRate': 
            origin[key] = voiceRate_int2float(value)
        else:
            origin[key] = value
    dic = origin
    if debug: print origin
    
    raw = ['\x89']
    for k,v in dic.items():
        raw.append(pack_a_data(k))
        if k == 'hsWish': #整数列表
            raw.append(struct.pack('b',-112+len(v)))
            for i in v: raw.append(pack_a_data(i))
        else:
            raw.append(pack_a_data(v))
    if debug: print raw
    return ''.join(raw)
def pack_GameInfo(origin,usrData):
    for key,value in usrData.items():
        if type(value) is dict: #tempBound moodBound flavorState desireDefVal desireBuffVal normalSkill hSkill
            for k,v in value.items():
                origin[key][k] = v
        else:
            origin[key] = value
    t = 0
    for v in origin['flavorState'].values(): t+=v
    # if t>1000:
        # t = 1000
    origin['totalFlavor'] = t
    
    dic = origin
    
    raw = ['\xDE\x00\x12']
    for key,value in origin.items():
        raw.append(pack_a_data(key))
        if isinstance(value,collections.OrderedDict):
            raw.append(pack_a_basic_dict(value))
        else:
            raw.append(pack_a_data(value))
    if debug: print raw
    return ''.join(raw)
def pack_lstInfo_block(lstInfo):
    raw = ['\x81']
    raw.append(pack_a_data('lstInfo'))
    raw.append(pack_a_data(-112 + len(lstInfo)))
    for dic in lstInfo:
        raw.append(pack_a_basic_dict(dic))
    tmp = ''.join(raw)
    return pack_length_tag_4(len(tmp)) + tmp
    


def block0_png(fp):
    raw = []
    header = fp.read(8)  #文件格式
    raw.append(header)
    while 1: 
        sizeBlock = fp.read(4) #长度
        size  = struct.unpack('>I',sizeBlock)[0]
        dtype = fp.read(4) #类型
        data  = fp.read(size) #数据
        crc32 = fp.read(4)  #校验
        raw.append(sizeBlock+dtype+data+crc32)
        if dtype == 'IEND': break
    return ''.join(raw)
def block1_header(fpin): #第一个数据区
    #读取原来的数据区,重新生成uuid  
    rst = []
    
    raw = fpin.read(4)
    rst.append(raw)
    
    size = struct.unpack(',raw)[0]
    
    #同时更更换两个 uuid 不识别
    # rst.append(fpin.read(size-37*2)) #扣除2个uuid及长度标记
    # rst.append('\x24' + str2bin(str(uuid.uuid1())))
    # rst.append('\x24' + str2bin(str(uuid.uuid1())))
    # fpin.read(37*2)
    
    #更换一个uuid 部分不识别
    # rst.append(fpin.read(size-37)) #扣除1个uuid及长度标记
    # rst.append('\x24' + str2bin(str(uuid.uuid1())))
    # fpin.read(37)
    
    # uuid 可以完全相同! 可以同时识别
    rst.append(fpin.read(size))
    
    return ''.join(rst)
    
def block2_lstInfo_get(fpin): #第二个数据区  数据索引信息  获取数据供后续修改
    #由于至改动
    raw = fpin.read(4)
    if debug:print bin2str(raw)
    if debug:print hex(fpin.tell())
    size = struct.unpack(',raw)[0]
    return size,read_a_data(fpin)
def block3_data_get(fp,lstInfo): #第三个数据区 
    #第3个数据区的总长度
    if debug:print hex(fp.tell())
    globalsize = struct.unpack(',fp.read(8))[0]
    
    with file('tmp.ai4','wb') as fpot:
        fpot.write(fp.read(globalsize))
    
    with file('tmp.ai4','rb') as fp:
        for dic in lstInfo:
            fp.seek(dic['pos'])
            if dic['name'] == 'Parameter':
                Parameter = read_a_data(fp)
            elif dic['name'] == 'GameInfo':
                GameInfo = read_a_data(fp)
            elif dic['name'] == 'KKEx':
                raw_KKEx = fp.read(dic['size'])
            elif dic['name'] == 'Custom':
                raw_Custom = fp.read(dic['size'])
            elif dic['name'] == 'Coordinate':
                raw_Coordinate = fp.read(dic['size'])
            elif dic['name'] == 'Status':
                raw_Status = fp.read(dic['size'])
            else:
                raise TypeError,'unkown data tag'
    
    os.remove('tmp.ai4')
    
    if debug:
        with file('tmp.cus','wb') as fp: fp.write(raw_Custom)
        with file('tmp.coo','wb') as fp: fp.write(raw_Coordinate)
        with file('tmp.sta','wb') as fp: fp.write(raw_Status)
        print Parameter
        print GameInfo
    if len(lstInfo) == 6:
        return raw_KKEx, raw_Custom, raw_Coordinate, Parameter, GameInfo, raw_Status
    elif len(lstInfo) == 5:
        return raw_Custom, raw_Coordinate, Parameter, GameInfo, raw_Status
    else:
        raise TypeError,'unkown data tag'
    
    
def read_a_data(fp,isOrderedDict=True): #从文件读取一个数据  数据包含数据标签及数据 
    raw = fp.read(1)                 #数据类型
    flag = struct.unpack('b',raw)[0] #有符号整数
    # print repr(raw),flag
    if   flag <=-113:#8F #80~8F 字典   key:value --> (key,value),(key,value)
        L = flag - (-128)#80
        tmp = collections.OrderedDict() if isOrderedDict else {}
        for i in range(L):
            key = read_a_data(fp)
            val = read_a_data(fp)
            tmp[key] = val
        return tmp
    elif flag <= -97:#9F # 90~9F 数组,可以混合类型 (列表)
        L = flag - (-112) #90
        return [read_a_data(fp) for i in range(L)]
    elif flag <= -65:#BF  #A0~BF 字符串
        L = flag - (-96)#A0
        return fp.read(L) #.decode('utf8')
    elif flag == -62: #C2布尔型
        return False
    elif flag == -61: #C3布尔型
        return True
    elif flag == -60: #C4 单字节整数数组  C4 □□   单字节整数数组  □□为长度
        L = struct.unpack('B',fp.read(1))[0] #无符号整数标识长度
        return [struct.unpack('b',fp.read(1))[0] for i in range(L)] #有符号
    elif flag == -54: #CA 单精度浮点数  Big Endian大端在前 
        return struct.unpack('>f',fp.read(4))[0]
    
    #CB--00
    elif flag == -52: #CC □□    无符号整数  单字节 其后为整数值
        return struct.unpack('B',fp.read(1))[0] 
    elif flag == -51: #CD □□ □□  整数  双字节  > 大端在前  0~65535
        return struct.unpack('>H',fp.read(2))[0]
    elif flag == -50: #CE □□ □□ □□ □□  整数  四字节   大端在前 
        return struct.unpack('>I',fp.read(4))[0]
    elif flag == -39:#D9 D9 □□  字符串  □□表示长度,无符号,32~255个字节
        L = struct.unpack('B',fp.read(1))[0] #无符号整数标识长度
        return fp.read(L) #decode
    elif flag == -38:#DA DA □□ □□  字符串  两个字节表示字符串长度,无符号,256~65535个字符 >
        L = struct.unpack('>H',fp.read(2))[0] #无符号整数标识长度
        return fp.read(L) #decode
    elif flag == -36:#DC DC □□ □□  布尔数组,CA数组 0~65535?,  两个字节表示长度,无符号  >大端在前  布尔数组?
        L = struct.unpack('>H',fp.read(2))[0] #无符号整数标识长度
        return [read_a_data(fp) for i in range(L)]
    elif flag == -34: #DE 长字典 DE □□ □□  混合数据结构?   >? 
        L = struct.unpack('>H',fp.read(2))[0] #无符号整数标识长度
        tmp = collections.OrderedDict() if isOrderedDict else {}
        for i in range(L):
            key = read_a_data(fp)
            val = read_a_data(fp)
            tmp[key] = val
        return tmp
    elif flag>=-32: #整数值 -32~127  
        return flag
    else:
        print struct.pack('b',flag)
        return None

def pack_length_tag_4(value): #标识长度的整数,4字节,
    #□□ □□ □□ □□
    return struct.pack(',value)
def pack_length_tag_8(value): #标识长度的整数,8字节,
    #□□ □□ □□ □□ □□ □□ □□ □□
    return struct.pack(',value)
def pack_length_tag_1(value): #标识长度的整数,1字节,B 无符号
    return struct.pack('B',value)
def pack_a_data(data):#打包一个数据为二进制
    #浮点数  CA □□ □□ □□ □□
    #布尔值  C2 或 C3
    #整数值  
    #字符串
    dtype = type(data)
    if dtype   is float:
        return '\xCA' + struct.pack('>f',data)
    elif dtype is bool:
        return '\xC3' if data else '\xC2'
    elif dtype is int:
        if data<-128:
            raise RangeError,'not supported integer range: %d' % data
        elif data < 128: #-128~127   □□  有符号整数,单字节标识
            return struct.pack('b',data)
        elif data<256: #128~255   CC □□    无符号整数  单字节 其后为整数值
            return '\xCC'+struct.pack('B',data)
        elif data<65536:#256~65535  CD □□ □□  无符号整数  双字节  > 大端在前  0~65535
            return '\xCD'+struct.pack('>H',data)
        else: #>=65536  CE □□ □□ □□ □□  无符号整数  四字节   大端在前 
            #更长的整数会出错
            return '\xCE'+struct.pack('>I',data)
    elif dtype is str:
        raw = data
        length = len(raw)
        if length<= 31: ##A0~BF 字符串
            return struct.pack('b',-96+length) + raw
        elif length <=255:# D9 □□  字符串  □□表示长度,无符号,32~255个字节
            return '\xD9'+struct.pack('B',length) + raw
        else: #256~65535 DA □□ □□  字符串  两个字节表示字符串长度,无符号,256~65535个字符 >
            return '\xDA'+struct.pack('>H',length) + raw
    elif dtype is unicode:
        raw = data.encode('utf8')
        # if 1: print repr(raw)
        if debug: print repr(raw)
        length = len(raw)
        if length<= 31: ##A0~BF 字符串
            # print repr(struct.pack('b',-96+length) + raw)
            return struct.pack('b',-96+length) + raw
        elif length <=255:# D9 □□  字符串  □□表示长度,无符号,32~255个字节
            return '\xD9'+struct.pack('B',length) + raw
        else: #256~65535 DA □□ □□  字符串  两个字节表示字符串长度,无符号,256~65535个字符 >
            return '\xDA'+struct.pack('>H',length) + raw
        
def pack_a_basic_dict(dic): #单层有序字典
    raw = []
    if len(dic) <=15:
        raw.append(pack_a_data(-128+len(dic)))
    else: #16~65535
        raw.append('\xDE')
        raw.append(struct.pack('>H',len(dic)))
    for k,v in dic.items():
        raw.append(pack_a_data(k))
        raw.append(pack_a_data(v))
    return ''.join(raw)

def converter(fileName,ends='_cheated.png'):
    with file(fileName,'rb') as fp:
        block0 = block0_png(fp)
        if debug:
            with file('tmp.png','wb') as ftmp:ftmp.write(block0)
        block1 = block1_header(fp)
        if debug:
            with file('tmp.blk1','wb') as ftmp:ftmp.write(block1)
        
        block2_size,block2_info = block2_lstInfo_get(fp)
        if debug:
            print hex(fp.tell())
            print block2_size,block2_info
        
        lstInfo = block2_info['lstInfo']
        if debug:
            print lstInfo
        block3_lst = block3_data_get(fp,lstInfo)
    
    usrParam,usrGameInfo = do_it_yourself()
    if len(block3_lst) == 6:
        isKKEx = True
        raw_KKEx, raw_Custom, raw_Coordinate, Parameter, GameInfo, raw_Status = block3_lst
    else:
        isKKEx = False
        raw_Custom, raw_Coordinate, Parameter, GameInfo, raw_Status = block3_lst
    raw_Parameter = pack_Parameter(Parameter,usrParam)
    raw_GameInfo = pack_GameInfo(GameInfo,usrGameInfo)
    
    len_custom = len(raw_Custom)
    len_coordinate = len(raw_Coordinate)
    len_parameter = len(raw_Parameter)
    len_gameInfo = len(raw_GameInfo)
    len_status = len(raw_Status)
    if isKKEx: #有序为前提,如果无序则不行!此处为列表,有序,所以没有问题
        len_KKEx = len(raw_KKEx)
        globalsize = len_custom+len_coordinate+len_parameter+len_gameInfo+len_status+len_KKEx
        
        lstInfo[3]['size'] = len_parameter
        lstInfo[3]['pos'] = lstInfo[2]['pos']+lstInfo[2]['size']
        
        lstInfo[4]['size'] = len_gameInfo
        lstInfo[4]['pos'] = lstInfo[3]['pos']+lstInfo[3]['size']
        
        lstInfo[5]['size'] = len_status
        lstInfo[5]['pos'] = lstInfo[4]['pos']+lstInfo[4]['size']
        
        lstInfo[0]['pos'] = lstInfo[5]['pos']+lstInfo[5]['size']
        
        block3 = pack_length_tag_8(globalsize) + raw_Custom + raw_Coordinate + raw_Parameter+raw_GameInfo+raw_Status+raw_KKEx
    else:
        globalsize = len_custom+len_coordinate+len_parameter+len_gameInfo+len_status
        lstInfo[2]['size'] = len_parameter
        lstInfo[2]['pos'] = lstInfo[1]['pos']+lstInfo[1]['size']
        
        lstInfo[3]['size'] = len_gameInfo
        lstInfo[3]['pos'] = lstInfo[2]['pos']+lstInfo[2]['size']
        
        lstInfo[4]['size'] = len_status
        lstInfo[4]['pos'] = lstInfo[3]['pos']+lstInfo[3]['size']
        
        block3 = pack_length_tag_8(globalsize) + raw_Custom + raw_Coordinate + raw_Parameter+raw_GameInfo+raw_Status
   
    block2 = pack_lstInfo_block(lstInfo)
    
    with file(fileName[:-4]+ends,'wb') as fp: 
        fp.write(block0)
        fp.write(block1)
        fp.write(block2)
        fp.write(block3)
   

if __name__ == '__main__':
    #此处输入安装路径,可自定义修改do_it_yourself函数
    path = r'C:\Game\ai\AI-Syoujyo\UserData\chara\female'
    for name in os.listdir(path):
        if name.endswith('.png') and not name.endswith('_cheated.png'):
            converter(os.path.join(path,name))

你可能感兴趣的:(python,游戏)