|
#!/usr/bin/env python3 |
|
import argparse |
|
import sys |
|
from pathlib import Path |
|
|
|
def read_le16(data, offset): |
|
return int.from_bytes(data[offset:offset + 2], "little") |
|
|
|
def write_le16(data, offset, value): |
|
data[offset:offset + 2] = value.to_bytes(2, "little") |
|
|
|
def fat12_get(data, cluster): |
|
offset = (cluster * 3) // 2 |
|
if cluster & 1: |
|
return ((data[offset] >> 4) | (data[offset + 1] << 4)) & 0x0FFF |
|
return (data[offset] | ((data[offset + 1] & 0x0F) << 8)) & 0x0FFF |
|
|
|
def fat12_set(data, cluster, value): |
|
value &= 0x0FFF |
|
offset = (cluster * 3) // 2 |
|
if cluster & 1: |
|
data[offset] = (data[offset] & 0x0F) | ((value & 0x0F) << 4) |
|
data[offset + 1] = (value >> 4) & 0xFF |
|
else: |
|
data[offset] = value & 0xFF |
|
data[offset + 1] = (data[offset + 1] & 0xF0) | ((value >> 8) & 0x0F) |
|
|
|
def parse_args(): |
|
parser = argparse.ArgumentParser( |
|
description="Renumber FAT12 cluster numbers by a fixed delta in directory entries and FAT chains.", |
|
) |
|
parser.add_argument("input", help="Input FAT12 image") |
|
parser.add_argument("output", help="Output FAT12 image") |
|
parser.add_argument( |
|
"-d", |
|
"--delta", |
|
type=int, |
|
required=True, |
|
help="Signed cluster delta to apply to directory start clusters and FAT entry numbers", |
|
) |
|
return parser.parse_args() |
|
|
|
def main(): |
|
args = parse_args() |
|
src = Path(args.input) |
|
dst = Path(args.output) |
|
image = bytearray(src.read_bytes()) |
|
|
|
bytes_per_sector = read_le16(image, 11) |
|
sectors_per_cluster = image[13] |
|
reserved_sectors = read_le16(image, 14) |
|
fat_count = image[16] |
|
root_entries = read_le16(image, 17) |
|
total_sectors = read_le16(image, 19) |
|
sectors_per_fat = read_le16(image, 22) |
|
|
|
if bytes_per_sector == 0 or sectors_per_cluster == 0: |
|
print("Error: Invalid BPB in input image.", file=sys.stderr) |
|
return 1 |
|
|
|
root_dir_sectors = (root_entries * 32 + bytes_per_sector - 1) // bytes_per_sector |
|
data_start_sector = reserved_sectors + fat_count * sectors_per_fat + root_dir_sectors |
|
cluster_count = (total_sectors - data_start_sector) // sectors_per_cluster |
|
max_cluster = cluster_count + 1 |
|
|
|
fat_size_bytes = sectors_per_fat * bytes_per_sector |
|
fat_offsets = [] |
|
fat_start = reserved_sectors * bytes_per_sector |
|
for fat_index in range(fat_count): |
|
fat_offsets.append(fat_start + fat_index * fat_size_bytes) |
|
|
|
root_offset = (reserved_sectors + fat_count * sectors_per_fat) * bytes_per_sector |
|
root_size = root_entries * 32 |
|
root = image[root_offset:root_offset + root_size] |
|
|
|
updated_entries = 0 |
|
for offset in range(0, len(root), 32): |
|
entry = root[offset:offset + 32] |
|
if entry[0] in (0x00, 0xE5): |
|
continue |
|
attr = entry[11] |
|
if attr == 0x0F or (attr & 0x08): |
|
continue |
|
start_cluster = read_le16(entry, 26) |
|
if start_cluster == 0: |
|
continue |
|
new_cluster = start_cluster + args.delta |
|
if not (2 <= new_cluster <= max_cluster): |
|
print(f"Error: Directory entry cluster {start_cluster} would become invalid ({new_cluster}).", file=sys.stderr) |
|
return 1 |
|
write_le16(root, offset + 26, new_cluster) |
|
updated_entries += 1 |
|
|
|
image[root_offset:root_offset + root_size] = root |
|
|
|
original_fat = bytearray(image[fat_offsets[0]:fat_offsets[0] + fat_size_bytes]) |
|
primary_fat = bytearray(original_fat) |
|
updated_fat_entries = 0 |
|
|
|
for cluster in range(2, max_cluster + 1): |
|
fat12_set(primary_fat, cluster, 0) |
|
|
|
for cluster in range(2, max_cluster + 1): |
|
value = fat12_get(original_fat, cluster) |
|
if value == 0: |
|
continue |
|
|
|
new_cluster = cluster + args.delta |
|
if not (2 <= new_cluster <= max_cluster): |
|
print(f"Error: FAT entry index {cluster} would become invalid ({new_cluster}).", file=sys.stderr) |
|
return 1 |
|
|
|
new_value = value |
|
if 2 <= value < 0x0FF0: |
|
new_value = value + args.delta |
|
if not (2 <= new_value <= max_cluster): |
|
print(f"Error: FAT entry {cluster} target {value} would become invalid ({new_value}).", file=sys.stderr) |
|
return 1 |
|
|
|
fat12_set(primary_fat, new_cluster, new_value) |
|
updated_fat_entries += 1 |
|
|
|
for fat_offset in fat_offsets: |
|
image[fat_offset:fat_offset + fat_size_bytes] = primary_fat |
|
|
|
dst.write_bytes(image) |
|
print(f"Wrote {dst}") |
|
print(f" updated directory entries: {updated_entries}") |
|
print(f" updated FAT entries: {updated_fat_entries}") |
|
print(f" cluster delta: {args.delta}") |
|
return 0 |
|
|
|
if __name__ == "__main__": |
|
raise SystemExit(main()) |