Differences between revisions 11 and 12
Revision 11 as of 2011-02-05 22:32:45
Size: 62347
Editor: 125
Comment:
Revision 12 as of 2011-02-05 22:33:38
Size: 62347
Editor: 125
Comment:
No differences found!

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: http://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.