1from pyfatfs._exceptions import PyFATException
2from pyfatfs.PyFat import PyFat
3from structlog import get_logger
4
5from unblob.extractors.command import Command
6from unblob.file_utils import InvalidInputFormat
7
8from ...models import (
9 File,
10 Handler,
11 HandlerDoc,
12 HandlerType,
13 HexString,
14 Reference,
15 ValidChunk,
16)
17
18logger = get_logger()
19
20
21class PyFatNoClose(PyFat):
22 def close(self):
23 return
24
25
26def get_max_offset(fs: PyFat) -> int:
27 # see PyFat.get_cluster_chain for context
28 i = len(fs.fat)
29 while i > 0 and fs.fat[i - 1] == 0:
30 i -= 1
31 # i - 1 is the last cluster that is part of a filesystem object
32 return fs.get_data_cluster_address(i)
33
34
35class FATHandler(Handler):
36 NAME = "fat"
37
38 PATTERNS = [
39 HexString(
40 """
41 // An initial x86 short jump instruction
42 // OEMName (8 bytes)
43 // BytesPerSec (2 bytes)
44 // SecPerClus (1 byte) "Must be one of 1, 2, 4, 8, 16, 32, 64, 128."
45 // 495 (0x1EF) bytes of whatever
46 // 55 AA is the "signature". "This will be the end of the sector only in case the
47 // sector size is 512."
48 ( EB | E9 ) [13] ( 01 | 02 | 04 | 08 | 10 | 20 | 40 | 80 ) [495] 55 AA
49 """
50 )
51 ]
52
53 EXTRACTOR = Command("7z", "x", "-y", "{inpath}", "-o{outdir}")
54
55 DOC = HandlerDoc(
56 name="FAT",
57 description="FAT (File Allocation Table) is a file system format used for organizing and managing files on storage devices, supporting FAT12, FAT16, and FAT32 variants. It uses a table to map file clusters, enabling efficient file storage and retrieval.",
58 handler_type=HandlerType.FILESYSTEM,
59 vendor=None,
60 references=[
61 Reference(
62 title="FAT Wikipedia",
63 url="https://en.wikipedia.org/wiki/File_Allocation_Table",
64 ),
65 ],
66 limitations=[],
67 )
68
69 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
70 pyfat_fs = PyFatNoClose(offset=start_offset)
71 try:
72 pyfat_fs.set_fp(file) # type: ignore
73 except PyFATException as e:
74 raise InvalidInputFormat from e
75
76 # we have exactly one of these set to non-0, depending on FAT version
77 total_sectors = max(
78 pyfat_fs.bpb_header["BPB_TotSec16"],
79 pyfat_fs.bpb_header["BPB_TotSec32"],
80 )
81
82 size = total_sectors * pyfat_fs.bpb_header["BPB_BytsPerSec"]
83 file_size = file.size()
84 if start_offset + size > file_size:
85 size = get_max_offset(pyfat_fs)
86
87 return ValidChunk(
88 start_offset=start_offset,
89 end_offset=start_offset + size,
90 )