this is a german Web-Mirror of PYTHON.ORG powered by Domainunion AG

Differences between revisions 1 and 11 (spanning 10 versions)
Revision 1 as of 2002-10-07 06:40:14
Size: 492
Editor: ppp129-174
Comment:
Revision 11 as of 2011-02-05 22:32:45
Size: 62347
Editor: 125
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
There are several lists of Python-related projects:

 * [https://py.vaults.ca/parnassus/apyllo.py/ Vaults of Parnassus] - possibly main Python resource list.
 * Many Python projects located on Sourceforge

  * You can [https://sourceforge.net/search/?q=python search] for them
  * Browse for projects written on [https://sourceforge.net/softwaremap/trove_list.php?form_cat=178 Python]
  * and look for information in Sourceforge [https://python.foundries.sourceforge.net/ Python Foundry]
# -*- coding: utf-8 -*-
################################################################
## LICENCING / LEGAL
################################################################
## Disclaimers:
## - This script is provided as is, without any warranty.
## - If you think your sVERSION isn't an original sVERSION, contact the authors.
## - Authors of this script are not responsible of its usages and results.
## - The script licence can be modified at any time without warnings.
## - Source code is intellectual property of its authors.
##
## Distribution:
## - This script cannot be distributed, shared, packed, ... in any manner without authors' agreement.
## - Doing links to authorized hosts are allowed, as long as you send this information to the authors
## ( in order them to be warned of bugs, thanks and suggestions )
## - Authorized hosts list ( on Apr. 25 2010 )
## - www.dragonagenexus.com
## - social.bioware.com
## Usage:
## - This script is free to use for non-commercial purposes.
## - Any commercial use needs authors' agreement.
## - Importing elements of source code is allowed for non-commercial purpose.
##
## Modification:
## - Script modifications are allowed for personal use only.
## - Modified sVERSIONs cannot be distributed without agreement of authors.
## - If you've made a better sVERSION and want to share it, contact the authors.
##
## Involvment:
## - Testers and coders are welcome to participate to this script. Contact authors for more informations.
##
## Help and questions:
## - Contact the authors for help about using this script.
## - Contact the authors for any other questions about this script or this licence.
##
################################################################

################################################################
## History
################################################################
sAPPNAME="DragonAge Face Replacer"
sVERSION = "2.08"
# 0.10 : Very first version
# 0.11 : Fixed a bug on file mismatch, added some infos, changing processus method
# 0.12 : added a Tkinter UI for file selection, old method is still valuable set UITOOL to False to enable
# 0.20 : Now able to exchange with different filesizes ( MOR is written at the end of the file )
# 0.21 : Able to import from mor file instead of savegame
# 0.22 : 0.21 bugfix and eyecandy
# 1.00 : Final version, now able to retrieve MOR files from ERF resources
# 1.01 : Adding some tweaking for destination savegames ( name ), as XunAmarox suggested
# 1.02 : Some bugfix and eyecandy (list scrollbar), added inventory size tweaking ( always suggested by XunAmarox )
# 1.03 : Fixed some errors appearing with 1.02 and the separation between tweak and exchange face
# 1.04 : Added compatibilty with Awakening and some save formats
# 1.10 : Now save the path of the source and destination file, fixed the non-acsii names and paths
# 1.11 : Fix 1.10 problem when trying to launch with bad paths and weird name problems
# 1.20 : Now handle characters and saves in one window. UI improvements. Fixes problem with non-ascii paths
# 1.30 : Now you can edit the face : model parts & tints. Log is less verbose
# 2.00 : 1.30 version with reworked UI and handling
# 2.01 : Fixes major bug in 2.00 with autocheck for characters and resource files
# 2.02 : Fixes 2.01 problems in exchange of features, add more clearer paths and a "reset face" button
# 2.03 : Fixes a script encoding error to save path, name exchange problems, features missing
# 2.04 : Allowing to see source features values, limit inventory size, reworked code to handle a CheckList, more reliable log
# 2.05 : Last bugfix of 2.0x serie, now things are 100% OK
# 2.06 : Fixes a write error on savegames that prevent faces to be updated in some cases. Now faces are only appended, not replaced.
# Fixes also an error in getting the last modified face
# 2.07 : Simple fix about name problem: now if the name found is greater than nMAX_NAME, name cannot be changed (errors)
# sAUTO_SELECT savegame is auto selected in character folder (based on a suggestion by setiweb)
# Tint 11 & 12 is now eyebrow texture (thanks to setiweb)
# 2.08 : 2.07 bugfix and code rewriting. It seems that some features (tatooes, age map) are not into features list
# Also, now edited files can be put in another folder, use nSAVE_METHOD to achieve this
################################################################
## EXTERNAL MODULES
################################################################
# Python modules
import struct,os
import os.path as OP
import struct
import shutil
# Tkinter modules
from Tkinter import * # Tkinter main
import tkMessageBox as TKMB # Message box
import tkFileDialog as TKFD # File dialog
import tkFont
import Tix # Tkinter extension
from Tix import CheckList # Checklist
################################################################
## USER PARAMETERS & CONSTANTS
################################################################
# Log level (0: errors only, 1: with informations, 2: with program flow)
nLOG_LEVEL=0
# Save method : Set to
# -1 to replace save witout backup
# 0 to replace with making a backup
# 1 to create a new slot
nSAVE_METHOD=1
# previous paths for inputs
# Default ERF resource may be "C:\MyGames\Dragon Age\packages\core\data\face.erf"
pLAST_CHR=""
pLAST_DAS=""
pLAST_MOR=""
pLAST_ERF=""
# set to the directory of the characters in "(My Documents)\Bioware\Dragon Age\Characters"
pCHARACTERS_DIR=""
# Maximum inventory size allowed
nMAX_INVSIZE=100000
# Max name size (only for file reading)
nMAX_NAME=32
# select this savegame rather than first alphabetical save
sAUTO_SELECT="QuickSave_1"

# Do not modify these lines
nREL_OFFSET_INVSIZE=168
sPYTHON_FILE="DAFaceReplacer%s.py"%sVERSION.replace(".","")
aMOR_FEATURES_PARTS=["P_head","P_eyes","P_hair","P_beard","P_part5","P_lashes"]
aMOR_FEATURES_TINTS=["T_skin","T_lips","T_eyes","T_hair","T_eyelids","T_blush",
                    "T_tatoo1","T_tatoo2","T_tatoo3","T_tatoo4","T_tint11","T_tint12"]
# Lines commented are features wanting a color mask editing
aMOR_EDIT=(("Hair model",aMOR_FEATURES_PARTS[2]),
          ("Beard model",aMOR_FEATURES_PARTS[3]),
          ("Eye model",aMOR_FEATURES_PARTS[1]),
          ("Lashes model",aMOR_FEATURES_PARTS[5]),
          ("Head model",aMOR_FEATURES_PARTS[0]),
          ("Skin color",aMOR_FEATURES_TINTS[0]),
          ("Hair color",aMOR_FEATURES_TINTS[3]),
          ("Eyes color",aMOR_FEATURES_TINTS[2]),
          ("Eye Make-up",aMOR_FEATURES_TINTS[4]),
          ("Lips color",aMOR_FEATURES_TINTS[1]),
          ("Blush color",aMOR_FEATURES_TINTS[5]),
          ("Eyebrow color",aMOR_FEATURES_TINTS[10]),
          ("Tatoo color 1",aMOR_FEATURES_TINTS[6]),
          ("Tatoo color 2",aMOR_FEATURES_TINTS[7]),
          ("Tatoo color 3",aMOR_FEATURES_TINTS[8]),
          ("Tatoo color 4",aMOR_FEATURES_TINTS[9]),
          ("?Model #5?",aMOR_FEATURES_PARTS[4]),
          ("?Tint #12?",aMOR_FEATURES_TINTS[11]))

sMOR_HEADER="GFF V4.0PC MORPV0.1"

if not OP.exists(pLAST_DAS): pLAST_DAS=""
if not OP.exists(pLAST_MOR): pLAST_MOR=""
if not OP.exists(pLAST_ERF): pLAST_ERF=""
if not OP.exists(pCHARACTERS_DIR): pCHARACTERS_DIR=os.getcwd()

################################################################
## DAS Savegame Editing
################################################################
def ChangeSaveData(daspath,new_name,new_invsize,morface):
    Log("1INFO: Changing DAS savegame data of %s"%daspath)
    # Open the DAS file
    dasfile=open(daspath,"rb")
    dasdata=dasfile.read()
    dasfile.close()
    # Backup it
    if nSAVE_METHOD==0:
        bak_file=open(daspath+".bak","wb")
        bak_file.write(dasdata)
        bak_file.close()
        Log("2 DAS file backup: %s.bak"%daspath)
    if morface:
        # morface MUST be Update() before to register new data
        dasdata=ChangeFaceData(dasdata,morface.data)
    # try to change name
    dasdata,changed=SetName(dasdata,new_name)
    # replace inv size
    if int(new_invsize)>nMAX_INVSIZE: # limit inventory size
        Log("0ERROR: Inventory size exceed maximum, DAFR limits to %s)"%nMAX_INVSIZE)
        new_invsize=nMAX_INVSIZE
    if int(new_invsize)<0:
        Log("0ERROR: Inventory size below 0, passing")
    else:
        dasdata=SetInvSize(dasdata,new_invsize)
    # write to file
    out_file=open(daspath,"wb")
    out_file.write(dasdata)
    out_file.close()
    return True

def ChangeFaceData(dasdata,mordata):
    # TODO : remove old unecessary faces
    Log("1INFO: Changing face in DAS data")
    # get the content offset
    das_content_offset=struct.unpack("I",dasdata[24:28])[0]
    Log("2 DAS content offset=%s"%das_content_offset)
    # find the mor (binary) offset declaration
    offset=dasdata.find("d\x00e\x00f\x00a\x00u\x00l\x00t\x00_\x00p\x00l\x00a\x00y\x00e\x00r\x00")-40
    Log("2 MOR declaration offset @%s"%offset)
    # check if it is the correct offset
    start=struct.unpack("I",dasdata[offset:offset+4])[0]+das_content_offset
    Log("2 Checking MOR face data @%s"%start)
    if dasdata[start+4:start+24]==sMOR_HEADER:
        Log("2 Found MOR face data offset=%s @%s"%(start,offset))
        # change the offset declaration
        new_start=len(dasdata)-das_content_offset
        dasdata=dasdata[0:offset]+struct.pack("I",new_start)+dasdata[offset+4:]
        Log("2 Changed MOR face data offset=%s @%s"%(new_start,offset))
        # append binary data to destination data
        dasdata=dasdata+struct.pack("I",len(mordata))+mordata
        Log("2 MOR face data appended to DAS file")
        Log("2 DAS size changed : %s -> %s)"%(len(dasdata)-len(mordata),len(dasdata)))
        return dasdata
    else:
        Log("0ERROR: MOR offset not found ! Face not changed.")
        return dasdata

def GetName(daspath,log=True):
    # open the file
    if daspath=="": return ""
    if log:Log("1INFO: Get character name in DAS file: %s"%daspath)
    dasfile=open(daspath,"rb")
    dasdata=dasfile.read()
    dasfile.close()
    # get the content offset
    das_content_offset=struct.unpack("I",dasdata[24:28])[0]
    # find the name offset declaration
    offset=dasdata.find("\x07\x00\x00\x00p\x00l\x00a\x00y\x00e\x00r\x00\x00\x00")+20
    name=""
    if offset>=0:
        # get the original name
        mode=0
        length=struct.unpack("I",dasdata[offset:offset+4])[0]
        name=dasdata[offset+4:offset+4+length*2]
        # with python 2.5 and over, use decode function to have name from unicode
        try:
            name=name.decode("u16").strip("\x00")
        except:
            name=name.replace("\x00","")
        if dasdata.count(struct.pack("I",offset-das_content_offset))>1:
            mode=1
            if log:
                Log("2 Found %s possible offsets for name"%dasdata.count(struct.pack("I",offset-das_content_offset)))
                Log("0ERROR: Name cannot be changed in this savegame ! Unable to get the name offset")
            name=""
        if len(name)>nMAX_NAME:
            mode=1
            if log:
                Log("0ERROR: Name cannot be retrieved ! Found name of length %s @ %s : too long, maybe offset error"%(len(name),struct.pack("I",offset)))
            name=""
    if name and log: Log('2 Found character name="%s" @%s'%(name,offset))
    elif log: Log("2 Character name has errors !")
    return name,mode

def SetName(dasdata,newname):
    Log('1INFO: Set character name "%s" in DAS file'%newname)
    # get the content offset
    das_content_offset=struct.unpack("I",dasdata[24:28])[0]
    # find the name of character
    offset=dasdata.find("\x07\x00\x00\x00p\x00l\x00a\x00y\x00e\x00r\x00\x00\x00")+20
    # get the original name
    length=struct.unpack("I",dasdata[offset:offset+4])[0]
    oldname=dasdata[offset+4:offset+4+length*2].replace("\x00","")
    Log("2 Old name : %s"%oldname)
    if dasdata.count(struct.pack("I",offset-das_content_offset))>1:
        Log("2 Found %s possible offsets"%dasdata.count(struct.pack("I",offset-das_content_offset)))
        Message(None,"Unable to change name this for this savegame\nTry with another savegame for this character.","e")
        return dasdata,False
    if oldname!=newname:
        Log('2 New character name : "%s"'%newname)
        # find the name offset declaration
        decl=dasdata.find(struct.pack("I",offset-das_content_offset))
        # new offset at the end
        dasdata=dasdata[0:decl]+struct.pack("I",len(dasdata)-das_content_offset)+dasdata[decl+4:]
        # with python 2.5 and over, use encode function to have unicode name
        try:
            name_u16=newname.encode("u16")[2:]+"\x00\x00"
        except:
            name_u16=""
            for c in newname+"\x00": name_u16+=c+"\x00"
        # append name to dst_data
        dasdata+=struct.pack("I",len(newname)+1)+name_u16
        return dasdata,True
    else:
        Log("2 Character name not changed")
        return dasdata,False

def GetInvSize(daspath,log=True):
    if log: Log("1INFO: Get Inventory Size in DAS file: %s"%daspath)
    # open the file
    if daspath=="": return ""
    dasfile=open(daspath,"rb")
    dasfile.seek(24)
    # get the content offset
    das_content_offset=struct.unpack("I",dasfile.read(4))[0]
    invsize_offset=das_content_offset+nREL_OFFSET_INVSIZE
    dasfile.seek(invsize_offset)
    invsize=struct.unpack("I",dasfile.read(4))[0]
    dasfile.close()
    if log: Log("2 Inventory Size=%s @%s"%(invsize,invsize_offset))
    return invsize

def SetInvSize(dasdata,invsize):
    Log("1INFO: Set Inventory Size %s in DAS file"%invsize)
    # get the content offset
    das_content_offset=struct.unpack("I",dasdata[24:28])[0]
    invsize_offset=das_content_offset+nREL_OFFSET_INVSIZE
    Log("2 Inventory Size @%s"%invsize_offset)
    isd=struct.pack("I",invsize)
    dasdata=dasdata[0:invsize_offset]+isd+dasdata[invsize_offset+4:]
    return dasdata

def GetMORData(daspath):
    # recover dasdata
    if daspath=="": return ""
    dasfile=open(daspath,"rb")
    Log("1INFO: Open DAS file to get MOR face data = %s"%daspath)
    dasdata=dasfile.read()
    dasfile.close()
    # find morph data
    cnt=dasdata.count(sMOR_HEADER)
    if cnt>1: Log("2 Multiple MOR face data, taking the last")
    start=0
    for i in range(cnt):
        start=dasdata.find(sMOR_HEADER,start+len(sMOR_HEADER))
    if start>=0:
        length=struct.unpack("I",dasdata[start-4:start])[0]
        Log("2 MOR face data @%s length=%s"%(start,length))
        # return morph data
        return dasdata[start:start+length]
    Log("0ERROR: No MOR face data in DAS file")
    return ""

################################################################
## ERF HANDLER
################################################################
class ERFFile():
    def __init__(self,path):
        self.infos={"type":u"",
                   "files":0}
        self.registry={}
        self.path=path
        self.file=None
        if OP.exists(path):
            self.file=open(path,"rb")
            # Fill the parser
            self.file.seek(0)
            head=self.file.read(16)
            head=head.replace("\x00","")
            self.infos["type"]=head
            self.file.seek(16)
            nfiles=struct.unpack("I",self.file.read(4))[0]
            self.infos["files"]=nfiles
            Log("1INFO: Loaded: %s (%s, %s files)"%(path,head,nfiles))
            # Files registry
            self.file.seek(32)
            for i in range(0,nfiles):
                name=self.file.read(64)
                name=name.replace("\x00","")
                offset,lenght=struct.unpack("II",self.file.read(8))
                self.registry[name]=(offset,lenght)
                
    def Search(self,match="",log=False,ext=""):
        if match: Log('1INFO: Searching in ERF "%s"'%match)
        rtn=[]
        if match:
            for f in self.registry.keys():
                if match.lower() in f.lower():
                    if log: Log("2 Match %s"%f)
                    if ext and f.endswith(ext): rtn.append(f)
                    else: rtn.append(f)
        else:
            rtn=self.registry.keys()
        return rtn
    
    def GetFileData(self,name):
        o,l=self.registry[name]
        self.file.seek(o)
        return self.file.read(l)
    
    def Close(self):
        self.file.close()

    # Will come in 2.1x maybe
    def AddFile(self,name,filedata):
        return

    def RemoveFile(self,name):
        return

    def SaveERF(self,path):
        return
################################################################
## MOR HANDLER
################################################################
class MORFile():
    MARK="DAFR"
    ids={2:"NAME",
         23000:"MORPH_PARTS",
         23001:"MORPH_TINTFILENAMES",
         23002:"MORPH_NODES"}
    
    def __init__(self,raw_data="",name="<none>"):
        self.name=name
        self.data=raw_data
        self.mod=False # modified flag
        self.nodes={}
        self.MP={}
        self.MT={}
        if raw_data:
            self._parse()

    def _parse(self):
        # build nodes definition
        ncount=self._read(20,"I")[0]
        for i in range(ncount):
            no,nn,nc,nd=self._read(24+16*i,"I4sII")
            self.nodes[nn]=(no,nc,nd)
# print nodes
        # get the morp node
        mo,mc,md=self.nodes["morp"]
        morp={}
        for i in range(mc):
            mii,mit,mif,mio=self._read(md+12*i,"IHHI")
            morp[self.ids[mii]]=(mit,mif,mio)
# print morp
        # MORPH_PARTS
        # offset of the string list declaration
        offset=self._read(mo+morp["MORPH_PARTS"][2],"I")[0]+mo
        # get the stringlist count & locations
        count=self._read(offset,"I")[0]
        for i in range(count):
            p=aMOR_FEATURES_PARTS[i]
            o=self._read(offset+4*i+4,"I")[0]
            if o!=0xffffffff:
                l=self._read(o+mo,"I")[0]
                s=self._read(o+mo+4,"%ss"%(l*2))[0]
                # s have only ascii chars, so use a replace() instead of decode():
                s=s.replace("\x00","")
            else:
                s=""
            self.MP[p]=s
        self.MP["_off"]=offset
        self.MP["_cnt"]=count
# print self.MP
        # MORPH_TINTFILENAMES
        # offset of the string list declaration
        offset=self._read(mo+morp["MORPH_TINTFILENAMES"][2],"I")[0]+mo
        # get the stringlist count & location
        count=self._read(offset,"I")[0]
        for i in range(count):
            p=aMOR_FEATURES_TINTS[i]
            o=self._read(offset+4*i+4,"I")[0]
            if o!=0xffffffff:
                l=self._read(o+mo,"I")[0]
                s=self._read(o+mo+4,"%ss"%(l*2))[0]
                # s have only ascii chars, so use a replace() instead of decode():
                s=s.replace("\x00","")
            else:
                s=""
            self.MT[p]=s
        self.MT["_off"]=offset
        self.MT["_cnt"]=count
# print self.MT
        
    def _read(self,start,fmt):
        # read %fmt at %start
        length=struct.calcsize(fmt)
        rtn=self.data[start:start+length]
        rtn=struct.unpack(fmt,rtn)
        return rtn
    
    def _replace(self,start,string):
        self.data=self.data[0:start]+string+self.data[start+len(string):]
        
    def Update(self):
        # do not proceed if file not modified
        if self.mod==False:
            return
        Log("1INFO: Updating MOR file")
        # update the data
        mo,mc,md=self.nodes["morp"]
        morp={}
        for i in range(mc):
            mii,mit,mif,mio=self._read(md+12*i,"IHHI")
            morp[self.ids[mii]]=(mit,mif,mio)
        
        # MORPH_PARTS
        start=len(self.data)-mo
        self._replace(mo+morp["MORPH_PARTS"][2],struct.pack("I",start))
        # build string list : declaration
        sl=struct.pack("I",6)
        start+=4+4*6
        tlst=""
        for p in aMOR_FEATURES_PARTS:
# print p
            s=self.MP[p]+"\x00"
            if s!="\x00": # string is defined
# print s,start
                l=len(s)
                ns=""
                for c in s: ns+=c+"\x00"
                sl+=struct.pack("I",start)
                tlst+=struct.pack("I",l)+ns
                start+=4+len(ns)
            else: # string not defined
                sl+=struct.pack("I",0xffffffff)
        # append list to string list declaration
        sl+=tlst
        # append string list to data
        self.data+=sl
        # MORPH_TINTFILENAMES
        # new offset: at the end of file:
        start=len(self.data)-mo
        self._replace(mo+morp["MORPH_TINTFILENAMES"][2],struct.pack("I",start))
        # build string list : declaration
        sl=struct.pack("I",12)
        start+=4+4*12
        tlst=""
        for t in aMOR_FEATURES_TINTS:
# print t
            s=self.MT[t]+"\x00"
            if s!="\x00": # string is defined
# print s,start
                l=len(s)
                ns=""
                for c in s: ns+=c+"\x00"
                sl+=struct.pack("I",start)
                tlst+=struct.pack("I",l)+ns
                start+=4+len(ns)
            else: # string not defined
                sl+=struct.pack("I",0xffffffff)
        # append list to string list declaration
        sl+=tlst
        # append string list to data
        self.data+=sl

        # append a mark to show that MOR is edited
        self.data+=self.MARK
        Log("1INFO: MOR file size=%s"%len(self.data))

    def Save(self,path):
        # Save MORFile.data to path
        if OP.exists(path):
            Log("1INFO: Backing up MOR file")
            fin=open(path,"rb")
            txt=fin.read()
            fin.close()
            fout=open(path+".bak","wb")
            fout.write(txt)
            fout.close()
        fout=open(path,"wb")
        fout.write(self.data)
        fout.close()
        
    def GetString(self,part):
        # Get String for part
        if part in self.MP.keys(): return self.MP[part]
        if part in self.MT.keys(): return self.MT[part]
        return ""

    def SetString(self,part,string):
        # Set String for part
        self.mod=True
        if part in self.MP.keys(): self.MP[part]=string
        elif part in self.MT.keys(): self.MT[part]=string
        else: self.mod=False
        
################################################################
## INTERFACE
################################################################
class DAFR(Frame):
    HELP='''Source\t-> Destination:
-----------------------------
None\t-> *.das
-Edit face features, name, and inventory size

*.das\t-> *.das
*.mor\t-> *.das
*.erf\t-> *.das
-Change face & edit face features, name and inventory size

None\t-> *.mor
-Modify face in a exchangeable *.mor face file

*.mor\t-> *.mor
-Edit *.mor face with another

*.das\t-> *.mor
*.erf\t-> *.mor
-Extract & modify face in a exchangeable *.mor face file
-Edit *.mor face with another mor in savegame or resource

 For more deep changes of face (colors for example):
- extract the face from *.das or *.erf
- edit the *.mor file in the Toolset
- use as source for your savegame'''
    
    def __init__(self,master=None):
        Frame.__init__(self, master)
        self.sel="" # selected path
        self.src="" # source path
        self.dst="" # destination path
        # source and destination MOR files
        self.SRC_MOR=MORFile("","<source>")
        self.DST_MOR=MORFile("","<destination>")
        # resource file and facename
        self.res_file=ERFFile(pLAST_ERF)
        self.res_facename=""
        # key for face editing
        self.entry_key=""
        # copy flags for src to dest
        self.copy_face=True
        self.copy_name=True

        # check if there is chars in CHARACTER_DIR
        self.ScanForChars(pCHARACTERS_DIR)
        
        # start UI
        self.CreateWidgets()

    def CreateWidgets(self):
        self.master.title("%s v%s"%(sAPPNAME,sVERSION))
        self.BuildWidgets()
        self.PackWidgets()
        self.BindWidgets()
        self.InitVars()
        
    def BuildWidgets(self):
        # self.f_buttons
        self.f_buttons=Frame(self,bg="darkgrey")
        self.fB_credits=Label(self.f_buttons,text="(c)2010 by NewByPower",fg="white",bg="darkgrey")
        self.fB_bQuit=Button(self.f_buttons,text='QUIT',fg="white",bg='darkred',width=10,command=self.quit)
        self.fB_bHelp=Button(self.f_buttons,text='?',fg="white",bg='darkblue',width=1,command=self.ShowHelp)
        
        self.f_main=Frame(self)
        # self.f_files : all means to open files
        self.f_files=Frame(self.f_main)
        self.fF_title=Label(self.f_files,text="FILE SELECTED (None)")
        self.fF_path=Label(self.f_files,text="...\n...",fg="white",bg="black",width=40,justify=RIGHT)
        self.fF_buttons=Frame(self.f_files)
        self.fFB_bSetSRC=Button(self.fF_buttons,text="Set as Source",width=20,command=self.SetSRC)
        self.fFB_bSetDST=Button(self.fF_buttons,text="Set as Destination",width=20,command=self.SetDST)
        self.fF_infos=Label(self.f_files,text="1- Select a file and set it as source or destination\n2- Copy what you want from source\n3- Save the destination file",justify=LEFT)
        
        # File type tabs
        self.fF_tabs=Frame(self.f_files)
        self.fFT_bChar=Button(self.fF_tabs,text="Character",width=10,command=self.ShowCHR)
        self.fFT_bSave=Button(self.fF_tabs,text="Savegame",width=10,command=self.ShowDAS)
        self.fFT_bFace=Button(self.fF_tabs,text="Face",width=10,command=self.ShowMOR)
        self.fFT_bResF=Button(self.fF_tabs,text="Resource",width=10,command=self.ShowERF)
        # CHR tab
        self.fF_chr=Frame(self.f_files)
        self.fFC_path=Label(self.fF_chr,text="...\n...",fg="white",bg="black",justify=RIGHT)
        self.fFC_chardir=Button(self.fF_chr,text="%process character folder",command=self.SetCharDir,width=28)
        self.fFC_browser=Frame(self.fF_chr)
        self.fFCB_chars=Frame(self.fFC_browser)
        self.fFCBC_list=Listbox(self.fFCB_chars,width=26,height=4,activestyle=DOTBOX)
        self.fFCBC_sblist=Scrollbar(self.fFCB_chars,orient=VERTICAL)
        self.fFCB_label=Label(self.fFC_browser,text="View: %char",width=28)
        self.fFCB_saves=Frame(self.fFC_browser)
        self.fFCBS_list=Listbox(self.fFCB_saves,width=26,height=5,activestyle=DOTBOX)
        self.fFCBS_sblist=Scrollbar(self.fFCB_saves,orient=VERTICAL)
        # DAS tab
        self.fF_das=Frame(self.f_files)
        self.fFD_path=Label(self.fF_das,text="...\n...",fg="white",bg="black",justify=RIGHT)
        self.fFD_bOpenDAS=Button(self.fF_das,text="Open Savegame",width=28,command=self.OpenDAS)
        # MOR tab
        self.fF_mor=Frame(self.f_files)
        self.fFM_path=Label(self.fF_mor,text="...\n...",fg="white",bg="black",justify=RIGHT)
        self.fFM_bNewMOR=Button(self.fF_mor,text="New Face file",width=28,command=self.NewMOR)
        self.fFM_bOpenMOR=Button(self.fF_mor,text="Open Face file",width=28,command=self.OpenMOR)
        # ERF tab
        self.fF_res=Frame(self.f_files)
        self.fFR_path=Label(self.fF_res,text="...\n...",fg="white",bg="black",justify=RIGHT)
        self.fFR_bOpenERF=Button(self.fF_res,text='Open Resource file',width=28,command=self.OpenERF)
        self.fFR_browser=Frame(self.fF_res)
        self.fFRB_infos=Label(self.fFR_browser,text="Faces files (%filter/%total)")
        self.fFRB_filter=Frame(self.fFR_browser)
        self.fFRBF_label=Label(self.fFRB_filter,text="Filter:",width=5)
        self.fFRBF_filter=Entry(self.fFRB_filter,width=22)
        self.fFRB_list=Frame(self.fFR_browser)
        self.fFRBL_list=Listbox(self.fFRB_list,width=26,height=8,activestyle=DOTBOX)
        self.fFRBL_sblist=Scrollbar(self.fFRB_list,orient=VERTICAL)
        
        # self.f_src : source editing
        self.f_src=Frame(self.f_main)
        self.fS_title=Label(self.f_src,text="SOURCE FILE (None)",width=40)
        self.fS_path=Label(self.f_src,text="...\n...",fg="white",bg="black",justify=RIGHT)
        self.fS_bCopyToDst=Button(self.f_src,text="Copy selection to Destination file",command=self.CopyData)
        # Face features
        self.fSF_cMorph=Checkbutton(self.f_src,text="Face shape (with tatooes, wrinkles, scars, ...)")
        self.fS_face=Frame(self.f_src)
        self.fSF_header=Frame(self.fS_face)
        self.fSFH_info=Label(self.fSF_header,text="Face features:",width=20)
        self.fSFH_bToggle=Button(self.fSF_header,text="Select all / none",command=self.Toggle,width=20)
# self.fSF={}
        self.fSF_clFeatures=CheckList(self.fS_face)
        self.fSF_clFeatures.hlist.delete_all()
        self.fSF_clFeatures.hlist.configure(bg="white")
        for i,ff in enumerate(aMOR_EDIT):
# self.fSF[ff[1]]=Checkbutton(self.fS_face,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1])))
            self.fSF_clFeatures.hlist.add(i,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1])))
            self.fSF_clFeatures.setstatus(i,"on")
            i+=1
        # Other features
        self.fS_cName=Checkbutton(self.f_src,text="Character name (%name)")

        # self.f_dst : destination editing
        self.f_dst=Frame(self.f_main)
        self.fD_title=Label(self.f_dst,text="DESTINATION FILE (None)",width=40)
        self.fD_path=Label(self.f_dst,text="...\n...",fg="white",bg="black",justify=RIGHT)
        # Face Editing
        self.fD_face=Frame(self.f_dst)
        self.fDF_label=Label(self.fD_face,text="Manual face editing:")
        self.fDF_list=Frame(self.fD_face)
        self.fDFL_list=Listbox(self.fDF_list,width=38,height=6,activestyle=DOTBOX)
        self.fDFL_sblist=Scrollbar(self.fDF_list,orient=VERTICAL)
        self.fDF_edit=Frame(self.fD_face)
        self.fDFE_label=Label(self.fDF_edit,text="%entry",width=15)
        self.fDFE_eValue=Entry(self.fDF_edit,width=20)
        self.fDFE_bSetValue=Button(self.fDF_edit,text="Set",width=5,command=self.SetValToDest)
        self.fDF_bResetFace=Button(self.fD_face,text="Reset destination face data",width=30,command=self.ResetFace)
        # DAS editing
        self.fD_savegame=Frame(self.f_dst)
        self.fDS_label=Label(self.fD_savegame,text="Savegame Editing",width=40)
        self.fDS_name=Frame(self.fD_savegame)
        self.fDSN_label=Label(self.fDS_name,text="Name",width=20,anchor=W)
        self.fDSN_eName=Entry(self.fDS_name,width=20)
        self.fDS_inventory=Frame(self.fD_savegame)
        self.fDSI_label=Label(self.fDS_inventory,text="Inventory size",width=20,anchor=W)
        self.fDSI_eInvsize=Entry(self.fDS_inventory,width=20)
        # Save
        bfont=tkFont.Font (family="Helvetica", size=8, weight="bold" )
        self.fD_bSave=Button(self.f_dst,text='SAVE DESTINATION FILE',font=bfont,width=28,command=self.Save)
        
# # ERF editing - forget in 2.00, mean to add faces into an ERF package
# self.fD_resource=Frame(self.f_dst)
# self.fDR_label=Label(self.fD_resource,text="Add to Resource",width=20)
# self.fDR_name=Frame(self.fD_resource)
# self.fDRN_label=Label(self.fDR_name,text="Filename",width=6)
# self.fDRN_eName=Entry(self.fDR_name,width=14)
        
    def PackWidgets(self):
        # buttons
        self.fB_credits.pack(side=LEFT,fill=X)
        self.fB_bHelp.pack(side=RIGHT)
        self.fB_bQuit.pack(side=RIGHT)
        self.f_buttons.pack(fill=X)
        # files
        self.fF_title.pack(fill=X)
        self.fF_path.pack(fill=X,pady=2)
        self.fFB_bSetSRC.pack(side=LEFT,fill=X)
        self.fFB_bSetDST.pack(side=RIGHT,fill=X)
        self.fF_buttons.pack(fill=X,pady=2)
        self.fF_infos.pack()
        self.fFT_bChar.pack(pady=2)
        self.fFT_bSave.pack(pady=2)
        self.fFT_bFace.pack(pady=2)
        self.fFT_bResF.pack(pady=2)
        self.fF_tabs.pack(side=LEFT,fill=Y,padx=2)
        spacer1=Frame(self.f_files,height=1,bg="darkgrey")
        spacer1.pack(side=LEFT,fill=Y,padx=1)
# self.fFC_path.pack(fill=X)
        self.fFC_chardir.pack(pady=2,padx=2)
        self.fFCBC_list.pack(side=LEFT)
        self.fFCBC_sblist.pack(side=LEFT,fill=Y)
        self.fFCBS_list.pack(side=LEFT)
        self.fFCBS_sblist.pack(side=LEFT,fill=Y)
        self.fFCB_chars.pack()
        self.fFCB_label.pack(anchor=W)
        self.fFCB_saves.pack()
        self.fFC_browser.pack()
# self.fF_chr.pack()
# self.fFD_path.pack(fill=X)
        self.fFD_bOpenDAS.pack(pady=2,padx=2)
# self.fF_das.pack()
# self.fFM_path.pack(fill=X)
        self.fFM_bNewMOR.pack(pady=2,padx=2)
        self.fFM_bOpenMOR.pack(pady=2,padx=2)
# self.fF_mor.pack()
# self.fFR_path.pack(fill=X)
        self.fFR_bOpenERF.pack(pady=2,padx=2)
        self.fFRB_infos.pack(side=TOP)
        self.fFRBF_label.pack(side=LEFT)
        self.fFRBF_filter.pack(side=LEFT)
        self.fFRB_filter.pack()
        self.fFRBL_list.pack(side=LEFT)
        self.fFRBL_sblist.pack(side=LEFT,fill=Y)
        self.fFRB_list.pack()
        self.fFR_browser.pack()
# self.fF_res.pack()
        self.f_files.pack(side=LEFT,fill=Y,padx=2)
        # spacer
        spacer2=Frame(self.f_main,height=1,bg="darkgrey")
        spacer2.pack(side=LEFT,fill=Y,padx=1)
        # source
        self.fS_title.pack(fill=X)
        self.fS_path.pack(fill=X,pady=2)
        self.fS_bCopyToDst.pack(pady=2,fill=X)
# self.fSF_info.grid(row=0,column=0,sticky=W)
# self.fSF_bToggle.grid(row=0,column=1)
# self.fSF_cMorph.grid(row=1,column=0,sticky=W)
        self.fSF_cMorph.pack(anchor=W)
        # spacer
        spacer4=Frame(self.f_src,height=1,bg="darkgrey")
        spacer4.pack(fill=X,pady=1)
        self.fSFH_info.pack(side=LEFT,anchor=W)
        self.fSFH_bToggle.pack(side=LEFT,fill=X)
        self.fSF_header.pack()
        self.fSF_clFeatures.pack(fill=X)
# for col in range(2):
# for row in range(9):
# if col==0 and row==0: continue
# else:
# ff=aMOR_EDIT[9*col+row-1]
# self.fSF[ff[1]].grid(row=row+1,column=col,sticky=W)
        self.fS_face.pack(pady=2)
        # spacer
        spacer5=Frame(self.f_src,height=1,bg="darkgrey")
        spacer5.pack(fill=X,pady=1)
        self.f_src.pack(side=LEFT,fill=Y,padx=2)
        # spacer
        spacer3=Frame(self.f_main,height=1,bg="darkgrey")
        spacer3.pack(side=LEFT,fill=Y,padx=1)
        # destination
        self.fD_title.pack(fill=X)
        self.fD_path.pack(fill=X,pady=2)
        self.fDF_label.pack()
        self.fDFL_list.pack(side=LEFT)
        self.fDFL_sblist.pack(side=LEFT,fill=Y)
        self.fDF_list.pack()
        self.fDFE_label.pack(side=LEFT)
        self.fDFE_eValue.pack(side=LEFT)
        self.fDFE_bSetValue.pack(side=LEFT)
        self.fDF_edit.pack()
        self.fDF_bResetFace.pack(pady=2,fill=X)
        self.fD_face.pack()
        # spacer
        spacer6=Frame(self.f_dst,height=1,bg="darkgrey")
        spacer6.pack(fill=X,pady=1)
        self.fDS_label.pack()
        self.fDSN_label.pack(side=LEFT)
        self.fDSN_eName.pack(side=LEFT)
        self.fDS_name.pack()
        self.fDSI_label.pack(side=LEFT)
        self.fDSI_eInvsize.pack(side=LEFT)
        self.fDS_inventory.pack()
        self.fD_bSave.pack(side=BOTTOM,fill=X)
        
# self.fDR_label.pack()
# self.fDRN_label.pack(side=LEFT)
# self.fDRN_eName.pack(side=LEFT)
# self.fDR_name.pack()
# self.fD_resource.pack()
        
        self.f_dst.pack(side=LEFT,fill=Y,padx=2)
        self.f_main.pack()

        self.pack()

    def BindWidgets(self):
        # binding
        self.fFRBF_filter.bind('<Key-Return>',self.Filter)
        
        self.fFRBL_list.bind('<Button1-ButtonRelease>',self.SelectFace)
        self.fFRBL_list.configure(yscrollcommand=self.fFRBL_sblist.set)
        self.fFRBL_sblist.configure(command=self.fFRBL_list.yview)
        
        self.fFCBC_list.bind('<Button1-ButtonRelease>',self.SelectChar)
        self.fFCBC_list.configure(yscrollcommand=self.fFCBC_sblist.set)
        self.fFCBC_sblist.configure(command=self.fFCBC_list.yview)
        
        self.fFCBS_list.bind('<Button1-ButtonRelease>',self.SelectSave)
        self.fFCBS_list.configure(yscrollcommand=self.fFCBS_sblist.set)
        self.fFCBS_sblist.configure(command=self.fFCBS_list.yview)
            
        self.fDFL_list.bind('<Button1-ButtonRelease>',self.SelectEntry)
        self.fDFL_sblist.configure(command=self.fDFL_list.yview)
        self.fDFL_list.configure(yscrollcommand=self.fDFL_sblist.set)

    def InitVars(self):
        # Init vars
        self.filter_string=StringVar()
        self.files_list=Variable()
        self.chars_list=Variable()
        self.saves_list=Variable()
        self.dst_name=StringVar()
        self.dst_invsize=IntVar()
        self.entries_list=Variable()
        self.entry_value=StringVar()
        
        self.fFRBF_filter["textvariable"]=self.filter_string
        self.fFRBL_list["listvariable"]=self.files_list
        self.fFCBC_list["listvariable"]=self.chars_list
        self.fFCBS_list["listvariable"]=self.saves_list
        self.fDFL_list["listvariable"]=self.entries_list
        self.fDSN_eName["textvariable"]=self.dst_name
        self.fDSI_eInvsize["textvariable"]=self.dst_invsize
        self.fDFE_eValue["textvariable"]=self.entry_value
# self.fDRN_eName["textvariable"]=self.res_filename

# self.copy_mask=[BooleanVar()]
# self.fSF_cMorph["variable"]=self.copy_mask[0]
# for x in range (len(aMOR_EDIT)):
# v=BooleanVar()
# self.copy_mask.append(v)
# self.fSF[aMOR_EDIT[x][1]]["variable"]=self.copy_mask[x+1]
# self.copy_mask.append(BooleanVar())
# self.fS_cName["variable"]=self.copy_mask[len(aMOR_EDIT)+1]
        self.copy_face=BooleanVar()
        self.fSF_cMorph["variable"]=self.copy_face
        self.copy_name=BooleanVar()
        self.fS_cName["variable"]=self.copy_name
        
        self.entries_list.set(tuple(map(lambda me: me[0],aMOR_EDIT)))
        self.chars_list.set(tuple(map(lambda c: c[0],self.chars)))
        self.fFCBC_list.selection_set(self.cs[0],self.cs[0])

        self.Filter()
        self.SelectEntry(None)
        self.SelectFace(None)
        self.SelectChar(None)
        
# for ff in aMOR_EDIT: self.fSF[ff[1]].select()
        self.fSF_cMorph.select()
        self.fS_cName.select()
        self.RebuildFeaturesList()
        self.Show("C")
        
    def ShowCHR(self):
        self.SetSelected(pLAST_CHR)
        self.fFC_path["text"]=TruncatePath(pLAST_DAS,"",22)
        charname=self.chars_list.get()[self.cs[0]]
        savename=self.saves_list.get()[self.cs[1]]
        if self.chars_list.get():
            self.fFCB_label["text"]="%s : %s"%(charname,savename)
        self.SetCharDirText()
        self.Show("C")
        
    def ShowDAS(self):
        self.SetSelected(pLAST_DAS)
        self.fFD_path["text"]=TruncatePath(pLAST_DAS,"",22)
        self.Show("S")
        
    def ShowMOR(self):
        self.SetSelected(pLAST_MOR,False)
        self.fFM_path["text"]=TruncatePath(pLAST_MOR,"",22)
        self.Show("F")
        
    def ShowERF(self):
        self.SetSelected(pLAST_ERF)
        if pLAST_ERF:
            self.fF_path["text"]=TruncatePath(pLAST_ERF,"",45)+"\n"+self.res_facename
            self.fFR_path["text"]=TruncatePath(pLAST_ERF,"",22)
        self.Filter(self.filter_string.get())
        self.Show("R")

    def Show(self,what=""):
        pairs={"C":(self.fF_chr,self.fFT_bChar),
               "S":(self.fF_das,self.fFT_bSave),
               "F":(self.fF_mor,self.fFT_bFace),
               "R":(self.fF_res,self.fFT_bResF)}
        for v in pairs.values():
            v[0].forget()
            v[1].config(relief="raised")
        for c in what:
            if c in pairs.keys():
                pairs[c][0].pack()
                pairs[c][1].config(relief="sunken")
                
    def ShowHelp(self):
        Help=TKMB.Message(self,message=self.HELP,icon=TKMB.INFO,title="%s : Help"%sAPPNAME)
        Help.show()

# Source and destination loading
    def OpenDAS(self):
        global pLAST_DAS
        Dialog=TKFD.Open(filetypes=(("DragonAge Savegame",".das"),),
                         initialdir=OP.dirname(pLAST_DAS))
        path=Dialog.show()
        if GetType(path)==1:
            pLAST_DAS=path
            Log("1INFO: Open savegame: %s"%pLAST_DAS)
            self.ShowDAS()
        
    def NewMOR(self):
        global pLAST_MOR
        Dialog=TKFD.SaveAs(filetypes=(("Face file",".mor"),),
                           initialdir=OP.dirname(pLAST_MOR),
                           defaultextension=".mor",
                           initialfile=self.dst_name.get())
        path=Dialog.show()
        if GetType(path)==2:
            pLAST_MOR=path
            Log("1INFO: New face: %s"%pLAST_MOR)
            self.ShowMOR()
        
    def OpenMOR(self):
        global pLAST_MOR
        Dialog=TKFD.Open(filetypes=(("Face file",".mor"),),
                         initialdir=OP.dirname(pLAST_MOR))
        path=Dialog.show()
        if GetType(path)==2:
            pLAST_MOR=path
            Log("1INFO: Open face: %s"%pLAST_MOR)
            self.ShowMOR()
        
    def OpenERF(self):
        global pLAST_ERF
        Dialog=TKFD.Open(filetypes=(("DragonAge Resource",".erf .rim"),),
                         initialdir=OP.dirname(pLAST_ERF))
        path=Dialog.show()
        if GetType(path)==3:
            pLAST_ERF=path
            Log("1INFO: Open resource: %s"%pLAST_ERF)
            self.res_file=ERFFile(pLAST_ERF)
            self.Filter()
            if self.files_list.get():
                self.fFRBL_list.selection_set(0)
                self.res_facename=self.files_list.get()[0]
            self.ShowERF()
        
    def SetSelected(self,path,checkpath=True):
        path=OP.normpath(path)
        self.sel=path
        if checkpath and not OP.exists(path) or path in ("",".","/."): SetPathText(self.fF_path,"","",45)
        else: SetPathText(self.fF_path,path,"",45)
        self.fF_title["text"]="FILE SELECTED (%s)"%GetTypeName(path)
        self.fF_title["fg"]=GetTypeColor(path)

    def SetSRC(self,path=""):
        if path: self.sel=path
        if OP.exists(self.sel):
            self.SetSrcFile(self.sel)
            self.CheckSRC()
        
    def CheckSRC(self):
        self.fS_cName.forget()
        if GetType(self.src)==1 and GetType(self.dst)==1: #DAS 2 DAS
            self.fS_cName.pack(pady=2,anchor=W)
            name,mode=GetName(self.src,log=False)
            if name:
                self.fS_cName["text"]="Character name (%s)"%name
                self.fS_cName.select()
        else:
            self.fS_cName.forget()
        self.fSF_clFeatures.hlist.delete_all()
        for i,ff in enumerate(aMOR_EDIT):
# self.fSF[ff[1]]["text"]="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1]))
# self.fSF[ff[1]].select()
            self.fSF_clFeatures.hlist.add(i,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1])))
            self.fSF_clFeatures.setstatus(i)
            
        self.fSF_cMorph.select()
        
    def SetDST(self,path=""):
        if path: self.sel=path
        if GetType(self.sel)==1 or GetType(self.sel)==2:
            self.SetDstFile(self.sel)
            self.CheckDST()
        
    def CheckDST(self):
        entries=map(lambda me: me[0]+" (%s)"%self.DST_MOR.GetString(me[1]),aMOR_EDIT)
        self.entries_list.set(tuple(entries))
        self.SelectEntry()
        self.fD_savegame.forget()
        if GetType(self.dst)==1: #DAS
            self.fD_savegame.pack()
            name,mode=GetName(self.dst,log=False)
            if mode==1: self.fDSN_eName["bg"]="grey" # not change possible
            elif mode==2: self.fDSN_eName["bg"]="yellow" # name error
            else: self.fDSN_eName["bg"]="white"
            self.dst_name.set(name)
            self.dst_invsize.set(GetInvSize(self.dst,log=False))
        if GetType(self.src)==1 and GetType(self.dst)==1: #DAS 2 DAS
            self.fS_cName.pack(pady=2,anchor=W)
            name,mode=GetName(self.src,log=False)
            if name:
                self.fS_cName["text"]="Character name (%s)"%name
                self.fS_cName.select()
        
# Character selection
    def SetCharDir(self):
        global pCHARACTERS_DIR
        Dialog=TKFD.Directory(initialdir=OP.dirname(pCHARACTERS_DIR))
        char_dir=OP.normpath(Dialog.show()+"/")
        self.ScanForChars(char_dir)
        if self.chars:
            Log("1INFO: New Characters directory : %s"%char_dir)
            pCHARACTERS_DIR=char_dir
            self.chars_list.set(tuple(map(lambda c: c[0],self.chars)))
            self.fFCBC_list.selection_set(self.cs[0],self.cs[0])
            self.SelectChar(None)
            self.SetCharDirText()
            
    def SetCharDirText(self):
        if self.chars: self.fFC_chardir.config(text="Change character folder")
        else: self.fFC_chardir.config(text="Set character folder")
        
    def ScanForChars(self,char_dir=pCHARACTERS_DIR):
        self.chars=[]
        if OP.exists(char_dir):
            for p in os.listdir(char_dir):
                sp=OP.normpath(OP.join(char_dir,p,"Saves"))
                if OP.isdir(sp): # path is a directory
                    saves=[]
                    for pp in os.listdir(sp):
                        spp=OP.normpath(OP.join(char_dir,p,"Saves",pp))
                        if not OP.exists(spp): continue
                        if OP.isdir(spp):
                            for f in os.listdir(spp):
                                if OP.splitext(f)[1]==".das":
                                    saves.append(pp)
                    if saves: self.chars.append((p,saves))
        self.cs=[0,0]
        self.sel=""
        Log("1INFO: Found %s characters"%len(self.chars))

    def SelectChar(self,event):
        try:
            ci=int(self.fFCBC_list.curselection()[0])
        except:
            if pLAST_CHR:
                charname=pLAST_CHR[pLAST_CHR.lower().index("characters")+11:].split(os.sep)[0]
                names=self.chars_list.get()
                if charname in names: ci=names.index(charname)
            else: ci=0
        self.fFCBS_list.selection_clear(0)
        self.fFCBC_list.selection_set(ci)
        self.cs=[ci,0]
        if self.chars:
            self.saves_list.set(tuple(map(lambda s: s,self.chars[self.cs[0]][1])))
            self.fFCBS_list.selection_clear(0)
            self.fFCBS_list.selection_set(0)
            self.SelectSave(None,False)
    
    def SelectSave(self,event,log=True):
        global pLAST_CHR
        try:
            si=int(self.fFCBS_list.curselection()[0])
        except:
            if pLAST_CHR:
                charsave=pLAST_CHR[pLAST_CHR.lower().index("saves")+6:].split(os.sep)[0]
                saves=self.saves_list.get()
                if charsave in saves: si=saves.index(savename)
            else: si=self.cs[1]
        self.fFCBS_list.selection_clear(0)
        self.fFCBS_list.selection_set(si)
        if sAUTO_SELECT and not event:
            lst=map(lambda s:s.lower(),self.saves_list.get())
            if sAUTO_SELECT.lower() in lst:
                si=lst.index(sAUTO_SELECT.lower())
                self.fFCBS_list.selection_clear(0)
                self.fFCBS_list.selection_set(si)
                self.fFCBS_list.see(si)
        self.cs[1]=si
        charname=self.chars_list.get()[self.cs[0]]
        savename=self.saves_list.get()[self.cs[1]]
        savedir=OP.normpath(OP.join(pCHARACTERS_DIR,charname,"Saves",savename))
        for f in os.listdir(savedir):
            if OP.splitext(f)[1]==".das":
                pLAST_CHR=OP.normpath(OP.join(savedir,f))
        Log("1INFO: Selected character savegame: %s"%pLAST_CHR)
        self.ShowCHR()
        
# Resource File selection
    def Filter(self,match=""):
        faces=[]
        if self.res_file:
            match=self.filter_string.get()
            faces=self.res_file.Search(match,False,".mor")
            lst=[]
            for f in faces:
                if match in f: lst.append(f)
            self.files_list.set(tuple(lst))
        self.fFRB_infos["text"]="Faces Files (%s/%s)"%(len(lst),len(faces))
    
    def SelectFace(self,event):
        if self.res_file:
            try:
                selection=int(self.fFRBL_list.curselection()[0])
            except:
                selection=0
                self.fFRBL_list.selection_set(0)
            if self.files_list.get():
                self.res_facename=self.files_list.get()[selection]
                Log("1INFO: Selected Face file: %s (in %s)"%(self.res_facename,self.res_file.path))
            self.ShowERF()
            
# Source Edition
    def CopyData(self,mask=""):
        if self.SRC_MOR.data=="" or self.DST_MOR.data=="":
            Message(self,"Set a source and a destination first !")
            return
        Log("1INFO: Copying Data")
        features=self.fSF_clFeatures.getselection()
        
        # face morph : build new mor with source or dest
        if self.copy_face.get():
            NEW_MOR=MORFile(self.SRC_MOR.data,"<new>")
            Log("2 Get Morph from SRC_MOR")
        else:
            NEW_MOR=MORFile(self.DST_MOR.data,"<new>")
            Log("2 Get Morph from DST_MOR")
        # At this point, all the morph has been replaced
        if len(features)==len(aMOR_EDIT) and self.copy_face.get():
            # useless to copy features if all selected and new morph is src morph
            Log("2 All features are already sets")
        else:
            # face features : replace values in new mor by src mor or dst mor values
            for i in range(len(aMOR_EDIT)):
                key=aMOR_EDIT[int(i)][1]
                valS=self.SRC_MOR.GetString(key)
                valD=self.DST_MOR.GetString(key)
                if str(i) in features:
                    Log("2 Get %s from SRC_MOR (%s)"%(key,valS))
                    NEW_MOR.SetString(key,valS)
                else:
                    Log("2 Get %s from DST_MOR (%s)"%(key,valD))
                    NEW_MOR.SetString(key,valD)
        # Set charname in UI
        if self.copy_name.get() and GetType(self.src)==1 and GetType(self.dst)==1:
            dstname,mode=GetName(self.dst,log=False)
            srcname,mode=GetName(self.src,log=False)
            if srcname:
                self.dst_name.set(srcname)
                Log("2 Get Name from SRC : (%s)"%(srcname))
        # Copy new mor to dest mor
        self.DST_MOR=NEW_MOR
        self.RebuildFeaturesList()
        self.SelectEntry()
        
    def Toggle(self):
        features=self.fSF_clFeatures.getselection()
        if features:
            for i,ff in enumerate(aMOR_EDIT):
# self.fSF[ff[1]].deselect()
                self.fSF_clFeatures.setstatus(i,"off")
            #self.fSF_cMorph.deselect()
            #self.fS_cName.deselect()
        else:
            for i,ff in enumerate(aMOR_EDIT):
# self.fSF[ff[1]].select()
                self.fSF_clFeatures.setstatus(i,"on")
            #self.fSF_cMorph.select()
            #self.fS_cName.select()
        
# Destination face editing
    def SelectEntry(self,event=None):
        try:
            entry=int(self.fDFL_list.curselection()[0])
        except:
            entry=0
            self.fDFL_list.selection_set(entry)
        self.edit_key=aMOR_EDIT[entry][1]
        self.fDFE_label["text"]=aMOR_EDIT[entry][0]
        self.entry_value.set(self.DST_MOR.GetString(self.edit_key))
        
    def SetValToDest(self):
        if self.DST_MOR.data=="":
            Message(self,"Set a destination first !")
            return
        key=self.edit_key
        val=self.fDFE_eValue.get()
        self.DST_MOR.SetString(key,val)
        self.RebuildFeaturesList()
        Log('1INFO: Changed feature <%s> to "%s" in destination face'%(key,val))
        
    def ResetFace(self):
        if GetType(self.dst)==1:
            self.DST_MOR=MORFile(GetMORData(self.dst),"<destination savegame>")
        if GetType(self.dst)==2:
            if OP.exists(self.dst):
                fin=open(self.dst,"rb")
                txt=fin.read()
                fin.close()
                self.DST_MOR=MORFile(txt)
            else: # new file
                self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>")
                if not self.SRC_MOR.data: Log("1INFO: New mor file waiting to be filled with any source")
        self.RebuildFeaturesList()
        
    def RebuildFeaturesList(self):
        entries=map(lambda me: me[0]+" (%s)"%self.DST_MOR.GetString(me[1]),aMOR_EDIT)
        self.entries_list.set(tuple(entries))
        self.SelectEntry()
        
# Saving process
    def Save(self):
        if not self.dst:
            Message(self,"Set a destination first !")
            return
        if GetType(self.dst)==1 and OP.exists(self.dst) and nSAVE_METHOD>=1:
            # list files in the save folder
            folder=OP.dirname(self.dst)
            if nSAVE_METHOD==1: # copy to Slot_n+1
                inc=1
                while OP.exists(OP.dirname(folder)+os.sep+"Slot_%d"%inc): inc+=1
                newfolder=OP.dirname(folder)+os.sep+"Slot_%d"%inc
            # create folder if necessary and copy files
            if not OP.exists(newfolder): os.mkdir(newfolder)
            for f in os.listdir(folder): shutil.copy(OP.join(folder,f),OP.join(newfolder,f))
            # set self.dst as the new das file
            destpath=OP.join(newfolder,OP.basename(self.dst))
        else:
            destpath=self.dst
        if destpath and self.DST_MOR.data!="":
            Log("1INFO: Saving to destination file : %s"%destpath)
            ok=False
            # First, update destination face with the new strings (build the raw data)
            self.DST_MOR.Update()
            # Dest is a DAS, proceed with tweaks
            if GetType(destpath)==1:
                ok=ChangeSaveData(destpath,self.dst_name.get(),self.dst_invsize.get(),self.DST_MOR)
                if ok:
                    if nSAVE_METHOD<0: Message(self,"Savegame modified (without backup):\n%s"%destpath)
                    elif nSAVE_METHOD==0: Message(self,"Savegame modified (with backup):\n%s"%destpath)
                    elif nSAVE_METHOD==1: Message(self,"Savegame created (in new slot):\n%s"%(destpath))
            # Dest is a MOR, proceed with writing mor face to file
            elif GetType(destpath)==2:
                self.DST_MOR.Save(destpath)
                Message(self,"Face file created:\n%s"%destpath)
        
    def SetSrcFile(self,path):
        path=OP.normpath(path)
        self.SRC_MOR=MORFile("","<source>")
        if path:
            if GetType(path)==1:
                Log("1INFO: Selected DAS Source file: %s"%path)
                self.src=path
                self.SRC_MOR=MORFile(GetMORData(path),"<source savegame>")
                SetPathText(self.fS_path,path,"",45)
            elif GetType(path)==2:
                Log("1INFO: Selected MOR Source file: %s"%path)
                self.src=path
                fin=open(path,"rb")
                txt=fin.read()
                fin.close()
                self.SRC_MOR=MORFile(txt)
                SetPathText(self.fS_path,path,"",45)
            elif GetType(path)==3:
                Log("1INFO: Selected ERF Face Source file: %s in %s"%(self.res_facename,path))
                self.src=path
                facefile=self.res_file.GetFileData(self.res_facename)
                self.SRC_MOR=MORFile(facefile,"<%s>"%self.res_facename)
                SetPathText(self.fS_path,path,"",45)
                self.fS_path["text"]=TruncatePath(path,"",45)+"\n"+self.res_facename
            if GetType(path) in (1,2,3):
                self.fS_title["text"]="SOURCE FILE (%s)"%GetTypeName(path)
                self.fS_title["fg"]=GetTypeColor(path)
            if self.dst and self.DST_MOR.data=="": # fill dst mor with source if empty
                self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>")
                self.RebuildFeaturesList()
                
    def SetDstFile(self,path):
        path=OP.normpath(path)
        self.DST_MOR=MORFile("","<destination>")
        if path:
            if GetType(path)==1:
                Log("1INFO: Selected DAS Destination file: %s"%path)
                self.dst=path
                dstname,mode=GetName(path)
                self.dst_name.set(dstname)
                self.dst_invsize.set(GetInvSize(path))
                self.DST_MOR=MORFile(GetMORData(path),"<%s savegame>"%dstname)
                SetPathText(self.fD_path,path,"",45)
            elif GetType(path)==2:
                Log("1INFO: Selected MOR Destination file: %s"%path)
                self.dst=path
                if OP.exists(path): # opening file
                    fin=open(path,"rb")
                    txt=fin.read()
                    fin.close()
                    self.DST_MOR=MORFile(txt)
                else: # new file
                    self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>")
                    if not self.SRC_MOR.data: Log("1INFO: New mor file waiting to be filled with any source")
                SetPathText(self.fD_path,path,"",45)
            if GetType(path) in (1,2):
                self.fD_title["text"]="DESTINATION FILE (%s)"%GetTypeName(path)
                self.fD_title["fg"]=GetTypeColor(path)
        
################################################################
## FUNCTIONS
################################################################
def Log(text,write=True):
    log=2
    if text[0].isdigit():
        log=int(text[0])
        text=log*"."+text[1:]
    # only show some messages, based on nLOG_LEVEL
    try: # Python v2.5 and upper
        if log<=nLOG_LEVEL: print(text)
    except: # Python v2.4 and lower
        if log<=nLOG_LEVEL: print text
    # always write all flow into logfile
    if write:
        try: LOG.write(text+"\n")
        except: LOG.write("ERROR: Log Error\n")
        LOG.flush()
    
def Message(frame,msg="A message",msgtype="i",log=True):
    if log and msgtype in ("e","i"): Log({"e":"0ERROR: ","i":"1INFO: "}[msgtype]+msg)
    if msgtype=="e": icotype=TKMB.ERROR
    else: icotype=TKMB.INFO
    Dialog=TKMB.Message(frame,message=msg,icon=icotype,title=sAPPNAME)
    Dialog.show()

def UpdateScript(src,dst,text):
    chardir=pCHARACTERS_DIR.strip("\ /")
    Log("1INFO: Updating script: %s"%sPYTHON_FILE)
    for k,v in zip(("pCHARACTERS_DIR=","pLAST_CHR=","pLAST_DAS=","pLAST_MOR=","pLAST_ERF=","nSAVE_METHOD="),
                   (chardir,pLAST_CHR,pLAST_DAS,pLAST_MOR,pLAST_ERF,nSAVE_METHOD)):
        # replace the first occurence of data
        start=text.find(k,0)
        Log("3 Updating : %s%s"%(k,v))
        end=text.find("\n",start)
        if k.startswith("p"):
            text=text[0:start]+k+'"'+OP.normpath(v).strip(".")+'"'+text[end:]
        elif k.startswith("b"):
            text=text[0:start]+k+("False","True")[v]+text[end:]
    return text

def SetPathText(control,path,prefix="",length=35):
    # set the path and colorize
    if not path: control["text"]="...\n..."
    else:
        control["text"]=TruncatePath(OP.dirname(path),prefix,length)+"\\\n"+OP.basename(path)
    control["bg"]=GetTypeColor(path)
    
def TruncatePath(path,prefix,length=35):
    if not path: return ""
    if len(path)>length: path="..."+path[-(length-3):]
    return prefix+path

def GetType(path):
    if path.endswith(".das"): return 1
    if path.endswith(".mor"): return 2
    if path.endswith(".erf"): return 3
    if path.endswith(".rim"): return 3
    return 0
def GetTypeName(path): return ("None","Savegame","Face","Resource")[GetType(path)]
def GetTypeColor(path): return ("black","darkgreen","darkblue","darkred")[GetType(path)]

################################################################
## MAIN ROUTINE
################################################################
LOG=open("DAFR_%s.log"%sVERSION,"w")
Log("0Starting DragonAge Face Replacer v%s"%sVERSION)
Log("0 Code is (c) 2010 NewByPower. See licence information in the script.")
Log("0 Homepage: https://www.dragonagenexus.com/downloads/file.php?id=428")
Log("0 Use [?] button to see a short help. See the readme for more explanations.")

root=Tix.Tk()
UI=DAFR(root)
UI.mainloop()
root.destroy()

# Updating script globals
# load python script
pyfile=open(sPYTHON_FILE,"rb")
txt=pyfile.read()
pyfile.close()
# update code
txt=UpdateScript(UI.src,UI.dst,txt).encode("utf8")
# rewrite python script
pyfile=open(sPYTHON_FILE,"wb")
pyfile.write(txt)
pyfile.close()

Log("0Ending DragonAge Face Replacer")
LOG.close()

sAPPNAME="DragonAge Face Replacer" sVERSION = "2.08" # 0.10 : Very first version # 0.11 : Fixed a bug on file mismatch, added some infos, changing processus method # 0.12 : added a Tkinter UI for file selection, old method is still valuable set UITOOL to False to enable # 0.20 : Now able to exchange with different filesizes ( MOR is written at the end of the file ) # 0.21 : Able to import from mor file instead of savegame # 0.22 : 0.21 bugfix and eyecandy # 1.00 : Final version, now able to retrieve MOR files from ERF resources # 1.01 : Adding some tweaking for destination savegames ( name ), as XunAmarox suggested # 1.02 : Some bugfix and eyecandy (list scrollbar), added inventory size tweaking ( always suggested by XunAmarox ) # 1.03 : Fixed some errors appearing with 1.02 and the separation between tweak and exchange face # 1.04 : Added compatibilty with Awakening and some save formats # 1.10 : Now save the path of the source and destination file, fixed the non-acsii names and paths # 1.11 : Fix 1.10 problem when trying to launch with bad paths and weird name problems # 1.20 : Now handle characters and saves in one window. UI improvements. Fixes problem with non-ascii paths # 1.30 : Now you can edit the face : model parts & tints. Log is less verbose # 2.00 : 1.30 version with reworked UI and handling # 2.01 : Fixes major bug in 2.00 with autocheck for characters and resource files # 2.02 : Fixes 2.01 problems in exchange of features, add more clearer paths and a "reset face" button # 2.03 : Fixes a script encoding error to save path, name exchange problems, features missing # 2.04 : Allowing to see source features values, limit inventory size, reworked code to handle a CheckList, more reliable log # 2.05 : Last bugfix of 2.0x serie, now things are 100% OK # 2.06 : Fixes a write error on savegames that prevent faces to be updated in some cases. Now faces are only appended, not replaced. # Fixes also an error in getting the last modified face # 2.07 : Simple fix about name problem: now if the name found is greater than nMAX_NAME, name cannot be changed (errors) # sAUTO_SELECT savegame is auto selected in character folder (based on a suggestion by setiweb) # Tint 11 & 12 is now eyebrow texture (thanks to setiweb) # 2.08 : 2.07 bugfix and code rewriting. It seems that some features (tatooes, age map) are not into features list # Also, now edited files can be put in another folder, use nSAVE_METHOD to achieve this

# Python modules import struct,os import os.path as OP import struct import shutil # Tkinter modules from Tkinter import * # Tkinter main import tkMessageBox as TKMB # Message box import tkFileDialog as TKFD # File dialog import tkFont import Tix # Tkinter extension from Tix import CheckList # Checklist

# Log level (0: errors only, 1: with informations, 2: with program flow) nLOG_LEVEL=0 # Save method : Set to # -1 to replace save witout backup # 0 to replace with making a backup # 1 to create a new slot nSAVE_METHOD=1 # previous paths for inputs # Default ERF resource may be "C:\MyGames\Dragon Age\packages\core\data\face.erf" pLAST_CHR="" pLAST_DAS="" pLAST_MOR="" pLAST_ERF="" # set to the directory of the characters in "(My Documents)\Bioware\Dragon Age\Characters" pCHARACTERS_DIR="" # Maximum inventory size allowed nMAX_INVSIZE=100000 # Max name size (only for file reading) nMAX_NAME=32 # select this savegame rather than first alphabetical save sAUTO_SELECT="QuickSave_1"

# Do not modify these lines nREL_OFFSET_INVSIZE=168 sPYTHON_FILE="DAFaceReplacer%s.py"%sVERSION.replace(".","") aMOR_FEATURES_PARTS=["P_head","P_eyes","P_hair","P_beard","P_part5","P_lashes"] aMOR_FEATURES_TINTS=["T_skin","T_lips","T_eyes","T_hair","T_eyelids","T_blush",

  • "T_tatoo1","T_tatoo2","T_tatoo3","T_tatoo4","T_tint11","T_tint12"]

# Lines commented are features wanting a color mask editing aMOR_EDIT=(("Hair model",aMOR_FEATURES_PARTS[2]),

  • ("Beard model",aMOR_FEATURES_PARTS[3]), ("Eye model",aMOR_FEATURES_PARTS[1]), ("Lashes model",aMOR_FEATURES_PARTS[5]), ("Head model",aMOR_FEATURES_PARTS[0]), ("Skin color",aMOR_FEATURES_TINTS[0]), ("Hair color",aMOR_FEATURES_TINTS[3]), ("Eyes color",aMOR_FEATURES_TINTS[2]), ("Eye Make-up",aMOR_FEATURES_TINTS[4]), ("Lips color",aMOR_FEATURES_TINTS[1]), ("Blush color",aMOR_FEATURES_TINTS[5]), ("Eyebrow color",aMOR_FEATURES_TINTS[10]), ("Tatoo color 1",aMOR_FEATURES_TINTS[6]), ("Tatoo color 2",aMOR_FEATURES_TINTS[7]), ("Tatoo color 3",aMOR_FEATURES_TINTS[8]), ("Tatoo color 4",aMOR_FEATURES_TINTS[9]), ("?Model #5?",aMOR_FEATURES_PARTS[4]), ("?Tint #12?",aMOR_FEATURES_TINTS[11]))

sMOR_HEADER="GFF V4.0PC MORPV0.1"

if not OP.exists(pLAST_DAS): pLAST_DAS="" if not OP.exists(pLAST_MOR): pLAST_MOR="" if not OP.exists(pLAST_ERF): pLAST_ERF="" if not OP.exists(pCHARACTERS_DIR): pCHARACTERS_DIR=os.getcwd()

def ChangeSaveData(daspath,new_name,new_invsize,morface):

  • Log("1INFO: Changing DAS savegame data of %s"%daspath) # Open the DAS file dasfile=open(daspath,"rb") dasdata=dasfile.read() dasfile.close() # Backup it if nSAVE_METHOD==0:
    • bak_file=open(daspath+".bak","wb") bak_file.write(dasdata) bak_file.close() Log("2 DAS file backup: %s.bak"%daspath)
    if morface:
    • # morface MUST be Update() before to register new data

      dasdata=ChangeFaceData(dasdata,morface.data)

    # try to change name

    dasdata,changed=SetName(dasdata,new_name) # replace inv size if int(new_invsize)>nMAX_INVSIZE: # limit inventory size

    • Log("0ERROR: Inventory size exceed maximum, DAFR limits to %s)"%nMAX_INVSIZE) new_invsize=nMAX_INVSIZE

    if int(new_invsize)<0:

    • Log("0ERROR: Inventory size below 0, passing")
    else: # write to file out_file=open(daspath,"wb") out_file.write(dasdata) out_file.close() return True

def ChangeFaceData(dasdata,mordata):

  • # TODO : remove old unecessary faces Log("1INFO: Changing face in DAS data") # get the content offset das_content_offset=struct.unpack("I",dasdata[24:28])[0] Log("2 DAS content offset=%s"%das_content_offset) # find the mor (binary) offset declaration offset=dasdata.find("d\x00e\x00f\x00a\x00u\x00l\x00t\x00_\x00p\x00l\x00a\x00y\x00e\x00r\x00")-40 Log("2 MOR declaration offset @%s"%offset) # check if it is the correct offset start=struct.unpack("I",dasdata[offset:offset+4])[0]+das_content_offset Log("2 Checking MOR face data @%s"%start) if dasdata[start+4:start+24]==sMOR_HEADER:
    • Log("2 Found MOR face data offset=%s @%s"%(start,offset)) # change the offset declaration new_start=len(dasdata)-das_content_offset dasdata=dasdata[0:offset]+struct.pack("I",new_start)+dasdata[offset+4:] Log("2 Changed MOR face data offset=%s @%s"%(new_start,offset)) # append binary data to destination data dasdata=dasdata+struct.pack("I",len(mordata))+mordata Log("2 MOR face data appended to DAS file")

      Log("2 DAS size changed : %s -> %s)"%(len(dasdata)-len(mordata),len(dasdata))) return dasdata

    else:
    • Log("0ERROR: MOR offset not found ! Face not changed.") return dasdata

def GetName(daspath,log=True):

  • # open the file if daspath=="": return "" if log:Log("1INFO: Get character name in DAS file: %s"%daspath) dasfile=open(daspath,"rb") dasdata=dasfile.read() dasfile.close() # get the content offset das_content_offset=struct.unpack("I",dasdata[24:28])[0] # find the name offset declaration offset=dasdata.find("\x07\x00\x00\x00p\x00l\x00a\x00y\x00e\x00r\x00\x00\x00")+20 name=""

    if offset>=0:

    • # get the original name mode=0 length=struct.unpack("I",dasdata[offset:offset+4])[0] name=dasdata[offset+4:offset+4+length*2] # with python 2.5 and over, use decode function to have name from unicode try:
      • name=name.decode("u16").strip("\x00")
      except:
      • name=name.replace("\x00","")

      if dasdata.count(struct.pack("I",offset-das_content_offset))>1:

      • mode=1 if log:
        • Log("2 Found %s possible offsets for name"%dasdata.count(struct.pack("I",offset-das_content_offset))) Log("0ERROR: Name cannot be changed in this savegame ! Unable to get the name offset")
        name=""

      if len(name)>nMAX_NAME:

      • mode=1 if log:
        • Log("0ERROR: Name cannot be retrieved ! Found name of length %s @ %s : too long, maybe offset error"%(len(name),struct.pack("I",offset)))
        name=""
    if name and log: Log('2 Found character name="%s" @%s'%(name,offset)) elif log: Log("2 Character name has errors !") return name,mode

def SetName(dasdata,newname):

  • Log('1INFO: Set character name "%s" in DAS file'%newname) # get the content offset das_content_offset=struct.unpack("I",dasdata[24:28])[0] # find the name of character offset=dasdata.find("\x07\x00\x00\x00p\x00l\x00a\x00y\x00e\x00r\x00\x00\x00")+20 # get the original name length=struct.unpack("I",dasdata[offset:offset+4])[0] oldname=dasdata[offset+4:offset+4+length*2].replace("\x00","") Log("2 Old name : %s"%oldname)

    if dasdata.count(struct.pack("I",offset-das_content_offset))>1:

    • Log("2 Found %s possible offsets"%dasdata.count(struct.pack("I",offset-das_content_offset))) Message(None,"Unable to change name this for this savegame\nTry with another savegame for this character.","e") return dasdata,False
    if oldname!=newname:
    • Log('2 New character name : "%s"'%newname) # find the name offset declaration decl=dasdata.find(struct.pack("I",offset-das_content_offset)) # new offset at the end dasdata=dasdata[0:decl]+struct.pack("I",len(dasdata)-das_content_offset)+dasdata[decl+4:] # with python 2.5 and over, use encode function to have unicode name try:
      • name_u16=newname.encode("u16")[2:]+"\x00\x00"
      except:
      • name_u16="" for c in newname+"\x00": name_u16+=c+"\x00"
      # append name to dst_data dasdata+=struct.pack("I",len(newname)+1)+name_u16 return dasdata,True
    else:
    • Log("2 Character name not changed") return dasdata,False

def GetInvSize(daspath,log=True):

  • if log: Log("1INFO: Get Inventory Size in DAS file: %s"%daspath) # open the file if daspath=="": return "" dasfile=open(daspath,"rb") dasfile.seek(24) # get the content offset das_content_offset=struct.unpack("I",dasfile.read(4))[0] invsize_offset=das_content_offset+nREL_OFFSET_INVSIZE dasfile.seek(invsize_offset) invsize=struct.unpack("I",dasfile.read(4))[0] dasfile.close() if log: Log("2 Inventory Size=%s @%s"%(invsize,invsize_offset)) return invsize

def SetInvSize(dasdata,invsize):

  • Log("1INFO: Set Inventory Size %s in DAS file"%invsize) # get the content offset das_content_offset=struct.unpack("I",dasdata[24:28])[0] invsize_offset=das_content_offset+nREL_OFFSET_INVSIZE Log("2 Inventory Size @%s"%invsize_offset) isd=struct.pack("I",invsize) dasdata=dasdata[0:invsize_offset]+isd+dasdata[invsize_offset+4:] return dasdata

def GetMORData(daspath):

  • # recover dasdata if daspath=="": return "" dasfile=open(daspath,"rb") Log("1INFO: Open DAS file to get MOR face data = %s"%daspath) dasdata=dasfile.read() dasfile.close() # find morph data cnt=dasdata.count(sMOR_HEADER)

    if cnt>1: Log("2 Multiple MOR face data, taking the last") start=0 for i in range(cnt):

    • start=dasdata.find(sMOR_HEADER,start+len(sMOR_HEADER))

    if start>=0:

    • length=struct.unpack("I",dasdata[start-4:start])[0] Log("2 MOR face data @%s length=%s"%(start,length)) # return morph data return dasdata[start:start+length]
    Log("0ERROR: No MOR face data in DAS file") return ""

class ERFFile():

  • def init(self,path):

    • self.infos={"type":u"",
      • "files":0}
      self.registry={} self.path=path self.file=None if OP.exists(path):
      • self.file=open(path,"rb") # Fill the parser self.file.seek(0) head=self.file.read(16) head=head.replace("\x00","") self.infos["type"]=head self.file.seek(16) nfiles=struct.unpack("I",self.file.read(4))[0] self.infos["files"]=nfiles Log("1INFO: Loaded: %s (%s, %s files)"%(path,head,nfiles)) # Files registry self.file.seek(32) for i in range(0,nfiles):
        • name=self.file.read(64) name=name.replace("\x00","") offset,lenght=struct.unpack("II",self.file.read(8)) self.registry[name]=(offset,lenght)
    def Search(self,match="",log=False,ext=""):
    • if match: Log('1INFO: Searching in ERF "%s"'%match) rtn=[] if match:
      • for f in self.registry.keys():
        • if match.lower() in f.lower():
          • if log: Log("2 Match %s"%f) if ext and f.endswith(ext): rtn.append(f) else: rtn.append(f)
      else:
      • rtn=self.registry.keys()
      return rtn

    def GetFileData(self,name):

    • o,l=self.registry[name] self.file.seek(o) return self.file.read(l)
    def Close(self):
    • self.file.close()
    # Will come in 2.1x maybe

    def AddFile(self,name,filedata):

    • return

    def RemoveFile(self,name):

    • return
    def SaveERF(self,path):
    • return

class MORFile():

  • MARK="DAFR" ids={2:"NAME",
    • 23000:"MORPH_PARTS", 23001:"MORPH_TINTFILENAMES", 23002:"MORPH_NODES"}

    def init(self,raw_data="",name="<none>"):

    • self.name=name self.data=raw_data self.mod=False # modified flag self.nodes={} self.MP={} self.MT={} if raw_data:
      • self._parse()
    def _parse(self):
    • # build nodes definition ncount=self._read(20,"I")[0] for i in range(ncount):
      • no,nn,nc,nd=self._read(24+16*i,"I4sII") self.nodes[nn]=(no,nc,nd)

# print nodes

  • # get the morp node mo,mc,md=self.nodes["morp"] morp={} for i in range(mc):
    • mii,mit,mif,mio=self._read(md+12*i,"IHHI") morp[self.ids[mii]]=(mit,mif,mio)

# print morp

  • # MORPH_PARTS # offset of the string list declaration offset=self._read(mo+morp["MORPH_PARTS"][2],"I")[0]+mo

    # get the stringlist count & locations count=self._read(offset,"I")[0] for i in range(count):

    • p=aMOR_FEATURES_PARTS[i] o=self._read(offset+4*i+4,"I")[0] if o!=0xffffffff:
      • l=self._read(o+mo,"I")[0] s=self._read(o+mo+4,"%ss"%(l*2))[0] # s have only ascii chars, so use a replace() instead of decode(): s=s.replace("\x00","")
      else:
      • s=""
      self.MP[p]=s
    self.MP["_off"]=offset self.MP["_cnt"]=count

# print self.MP

  • # MORPH_TINTFILENAMES # offset of the string list declaration offset=self._read(mo+morp["MORPH_TINTFILENAMES"][2],"I")[0]+mo

    # get the stringlist count & location count=self._read(offset,"I")[0] for i in range(count):

    • p=aMOR_FEATURES_TINTS[i] o=self._read(offset+4*i+4,"I")[0] if o!=0xffffffff:
      • l=self._read(o+mo,"I")[0] s=self._read(o+mo+4,"%ss"%(l*2))[0] # s have only ascii chars, so use a replace() instead of decode(): s=s.replace("\x00","")
      else:
      • s=""
      self.MT[p]=s
    self.MT["_off"]=offset self.MT["_cnt"]=count

# print self.MT

  • def _read(self,start,fmt):
    • # read %fmt at %start length=struct.calcsize(fmt) rtn=self.data[start:start+length] rtn=struct.unpack(fmt,rtn) return rtn
    def _replace(self,start,string):
    • self.data=self.data[0:start]+string+self.data[start+len(string):]
    def Update(self):
    • # do not proceed if file not modified if self.mod==False:
      • return
      Log("1INFO: Updating MOR file") # update the data mo,mc,md=self.nodes["morp"] morp={} for i in range(mc):
      • mii,mit,mif,mio=self._read(md+12*i,"IHHI") morp[self.ids[mii]]=(mit,mif,mio)
      # MORPH_PARTS start=len(self.data)-mo self._replace(mo+morp["MORPH_PARTS"][2],struct.pack("I",start)) # build string list : declaration sl=struct.pack("I",6) start+=4+4*6 tlst="" for p in aMOR_FEATURES_PARTS:

# print p

  • s=self.MP[p]+"\x00" if s!="\x00": # string is defined

# print s,start

  • l=len(s) ns="" for c in s: ns+=c+"\x00" sl+=struct.pack("I",start) tlst+=struct.pack("I",l)+ns start+=4+len(ns)
  • else: # string not defined
    • sl+=struct.pack("I",0xffffffff)
  • # append list to string list declaration sl+=tlst # append string list to data self.data+=sl # MORPH_TINTFILENAMES # new offset: at the end of file: start=len(self.data)-mo self._replace(mo+morp["MORPH_TINTFILENAMES"][2],struct.pack("I",start)) # build string list : declaration sl=struct.pack("I",12) start+=4+4*12 tlst="" for t in aMOR_FEATURES_TINTS:

# print t

  • s=self.MT[t]+"\x00" if s!="\x00": # string is defined

# print s,start

  • l=len(s) ns="" for c in s: ns+=c+"\x00" sl+=struct.pack("I",start) tlst+=struct.pack("I",l)+ns start+=4+len(ns)
  • else: # string not defined
    • sl+=struct.pack("I",0xffffffff)
  • # append list to string list declaration sl+=tlst # append string list to data self.data+=sl # append a mark to show that MOR is edited self.data+=self.MARK Log("1INFO: MOR file size=%s"%len(self.data))
  • def Save(self,path):
    • # Save MORFile.data to path if OP.exists(path):
      • Log("1INFO: Backing up MOR file") fin=open(path,"rb") txt=fin.read() fin.close() fout=open(path+".bak","wb") fout.write(txt) fout.close()
      fout=open(path,"wb") fout.write(self.data) fout.close()

    def GetString(self,part):

    • # Get String for part if part in self.MP.keys(): return self.MP[part] if part in self.MT.keys(): return self.MT[part] return ""

    def SetString(self,part,string):

    • # Set String for part self.mod=True if part in self.MP.keys(): self.MP[part]=string elif part in self.MT.keys(): self.MT[part]=string else: self.mod=False

class DAFR(Frame):

  • HELP=Source\t-> Destination:


None\t-> *.das -Edit face features, name, and inventory size

*.das\t-> *.das *.mor\t-> *.das *.erf\t-> *.das -Change face & edit face features, name and inventory size

None\t-> *.mor -Modify face in a exchangeable *.mor face file

*.mor\t-> *.mor -Edit *.mor face with another

*.das\t-> *.mor *.erf\t-> *.mor -Extract & modify face in a exchangeable *.mor face file -Edit *.mor face with another mor in savegame or resource

  • For more deep changes of face (colors for example):

- extract the face from *.das or *.erf - edit the *.mor file in the Toolset - use as source for your savegame

  • def init(self,master=None):

    • Frame.init(self, master) self.sel="" # selected path self.src="" # source path self.dst="" # destination path # source and destination MOR files self.SRC_MOR=MORFile("","<source>") self.DST_MOR=MORFile("","<destination>") # resource file and facename self.res_file=ERFFile(pLAST_ERF) self.res_facename="" # key for face editing self.entry_key="" # copy flags for src to dest self.copy_face=True self.copy_name=True # check if there is chars in CHARACTER_DIR

      self.ScanForChars(pCHARACTERS_DIR) # start UI

      self.CreateWidgets()

    def CreateWidgets(self):

    def BuildWidgets(self):

    • # self.f_buttons self.f_buttons=Frame(self,bg="darkgrey")

      self.fB_credits=Label(self.f_buttons,text="(c)2010 by NewByPower",fg="white",bg="darkgrey") self.fB_bQuit=Button(self.f_buttons,text='QUIT',fg="white",bg='darkred',width=10,command=self.quit) self.fB_bHelp=Button(self.f_buttons,text='?',fg="white",bg='darkblue',width=1,command=self.ShowHelp) self.f_main=Frame(self) # self.f_files : all means to open files self.f_files=Frame(self.f_main) self.fF_title=Label(self.f_files,text="FILE SELECTED (None)") self.fF_path=Label(self.f_files,text="...\n...",fg="white",bg="black",width=40,justify=RIGHT) self.fF_buttons=Frame(self.f_files) self.fFB_bSetSRC=Button(self.fF_buttons,text="Set as Source",width=20,command=self.SetSRC) self.fFB_bSetDST=Button(self.fF_buttons,text="Set as Destination",width=20,command=self.SetDST) self.fF_infos=Label(self.f_files,text="1- Select a file and set it as source or destination\n2- Copy what you want from source\n3- Save the destination file",justify=LEFT) # File type tabs self.fF_tabs=Frame(self.f_files) self.fFT_bChar=Button(self.fF_tabs,text="Character",width=10,command=self.ShowCHR) self.fFT_bSave=Button(self.fF_tabs,text="Savegame",width=10,command=self.ShowDAS) self.fFT_bFace=Button(self.fF_tabs,text="Face",width=10,command=self.ShowMOR) self.fFT_bResF=Button(self.fF_tabs,text="Resource",width=10,command=self.ShowERF) # CHR tab self.fF_chr=Frame(self.f_files) self.fFC_path=Label(self.fF_chr,text="...\n...",fg="white",bg="black",justify=RIGHT)

      self.fFC_chardir=Button(self.fF_chr,text="%process character folder",command=self.SetCharDir,width=28) self.fFC_browser=Frame(self.fF_chr) self.fFCB_chars=Frame(self.fFC_browser) self.fFCBC_list=Listbox(self.fFCB_chars,width=26,height=4,activestyle=DOTBOX) self.fFCBC_sblist=Scrollbar(self.fFCB_chars,orient=VERTICAL) self.fFCB_label=Label(self.fFC_browser,text="View: %char",width=28) self.fFCB_saves=Frame(self.fFC_browser) self.fFCBS_list=Listbox(self.fFCB_saves,width=26,height=5,activestyle=DOTBOX) self.fFCBS_sblist=Scrollbar(self.fFCB_saves,orient=VERTICAL) # DAS tab self.fF_das=Frame(self.f_files) self.fFD_path=Label(self.fF_das,text="...\n...",fg="white",bg="black",justify=RIGHT) self.fFD_bOpenDAS=Button(self.fF_das,text="Open Savegame",width=28,command=self.OpenDAS) # MOR tab self.fF_mor=Frame(self.f_files) self.fFM_path=Label(self.fF_mor,text="...\n...",fg="white",bg="black",justify=RIGHT) self.fFM_bNewMOR=Button(self.fF_mor,text="New Face file",width=28,command=self.NewMOR) self.fFM_bOpenMOR=Button(self.fF_mor,text="Open Face file",width=28,command=self.OpenMOR) # ERF tab self.fF_res=Frame(self.f_files) self.fFR_path=Label(self.fF_res,text="...\n...",fg="white",bg="black",justify=RIGHT) self.fFR_bOpenERF=Button(self.fF_res,text='Open Resource file',width=28,command=self.OpenERF) self.fFR_browser=Frame(self.fF_res) self.fFRB_infos=Label(self.fFR_browser,text="Faces files (%filter/%total)") self.fFRB_filter=Frame(self.fFR_browser) self.fFRBF_label=Label(self.fFRB_filter,text="Filter:",width=5) self.fFRBF_filter=Entry(self.fFRB_filter,width=22) self.fFRB_list=Frame(self.fFR_browser) self.fFRBL_list=Listbox(self.fFRB_list,width=26,height=8,activestyle=DOTBOX) self.fFRBL_sblist=Scrollbar(self.fFRB_list,orient=VERTICAL) # self.f_src : source editing self.f_src=Frame(self.f_main) self.fS_title=Label(self.f_src,text="SOURCE FILE (None)",width=40) self.fS_path=Label(self.f_src,text="...\n...",fg="white",bg="black",justify=RIGHT)

      self.fS_bCopyToDst=Button(self.f_src,text="Copy selection to Destination file",command=self.CopyData) # Face features self.fSF_cMorph=Checkbutton(self.f_src,text="Face shape (with tatooes, wrinkles, scars, ...)") self.fS_face=Frame(self.f_src) self.fSF_header=Frame(self.fS_face) self.fSFH_info=Label(self.fSF_header,text="Face features:",width=20) self.fSFH_bToggle=Button(self.fSF_header,text="Select all / none",command=self.Toggle,width=20)

# self.fSF={}

  • self.fSF_clFeatures=CheckList(self.fS_face) self.fSF_clFeatures.hlist.delete_all() self.fSF_clFeatures.hlist.configure(bg="white") for i,ff in enumerate(aMOR_EDIT):

# self.fSF[ff[1]]=Checkbutton(self.fS_face,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1])))

  • self.fSF_clFeatures.hlist.add(i,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1]))) self.fSF_clFeatures.setstatus(i,"on") i+=1

  • # Other features self.fS_cName=Checkbutton(self.f_src,text="Character name (%name)") # self.f_dst : destination editing self.f_dst=Frame(self.f_main) self.fD_title=Label(self.f_dst,text="DESTINATION FILE (None)",width=40) self.fD_path=Label(self.f_dst,text="...\n...",fg="white",bg="black",justify=RIGHT) # Face Editing self.fD_face=Frame(self.f_dst) self.fDF_label=Label(self.fD_face,text="Manual face editing:") self.fDF_list=Frame(self.fD_face) self.fDFL_list=Listbox(self.fDF_list,width=38,height=6,activestyle=DOTBOX) self.fDFL_sblist=Scrollbar(self.fDF_list,orient=VERTICAL) self.fDF_edit=Frame(self.fD_face) self.fDFE_label=Label(self.fDF_edit,text="%entry",width=15) self.fDFE_eValue=Entry(self.fDF_edit,width=20)

    self.fDFE_bSetValue=Button(self.fDF_edit,text="Set",width=5,command=self.SetValToDest) self.fDF_bResetFace=Button(self.fD_face,text="Reset destination face data",width=30,command=self.ResetFace) # DAS editing self.fD_savegame=Frame(self.f_dst) self.fDS_label=Label(self.fD_savegame,text="Savegame Editing",width=40) self.fDS_name=Frame(self.fD_savegame) self.fDSN_label=Label(self.fDS_name,text="Name",width=20,anchor=W) self.fDSN_eName=Entry(self.fDS_name,width=20) self.fDS_inventory=Frame(self.fD_savegame) self.fDSI_label=Label(self.fDS_inventory,text="Inventory size",width=20,anchor=W) self.fDSI_eInvsize=Entry(self.fDS_inventory,width=20) # Save bfont=tkFont.Font (family="Helvetica", size=8, weight="bold" ) self.fD_bSave=Button(self.f_dst,text='SAVE DESTINATION FILE',font=bfont,width=28,command=self.Save)

# # ERF editing - forget in 2.00, mean to add faces into an ERF package # self.fD_resource=Frame(self.f_dst) # self.fDR_label=Label(self.fD_resource,text="Add to Resource",width=20) # self.fDR_name=Frame(self.fD_resource) # self.fDRN_label=Label(self.fDR_name,text="Filename",width=6) # self.fDRN_eName=Entry(self.fDR_name,width=14)

  • def PackWidgets(self):

    • # buttons self.fB_credits.pack(side=LEFT,fill=X) self.fB_bHelp.pack(side=RIGHT) self.fB_bQuit.pack(side=RIGHT) self.f_buttons.pack(fill=X) # files self.fF_title.pack(fill=X) self.fF_path.pack(fill=X,pady=2) self.fFB_bSetSRC.pack(side=LEFT,fill=X) self.fFB_bSetDST.pack(side=RIGHT,fill=X) self.fF_buttons.pack(fill=X,pady=2) self.fF_infos.pack() self.fFT_bChar.pack(pady=2) self.fFT_bSave.pack(pady=2) self.fFT_bFace.pack(pady=2) self.fFT_bResF.pack(pady=2) self.fF_tabs.pack(side=LEFT,fill=Y,padx=2) spacer1=Frame(self.f_files,height=1,bg="darkgrey") spacer1.pack(side=LEFT,fill=Y,padx=1)

# self.fFC_path.pack(fill=X)

  • self.fFC_chardir.pack(pady=2,padx=2) self.fFCBC_list.pack(side=LEFT) self.fFCBC_sblist.pack(side=LEFT,fill=Y) self.fFCBS_list.pack(side=LEFT) self.fFCBS_sblist.pack(side=LEFT,fill=Y) self.fFCB_chars.pack() self.fFCB_label.pack(anchor=W) self.fFCB_saves.pack() self.fFC_browser.pack()

# self.fF_chr.pack() # self.fFD_path.pack(fill=X)

  • self.fFD_bOpenDAS.pack(pady=2,padx=2)

# self.fF_das.pack() # self.fFM_path.pack(fill=X)

  • self.fFM_bNewMOR.pack(pady=2,padx=2) self.fFM_bOpenMOR.pack(pady=2,padx=2)

# self.fF_mor.pack() # self.fFR_path.pack(fill=X)

  • self.fFR_bOpenERF.pack(pady=2,padx=2) self.fFRB_infos.pack(side=TOP) self.fFRBF_label.pack(side=LEFT) self.fFRBF_filter.pack(side=LEFT) self.fFRB_filter.pack() self.fFRBL_list.pack(side=LEFT) self.fFRBL_sblist.pack(side=LEFT,fill=Y) self.fFRB_list.pack() self.fFR_browser.pack()

# self.fF_res.pack()

  • self.f_files.pack(side=LEFT,fill=Y,padx=2) # spacer spacer2=Frame(self.f_main,height=1,bg="darkgrey") spacer2.pack(side=LEFT,fill=Y,padx=1) # source self.fS_title.pack(fill=X) self.fS_path.pack(fill=X,pady=2) self.fS_bCopyToDst.pack(pady=2,fill=X)

# self.fSF_info.grid(row=0,column=0,sticky=W) # self.fSF_bToggle.grid(row=0,column=1) # self.fSF_cMorph.grid(row=1,column=0,sticky=W)

  • self.fSF_cMorph.pack(anchor=W) # spacer spacer4=Frame(self.f_src,height=1,bg="darkgrey") spacer4.pack(fill=X,pady=1) self.fSFH_info.pack(side=LEFT,anchor=W) self.fSFH_bToggle.pack(side=LEFT,fill=X) self.fSF_header.pack() self.fSF_clFeatures.pack(fill=X)

# for col in range(2): # for row in range(9): # if col==0 and row==0: continue # else: # ff=aMOR_EDIT[9*col+row-1] # self.fSF[ff[1]].grid(row=row+1,column=col,sticky=W)

  • self.fS_face.pack(pady=2) # spacer spacer5=Frame(self.f_src,height=1,bg="darkgrey") spacer5.pack(fill=X,pady=1) self.f_src.pack(side=LEFT,fill=Y,padx=2) # spacer spacer3=Frame(self.f_main,height=1,bg="darkgrey") spacer3.pack(side=LEFT,fill=Y,padx=1) # destination self.fD_title.pack(fill=X) self.fD_path.pack(fill=X,pady=2) self.fDF_label.pack() self.fDFL_list.pack(side=LEFT) self.fDFL_sblist.pack(side=LEFT,fill=Y) self.fDF_list.pack() self.fDFE_label.pack(side=LEFT) self.fDFE_eValue.pack(side=LEFT) self.fDFE_bSetValue.pack(side=LEFT) self.fDF_edit.pack() self.fDF_bResetFace.pack(pady=2,fill=X) self.fD_face.pack() # spacer spacer6=Frame(self.f_dst,height=1,bg="darkgrey") spacer6.pack(fill=X,pady=1) self.fDS_label.pack() self.fDSN_label.pack(side=LEFT) self.fDSN_eName.pack(side=LEFT) self.fDS_name.pack() self.fDSI_label.pack(side=LEFT) self.fDSI_eInvsize.pack(side=LEFT) self.fDS_inventory.pack() self.fD_bSave.pack(side=BOTTOM,fill=X)

# self.fDR_label.pack() # self.fDRN_label.pack(side=LEFT) # self.fDRN_eName.pack(side=LEFT) # self.fDR_name.pack() # self.fD_resource.pack()

  • self.f_dst.pack(side=LEFT,fill=Y,padx=2) self.f_main.pack() self.pack()
  • def BindWidgets(self):

    • # binding

      self.fFRBF_filter.bind('<Key-Return>',self.Filter)

      self.fFRBL_list.bind('<Button1-ButtonRelease>',self.SelectFace) self.fFRBL_list.configure(yscrollcommand=self.fFRBL_sblist.set) self.fFRBL_sblist.configure(command=self.fFRBL_list.yview)

      self.fFCBC_list.bind('<Button1-ButtonRelease>',self.SelectChar) self.fFCBC_list.configure(yscrollcommand=self.fFCBC_sblist.set) self.fFCBC_sblist.configure(command=self.fFCBC_list.yview)

      self.fFCBS_list.bind('<Button1-ButtonRelease>',self.SelectSave) self.fFCBS_list.configure(yscrollcommand=self.fFCBS_sblist.set) self.fFCBS_sblist.configure(command=self.fFCBS_list.yview)

      self.fDFL_list.bind('<Button1-ButtonRelease>',self.SelectEntry) self.fDFL_sblist.configure(command=self.fDFL_list.yview) self.fDFL_list.configure(yscrollcommand=self.fDFL_sblist.set)

    def InitVars(self):

    • # Init vars

      self.filter_string=StringVar() self.files_list=Variable() self.chars_list=Variable() self.saves_list=Variable() self.dst_name=StringVar() self.dst_invsize=IntVar() self.entries_list=Variable() self.entry_value=StringVar() self.fFRBF_filter["textvariable"]=self.filter_string self.fFRBL_list["listvariable"]=self.files_list self.fFCBC_list["listvariable"]=self.chars_list self.fFCBS_list["listvariable"]=self.saves_list self.fDFL_list["listvariable"]=self.entries_list self.fDSN_eName["textvariable"]=self.dst_name self.fDSI_eInvsize["textvariable"]=self.dst_invsize self.fDFE_eValue["textvariable"]=self.entry_value

# self.fDRN_eName["textvariable"]=self.res_filename

# self.copy_mask=[BooleanVar()] # self.fSF_cMorph["variable"]=self.copy_mask[0] # for x in range (len(aMOR_EDIT)): # v=BooleanVar() # self.copy_mask.append(v) # self.fSF[aMOR_EDIT[x][1]]["variable"]=self.copy_mask[x+1] # self.copy_mask.append(BooleanVar()) # self.fS_cName["variable"]=self.copy_mask[len(aMOR_EDIT)+1]

  • self.copy_face=BooleanVar() self.fSF_cMorph["variable"]=self.copy_face self.copy_name=BooleanVar() self.fS_cName["variable"]=self.copy_name self.entries_list.set(tuple(map(lambda me: me[0],aMOR_EDIT))) self.chars_list.set(tuple(map(lambda c: c[0],self.chars))) self.fFCBC_list.selection_set(self.cs[0],self.cs[0]) self.Filter()

    self.SelectEntry(None) self.SelectFace(None) self.SelectChar(None)

# for ff in aMOR_EDIT: self.fSF[ff[1]].select()

  • def ShowCHR(self):
    • self.SetSelected(pLAST_CHR) self.fFC_path["text"]=TruncatePath(pLAST_DAS,"",22) charname=self.chars_list.get()[self.cs[0]] savename=self.saves_list.get()[self.cs[1]] if self.chars_list.get():

      • self.fFCB_label["text"]="%s : %s"%(charname,savename)

      self.SetCharDirText() self.Show("C")

    def ShowDAS(self): def ShowMOR(self): def ShowERF(self):
    • self.SetSelected(pLAST_ERF) if pLAST_ERF:

      • self.fF_path["text"]=TruncatePath(pLAST_ERF,"",45)+"\n"+self.res_facename self.fFR_path["text"]=TruncatePath(pLAST_ERF,"",22)

      self.Filter(self.filter_string.get()) self.Show("R")
    def Show(self,what=""):
    • pairs={"C":(self.fF_chr,self.fFT_bChar),
      • "S":(self.fF_das,self.fFT_bSave), "F":(self.fF_mor,self.fFT_bFace), "R":(self.fF_res,self.fFT_bResF)}
      for v in pairs.values():
      • v[0].forget() v[1].config(relief="raised")
      for c in what:
      • if c in pairs.keys():
        • pairs[c][0].pack() pairs[c][1].config(relief="sunken")

    def ShowHelp(self):

    • Help=TKMB.Message(self,message=self.HELP,icon=TKMB.INFO,title="%s : Help"%sAPPNAME) Help.show()

# Source and destination loading

  • def OpenDAS(self):
    • global pLAST_DAS

      Dialog=TKFD.Open(filetypes=(("DragonAge Savegame",".das"),),

      • initialdir=OP.dirname(pLAST_DAS))
      path=Dialog.show()

      if GetType(path)==1:

      • pLAST_DAS=path Log("1INFO: Open savegame: %s"%pLAST_DAS) self.ShowDAS()
    def NewMOR(self):
    • global pLAST_MOR

      Dialog=TKFD.SaveAs(filetypes=(("Face file",".mor"),),

      • initialdir=OP.dirname(pLAST_MOR), defaultextension=".mor", initialfile=self.dst_name.get())
      path=Dialog.show()

      if GetType(path)==2:

      • pLAST_MOR=path Log("1INFO: New face: %s"%pLAST_MOR) self.ShowMOR()
    def OpenMOR(self):
    • global pLAST_MOR Dialog=TKFD.Open(filetypes=(("Face file",".mor"),),
      • initialdir=OP.dirname(pLAST_MOR))
      path=Dialog.show()

      if GetType(path)==2:

      • pLAST_MOR=path Log("1INFO: Open face: %s"%pLAST_MOR) self.ShowMOR()
    def OpenERF(self):
    • global pLAST_ERF

      Dialog=TKFD.Open(filetypes=(("DragonAge Resource",".erf .rim"),),

      • initialdir=OP.dirname(pLAST_ERF))
      path=Dialog.show()

      if GetType(path)==3:

      • pLAST_ERF=path Log("1INFO: Open resource: %s"%pLAST_ERF) self.res_file=ERFFile(pLAST_ERF) self.Filter() if self.files_list.get():
        • self.fFRBL_list.selection_set(0) self.res_facename=self.files_list.get()[0]
        self.ShowERF()

    def SetSelected(self,path,checkpath=True):

    • path=OP.normpath(path) self.sel=path

      if checkpath and not OP.exists(path) or path in ("",".","/."): SetPathText(self.fF_path,"","",45) else: SetPathText(self.fF_path,path,"",45) self.fF_title["text"]="FILE SELECTED (%s)"%GetTypeName(path) self.fF_title["fg"]=GetTypeColor(path)

    def SetSRC(self,path=""):
    • if path: self.sel=path if OP.exists(self.sel):
    def CheckSRC(self):
    • self.fS_cName.forget()

      if GetType(self.src)==1 and GetType(self.dst)==1: #DAS 2 DAS

      • self.fS_cName.pack(pady=2,anchor=W)

        name,mode=GetName(self.src,log=False) if name:

        • self.fS_cName["text"]="Character name (%s)"%name self.fS_cName.select()
      else:
      • self.fS_cName.forget()
      self.fSF_clFeatures.hlist.delete_all() for i,ff in enumerate(aMOR_EDIT):

# self.fSF[ff[1]]["text"]="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1])) # self.fSF[ff[1]].select()

  • self.fSF_clFeatures.hlist.add(i,text="%s (%s)"%(ff[0],self.SRC_MOR.GetString(ff[1]))) self.fSF_clFeatures.setstatus(i)

  • self.fSF_cMorph.select()
  • def SetDST(self,path=""): def CheckDST(self):
    • entries=map(lambda me: me[0]+" (%s)"%self.DST_MOR.GetString(me[1]),aMOR_EDIT) self.entries_list.set(tuple(entries)) self.SelectEntry() self.fD_savegame.forget() if GetType(self.dst)==1: #DAS

      • self.fD_savegame.pack()

        name,mode=GetName(self.dst,log=False) if mode==1: self.fDSN_eName["bg"]="grey" # not change possible elif mode==2: self.fDSN_eName["bg"]="yellow" # name error else: self.fDSN_eName["bg"]="white" self.dst_name.set(name) self.dst_invsize.set(GetInvSize(self.dst,log=False))

      if GetType(self.src)==1 and GetType(self.dst)==1: #DAS 2 DAS

      • self.fS_cName.pack(pady=2,anchor=W)

        name,mode=GetName(self.src,log=False) if name:

        • self.fS_cName["text"]="Character name (%s)"%name self.fS_cName.select()

# Character selection

  • def SetCharDir(self):

    • global pCHARACTERS_DIR Dialog=TKFD.Directory(initialdir=OP.dirname(pCHARACTERS_DIR)) char_dir=OP.normpath(Dialog.show()+"/")

      self.ScanForChars(char_dir) if self.chars:

      • Log("1INFO: New Characters directory : %s"%char_dir) pCHARACTERS_DIR=char_dir self.chars_list.set(tuple(map(lambda c: c[0],self.chars))) self.fFCBC_list.selection_set(self.cs[0],self.cs[0])

        self.SelectChar(None) self.SetCharDirText()

    def SetCharDirText(self):

    • if self.chars: self.fFC_chardir.config(text="Change character folder") else: self.fFC_chardir.config(text="Set character folder")

    def ScanForChars(self,char_dir=pCHARACTERS_DIR):

    • self.chars=[] if OP.exists(char_dir):
      • for p in os.listdir(char_dir):
        • sp=OP.normpath(OP.join(char_dir,p,"Saves")) if OP.isdir(sp): # path is a directory
          • saves=[] for pp in os.listdir(sp):
            • spp=OP.normpath(OP.join(char_dir,p,"Saves",pp)) if not OP.exists(spp): continue if OP.isdir(spp):
              • for f in os.listdir(spp):
                • if OP.splitext(f)[1]==".das":
                  • saves.append(pp)
            if saves: self.chars.append((p,saves))
      self.cs=[0,0] self.sel="" Log("1INFO: Found %s characters"%len(self.chars))

    def SelectChar(self,event):

    • try:
      • ci=int(self.fFCBC_list.curselection()[0])
      except:
      • if pLAST_CHR:
        • charname=pLAST_CHR[pLAST_CHR.lower().index("characters")+11:].split(os.sep)[0] names=self.chars_list.get() if charname in names: ci=names.index(charname)
        else: ci=0
      self.fFCBS_list.selection_clear(0) self.fFCBC_list.selection_set(ci) self.cs=[ci,0] if self.chars:
      • self.saves_list.set(tuple(map(lambda s: s,self.chars[self.cs[0]][1]))) self.fFCBS_list.selection_clear(0) self.fFCBS_list.selection_set(0)

        self.SelectSave(None,False)

    def SelectSave(self,event,log=True):

    • global pLAST_CHR try:
      • si=int(self.fFCBS_list.curselection()[0])
      except:
      • if pLAST_CHR:
        • charsave=pLAST_CHR[pLAST_CHR.lower().index("saves")+6:].split(os.sep)[0] saves=self.saves_list.get() if charsave in saves: si=saves.index(savename)
        else: si=self.cs[1]
      self.fFCBS_list.selection_clear(0) self.fFCBS_list.selection_set(si) if sAUTO_SELECT and not event:
      • lst=map(lambda s:s.lower(),self.saves_list.get()) if sAUTO_SELECT.lower() in lst:
        • si=lst.index(sAUTO_SELECT.lower()) self.fFCBS_list.selection_clear(0) self.fFCBS_list.selection_set(si) self.fFCBS_list.see(si)
      self.cs[1]=si charname=self.chars_list.get()[self.cs[0]] savename=self.saves_list.get()[self.cs[1]] savedir=OP.normpath(OP.join(pCHARACTERS_DIR,charname,"Saves",savename)) for f in os.listdir(savedir):
      • if OP.splitext(f)[1]==".das":
        • pLAST_CHR=OP.normpath(OP.join(savedir,f))
      Log("1INFO: Selected character savegame: %s"%pLAST_CHR) self.ShowCHR()

# Resource File selection

  • def Filter(self,match=""):
    • faces=[] if self.res_file:
      • match=self.filter_string.get() faces=self.res_file.Search(match,False,".mor") lst=[] for f in faces:
        • if match in f: lst.append(f)
        self.files_list.set(tuple(lst))
      self.fFRB_infos["text"]="Faces Files (%s/%s)"%(len(lst),len(faces))

    def SelectFace(self,event):

    • if self.res_file:
      • try:
        • selection=int(self.fFRBL_list.curselection()[0])
        except:
        • selection=0 self.fFRBL_list.selection_set(0)
        if self.files_list.get():
        • self.res_facename=self.files_list.get()[selection] Log("1INFO: Selected Face file: %s (in %s)"%(self.res_facename,self.res_file.path))
        self.ShowERF()

# Source Edition

  • def CopyData(self,mask=""):

    • if self.SRC_MOR.data=="" or self.DST_MOR.data=="":
      • Message(self,"Set a source and a destination first !") return
      Log("1INFO: Copying Data") features=self.fSF_clFeatures.getselection() # face morph : build new mor with source or dest if self.copy_face.get():
      • NEW_MOR=MORFile(self.SRC_MOR.data,"<new>") Log("2 Get Morph from SRC_MOR")

      else:
      • NEW_MOR=MORFile(self.DST_MOR.data,"<new>") Log("2 Get Morph from DST_MOR")

      # At this point, all the morph has been replaced if len(features)==len(aMOR_EDIT) and self.copy_face.get():
      • # useless to copy features if all selected and new morph is src morph Log("2 All features are already sets")
      else:
      • # face features : replace values in new mor by src mor or dst mor values for i in range(len(aMOR_EDIT)):
        • key=aMOR_EDIT[int(i)][1]

          valS=self.SRC_MOR.GetString(key) valD=self.DST_MOR.GetString(key) if str(i) in features:

          • Log("2 Get %s from SRC_MOR (%s)"%(key,valS))

            NEW_MOR.SetString(key,valS)

          else:
          • Log("2 Get %s from DST_MOR (%s)"%(key,valD))

            NEW_MOR.SetString(key,valD)

      # Set charname in UI

      if self.copy_name.get() and GetType(self.src)==1 and GetType(self.dst)==1:

      • dstname,mode=GetName(self.dst,log=False) srcname,mode=GetName(self.src,log=False) if srcname:

        • self.dst_name.set(srcname) Log("2 Get Name from SRC : (%s)"%(srcname))
      # Copy new mor to dest mor self.DST_MOR=NEW_MOR

      self.RebuildFeaturesList() self.SelectEntry()

    def Toggle(self):
    • features=self.fSF_clFeatures.getselection() if features:
      • for i,ff in enumerate(aMOR_EDIT):

# self.fSF[ff[1]].deselect()

  • self.fSF_clFeatures.setstatus(i,"off")
  • #self.fSF_cMorph.deselect() #self.fS_cName.deselect()
  • else:
    • for i,ff in enumerate(aMOR_EDIT):

# self.fSF[ff[1]].select()

  • self.fSF_clFeatures.setstatus(i,"on")
  • #self.fSF_cMorph.select() #self.fS_cName.select()

# Destination face editing

  • def SelectEntry(self,event=None):

    • try:
      • entry=int(self.fDFL_list.curselection()[0])
      except:
      • entry=0 self.fDFL_list.selection_set(entry)
      self.edit_key=aMOR_EDIT[entry][1] self.fDFE_label["text"]=aMOR_EDIT[entry][0]

      self.entry_value.set(self.DST_MOR.GetString(self.edit_key))

    def SetValToDest(self):

    • if self.DST_MOR.data=="":
      • Message(self,"Set a destination first !") return
      key=self.edit_key val=self.fDFE_eValue.get()

      self.DST_MOR.SetString(key,val) self.RebuildFeaturesList() Log('1INFO: Changed feature <%s> to "%s" in destination face'%(key,val))

    def ResetFace(self):

    • if GetType(self.dst)==1:

      • self.DST_MOR=MORFile(GetMORData(self.dst),"<destination savegame>")

      if GetType(self.dst)==2:

      • if OP.exists(self.dst):
        • fin=open(self.dst,"rb") txt=fin.read() fin.close() self.DST_MOR=MORFile(txt)
        else: # new file
        • self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>") if not self.SRC_MOR.data: Log("1INFO: New mor file waiting to be filled with any source")

      self.RebuildFeaturesList()

    def RebuildFeaturesList(self):

    • entries=map(lambda me: me[0]+" (%s)"%self.DST_MOR.GetString(me[1]),aMOR_EDIT) self.entries_list.set(tuple(entries)) self.SelectEntry()

# Saving process

  • def Save(self):
    • if not self.dst:
      • Message(self,"Set a destination first !") return

      if GetType(self.dst)==1 and OP.exists(self.dst) and nSAVE_METHOD>=1:

      • # list files in the save folder folder=OP.dirname(self.dst) if nSAVE_METHOD==1: # copy to Slot_n+1
        • inc=1 while OP.exists(OP.dirname(folder)+os.sep+"Slot_%d"%inc): inc+=1 newfolder=OP.dirname(folder)+os.sep+"Slot_%d"%inc
        # create folder if necessary and copy files if not OP.exists(newfolder): os.mkdir(newfolder) for f in os.listdir(folder): shutil.copy(OP.join(folder,f),OP.join(newfolder,f)) # set self.dst as the new das file destpath=OP.join(newfolder,OP.basename(self.dst))
      else:
      • destpath=self.dst
      if destpath and self.DST_MOR.data!="":
      • Log("1INFO: Saving to destination file : %s"%destpath) ok=False # First, update destination face with the new strings (build the raw data) self.DST_MOR.Update() # Dest is a DAS, proceed with tweaks

        if GetType(destpath)==1:

        • ok=ChangeSaveData(destpath,self.dst_name.get(),self.dst_invsize.get(),self.DST_MOR) if ok:

          • if nSAVE_METHOD<0: Message(self,"Savegame modified (without backup):\n%s"%destpath) elif nSAVE_METHOD==0: Message(self,"Savegame modified (with backup):\n%s"%destpath) elif nSAVE_METHOD==1: Message(self,"Savegame created (in new slot):\n%s"%(destpath))

        # Dest is a MOR, proceed with writing mor face to file

        elif GetType(destpath)==2:

        • self.DST_MOR.Save(destpath) Message(self,"Face file created:\n%s"%destpath)

    def SetSrcFile(self,path):

    • path=OP.normpath(path)

      self.SRC_MOR=MORFile("","<source>") if path:

      • if GetType(path)==1:

        • Log("1INFO: Selected DAS Source file: %s"%path) self.src=path

          self.SRC_MOR=MORFile(GetMORData(path),"<source savegame>") SetPathText(self.fS_path,path,"",45)

        elif GetType(path)==2:

        • Log("1INFO: Selected MOR Source file: %s"%path) self.src=path fin=open(path,"rb") txt=fin.read() fin.close() self.SRC_MOR=MORFile(txt)

          SetPathText(self.fS_path,path,"",45)

        elif GetType(path)==3:

        • Log("1INFO: Selected ERF Face Source file: %s in %s"%(self.res_facename,path)) self.src=path

          facefile=self.res_file.GetFileData(self.res_facename) self.SRC_MOR=MORFile(facefile,"<%s>"%self.res_facename) SetPathText(self.fS_path,path,"",45) self.fS_path["text"]=TruncatePath(path,"",45)+"\n"+self.res_facename

        if GetType(path) in (1,2,3):

        if self.dst and self.DST_MOR.data=="": # fill dst mor with source if empty

    def SetDstFile(self,path):

    • path=OP.normpath(path)

      self.DST_MOR=MORFile("","<destination>") if path:

      • if GetType(path)==1:

        • Log("1INFO: Selected DAS Destination file: %s"%path) self.dst=path

          dstname,mode=GetName(path) self.dst_name.set(dstname) self.dst_invsize.set(GetInvSize(path)) self.DST_MOR=MORFile(GetMORData(path),"<%s savegame>"%dstname) SetPathText(self.fD_path,path,"",45)

        elif GetType(path)==2:

        • Log("1INFO: Selected MOR Destination file: %s"%path) self.dst=path if OP.exists(path): # opening file
          • fin=open(path,"rb") txt=fin.read() fin.close() self.DST_MOR=MORFile(txt)
          else: # new file
          • self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>") if not self.SRC_MOR.data: Log("1INFO: New mor file waiting to be filled with any source")

          SetPathText(self.fD_path,path,"",45)

        if GetType(path) in (1,2):

def Log(text,write=True):

  • log=2 if text[0].isdigit():
    • log=int(text[0]) text=log*"."+text[1:]
    # only show some messages, based on nLOG_LEVEL try: # Python v2.5 and upper
    • if log<=nLOG_LEVEL: print(text)

    except: # Python v2.4 and lower
    • if log<=nLOG_LEVEL: print text

    # always write all flow into logfile if write:
    • try: LOG.write(text+"\n") except: LOG.write("ERROR: Log Error\n") LOG.flush()

def Message(frame,msg="A message",msgtype="i",log=True):

  • if log and msgtype in ("e","i"): Log({"e":"0ERROR: ","i":"1INFO: "}[msgtype]+msg) if msgtype=="e": icotype=TKMB.ERROR else: icotype=TKMB.INFO Dialog=TKMB.Message(frame,message=msg,icon=icotype,title=sAPPNAME) Dialog.show()

def UpdateScript(src,dst,text):

  • chardir=pCHARACTERS_DIR.strip("\ /") Log("1INFO: Updating script: %s"%sPYTHON_FILE) for k,v in zip(("pCHARACTERS_DIR=","pLAST_CHR=","pLAST_DAS=","pLAST_MOR=","pLAST_ERF=","nSAVE_METHOD="),
    • (chardir,pLAST_CHR,pLAST_DAS,pLAST_MOR,pLAST_ERF,nSAVE_METHOD)):
    • # replace the first occurence of data start=text.find(k,0) Log("3 Updating : %s%s"%(k,v)) end=text.find("\n",start) if k.startswith("p"):
      • text=text[0:start]+k+'"'+OP.normpath(v).strip(".")+'"'+text[end:]
      elif k.startswith("b"):
      • text=text[0:start]+k+("False","True")[v]+text[end:]
    return text

def SetPathText(control,path,prefix="",length=35):

  • # set the path and colorize if not path: control["text"]="...\n..." else:
    • control["text"]=TruncatePath(OP.dirname(path),prefix,length)+"\\\n"+OP.basename(path)

    control["bg"]=GetTypeColor(path)

def TruncatePath(path,prefix,length=35):

  • if not path: return ""

    if len(path)>length: path="..."+path[-(length-3):] return prefix+path

def GetType(path):

  • if path.endswith(".das"): return 1 if path.endswith(".mor"): return 2 if path.endswith(".erf"): return 3 if path.endswith(".rim"): return 3 return 0

def GetTypeName(path): return ("None","Savegame","Face","Resource")[GetType(path)] def GetTypeColor(path): return ("black","darkgreen","darkblue","darkred")[GetType(path)]

LOG=open("DAFR_%s.log"%sVERSION,"w") Log("0Starting DragonAge Face Replacer v%s"%sVERSION) Log("0 Code is (c) 2010 NewByPower. See licence information in the script.") Log("0 Homepage: https://www.dragonagenexus.com/downloads/file.php?id=428") Log("0 Use [?] button to see a short help. See the readme for more explanations.")

root=Tix.Tk() UI=DAFR(root) UI.mainloop() root.destroy()

# Updating script globals # load python script pyfile=open(sPYTHON_FILE,"rb") txt=pyfile.read() pyfile.close() # update code txt=UpdateScript(UI.src,UI.dst,txt).encode("utf8") # rewrite python script pyfile=open(sPYTHON_FILE,"wb") pyfile.write(txt) pyfile.close()

Log("0Ending DragonAge Face Replacer") LOG.close()

PythonProjects (last edited 2015-01-08 11:09:16 by WolfgangMaier)

Unable to edit the page? See the FrontPage for instructions.