Skip to content

Instantly share code, notes, and snippets.

@Nisto
Created October 25, 2018 02:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nisto/65d45f5aea3d73ce1fc9625d1a752ed2 to your computer and use it in GitHub Desktop.
Save Nisto/65d45f5aea3d73ce1fc9625d1a752ed2 to your computer and use it in GitHub Desktop.
FFX extractor
import os
import struct
FS_HEADER_SECT = None # SLPS-25050: 279
DIRSIZE_TBL_TOC_ENT = None # SLPS-25050: 16
ISOPATH = r'path to iso here'
OUTDIR = r'path to output directory here'
def get_u32_le(buf, off=0):
return struct.unpack("<I", buf[off:off+4])[0]
def get_s16_le(buf, off=0):
return struct.unpack("<h", buf[off:off+2])[0]
def main():
if FS_HEADER_SECT is None \
or DIRSIZE_TBL_TOC_ENT is None \
or os.path.isfile(ISOPATH) is not True \
or os.path.isdir(OUTDIR) is not True:
input("Please configure the script!")
return 1
isopath = os.path.realpath(ISOPATH)
if not os.path.isfile(isopath):
print("Image path invalid!")
return 1
outdir = os.path.realpath(OUTDIR)
if not os.path.isdir(outdir):
os.makedirs(outdir)
with open(isopath, "rb") as iso:
iso.seek(FS_HEADER_SECT * 2048)
header = iso.read(0x30)
mdg_off = get_u32_le(header, 0x10) * 2048
mdg_size = get_u32_le(header, 0x14)
fid_off = get_u32_le(header, 0x18) * 2048
fid_size = get_u32_le(header, 0x1C)
# read in the TOC
iso.seek(mdg_off)
mdg_buf = iso.read(mdg_size)
# read in the folder ID table
iso.seek(fid_off)
fid_buf = iso.read(fid_size)
# read in the folder size table
word = get_u32_le(mdg_buf, DIRSIZE_TBL_TOC_ENT*4)
next_word = get_u32_le(mdg_buf, DIRSIZE_TBL_TOC_ENT*4+4)
sector = word & 0x3FFFFF
next_sector = next_word & 0x3FFFFF
size = (next_sector - sector) * 2048
excess = word >> 24
size -= excess * 8
iso.seek(sector * 2048)
bin_buf = iso.read(size)
for dir_idx in range(64):
print("*** extracting DIRECTORY %d" % dir_idx)
outsubdir = os.path.join(outdir, "ffx%d" % dir_idx)
if not os.path.isdir(outsubdir):
os.makedirs(outsubdir)
toc_dir_idx = get_s16_le(fid_buf, dir_idx*2) # the first TOC file entry of the folder
dir_sect_cnt = get_u32_le(bin_buf, dir_idx*4) # how many sectors the folder occupies
if toc_dir_idx > -1 and dir_sect_cnt > 0:
i = toc_dir_idx
while dir_sect_cnt > 0:
word = get_u32_le(mdg_buf, i*4)
sector = word & 0x3FFFFF
compressed = word & 0x400000
dummy = word & 0x800000
excess = word >> 24
if not dummy:
next_sector = get_u32_le(mdg_buf, i*4+4) & 0x3FFFFF
num_sectors = next_sector - sector
offset = sector * 2048
size = (num_sectors * 2048) - (excess * 8)
if compressed:
outpath = os.path.join(outsubdir, "TOC_ENT_%05d.cbin" % i)
else:
outpath = os.path.join(outsubdir, "TOC_ENT_%05d.bin" % i)
iso.seek(offset)
print("extracting file %d... " % i, end='')
with open(outpath, "wb") as bin:
bin.write( iso.read(size) )
print("ok")
dir_sect_cnt -= num_sectors
i += 1
return 0
if __name__=="__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment