import os from mippy_fast import MIPS2ASM from struct import unpack from sys import argv #Thanks for ZZT32 for these actor_types={ 0 : "type 0", 1 : "1 (Prop)", 2 : "type 2", 3 : "3 (Bomb)", 4 : "4 (NPC)", 5 : "5 (Enemy)", 6 : "6 (Prop)", 7 : "7 (Item/Action)", 8 : "8 (Miscellaneous)", 9 : "9 (Boss)", 10 : "type 10", 11 : "11 (Door?)", 12 : "type 12", 13 : "type 13", 14 : "type 14", 15 : "type 15" } #Thanks to MNGoldenEagle for the original version of the following list #TODO: map these out for MM and gigure out MM skyboxes skybox_types={ 0x00: "Black", 0x01: "Environment color (or blue skies)", 0x02: "Hylian Bazaar", 0x03: "Environment color (?)", 0x04: "Crashes/Market Ruins", 0x05: "Dark and cloudy but offset so it doesn't depend on having a ground plane, skybox rotates quickly", 0x06: "Environment color, possibly lighter? (or mildly pink)", 0x07: "Crashes/Link's House", 0x08: "Environment color (slightly lighter)", 0x09: "Crashes/Market", 0x0A: "Crashes/Market (Night)", 0x0B: "Happy Mask Shop; two sides of the skybox contain corrupt textures", 0x0C: "Crashes/Know-It-All Brothers' House", 0x0D: "Environment color (?)", 0x0E: "Kokiri Twins' House; all sides except back are visible, which is typical", 0x0F: "Crashes/Stable", 0x10: "Crashes/Stew Lady's House", 0x11: "Kokiri Shop", 0x12: "Environment color (?)", 0x13: "Goron Shop", 0x14: "Zora Shop", 0x15: "Environment color (?)", 0x16: "Kakariko Potions Shop", 0x17: "Hylian Potions Shop", 0x18: "Bomb Shop", 0x19: "Environment color (?)", 0x1A: "Crashes/Dog Lady's House", 0x1B: "Crashes/Impa's House", 0x1C: "Gerudo Tent", 0x1D: "Environment color, but the color is brighter (or a bluish gray)", 0x1E: "Environment color (?)", 0x1F: "Environment color (?)", 0x20: "Mido's House", 0x21: "Saria's House", 0x22: "Dog Guy's House" } def filterNonAscii(string): ret = "" for c in string: ordc = ord(c) if ordc > 0x7F or ordc < 32: ret+='.' elif c == ">": ret+=">" elif c == "<": ret+="<" elif c == "&": ret+="&" else: ret+=c return ret def COLLISION_TO_OBJ(infile,outfile,f_off,mdh_off): fopmo=f_off+mdh_off infile.seek(fopmo+12) ncverts = unpack(">H",infile.read(2))[0] infile.seek(fopmo+16) cvoff = (unpack(">L",infile.read(4))[0]&0xFFFFFF)+f_off nctris = unpack(">H",infile.read(2))[0] infile.seek(fopmo+24) ctoff = (unpack(">L",infile.read(4))[0]&0xFFFFFF)+f_off+2 fstr='#Exported from Zelda 64 formatted collision' infile.seek(cvoff) for i in range(ncverts): xyz=unpack(">hhh",infile.read(6)) fstr+="\nv %i %i %i"%xyz for i in range(nctris): infile.seek(ctoff) a,b,c=unpack(">HHH",infile.read(6)) a,b,c=(a&0xFFF)+1,(b&0xFFF)+1,(c&0xFFF)+1 fstr+="\nf %i %i %i"%(a,b,c) ctoff+=16 outfile.write(fstr) def hex2html(string,outfilename,title,PC): out=open(outfilename,"w") out.write('''%s
'''%title)
    for i in range(0,len(string),16):
        out.write("\n%08X | %08X %08X %08X %08X %s"%(PC,unpack(">L",string[i:i+4])[0],unpack(">L",string[i+4:i+8])[0],unpack(">L",string[i+8:i+12])[0],unpack(">L",string[i+12:i+16])[0],filterNonAscii(string[i:i+16])))
        PC+=16
    out.write('''
up''') out.close def str2html(string,outfilename,title): out=open(outfilename,"w") out.write('''%s
%s
up'''%(title,string)) out.close() def replace_zeros(string): ret='' for c in string: if c=='\x00': ret+=' ' else: ret+=c return ret def find_filetable(f,gbi=False): """Returns filetable offset if found, else None""" ret=None f.seek(0) for i in range(0,0x20000,16): DD=f.read(16) if len(DD.split("@srd"))==2: off=f.tell() if gbi==True: f.seek(f.tell()-16) gbi_=replace_zeros(f.read(0x30)) while gbi_[-1]==' ': gbi_=gbi_[:-1] break for i in range(0,0x80,16): off+=16 f.seek(off) D=f.read(8) if unpack(">Q",D)[0]==0x1060: ret = off break if gbi==False: return ret else: return ret, gbi_ def find_nametable(f): """Returns nametable offset if found, else None""" ret=None f.seek(0x1060) off=0x1060 for i in range(0,0x10000,16): off+=16 if unpack(">QQ",f.read(16))== (0x6D616B65726F6D00, 0x626F6F7400000000): ret=off-16 break return ret def find_code(f): """Returns code's offsets if found, else None""" ret=None ft_off=find_filetable(f) for i in range(0, 0x300, 16): f.seek(ft_off+i) vst,ve,pst,pe=unpack(">LLLL",f.read(16)) if pe==0: f.seek(ve-16) if unpack(">QQ",f.read(16))==(0x6A6E8276E707B8E3,0x7D8A471D6A6E18F9): ret=(vst,ve) break return ret def rip_files(f,directory,gbi): """Attempts to rip all files from ROM f to path directory""" if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) s_off,gbi_=find_filetable(f,gbi) n_off=find_nametable(f) foffs,fnams=[],[] fcount = 0 if s_off!=None: go=1 while go: f.seek(s_off) vst,ve,pst,pe=unpack(">LLLL",f.read(16)) if vst == ve: break if n_off!=None: name='' while len(name)<1: f.seek(n_off) name,char,oc='','',1 while oc != 0: name+=char char=f.read(1) oc=ord(char) n_off=f.tell() if name[0:4]=="ovl_": name+=".zactor" elif len(name.split("_room"))==2: name+=".zmap" elif name[0:7]=="object_": name+=".zobj" elif name[-6:]=="_scene": name+='.zscene' else: name+='.zdata' else: name="%08X-%08X.zdata"%(vst,ve) f.seek(pst) if pe!=0: #compressed name+=".yaz0" out=open(directory+name,"w") f.seek(pst) out.write(f.read(pe-pst)) else: #decompressed out=open(directory+name,"w") out.write(f.read(ve-vst)) out.close() s_off+=16 foffs.append((vst,ve)) fnams.append(name) fcount+=1 if gbi==False: return fnams, foffs, fcount else: return fnams, foffs, fcount, gbi_ def mk_frep(f,directory,fnams,foffs): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) html=open(directory+'index.html','w') html.write(''' MM US ROM File Extracts

MM US ROM File Extracts

File No\t\tStart (hex)\tEnd (hex)\tSize(kb)\tName''')
    for i in range(len(fnams)):
        html.write('''\n%06i\t\t%08X\t%08X\t%s\t\t%s'''%(i,foffs[i][0],foffs[i][1],('%f'%((foffs[i][1]-foffs[i][0])/1024.))[:6],fnams[i],fnams[i].split('.')[0]))
    html.write('''
up ''') html.close() #0xC74560 def mk_srep(f,directory): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) html=open(directory+'index.html','w') html.write(''' MM US ROM Scene listing

MM US ROM Scene listing

Scene No\tStart (hex)\tEnd (hex)\tSize(kb)\tName''')
    home = 0xC5A250
    snoff = 0xC74560
    for i in range(102):
        f.seek(home)
        s,e,ts,te=unpack(">LLLL",f.read(16))
        f.seek(snoff)
        c = f.read(1)
        fn = ""
        while (c != "\x00"):
            fn+=c
            c = f.read(1)
            snoff+=1
        while (c == "\x00"):
            c = f.read(1)
            snoff+=1
        if s == 0:
            html.write('''\n%03i (0x%02X)\t%08X\t%08X\t0.0000\t\t%s'''%(i,i,s,e,fn))
        else:
            html.write('''\n%03i (0x%02X)\t%08X\t%08X\t%s\t\t%s'''%(i,i,s,e,('%f'%((e-s)/1024.))[:6],i,fn))
            mk_s(f, directory+"%02X/"%i, "../../"+fn, fn.split(".")[0],(s,e),i,fn)
        home+=16
    html.write('''
up ''') html.close() def mk_s(f,directory,s_path,s_name,s_offs,s_number,name): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) html=open(directory+'index.html','w') html.write(''' Scene 0x%02X ("%s") info

Scene 0x%02X ("%s") info

Name: %08X-%08X
Number: 0x%04X
Offsets: %08X - %08X'''%(s_number,s_name,s_number,s_name,s_offs[0],s_offs[1],s_offs[0],s_offs[1],s_number,s_offs[0],s_offs[1]))
    f.seek(s_offs[0])
    header_offs=[0]
    f.seek(s_offs[0])
    if unpack(">B",f.read(1))[0]==0x18:
        f.seek(s_offs[0]+4)
        o_=unpack(">L",f.read(4))[0]
        f.seek(s_offs[0]+(o_&0xFFFFFF))
        while 1:
            w=unpack(">L",f.read(4))[0]
            if w>>24 == 2:
                header_offs.append(w&0xFFFFFF)
            else:
                break
        header_offs[0]=8
    html.write("\n%i object set"%len(header_offs))
    if len(header_offs)>1:
        html.write("s")
    j=0
    collision_made=1
    map_made=1
    for offset in header_offs:
        html.write("\nObject set %i:"%j)
        bo=s_offs[0]+offset
        while 1:
            f.seek(bo)
            w=unpack(">BBBBL",f.read(8))
            if w[0]==0x15:  html.write("\n  Music: 0x%02X"%(w[4]&0xFF))
            elif w[0]==0:
                html.write("\n  %03i Link setups:"%w[1])
                f.seek(s_offs[0]+(w[4]&0xFFFFFF))
                for i in range(w[1]):
                    li=unpack(">HhhhhhhH",f.read(16))
                    html.write("""\n    Link setup %03i:\n      Actor: 0x%04X\n      Position: %i, %i, %i\n      Rotation: %f\xb0, %f\xb0, %f\xb0\n      Variable: 0x%04X""" % (i,li[0],li[1],li[2],li[3],li[4]/182.044444444,li[5]/182.04444444,li[6]/182.044444444,li[7]))
            elif w[0]==3:
                if collision_made:
                    sc_obj=open("%s%s.obj"%(directory,s_name),"w")
                    COLLISION_TO_OBJ(f,sc_obj,s_offs[0],w[4]&0xFFFFFF)
                    collision_made=0
                html.write('\n  Collision data offset: %08X\n  Collision as object'%(w[4]&0xFFFFFF,s_name))
            elif w[0]==0xE:
                html.write("\n  %03i door setups:"%w[1])
                f.seek(s_offs[0]+(w[4]&0xFFFFFF))
                for i in range(w[1]):
                    li=unpack(">BBBBHhhhhH",f.read(16))
                    html.write("""\n    Door setup %03i:\n      From front\n        Room to: %i\n        Camera Variable: 0x%02X\n      From rear\n        Room to: %i\n        Camera Variable: 0x%02X\n      Actor number: 0x%04X\n      Position: %i, %i, %i\n      Rotation: %f\xb0\n      Variable: %04X""" % (i, li[0],li[1],li[2],li[3],li[4],li[4],li[5],li[6],li[7],li[8]/182.04444444444, li[9]))
            elif w[0]==0x4:
                html.write("\n  %03i Maps:"%w[1])
                mpoff=s_offs[0]+(w[4]&0xFFFFFF)
                for i in range(w[1]):
                    f.seek(mpoff)
                    m_offs=unpack(">LL",f.read(8))
                    m_name="%08X-%08X"%m_offs
                    if map_made==1:
                        m_path="../../%s"%(m_name)
                        m_dir="%sm%03i"%(directory,i)
                        mk_m(f,m_dir,m_name,m_path,m_offs)
                        map_made=0
                    html.write('\n    Map %03i\n      Map offsets: %08X - %08X\n      Map: %s' % (i,m_offs[0],m_offs[1],i,m_name))
                    mpoff+=8

#            elif w[0]==0x11:
#                if w[4]&0x100:
#                    html.write("\n  Skybox: disabled")
#                else:
#                    skybox_type=w[4]>>24
#                    html.write("\n  Skybox: enabled, 0x%02X (%s)"%(skybox_type,skybox_types[skybox_type]))

            elif w[0]==0x14:
                break
            bo+=8
            
        j+=1
    html.write('''
\n up\n \n''') html.close() def mk_m(f,directory,m_name,m_path,m_offs): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) html=open(directory+'index.html','w') html.write(''' Map %s info

Map %s info

Name: %s
Offsets: %08X - %08X'''%(m_name,m_name,m_path,m_name,m_offs[0],m_offs[1]))
    f.seek(m_offs[0])
    header_offs=[0]
    f.seek(m_offs[0])
    if (unpack(">B",f.read(1))[0] == 0x18):
        f.seek(m_offs[0]+4)
        o_=unpack(">L",f.read(4))[0]
        f.seek(m_offs[0]+(o_&0xFFFFFF))
        while 1:
            w=unpack(">L",f.read(4))[0]
            if w>>24 == 3:
                header_offs.append(w&0xFFFFFF)
            else:
                break
        header_offs[0]=8
    html.write("\n%i object set"%len(header_offs))
    if len(header_offs)>1:
        html.write("s")
    j=0
    for offset in header_offs:
        html.write("\nObject set %i:"%j)
        bo=m_offs[0]+offset
        while 1:
            f.seek(bo)
            w=unpack(">BBBBL",f.read(8))
            if w[0]==0x16:  html.write("\n  Echo: 0x%02X" % (w[0]&0xFF))
            elif w[0]==0x12:
                if w[4]>>24: html.write("\n  Skybox: disabled (overrides scene)")
                else: html.write("\n  Skybox: enabled (overrides scene)")
            elif w[0]==0x10:
                html.write("\n  Starting time: 0x%04X\n  Time speed: %i"% (w[4]>>16, (w[4]>>8)&0xFF))
            elif w[0]==0xA:
                #Map dump process - Not today
                pass
            elif w[0]==0xB:
                html.write("\n  %i objects loaded:"%w[1])
                ol_off=(w[4]&0xFFFFFF)+m_offs[0]
                for x in range(w[1]):
                    f.seek(ol_off)
                    obj=unpack(">H",f.read(2))[0]
                    f.seek(0xC58C80+8*obj)
                    obj_offs=unpack(">LL",f.read(8))
                    obj_name="%08X-%08X" % obj_offs
                    html.write('\n    0x%04X (%s)'%(obj,obj_name,obj_name.split(".")[0]))
                    ol_off+=2
            elif w[0]==1:
                html.write("\n  %i actors:"%w[1])
                f.seek((w[4]&0xFFFFFF)+m_offs[0])
                for x in range(w[1]):
                    an,ax,ay,az,au,av,aw,av=unpack(">HhhhhhhH",f.read(16))
                    html.write('\n    Actor %i:\n      Number: 0x%04X\n      Pos: %i, %i, %i\n      Rotation: %f\xb0, %f\xb0, %f\xb0\n      Variable: 0x%04X'%(x,an&0xFFF,an&0xFFF,ax,ay,az,au/182.044444444,av/182.044444444,aw/182.044444444,av))
            elif w[0]==0x14:
                break
            bo+=8
        j+=1
    html.write('''
\n up\n \n''') html.close() def mk_arep(f,directory): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) html=open(directory+'index.html','w') html.write(''' MM US ROM Actor listing

MM US ROM Actor listing

Number\t\tStart (hex)\tEnd (hex)\tvStart (hex)\tvEnd (hex)\tObject\tSize(kb)\tName''')
    offset=0xC45510
    for i in range(0x2B2):
        f.seek(offset)
        obj_no=0
        s,e,vs,ve,u,c,d=unpack(">LLLLLLL",f.read(28))
        if s == 0 and c == 0:
            name = "NULL"
        elif d == 0:
            name='(No name)'
        if e!=0:
            f.seek(s+(c-vs)+2)
            act_type=actor_types[unpack(">B",f.read(1))[0]]
            f.seek(s+(c-vs)+8)
            obj_no=unpack(">H",f.read(2))[0]
            if obj_no>0x282:
                obj_no=0
            mk_a(f,directory+"%04X"%i,i,name,(s,e),(vs,ve),obj_no,name,act_type)
            name='%s'%(i,name)
        html.write('''\n%04i (0x%04X)\t%08X\t%08X\t%08X\t%08X\t%04X\t%s\t\t%s'''%(i,i,s,e,vs,ve,obj_no,('%f'%((e-s)/1024.))[:6],name));
        offset+=0x20
    html.write('''
up ''') html.close() def mk_a(f,directory,actor_number,act_name,act_offs,act_voffs,obj_no,fname,a_type): if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) f.seek(0xC58C80+8*obj_no) obj_offs=unpack(">LL",f.read(8)) obj_name="%08X-%08X.zdata"%obj_offs f.seek(act_offs[0]) hex2html((f.read(act_offs[1]-act_offs[0])),('%sactor_%04X.html'%(directory,actor_number)),('Viewing '+act_name),(act_voffs[0])) f.seek(act_offs[1]-4) hdr_off=unpack(">L",f.read(4))[0] f.seek(act_offs[1]-hdr_off) hdr_data=unpack(">LLLLL",f.read(20)) ops_len=hdr_data[0] f.seek(act_offs[0]) act_asm=f.read(ops_len) str2html(MIPS2ASM(act_asm, ops_len/4,act_voffs[0]),('%sactor_%04X_text.html'%(directory,actor_number)),'Viewing '+act_name+' text block') html=open(directory+'index.html','w') html.write(''' Actor %s Information

Actor 0x%04X ("%s") Information

Name:\t\t\t%s
Type:\t\t\t%s
Physical Offsets:\t%08X - %08X
Virtual Offsets:\t%08X - %08X
Object Number:\t\t%04X
Object Name:\t\t%s
Object Offsets:\t\t%08X - %08X
Hexadecimal view of actor
Disassembled text block

up '''%(act_name,actor_number,act_name,act_name,act_name,a_type,act_offs[0],act_offs[1],act_voffs[0],act_voffs[1],obj_no,obj_name,obj_name.split('.')[0],obj_offs[0],obj_offs[1],actor_number,actor_number)) html.close() def main(): directory=argv[1] romname=argv[2] if directory[-1]!="/": directory+='/' if os.path.exists(directory)==False: os.mkdir(directory) f=open(romname,"rb") fnams,foffs,fcount,gbi=rip_files(f,directory,True) mk_frep(f,directory+'f/',fnams,foffs) mk_srep(f,directory+'s/') mk_arep(f,directory+'a/') html=open(directory+'index.html','w') html.write(''' All about the MM US ROM

MM US ROM info

Input file: %s
No. Files: %i
Build info: %s

Scene listing
File listing
Actor listing

Source
up

'''%(romname,fcount,gbi,argv[0].split("/")[-1])) html.close() main()