|
1 | 1 | #!/usr/bin/env lua |
2 | 2 |
|
3 | 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 |
| 4 | +The -z option will zero out unallocated clusters. Always backup the IMA before using this tool with the -z option on it.]] |
| 5 | +if #arg<1 then print(arg[-1].." "..arg[0].." [-z] [FILE...]"..'\n\n'..H)os.exit(1)end |
| 6 | +function A()F={} for _,v in ipairs(arg)do if v=="-z" then Z=true else table.insert(F,v)end end end |
| 7 | +function R16(s,o)local a,b=s:byte(o,o+1)return a+b*256 end |
| 8 | +function R32(s,o)local a,b,c,d=s:byte(o,o+3)return a+b*256+c*65536+d*16777216 end |
| 9 | +function F12(f,o,s)f:seek("set",o)local fd,fat,mc=f:read(s),{},math.floor(s*8/12) |
| 10 | + 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 |
| 11 | + for i=2,mc-1 do fat[i]=E(i)end return fat end |
| 12 | +function F16(f,o,s) |
| 13 | + f:seek("set",o)local fd,fat=f:read(s),{} |
| 14 | + 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 |
| 15 | +function FD(ts,rs,nf,sf,rd,sc) |
| 16 | + local cc=math.floor((ts-rs-(nf*sf)-rd)/sc) |
| 17 | + if cc<4085 then return 12 elseif cc<65525 then return 16 else return 32 end end |
| 18 | +function CC(fat,sc) |
| 19 | + local chain,current={},sc |
| 20 | + while current>=2 and current<0xFF8 do table.insert(chain,current)current=fat[current] end return chain end |
| 21 | +function FR(c) |
| 22 | + table.sort(c)local ranges,i={},1 |
| 23 | + while i <=#c do |
| 24 | + 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] |
| 25 | + if s==f then table.insert(ranges,string.format("<%d>",s)) |
| 26 | + else table.insert(ranges,string.format("<%d-%d>",s,f))end |
| 27 | + i=j+1 end return table.concat(ranges," ")end |
| 28 | +function DE(f,o,e,fat) |
| 29 | + f:seek("set",o) |
| 30 | + for _=1,e do |
| 31 | + local en,fb=f:read(32)if not en then break end fb=en:byte(1)if fb==0 then break end |
| 32 | + if fb~=229 and(en:byte(12)&8)==0 then |
| 33 | + local n,x=en:sub(1,8):gsub("+$",""),en:sub(9,11):gsub("+$","")n=n..(x~=""and"."..x or"") |
| 34 | + io.write(string.format("\t%12s: %s\n",n,FR(CC(fat,R16(en,27)))))end end end |
| 35 | +function FC(fat,f,st,s) |
| 36 | + io.write("\n\t Free Space: ") |
| 37 | + local free,zero={},string.rep("\0",s) |
| 38 | + for i=2,#fat do if fat[i]==0 then table.insert(free,i)if Z then local o=st+(i-2)*s f:seek("set",o)f:write(zero)end end end print(FR(free))end |
| 39 | +A()for _,file in ipairs(F)do |
| 40 | + local f=io.open(file,(Z and"r+b"or"rb")) |
| 41 | + if f then |
| 42 | + print(file..":") |
| 43 | + local boot=f:read(512)assert(boot:sub(1,1):byte()==0xEB or boot:sub(1,1):byte()==0xE9,"Invalid boot sector") |
| 44 | + local bs,sc,rs,nf,re,ts,sf=R16(boot,12),boot:byte(14),R16(boot,15),boot:byte(17),R16(boot,18),R16(boot,19)or R32(boot,32),R16(boot,23) |
| 45 | + local rd,fo,fs,ro=math.ceil(re*32/bs),rs*bs,sf*bs,(rs+nf*sf)*bs |
| 46 | + local ft=FD(ts,rs,nf,sf,rd,bs) |
| 47 | + print(string.format("\t FAT type: FAT%s\n\t Bytes/sector: %d\n\tSectors/cluster: %d\n\n",ft,bs,sc)) |
| 48 | + local fat=ft==12 and F12(f,fo,fs)or ft==16 and F16(f,fo,fs)or error("Non-FAT type") |
| 49 | + DE(f,ro,re,fat)FC(fat,f,(rs+(nf*sf)+rd)*bs,sc*bs)f:close()print()end end |
0 commit comments