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