Author Topic: [Xenogears] Filesystem  (Read 3206 times)

Micky

  • *
  • Posts: 300
    • View Profile
[Xenogears] Filesystem
« on: 2010-06-06 20:45:05 »
(In extension to this old thread)
I cross referenced the info from jpsxdec with the data from my extraction tool, and found a bug in my length calculation for movie scenes. But aside from that, it looks like my assumption that the negative number that shows the start of a directory is actually the number of files in the directory is correct. For the movie directory it matches the number of movies reported by jpsxdec, and interestingly the last two entries after the movies point to the executable and config file, at exactly the same sector positions as reported by the iso9660 filesystem.
Unfortunately this leaves quite a few files that don't get put into any directory, so there is maybe still something I'm missing.

Here is the current version of my extraction code:
Code: (Python) [Select]
import sys, os, struct, re, csv

re_name = re.compile(r"(.*);([0-9]+)")
block_size = 2352

def readSectorForm1(f, lba, count):
    f.seek(lba * block_size)
    data = ""
    for i in xrange(count):
block = f.read(block_size)
data += block[24:2048+24]
del block

    return data

def readDir( f, path, dir_pos, dir_size, parent):
    dir = readSectorForm1(f, dir_pos, (dir_size + 2047) / 2048)
    list = []
    pos = 0
    while pos < len(dir):
(entry_size, file_pos, file_len, attr, name_len) = struct.unpack_from("<BxIxxxxIxxxxxxxxxxxBxxxxxxB", dir, pos)
if entry_size > 0:
    hidden = (attr & 1) != 0
    subdir = (attr & 2) != 0

    if file_pos != dir_pos and file_pos != parent:
name = dir[pos+33:pos+33+name_len]
if not subdir:
    pat = re_name.match(name)
    if pat:
name = pat.group(1)

file_path = os.path.join(path, name)
if subdir:
    list.extend( readDir(f, file_path, file_pos, file_len, dir_pos) )
else:
    list.append( (file_path, file_pos, file_len) )
   
    pos += entry_size
else:
    pos = (pos + 2047) & ~2047

    del dir
    return list

def readFileTable(f):
    fileTable = readSectorForm1( f, 24, 16 )
    index = 0
    fileCount = 0
    dirCount = 0
    dirIndex = 0
    movies = False
    list = []
    while True:
startSector = struct.unpack_from("<I", fileTable, index * 7 + 0)[0] & 0xFFFFFF
if startSector == 0xFFFFFF:
    break
fileSize = struct.unpack_from("<i", fileTable, index * 7 + 3)[0]
if fileSize < 0:
    fileCount = 0
    dirIndex = dirCount
    movies = dirCount == 0
    dirCount += 1
elif fileSize > 0:
    file_path = os.path.join("dir%i" % dirIndex, "file%i.bin" % fileCount)
    list.append( (file_path, startSector, fileSize, movies) )
    fileCount += 1
index += 1
    return list

def main(*argv):
    for arg in argv:
f = open(arg, "rb")

# identify the disk
volume_descriptor = readSectorForm1( f, 16, 1 )
(system_identifier, volume_identifier) = struct.unpack_from("<32s32s", volume_descriptor, 8)
system_identifier = system_identifier.strip()
if system_identifier != "PLAYSTATION":
    print "Not a playstation image: \"%s\"" % system_identifier
    f.close()
    return
volume_identifier = volume_identifier.strip()
if volume_identifier != "XENOGEARS":
    print "Not a Xenogears image: \"%s\"" % volume_identifier
    f.close()
    return

# read the filesystem
(root_pos,) = struct.unpack_from("<I", volume_descriptor, 156 + 2)
(root_len,) = struct.unpack_from("<I", volume_descriptor, 156 + 10)
list = readDir( f, "", root_pos, root_len, root_pos)

# find if we're disk 1 or disk 2
disk = None
for file in list:
    if file[0] == 'SLUS_006.64':
disk = 1
break
    elif file[0] == 'SLUS_006.69':
disk = 2
break
if disk is None:
    print "Failed to find executable"
    print "Please post this to the tech-related forum on http://forums.qhimm.com/"
    print list
    f.close()
    return
   
# export normal files
if True:
    for (name, startSector, fileSize)  in list:
blocks = (fileSize + 2047) / 2048
data = readSectorForm1(f, startSector, blocks)

path = os.path.join("disk%i" % disk, name)
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
    os.makedirs(dirname)

d = open(path, "wb")
d.write(data[:fileSize])
d.close()
del data

# export directory table
if False:
    c = open("xenogears_iso%i.csv" % disk, "wt")
    w = csv.writer(c)
    w.writerows(list)
    c.close()

# get hidden file table
list = readFileTable(f)

# export hidden files
if True:
    for (name, startSector, fileSize, movieFlag) in list:
if movieFlag:
    blocks = (fileSize + 2335) / 2336
    f.seek(startSector * block_size)
    data = f.read(blocks * block_size)
else:
    blocks = (fileSize + 2047) / 2048
    data = readSectorForm1(f, startSector, blocks)

path = os.path.join("disk%i" % disk, name)
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
    os.makedirs(dirname)

d = open(path, "wb")
d.write(data[:fileSize])
d.close()
del data

# export file table
if False:
    c = open("xenogears_table%i.csv" % disk, "wt")
    w = csv.writer(c)
    w.writerows(list)
    c.close()
   
f.close()

if __name__ == "__main__":
    main(*sys.argv[1:])

It can give you CSV tables for the ISO filesystem and the file table used for game data. Note that both the config file and the boot file are referenced from both tables.
You need a raw disc image for it to work from.
« Last Edit: 2010-06-30 08:03:46 by Micky »