Skip to content

Commit 50070d9

Browse files
committed
Initial implementation of Lua script FATSTAT.LUA.
1 parent 2b2306b commit 50070d9

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

demo/xtra/FATSTAT.LUA

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env lua
2+
3+
H=[[This script shows what blocks are allocated to what files on a FAT12/16 formatted floppy disk image (.IMA).
4+
When the -z option is present any junk data from unallocated clusters will be zeroed out.
5+
This is sometimes referred to as "shredding" , doing so allows repeatable builds to be verified by checksum tools.
6+
Always backup the IMA before using this tool with the -z option on it.]]
7+
8+
if #arg < 1 then print(arg[-1] .. " " .. arg[0] .. " [-z] [IMA FILES...]" .. '\n\n' .. H) os.exit(1) end
9+
10+
local function args()
11+
F={} for _,v in ipairs(arg) do if v=="-z" then Z=true else table.insert(F, v) end end
12+
end
13+
14+
local function read_le16(str, offset)
15+
local a, b = str:byte(offset, offset + 1) return a + b * 256
16+
end
17+
18+
local function read_le32(str, offset)
19+
local a, b, c, d = str:byte(offset, offset + 3) return a + b * 256 + c * 65536 + d * 16777216
20+
end
21+
22+
local function read_fat12_table(f, fat_offset, fat_size)
23+
f:seek("set", fat_offset)
24+
local fat_data,fat,max_clusters=f:read(fat_size),{},math.floor(fat_size * 8 / 12)
25+
local function get_entry(i)
26+
local byte_index = math.floor(i * 1.5)
27+
local a,b=fat_data:byte(byte_index + 1)or 0,fat_data:byte(byte_index + 2)or 0
28+
if i % 2 == 0 then return a + (b & 0x0F) * 256 else return (a >> 4) + b * 16 end
29+
end
30+
for i = 2, max_clusters - 1 do fat[i] = get_entry(i) end return fat
31+
end
32+
33+
local function read_fat16_table(f, fat_offset, fat_size)
34+
f:seek("set", fat_offset) local fat_data,fat=f:read(fat_size),{}
35+
for i = 2, #fat_data - 1, 2 do
36+
local lo,hi=fat_data:byte(i),fat_data:byte(i + 1) fat[((i-2)//2)+2] = lo + hi * 256
37+
end return fat
38+
end
39+
40+
local function detect_fat_type(total_sectors, reserved_sectors, num_fats, sectors_per_fat, root_dir_sectors, sectors_per_cluster)
41+
local cluster_count = math.floor((total_sectors - reserved_sectors - (num_fats * sectors_per_fat) - root_dir_sectors) / sectors_per_cluster)
42+
if cluster_count < 4085 then return 12
43+
elseif cluster_count < 65525 then return 16
44+
else return 32 end
45+
end
46+
47+
local function cluster_chain(fat, start_cluster)
48+
local chain, current = {}, start_cluster
49+
while current >= 2 and current < 0xFF8 do
50+
table.insert(chain, current) current = fat[current]
51+
end return chain
52+
end
53+
54+
local function format_cluster_ranges(clusters)
55+
table.sort(clusters) local ranges, i = {}, 1
56+
while i <= #clusters do
57+
local start = clusters[i]
58+
local j = i
59+
while j + 1 <= #clusters and clusters[j + 1] == clusters[j] + 1 do
60+
j = j + 1
61+
end
62+
local finish = clusters[j]
63+
if start == finish then
64+
table.insert(ranges, string.format("<%d>", start))
65+
else
66+
table.insert(ranges, string.format("<%d-%d>", start, finish))
67+
end
68+
i = j + 1
69+
end
70+
return table.concat(ranges, " ")
71+
end
72+
73+
local function print_directory_entries(f, offset, entries, fat)
74+
f:seek("set", offset)
75+
for _ = 1, entries do
76+
local entry = f:read(32)
77+
if not entry then break end
78+
local first_byte = entry:byte(1)
79+
if first_byte == 0x00 then break end
80+
local attr = entry:byte(12)
81+
if first_byte ~= 0xE5 and (attr & 0x08) == 0 then
82+
local name,ext = entry:sub(1, 8):gsub(" +$", ""),entry:sub(9, 11):gsub(" +$", "")
83+
local fullname = name .. (ext ~= "" and "." .. ext or "")
84+
local start_cluster = read_le16(entry, 27)
85+
local clusters = cluster_chain(fat, start_cluster)
86+
io.write(string.format("\t%12s: %s\n", fullname, format_cluster_ranges(clusters)))
87+
end
88+
end
89+
end
90+
91+
local function print_free_clusters(fat, f, data_start, cluster_size)
92+
io.write("\n\t Free Space: ")
93+
local free, zero = {}, string.rep("\0",cluster_size)
94+
for i = 2, #fat do
95+
if fat[i] == 0 then
96+
table.insert(free, i)
97+
if Z then
98+
local offset = data_start + (i - 2) * cluster_size
99+
f:seek("set", offset)
100+
f:write(zero)
101+
end
102+
end
103+
end
104+
print(format_cluster_ranges(free))
105+
end
106+
107+
args() for _,file in ipairs(F) do
108+
local f = io.open(file, (Z and "r+b" or "rb"))
109+
if f then
110+
print(file .. ":")
111+
local boot = f:read(512)
112+
assert(boot:sub(1, 1):byte() == 0xEB or boot:sub(1, 1):byte() == 0xE9, "Invalid boot sector")
113+
114+
local bytes_per_sector = read_le16(boot, 12)
115+
local sectors_per_cluster = boot:byte(14)
116+
local reserved_sectors = read_le16(boot, 15)
117+
local num_fats = boot:byte(17)
118+
local root_entries = read_le16(boot, 18)
119+
local total_sectors = read_le16(boot, 19) or read_le32(boot, 32)
120+
local sectors_per_fat = read_le16(boot, 23)
121+
local root_dir_sectors = math.ceil(root_entries * 32 / bytes_per_sector)
122+
local fat_offset = reserved_sectors * bytes_per_sector
123+
local fat_size = sectors_per_fat * bytes_per_sector
124+
local root_dir_offset = (reserved_sectors + num_fats * sectors_per_fat) * bytes_per_sector
125+
126+
local fat_type = detect_fat_type(total_sectors, reserved_sectors,num_fats, sectors_per_fat, root_dir_sectors, bytes_per_sector)
127+
print(string.format("\t FAT type: FAT%s", fat_type))
128+
print(string.format("\t Bytes/sector: %d", bytes_per_sector))
129+
print(string.format("\tSectors/cluster: %d", sectors_per_cluster))print()
130+
131+
local fat = fat_type == 12 and read_fat12_table(f, fat_offset, fat_size) or fat_type == 16 and read_fat16_table(f, fat_offset, fat_size) or error("Not FAT12/16")
132+
133+
print_directory_entries(f, root_dir_offset, root_entries, fat)
134+
print_free_clusters(fat, f, (reserved_sectors + (num_fats * sectors_per_fat) + root_dir_sectors) * bytes_per_sector, sectors_per_cluster * bytes_per_sector)
135+
136+
f:close()print()
137+
end
138+
end

0 commit comments

Comments
 (0)