Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tools: Preserve superblock between unpacking and re-packing #19

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions extract_blocks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import sys
from fatx import FATX
from fatx.blocks import SuperBlock

if __name__ == "__main__":
cwd = os.getcwd()
Expand All @@ -9,9 +10,9 @@
os.mkdir(os.path.split(sys.argv[1])[1] + ".extract")
os.chdir(os.path.split(sys.argv[1])[1] + ".extract")
with open("superblock.bin", "w+b") as f:
f.write(fs.read(FATX.SUPERBLOCK_SIZE))
f.write(fs.read(SuperBlock.SUPERBLOCK_SIZE))
with open("fat.bin", "w+b") as f:
fat_size = FATX.Filesystem._calc_fat_size(size)
fat_size = FATX.Filesystem._calc_fat_size(size, 512*32)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 512 the sector size?
Isn't 32 the number of sectors per cluster?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and yes. I see where you are going here - extract_blocks.py will hopefully be removed from the project soonish. Its only purpose is to provide easy accessible binary data for debugging purposes that are much much smaller then the original image and also hopefully do not contain copyrighted material. This allows me to ask users of FATX-on-a-snake to run this script and send me over the extracted files, so I can take a look. However, this is good enough for now but it will be unnecessary in the future, hence I want to remove it from the project. In order to do so I still have to gain more confidence that FATX-on-a-snake works good enough and is somewhat error resistant so it will be able to produce text-based, human-readable log output. That log can then be send over for remote debugging and will be guaranteed be free from copyright material.

My reasoning behind picking 512*32 instead of automatically calculating it is, that when this script is run, something is most-likely heavily broken. i.e. if a user attempts to open the xbox hdd from offset 0x00 it won't see the FATX magic bytes, yet, extract_blocks.py will happily run and extract the "should-be superblock".

f.write(fs.read(fat_size))
for i in range(0, 10):
with open("chunk({0}).bin".format(i), "w+b") as f:
Expand Down
8 changes: 6 additions & 2 deletions fatx/FATX.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ def __init__(self, file: str, sector_size: int = 512):
self.root = RootObject(DirectoryEntryList(cluster, 1))

@classmethod
def new(cls, size: int, file: str, sector_size: int = 512):
def new(cls, size: int, file: str, sector_size: int = 512, sb: bytes = None):
self = cls.__new__(cls)
self.f = open(file, "w+b")

self.sb = SuperBlock.new(sector_size)
if sb:
self.sb = SuperBlock(sb, sector_size)
print("Yupp found old superblock")
else:
self.sb = SuperBlock.new(sector_size)
self.fat_size = self._calc_fat_size(size, self.sb.cluster_size)
self.fat = FAT.new(self.fat_size)
root_dl = DirectoryEntryList(b"\xFF" * 64, 1)
Expand Down
22 changes: 15 additions & 7 deletions fatx/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ def __init__(self, sb, sector_size):
raise BaseException(
"SuperBlock is not " + str(self.SUPERBLOCK_SIZE) + " bytes long"
)
self.name, self.volume, self.cluster_num, self.fatcopies = struct.unpack(
"<4sIIh4082x", sb
)

(
self.name,
self.volume,
self.cluster_num,
self.fatcopies,
self.padding,
) = struct.unpack("<4sIIh4082s", sb)
try:
self.name = self.name.decode("ascii")
except UnicodeDecodeError:
Expand All @@ -45,6 +50,7 @@ def new(cls, sector_size):
self.cluster_num = 32
self.cluster_size = self.cluster_num * self.SECTOR_SIZE
self.fatcopies = 1
self.padding = 4082*b'\xFF'
return self

def pack(self):
Expand All @@ -54,7 +60,7 @@ def pack(self):
self.volume,
self.cluster_num,
self.fatcopies,
4082 * b"\xFF",
self.padding,
)

def __str__(self):
Expand Down Expand Up @@ -91,7 +97,7 @@ def __init__(self, raw_clustermap):
# slice up the fat table
while len(self.f) > 0:
entry = int.from_bytes(self.f[: self.size], "little")
self.f = self.f[self.size :]
self.f = self.f[self.size:]
self.clustermap.append(entry)

if self.size == 2:
Expand Down Expand Up @@ -209,8 +215,10 @@ def linkClusterChain(self, cc):
self.setEntryType(index, EntryType.FATX_CLUSTER_END)

@classmethod
def new(cls, size):
self = cls(size * b"\x00")
def new(cls, fat_size: int):
self = cls.__new__(cls)
self.size = 2 if fat_size < (0xFFF5 * 2) else 4
self.clustermap = [0x0000] * (fat_size // self.size)
if self.size == 2:
self.clustermap[0] = 0xFFF8
self.clustermap[1] = 0xFFFF
Expand Down
27 changes: 21 additions & 6 deletions pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@
from fatx import FATX


def check_for_superblock(path):
# check if a superblock is given, if so, prepare to re-import it
path = os.path.join(path, ".FATX-on-a-snake/superblock.bin")
if os.path.exists(path):
with open(path, 'rb') as f:
sb = f.read()
return sb
else:
return None


def walkfs(fs):
with os.scandir(".") as it:
for entry in it:
if entry.is_file():
with open(entry.name, "rb") as f:
fs.import_file(entry.name, f.read())
else:
fs.create_dir(entry.name)
os.chdir(entry.name)
walkfs(fs.get(entry.name))
os.chdir("..")
if entry.name != ".FATX-on-a-snake":
fs.create_dir(entry.name)
os.chdir(entry.name)
walkfs(fs.get(entry.name))
os.chdir("..")


if __name__ == "__main__":
Expand Down Expand Up @@ -48,12 +60,15 @@ def walkfs(fs):
size = args.size
src = args.src[0]
file = args.image[0]
sb = None
FATX.READ_ONLY = False

if not os.path.isdir(src):
sys.exit("Fatal: src-dir is not a valid directory")
if os.path.exists(file):
sys.exit("Fatal: target file already exists")

FATX.READ_ONLY = False
fs = FATX.Filesystem.new(size, file, args.sector_size)
sb = check_for_superblock(src)
fs = FATX.Filesystem.new(size, file, args.sector_size, sb)
os.chdir(src)
walkfs(fs.root)
7 changes: 7 additions & 0 deletions unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ def walkfs(obj: FatxObject):
root = fs.root
os.chdir(dest)
print("Unpacked {0} files.".format(walkfs(root)))
os.mkdir(".FATX-on-a-snake")
os.chdir(".FATX-on-a-snake")
f = open("superblock.bin", "wb")
f.write(fs.sb.pack())
f.close()
print("Saved superblock as {0}/{1}/{2}".format(
dest, ".FATX-on-a-snake", "superblock.bin"))