492
Comment:
|
62347
|
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)
- # morface MUST be Update() before to register new data
dasdata=ChangeFaceData(dasdata,morface.data)
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")
dasdata=SetInvSize(dasdata,new_invsize)
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
- Log("0ERROR: MOR offset not found ! Face not changed.") return dasdata
- 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")
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")
- 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")
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)))
- # 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:
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
- 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"
- name_u16="" for c in newname+"\x00": name_u16+=c+"\x00"
- 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]
class ERFFile():
def init(self,path):
- self.infos={"type":u"",
- "files":0}
- 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)
- 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)
- if match.lower() in f.lower():
- rtn=self.registry.keys()
- for f in self.registry.keys():
def GetFileData(self,name):
- o,l=self.registry[name] self.file.seek(o) return self.file.read(l)
- self.file.close()
def AddFile(self,name,filedata):
- return
def RemoveFile(self,name):
- return
- return
- self.infos={"type":u"",
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()
- # 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","")
- s=""
- p=aMOR_FEATURES_PARTS[i] o=self._read(offset+4*i+4,"I")[0] if o!=0xffffffff:
# 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","")
- s=""
- p=aMOR_FEATURES_TINTS[i] o=self._read(offset+4*i+4,"I")[0] if o!=0xffffffff:
# 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
- self.data=self.data[0:start]+string+self.data[start+len(string):]
- # do not proceed if file not modified if self.mod==False:
- return
- mii,mit,mif,mio=self._read(md+12*i,"IHHI") morp[self.ids[mii]]=(mit,mif,mio)
# 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()
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
- # Save MORFile.data to path if OP.exists(path):
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
- # binding
# 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")
self.SetSelected(pLAST_DAS) self.fFD_path["text"]=TruncatePath(pLAST_DAS,"",22) self.Show("S")
self.SetSelected(pLAST_MOR,False) self.fFM_path["text"]=TruncatePath(pLAST_MOR,"",22) self.Show("F")
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)
- 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)}
- v[0].forget() v[1].config(relief="raised")
- 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))
if GetType(path)==1:
- pLAST_DAS=path Log("1INFO: Open savegame: %s"%pLAST_DAS) self.ShowDAS()
- global pLAST_MOR
Dialog=TKFD.SaveAs(filetypes=(("Face file",".mor"),),
- initialdir=OP.dirname(pLAST_MOR), defaultextension=".mor", initialfile=self.dst_name.get())
if GetType(path)==2:
- pLAST_MOR=path Log("1INFO: New face: %s"%pLAST_MOR) self.ShowMOR()
- global pLAST_MOR Dialog=TKFD.Open(filetypes=(("Face file",".mor"),),
- initialdir=OP.dirname(pLAST_MOR))
if GetType(path)==2:
- pLAST_MOR=path Log("1INFO: Open face: %s"%pLAST_MOR) self.ShowMOR()
- global pLAST_ERF
Dialog=TKFD.Open(filetypes=(("DragonAge Resource",".erf .rim"),),
- initialdir=OP.dirname(pLAST_ERF))
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]
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)
- if path: self.sel=path if OP.exists(self.sel):
self.SetSrcFile(self.sel) self.CheckSRC()
- 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()
- self.fS_cName.forget()
- self.fS_cName.pack(pady=2,anchor=W)
- global pLAST_DAS
# 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()
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()
- self.fD_savegame.pack()
- if path: self.sel=path
# 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()
- 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])
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 OP.splitext(f)[1]==".das":
- for f in os.listdir(spp):
- spp=OP.normpath(OP.join(char_dir,p,"Saves",pp)) if not OP.exists(spp): continue if OP.isdir(spp):
- saves=[] for pp in os.listdir(sp):
- sp=OP.normpath(OP.join(char_dir,p,"Saves")) if OP.isdir(sp): # path is a directory
- for p in os.listdir(char_dir):
def SelectChar(self,event):
- try:
- ci=int(self.fFCBC_list.curselection()[0])
- 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)
- 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])
- 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)
- 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)
- if OP.splitext(f)[1]==".das":
- pLAST_CHR=OP.normpath(OP.join(savedir,f))
- global pCHARACTERS_DIR Dialog=TKFD.Directory(initialdir=OP.dirname(pCHARACTERS_DIR)) char_dir=OP.normpath(Dialog.show()+"/")
# 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)
- match=self.filter_string.get() faces=self.res_file.Search(match,False,".mor") lst=[] for f in faces:
def SelectFace(self,event):
- if self.res_file:
- try:
- selection=int(self.fFRBL_list.curselection()[0])
- selection=0 self.fFRBL_list.selection_set(0)
- self.res_facename=self.files_list.get()[selection] Log("1INFO: Selected Face file: %s (in %s)"%(self.res_facename,self.res_file.path))
- try:
- faces=[] if self.res_file:
# 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
NEW_MOR=MORFile(self.SRC_MOR.data,"<new>") Log("2 Get Morph from SRC_MOR")
NEW_MOR=MORFile(self.DST_MOR.data,"<new>") Log("2 Get Morph from DST_MOR")
- # useless to copy features if all selected and new morph is src morph Log("2 All features are already sets")
- # face features : replace values in new mor by src mor or dst mor values for i in range(len(aMOR_EDIT)):
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))
self.RebuildFeaturesList() self.SelectEntry()
- features=self.fSF_clFeatures.getselection() if features:
- for i,ff in enumerate(aMOR_EDIT):
- if self.SRC_MOR.data=="" or self.DST_MOR.data=="":
# 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])
- entry=0 self.fDFL_list.selection_set(entry)
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
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)
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()
- try:
# 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
- destpath=self.dst
- 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))
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)
self.DST_MOR=MORFile(self.SRC_MOR.data,"<mor file>") self.RebuildFeaturesList()
- Log("1INFO: Selected DAS Source file: %s"%path) self.src=path
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)
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)
- Log("1INFO: Selected DAS Destination file: %s"%path) self.dst=path
- if not self.dst:
def Log(text,write=True):
- log=2 if text[0].isdigit():
- log=int(text[0]) text=log*"."+text[1:]
if log<=nLOG_LEVEL: print(text)
if log<=nLOG_LEVEL: print text
- 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:]
- text=text[0:start]+k+("False","True")[v]+text[end:]
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()