Skip to content

Commit 5d9da02

Browse files
committed
Added comments and nicer formatting of Lua scripts in the 'util' directory.
1 parent 532d6c2 commit 5d9da02

File tree

2 files changed

+391
-59
lines changed

2 files changed

+391
-59
lines changed

util/FATSTAT.LUA

Lines changed: 224 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,228 @@
11
#!/usr/bin/env lua
22

3-
H=[[This script shows what blocks are allocated to what files on a FAT12/16 formatted floppy disk image (.IMA).
3+
--[[ Implicit global variables:
4+
Z = Whether to zero out free clusters or not
5+
]]
6+
7+
-- H = Help string
8+
H = [[This script shows what blocks are allocated to what files on a FAT12/16 formatted floppy disk image (.IMA).
49
The -z option will shred unallocated clusters. Backup any file before using the -z option on it.]]
5-
if #arg<1 then print(arg[-1].." "..arg[0].." [-z] [IMA...]"..'\n\n'..H)os.exit(1)end
6-
function R16(s,o)local a,b=s:byte(o,o+1)return a+b*256 end
7-
function R32(s,o)local a,b,c,d=s:byte(o,o+3)return a+b*256+c*65536+d*16777216 end
8-
function F12(f,o,s)f:seek("set",o)local fd,fat,mc=f:read(s),{},math.floor(s*8/12)
9-
function E(i)local c=math.floor(i*1.5)local a,b=fd:byte(c+1)or 0,fd:byte(c+2)or 0 if i%2==0 then return a+(b&0x0F)*256 else return (a>>4)+b*16 end end
10-
for i=2,mc-1 do fat[i]=E(i)end return fat end
11-
function F16(f,o,s)
12-
f:seek("set",o)local fd,fat=f:read(s),{}
13-
for i=2,#fd-1,2 do local lo,hi=fd:byte(i),fd:byte(i+1)fat[((i-2)//2)+2]=lo+hi*256 end return fat end
14-
function FD(ts,rs,nf,sf,rd,sc)
15-
local c,r=math.floor((ts-rs-(nf*sf)-rd)/sc)r=c<4085 and 12 or c<65525 and 16 or 32 return r,c end
16-
function CC(fat,sc)
17-
local h,c={},sc
18-
while c>=2 and c<0xFF8 do table.insert(h,c)c=fat[c] end return h end
10+
11+
if #arg < 1 then -- Print help and exit when there's no arguments
12+
print(arg[-1] .. " " .. arg[0] .. " [-z] [IMA...]" .. '\n\n' .. H)
13+
os.exit(1)
14+
end
15+
16+
---Read 16 bits from an offset of a string
17+
---@param s string The string to read from
18+
---@param o number The offset in the string to read at
19+
---@return number The 16-bit value at the offset of the string
20+
function R16(s, o)
21+
local a, b = s:byte(o, o + 1)
22+
return a + b * 256
23+
end
24+
25+
---Read 32 bits from an offset of a string
26+
---@param s string The string to read from
27+
---@param o number The offset in the string to read at
28+
---@return number The 32-bit value at the offset of the string
29+
function R32(s, o)
30+
local a, b, c, d = s:byte(o, o + 3)
31+
return a + b * 256 + c * 65536 + d * 16777216
32+
end
33+
34+
---Read the allocation table of a FAT-12 partition
35+
---@param f file The file the partition is on
36+
---@param o number Offset relative to the file where the FAT partition starts
37+
---@param s number Size of the FAT partition
38+
---@return table A list containing each clusters allocation state
39+
function F12(f, o, s)
40+
f:seek("set", o)
41+
local fd, fat, mc = f:read(s), {}, math.floor(s * 8 / 12)
42+
43+
---Extract the FAT-12 cluster value
44+
---@param i number Number of the cluster to read
45+
---@return number The 12-bit cluster number
46+
function E(i)
47+
local c = math.floor(i * 1.5)
48+
local a, b = fd:byte(c + 1) or 0, fd:byte(c + 2) or 0
49+
if i % 2 == 0 then
50+
return a + (b & 0x0F) * 256
51+
else
52+
return (a >> 4) + b * 16
53+
end
54+
end
55+
56+
for i = 2, mc - 1 do
57+
fat[i] = E(i)
58+
end
59+
60+
return fat
61+
end
62+
63+
---Read the allocation table of a FAT-16 partition
64+
---@param f file The file the partition is on
65+
---@param o number Offset relative to the file where the FAT partition starts
66+
---@param s number Size of the FAT partition
67+
---@return table A list containing each clusters allocation state
68+
function F16(f, o, s)
69+
f:seek("set", o)
70+
local fd, fat = f:read(s), {}
71+
for i = 2, #fd - 1, 2 do
72+
local lo, hi = fd:byte(i), fd:byte(i + 1)
73+
fat[((i - 2) // 2) + 2] = lo + hi * 256
74+
end
75+
return fat
76+
end
77+
78+
---FAT Determine: Determine if the FAT type (12, 16 or 32) and total cluster count
79+
---@param ts number Total number of sectors on the disk
80+
---@param rs number Reserved sectors before the FAT header
81+
---@param nf number Number of FATs
82+
---@param sf number Sectors per FAT
83+
---@param rd number Root directory sectors
84+
---@param sc number Sectors per cluster
85+
---@return number, number The fat type as a number (12, 16, 32) and the cluster count
86+
function FD(ts, rs, nf, sf, rd, sc)
87+
local c, r = math.floor((ts - rs - (nf * sf) - rd) / sc)
88+
r = c < 4085 and 12 or c < 65525 and 16 or 32
89+
return r, c
90+
end
91+
92+
---Cluster Chain: Parse all clusters that are allocated to a single file
93+
---@param fat table The FAT table
94+
---@param sc number The starting cluster of the file
95+
---@return table List of all applicable clusters
96+
function CC(fat, sc)
97+
local h, c = {}, sc
98+
while c >= 2 and c < 0xFF8 do
99+
table.insert(h, c)
100+
c = fat[c]
101+
end
102+
return h
103+
end
104+
105+
---Format Ranges: Sort and group cluster ranges together for easy reading by humans
106+
---@param c table A list of clusters representing a file allocation
107+
---@return table A list strings representing the same clusters in a sorted and ranged order
19108
function FR(c)
20-
table.sort(c)local ranges,i={},1
21-
while i<=#c do
22-
local s,j,f=c[i],i while j+1<=#c and c[j+1]==c[j]+1 do j=j+1 end f=c[j]
23-
if s==f then table.insert(ranges,string.format("<%d>",s))
24-
else table.insert(ranges,string.format("<%d-%d>",s,f))end
25-
i=j+1 end return table.concat(ranges," ")end
26-
function DE(f,o,e,fat)
27-
f:seek("set",o)
28-
for _=1,e do
29-
local en,fb=f:read(32)if not en then break end fb=en:byte(1)if fb==0 then break end
30-
if fb~=229 and(en:byte(12)&8)==0 then
31-
local n,x=en:sub(1,8):gsub("+$",""),en:sub(9,11):gsub("+$","")
32-
io.write(string.format("\t%8s %3s %s\n",n,x,FR(CC(fat,R16(en,27)))))end end end
33-
function FC(fa,f,st,s,uc)
34-
local fr,z={},string.rep("\0",s)io.write("\n\tFree Space ")
35-
for i=2,uc+1 do if fa[i]==0 then table.insert(fr,i)if Z then f:seek("set",st+(i-2)*s)f:write(z)end end end print(FR(fr))end
36-
F={}for _,v in ipairs(arg)do if v=="-z" then Z=1 else table.insert(F,v)end end for _,fn in ipairs(F)do
37-
local f=io.open(fn,(Z and"r+b"or"rb"))if f then
38-
print(fn..":")
39-
local b,o=f:read(512)o=b:sub(1,1):byte()if o==235 or o==233 then
40-
local bs,sc,rs,nf,re,ts,sf=R16(b,12),b:byte(14),R16(b,15),b:byte(17),R16(b,18),R16(b,20),R16(b,23)if ts==0 then ts=R32(b,32)end
41-
local rd,fo,fs,ro,ft,uc=math.ceil(re*32/bs),rs*bs,sf*bs,(rs+nf*sf)*bs ft,uc=FD(ts,rs,nf,sf,rd,sc)
42-
print(string.format("\t FAT type: FAT%s\n\t Bytes/sector: %d\n\tSectors/cluster: %d\n\n",ft,bs,sc))
43-
local fat=ft==12 and F12(f,fo,fs)or ft==16 and F16(f,fo,fs)or error("Non-FAT type")
44-
DE(f,ro,re,fat)FC(fat,f,(rs+(nf*sf)+rd)*bs,sc*bs,uc)end f:close()print()end end
109+
table.sort(c)
110+
local ranges, i = {}, 1
111+
while i <= #c do
112+
local s, j, f = c[i], i
113+
while j + 1 <= #c and c[j + 1] == c[j] + 1 do
114+
j = j + 1
115+
end
116+
f = c[j]
117+
if s == f then
118+
table.insert(ranges, string.format("<%d>", s))
119+
else
120+
table.insert(ranges, string.format("<%d-%d>", s, f))
121+
end
122+
i = j + 1
123+
end
124+
return table.concat(ranges, " ")
125+
end
126+
127+
---Display Entries: print to the screen what entries belong to what files
128+
---@param f file The file the fat partition is on
129+
---@param o number The offset the directory entries begin at
130+
---@param e number The maximum entries to read
131+
---@param fat table The file allocation table
132+
function DE(f, o, e, fat)
133+
f:seek("set", o)
134+
for _ = 1, e do
135+
local en, fb = f:read(32)
136+
if not en then
137+
break
138+
end
139+
fb = en:byte(1)
140+
if fb == 0 then
141+
break
142+
end
143+
if fb ~= 229 and (en:byte(12) & 8) == 0 then
144+
local n, x = en:sub(1, 8):gsub("+$", ""), en:sub(9, 11):gsub("+$", "")
145+
io.write(string.format("\t%8s %3s %s\n", n, x, FR(CC(fat, R16(en, 27)))))
146+
end
147+
end
148+
end
149+
150+
---Free Clusters: Find and optionally zero out all free clusters
151+
---@param fa table File allocation table
152+
---@param f file File the allocation table is on
153+
---@param st number Starting area of the data area
154+
---@param s number The size of a cluster in bytes
155+
---@param uc number The number of usable clusters
156+
function FC(fa, f, st, s, uc)
157+
local fr, z = {}, string.rep("\0", s)
158+
io.write("\n\tFree Space ")
159+
for i = 2, uc + 1 do
160+
if fa[i] == 0 then
161+
table.insert(fr, i)
162+
if Z then
163+
f:seek("set", st + (i - 2) * s)
164+
f:write(z)
165+
end
166+
end
167+
end
168+
print(FR(fr))
169+
end
170+
171+
F = {} -- List of files to parse
172+
173+
for _, v in ipairs(arg) do -- Parse all arguments
174+
if v == "-z" then -- '-z' found, enable zeroing out of unallocated clusters
175+
Z = 1
176+
else
177+
table.insert(F, v)
178+
end
179+
end
180+
181+
for _, fn in ipairs(F) do -- Iterate over each file
182+
local f = io.open(fn, (Z and "r+b" or "rb")) -- Read only unless argument '-z' was found
183+
if f then
184+
print(fn .. ":")
185+
186+
-- b = The first 512 bytes of the file
187+
-- o = The first byte of the file
188+
local b, o = f:read(512)
189+
o = b:sub(1, 1):byte()
190+
if o == 235 or o == 233 then -- The boot sector has a valid jump instruction to a FAT boot sector
191+
192+
-- bs = Bytes per sector
193+
-- sc = Sector per cluster
194+
-- rs = Reserved sectors
195+
-- nf = Number of FATs
196+
-- re = Root directory entries
197+
-- ts = Total sectors
198+
-- sf = Sectors per FAT
199+
local bs, sc, rs, nf, re, ts, sf = R16(b, 12), b:byte(14), R16(b, 15), b:byte(17), R16(b, 18), R16(b, 20), R16(b, 23)
200+
201+
if ts == 0 then -- Use the larger 32-bit number of sectors
202+
ts = R32(b, 32)
203+
end
204+
205+
-- rd = Root directory sectors
206+
-- fo = FAT offset
207+
-- fs = FAT size in bytes
208+
-- ro = Root directory offset
209+
local rd, fo, fs, ro, ft, uc = math.ceil(re * 32 / bs), rs * bs, sf * bs, (rs + nf * sf) * bs
210+
211+
-- ft = FAT type
212+
-- uc = Usable cluster count
213+
ft, uc = FD(ts, rs, nf, sf, rd, sc)
214+
215+
-- Print some basic information about the FAT table
216+
print(string.format("\t FAT type: FAT%s\n\t Bytes/sector: %d\n\tSectors/cluster: %d\n\n", ft, bs, sc))
217+
218+
-- Parse the FAT table
219+
local fat = ft == 12 and F12(f, fo, fs) or ft == 16 and F16(f, fo, fs) or error("Non-FAT type")
220+
221+
-- Print state of each cluster
222+
DE(f, ro, re, fat)
223+
FC(fat, f, (rs + (nf * sf) + rd) * bs, sc * bs, uc)
224+
end
225+
f:close()
226+
print()
227+
end
228+
end

0 commit comments

Comments
 (0)