|
| 1 | +#!/usr/bin/env python3 |
| 2 | +import logging |
| 3 | +from errno import ENOENT, EIO |
| 4 | +from stat import S_IFDIR, S_IFLNK, S_IFREG |
| 5 | +from sys import argv, exit |
| 6 | +from time import time |
| 7 | +import subprocess |
| 8 | +from fuse import FUSE, FuseOSError, Operations, LoggingMixIn |
| 9 | +import struct |
| 10 | +class romfsFile: |
| 11 | + def __repr__(self): |
| 12 | + if self.isdir: |
| 13 | + s = "Directory '{}' with subdirs [".format(self.name) |
| 14 | + for subdir in self.children: |
| 15 | + s+=subdir.name+", " |
| 16 | + s+="] and files [" |
| 17 | + for fil in self.files: |
| 18 | + s+=fil.name+", " |
| 19 | + s+="]." |
| 20 | + return s |
| 21 | + return "File '{}' of size {} at {}".format(self.name,hex(self.length),hex(self.off)) |
| 22 | +class RomFS(LoggingMixIn, Operations): |
| 23 | + def traverse(self): |
| 24 | + parent,sibling,child,ffile,namelen=struct.unpack("<IIIIxxxxI",self.f.read(0x18)) |
| 25 | + fi = romfsFile() |
| 26 | + fi.isdir=True |
| 27 | + fi.name=self.f.read(namelen) |
| 28 | + fi.name=fi.name.decode("UTF-16") |
| 29 | + if len(fi.name) != 0: |
| 30 | + if fi.name[-1] == '\x00': |
| 31 | + fi.name=fi.name[:-1] |
| 32 | + fi.siblings=[] |
| 33 | + if sibling != 0xFFFFFFFF: |
| 34 | + self.f.seek(self.dirmetaOff+sibling) |
| 35 | + siblin = self.traverse() |
| 36 | + fi.siblings=[siblin]+siblin.siblings |
| 37 | + fi.children=[] |
| 38 | + if child != 0xFFFFFFFF: |
| 39 | + self.f.seek(self.dirmetaOff+child) |
| 40 | + chld = self.traverse() |
| 41 | + fi.children=[chld]+chld.siblings |
| 42 | + fi.files=[] |
| 43 | + if ffile != 0xFFFFFFFF: |
| 44 | + self.f.seek(self.filemetaOff+ffile) |
| 45 | + fil = self.traverse_file() |
| 46 | + fi.files=[fil]+fil.siblings |
| 47 | + return fi |
| 48 | + def traverse_file(self): |
| 49 | + parent,sibling,dataoff,datalen,namelen=struct.unpack("<IIQQxxxxI",self.f.read(0x20)) |
| 50 | + fi = romfsFile() |
| 51 | + fi.isdir=False |
| 52 | + fi.name=self.f.read(namelen).decode("UTF-16") |
| 53 | + if len(fi.name) != 0: |
| 54 | + if fi.name[-1] == '\x00': |
| 55 | + fi.name=fi.name[:-1] |
| 56 | + fi.siblings=[] |
| 57 | + if sibling != 0xFFFFFFFF: |
| 58 | + self.f.seek(self.filemetaOff+sibling) |
| 59 | + siblin = self.traverse_file() |
| 60 | + fi.siblings=[siblin]+siblin.siblings |
| 61 | + fi.off=dataoff |
| 62 | + fi.length=datalen |
| 63 | + return fi |
| 64 | + def gentree(self,fi,path=""): |
| 65 | + name=path+fi.name |
| 66 | + root=False |
| 67 | + if name=="": |
| 68 | + root=True |
| 69 | + name="/" |
| 70 | + if fi.isdir: |
| 71 | + name+="/" |
| 72 | + self.files[name[:-1]]=dict(st_mode=(S_IFDIR | 0o555), st_ctime=self.now, st_mtime=self.now, st_atime=self.now, st_nlink=2) |
| 73 | + if root: |
| 74 | + name=name[:-1] |
| 75 | + for subdir in fi.children: |
| 76 | + self.gentree(subdir, name) |
| 77 | + for fil in fi.files: |
| 78 | + self.gentree(fil, name) |
| 79 | + return |
| 80 | + self.filelocs[name] = fi.off |
| 81 | + self.files[name]=dict(st_mode=(S_IFREG | 0o555), st_ctime=self.now, st_mtime=self.now, st_atime=self.now, st_nlink=2, st_size=fi.length, st_blocks=(fi.length+511)//512) |
| 82 | + def __init__(self,fname,mount): |
| 83 | + def align(a,b): |
| 84 | + if a%b: |
| 85 | + return a-(a%b)+b |
| 86 | + return a |
| 87 | + self.f=open(fname,"rb") |
| 88 | + print("Reading header") |
| 89 | + self.header=self.f.read(0x5C) |
| 90 | + masterhashsize=struct.unpack("<I",self.header[0x08:0x0C])[0] |
| 91 | + hashblocksize=1<<struct.unpack("<I",self.header[0x4C:0x50])[0] |
| 92 | + v=align(0x5C+masterhashsize,hashblocksize) |
| 93 | + self.f.seek(v) |
| 94 | + print("Reading l3 header") |
| 95 | + self.level3h=self.f.read(0x28) |
| 96 | + self.dirmetaOff,self.dirmetaSize,self.filemetaOff,self.filemetaSize,self.fileDataOff=struct.unpack("<IIxxxxxxxxIII",self.level3h[0xC:0x28]) |
| 97 | + self.dirmetaOff+=v |
| 98 | + self.filemetaOff+=v |
| 99 | + self.fileDataOff+=v |
| 100 | + self.f.seek(self.dirmetaOff) |
| 101 | + self.root=self.traverse() |
| 102 | + self.filelocs={} |
| 103 | + self.fd=1 |
| 104 | + self.now=time() |
| 105 | + self.files={} |
| 106 | + self.gentree(self.root) |
| 107 | + def chmod(self, path, mode): |
| 108 | + raise FuseOSError(EIO) |
| 109 | + def chown(self, path, uid, gid): |
| 110 | + raise FuseOSError(EIO) |
| 111 | + def create(self, path, mode): |
| 112 | + raise FuseOSError(EIO) |
| 113 | + def getattr(self,path,fh=None): |
| 114 | + if path not in self.files: |
| 115 | + raise FuseOSError(ENOENT) |
| 116 | + return self.files[path] |
| 117 | + def getxattr(self,path,name,position=0): |
| 118 | + return '' |
| 119 | + def listxattr(self,path): |
| 120 | + return [] |
| 121 | + def mkdir(self,path,mode): |
| 122 | + raise FuseOSError(EIO) |
| 123 | + def open(self,path,flags): |
| 124 | + self.fd+=1 |
| 125 | + return self.fd |
| 126 | + def read(self,path,size,offset,fh): |
| 127 | + self.f.seek(self.filelocs[path]+offset+self.fileDataOff) |
| 128 | + return self.f.read(size) |
| 129 | + def readdir(self, path, fh): |
| 130 | + print(path) |
| 131 | + files=['.','..'] |
| 132 | + for fi in self.files.keys(): |
| 133 | + sp=fi.split("/") |
| 134 | + if len(sp) != 2 or path != "/": |
| 135 | + if '/'.join(sp[:-1]) != path: |
| 136 | + continue |
| 137 | + if fi=="/": |
| 138 | + continue |
| 139 | + files.append(sp[-1]) |
| 140 | + return files |
| 141 | + def readlink(self, path): |
| 142 | + return self.data[path] |
| 143 | + def removexattr(self, path, name): |
| 144 | + raise FuseOSError(EIO) |
| 145 | + def rename(self, old, new): |
| 146 | + raise FuseOSError(EIO) |
| 147 | + def rmdir(self, path): |
| 148 | + raise FuseOSError(EIO) |
| 149 | + def setxattr(self, path, name, value, options, position=0): |
| 150 | + raise FuseOSError(EIO) |
| 151 | + def statfs(self,path): |
| 152 | + return dict(f_bsize=512, f_blocks=4096, f_bavail=0) |
| 153 | + def symlink(self, target, source): |
| 154 | + raise FuseOSError(EIO) |
| 155 | + def truncate(self,path,length,fh=None): |
| 156 | + raise FuseOSError(EIO) |
| 157 | + def unlink(self,path): |
| 158 | + raise FuseOSError(EIO) |
| 159 | + def utimens(self,path,times=None): |
| 160 | + raise FuseOSError(EIO) |
| 161 | + def write(self,path,data,offset,fh): |
| 162 | + raise FuseOSError(EIO) |
| 163 | + def flush(self,path,fh): |
| 164 | + pass |
| 165 | + def release(self,path,fh): |
| 166 | + pass |
| 167 | + |
| 168 | +if len(argv) != 3: |
| 169 | + print("usage: {name} <ROMFS> <mountpoint>".format(name=argv[0])) |
| 170 | + exit(1) |
| 171 | +logging.basicConfig(level=logging.WARNING) |
| 172 | +fuse = FUSE(RomFS(argv[1], argv[2]),argv[2], foreground=False) |
0 commit comments