python-adfs
changeset 85:bab9a6eaf74c
Started to refactor the disc map and catalogue handling into separate classes.
| author | David Boddie <david@boddie.org.uk> |
|---|---|
| date | Fri Oct 14 00:01:08 2011 +0200 |
| parents | 9f1ea30aa03c |
| children | 71640463533b |
| files | ADFSlib.py |
| diffstat | 1 files changed, 614 insertions(+), 614 deletions(-) [+] |
line diff
1.1 --- a/ADFSlib.py Fri Oct 14 00:00:13 2011 +0200 1.2 +++ b/ADFSlib.py Fri Oct 14 00:01:08 2011 +0200 1.3 @@ -36,6 +36,85 @@ 1.4 between_epochs = ((365 * 70) + 17) * 24 * 360000L 1.5 1.6 1.7 +class Utilities: 1.8 + 1.9 + # Little endian reading 1.10 + 1.11 + def _read_signed_word(self, s): 1.12 + 1.13 + return struct.unpack("<i", s)[0] 1.14 + 1.15 + def _read_unsigned_word(self, s): 1.16 + 1.17 + return struct.unpack("<I", s)[0] 1.18 + 1.19 + def _read_signed_byte(self, s): 1.20 + 1.21 + return struct.unpack("<b", s)[0] 1.22 + 1.23 + def _read_unsigned_byte(self, s): 1.24 + 1.25 + return struct.unpack("<B", s)[0] 1.26 + 1.27 + def _read_unsigned_half_word(self, s): 1.28 + 1.29 + return struct.unpack("<H", s)[0] 1.30 + 1.31 + def _read_signed_half_word(self, s): 1.32 + 1.33 + return struct.unpack("<h", s)[0] 1.34 + 1.35 + def _str2num(self, size, s): 1.36 + 1.37 + i = 0 1.38 + n = 0 1.39 + while i < size: 1.40 + 1.41 + n = n | (ord(s[i]) << (i*8)) 1.42 + i = i + 1 1.43 + 1.44 + return n 1.45 + 1.46 + def _binary(self, size, n): 1.47 + 1.48 + new = "" 1.49 + while (n != 0) & (size > 0): 1.50 + 1.51 + if (n & 1)==1: 1.52 + new = "1" + new 1.53 + else: 1.54 + new = "0" + new 1.55 + 1.56 + n = n >> 1 1.57 + size = size - 1 1.58 + 1.59 + if size > 0: 1.60 + new = ("0"*size) + new 1.61 + 1.62 + return new 1.63 + 1.64 + def _safe(self, s, with_space = 0): 1.65 + 1.66 + new = "" 1.67 + if with_space == 1: 1.68 + lower = 31 1.69 + else: 1.70 + lower = 32 1.71 + 1.72 + for i in s: 1.73 + 1.74 + if ord(i) <= lower: 1.75 + break 1.76 + 1.77 + if ord(i) >= 128: 1.78 + c = ord(i)^128 1.79 + if c > 32: 1.80 + new = new + chr(c) 1.81 + else: 1.82 + new = new + i 1.83 + 1.84 + return new 1.85 + 1.86 class ADFS_exception(Exception): 1.87 1.88 pass 1.89 @@ -113,7 +192,531 @@ 1.90 return () 1.91 1.92 1.93 -class ADFSdisc: 1.94 +class ADFSmap(Utilities): 1.95 + 1.96 + def __getitem__(self, index): 1.97 + 1.98 + return self.disc_map[index] 1.99 + 1.100 + def _read_freespace(self): 1.101 + 1.102 + # Currently unused 1.103 + 1.104 + base = 0 1.105 + 1.106 + free = [] 1.107 + p = 0 1.108 + while self.sectors[base+p] != 0: 1.109 + 1.110 + free.append(self._str2num(3, self.sectors[base+p:base+p+3])) 1.111 + 1.112 + name = self.sectors[self.sector_size-9:self.sector_size-4] 1.113 + 1.114 + disc_size = self._str2num( 1.115 + 3, self.sectors[self.sector_size-4:self.sector_size-1] 1.116 + ) 1.117 + 1.118 + checksum0 = self._read_unsigned_byte(self.sectors[self.sector_size-1]) 1.119 + 1.120 + base = self.sector_size 1.121 + 1.122 + p = 0 1.123 + while self.sectors[base+p] != 0: 1.124 + 1.125 + free.append(self._str2num(3, self.sectors[base+p:base+p+3])) 1.126 + 1.127 + name = name + \ 1.128 + self.sectors[base+self.sector_size-10:base+self.sector_size-5] 1.129 + 1.130 + disc_id = self._str2num( 1.131 + 2, self.sectors[base+self.sector_size-5:base+self.sector_size-3] 1.132 + ) 1.133 + 1.134 + boot = self._read_unsigned_byte(self.sectors[base+self.sector_size-3]) 1.135 + 1.136 + checksum1 = self._read_unsigned_byte(self.sectors[base+self.sector_size-1]) 1.137 + 1.138 +class ADFSnewMap(ADFSmap): 1.139 + 1.140 + dir_markers = ('Hugo', 'Nick') 1.141 + root_dir_address = 0x800 1.142 + 1.143 + def __init__(self, header, begin, end, sectors, sector_size): 1.144 + 1.145 + self.header = header 1.146 + self.begin = begin 1.147 + self.end = end 1.148 + self.sectors = sectors 1.149 + self.sector_size = sector_size 1.150 + 1.151 + self.free_space = self._read_free_space() 1.152 + self.disc_map = self._read_disc_map() 1.153 + 1.154 + def _read_disc_map(self): 1.155 + 1.156 + disc_map = {} 1.157 + 1.158 + a = self.begin 1.159 + 1.160 + current_piece = None 1.161 + current_start = 0 1.162 + 1.163 + next_zone = self.header + self.sector_size 1.164 + 1.165 + # Copy the free space map. 1.166 + free_space = self.free_space[:] 1.167 + 1.168 + while a < self.end: 1.169 + 1.170 + # The next entry to be read will occur one byte after this one 1.171 + # unless one of the following checks override this behaviour. 1.172 + next = a + 1 1.173 + 1.174 + if (a % self.sector_size) < 4: 1.175 + 1.176 + # In a zone header. Not the first zone header as this 1.177 + # was already skipped when we started reading. 1.178 + next = a + 4 - (a % self.sector_size) 1.179 + 1.180 + # Set the next zone offset. 1.181 + next_zone = next_zone + self.sector_size 1.182 + 1.183 + # Reset the current piece and starting offset. 1.184 + current_piece = None 1.185 + current_start = 0 1.186 + 1.187 + elif free_space != [] and a >= free_space[0][0]: 1.188 + 1.189 + # In the next free space entry. Go to the entry following 1.190 + # it and discard this free space entry. 1.191 + next = free_space[0][1] 1.192 + 1.193 + free_space.pop(0) 1.194 + 1.195 + # Reset the current piece and starting offset. 1.196 + current_piece = None 1.197 + current_start = 0 1.198 + 1.199 + elif current_piece is None and (next_zone - a) >= 2: 1.200 + 1.201 + # If there is enough space left in this zone to allow 1.202 + # further fragments then read the next two bytes. 1.203 + value = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.204 + 1.205 + entry = value & 0x7fff 1.206 + 1.207 + # See ADFS/EAddrs.htm document for restriction on 1.208 + # the disc address and hence the file number. 1.209 + # i.e.the top bit of the file number cannot be set. 1.210 + 1.211 + if entry >= 1: 1.212 + 1.213 + # Defects (1), files or directories (greater than 1) 1.214 + next = a + 2 1.215 + 1.216 + # Define a new entry. 1.217 + #print "Begin:", hex(entry), hex(a) 1.218 + 1.219 + if not disc_map.has_key(entry): 1.220 + 1.221 + # Create a new map entry if none exists. 1.222 + disc_map[entry] = [] 1.223 + 1.224 + if (value & 0x8000) == 0: 1.225 + 1.226 + # Record the file number and start of this fragment. 1.227 + current_piece = entry 1.228 + current_start = a 1.229 + 1.230 + else: 1.231 + 1.232 + # For an immediately terminated fragment, add the 1.233 + # extents of the block to the list of pieces found 1.234 + # and implicitly finish reading this fragment 1.235 + # (current_piece remains None). 1.236 + 1.237 + start_addr = self.find_address_from_map( 1.238 + a, self.begin, entry 1.239 + ) 1.240 + end_addr = self.find_address_from_map( 1.241 + next, self.begin, entry 1.242 + ) 1.243 + 1.244 + if [start_addr, end_addr] not in disc_map[entry]: 1.245 + 1.246 + disc_map[entry].append( [start_addr, end_addr] ) 1.247 + 1.248 + else: 1.249 + 1.250 + # Search for a valid file number. 1.251 + # Should probably stop looking in this zone. 1.252 + next = a + 1 1.253 + 1.254 + elif current_piece is not None: 1.255 + 1.256 + # In a piece being read. 1.257 + 1.258 + value = ord(self.sectors[a]) 1.259 + 1.260 + if value == 0: 1.261 + 1.262 + # Still in the block. 1.263 + next = a + 1 1.264 + 1.265 + elif value == 0x80: 1.266 + 1.267 + # At the end of the block. 1.268 + next = a + 1 1.269 + 1.270 + # For relevant entries add the block to the list of 1.271 + # pieces found. 1.272 + start_addr = self.find_address_from_map( 1.273 + current_start, self.begin, current_piece 1.274 + ) 1.275 + end_addr = self.find_address_from_map( 1.276 + next, self.begin, current_piece 1.277 + ) 1.278 + 1.279 + if [start_addr, end_addr] not in disc_map[current_piece]: 1.280 + 1.281 + disc_map[current_piece].append( 1.282 + [ start_addr, end_addr] 1.283 + ) 1.284 + 1.285 + # Look for a new fragment. 1.286 + current_piece = None 1.287 + 1.288 + else: 1.289 + 1.290 + # The byte found was unexpected - backtrack to the 1.291 + # byte after the start of this block and try again. 1.292 + #print "Backtrack from %s to %s" % (hex(a), hex(current_start+1)) 1.293 + 1.294 + next = current_start + 1 1.295 + current_piece = None 1.296 + 1.297 + # Move to the next relevant byte. 1.298 + a = next 1.299 + 1.300 + return disc_map 1.301 + 1.302 + def _read_free_space(self): 1.303 + 1.304 + free_space = [] 1.305 + 1.306 + a = self.header 1.307 + 1.308 + while a < self.end: 1.309 + 1.310 + # The next zone starts a sector after this one. 1.311 + next_zone = a + self.sector_size 1.312 + 1.313 + a = a + 1 1.314 + 1.315 + # Start by reading the offset in bits from the start of the header 1.316 + # of the first item of free space in the map. 1.317 + offset = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.318 + 1.319 + # The top bit is apparently always set, so mask it off and convert 1.320 + # the result into bytes. * Not sure if this is the case for 1.321 + # entries in the map. * 1.322 + next = ((offset & 0x7fff) >> 3) 1.323 + 1.324 + if next == 0: 1.325 + 1.326 + # No more free space in this zone. Look at the free 1.327 + # space field in the next zone. 1.328 + a = next_zone 1.329 + continue 1.330 + 1.331 + # Update the offset to point to the free space in this zone. 1.332 + a = a + next 1.333 + 1.334 + while a < next_zone: 1.335 + 1.336 + # Read the offset to the next free fragment in this zone. 1.337 + offset = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.338 + 1.339 + # Convert this to a byte offset. 1.340 + next = ((offset & 0x7fff) >> 3) 1.341 + 1.342 + # Find the end of the free space. 1.343 + b = a + 1 1.344 + 1.345 + while b < next_zone: 1.346 + 1.347 + c = b + 1 1.348 + 1.349 + value = self._read_unsigned_byte(self.sectors[b]) 1.350 + 1.351 + if (value & 0x80) != 0: 1.352 + 1.353 + break 1.354 + 1.355 + b = c 1.356 + 1.357 + # Record the offset into the map of this item of free space 1.358 + # and the offset of the byte after it ends. 1.359 + free_space.append( (a, c) ) 1.360 + 1.361 + if next == 0: 1.362 + 1.363 + break 1.364 + 1.365 + # Move to the next free space entry. 1.366 + a = a + next 1.367 + 1.368 + # Whether we are at the end of the zone or not, move to the 1.369 + # beginning of the next zone. 1.370 + a = next_zone 1.371 + 1.372 + # Return the free space list. 1.373 + return free_space 1.374 + 1.375 + def read_catalogue(self, base): 1.376 + 1.377 + head = base 1.378 + p = 0 1.379 + 1.380 + dir_seq = self.sectors[head + p] 1.381 + dir_start = self.sectors[head+p+1:head+p+5] 1.382 + if dir_start not in self.dir_markers: 1.383 + 1.384 + if self.verify: 1.385 + 1.386 + self.verify_log.append( 1.387 + (WARNING, 'Not a directory: %s' % hex(head)) 1.388 + ) 1.389 + 1.390 + return '', [] 1.391 + 1.392 + p = p + 5 1.393 + 1.394 + files = [] 1.395 + 1.396 + while ord(self.sectors[head+p]) != 0: 1.397 + 1.398 + old_name = self.sectors[head+p:head+p+10] 1.399 + top_set = 0 1.400 + counter = 1 1.401 + for i in old_name: 1.402 + if (ord(i) & 128) != 0: 1.403 + top_set = counter 1.404 + counter = counter + 1 1.405 + 1.406 + name = self._safe(self.sectors[head+p:head+p+10]) 1.407 + 1.408 + load = self._read_unsigned_word(self.sectors[head+p+10:head+p+14]) 1.409 + exe = self._read_unsigned_word(self.sectors[head+p+14:head+p+18]) 1.410 + length = self._read_unsigned_word(self.sectors[head+p+18:head+p+22]) 1.411 + 1.412 + inddiscadd = self._read_new_address( 1.413 + self.sectors[head+p+22:head+p+25] 1.414 + ) 1.415 + newdiratts = self._read_unsigned_byte(self.sectors[head+p+25]) 1.416 + 1.417 + if inddiscadd == -1: 1.418 + 1.419 + if (newdiratts & 0x8) != 0: 1.420 + 1.421 + if self.verify: 1.422 + 1.423 + self.verify_log.append( 1.424 + (WARNING, "Couldn't find directory: %s" % name) 1.425 + ) 1.426 + self.verify_log.append( 1.427 + (WARNING, " at: %x" % (head+p+22)) 1.428 + ) 1.429 + self.verify_log.append( ( 1.430 + WARNING, " file details: %x" % \ 1.431 + self._str2num(3, self.sectors[head+p+22:head+p+25]) 1.432 + ) ) 1.433 + self.verify_log.append( 1.434 + (WARNING, " atts: %x" % newdiratts) 1.435 + ) 1.436 + 1.437 + elif length != 0: 1.438 + 1.439 + if self.verify: 1.440 + 1.441 + self.verify_log.append( 1.442 + (WARNING, "Couldn't find file: %s" % name) 1.443 + ) 1.444 + self.verify_log.append( 1.445 + (WARNING, " at: %x" % (head+p+22)) 1.446 + ) 1.447 + self.verify_log.append( ( 1.448 + WARNING, 1.449 + " file details: %x" % \ 1.450 + self._str2num(3, self.sectors[head+p+22:head+p+25]) 1.451 + ) ) 1.452 + self.verify_log.append( 1.453 + (WARNING, " atts: %x" % newdiratts) 1.454 + ) 1.455 + 1.456 + else: 1.457 + 1.458 + # Store a zero length file. This appears to be the 1.459 + # standard behaviour for storing empty files. 1.460 + files.append(ADFSfile(name, "", load, exe, length)) 1.461 + 1.462 + else: 1.463 + 1.464 + if (newdiratts & 0x8) != 0: 1.465 + 1.466 + # Remember that inddiscadd will be a sequence of 1.467 + # pairs of addresses. 1.468 + 1.469 + for start, end in inddiscadd: 1.470 + 1.471 + # Try to interpret the data at the referenced address 1.472 + # as a directory. 1.473 + 1.474 + lower_dir_name, lower_files = \ 1.475 + self.read_catalogue(start) 1.476 + 1.477 + # Store the directory name and file found therein. 1.478 + files.append(ADFSdirectory(name, lower_files)) 1.479 + 1.480 + else: 1.481 + 1.482 + # Remember that inddiscadd will be a sequence of 1.483 + # pairs of addresses. 1.484 + 1.485 + file = "" 1.486 + remaining = length 1.487 + 1.488 + for start, end in inddiscadd: 1.489 + 1.490 + amount = min(remaining, end - start) 1.491 + file = file + self.sectors[start : (start + amount)] 1.492 + remaining = remaining - amount 1.493 + 1.494 + files.append(ADFSfile(name, file, load, exe, length)) 1.495 + 1.496 + p = p + 26 1.497 + 1.498 + 1.499 + # Go to tail of directory structure (0x800 -- 0xc00) 1.500 + 1.501 + tail = head + self.sector_size 1.502 + 1.503 + dir_end = self.sectors[tail+self.sector_size-5:tail+self.sector_size-1] 1.504 + 1.505 + if dir_end not in self.dir_markers: 1.506 + 1.507 + if self.verify: 1.508 + 1.509 + self.verify_log.append( 1.510 + ( WARNING, 1.511 + 'Discrepancy in directory structure: [%x, %x]' % \ 1.512 + ( head, tail ) ) 1.513 + ) 1.514 + 1.515 + return '', files 1.516 + 1.517 + dir_name = self._safe( 1.518 + self.sectors[tail+self.sector_size-16:tail+self.sector_size-6] 1.519 + ) 1.520 + 1.521 + parent = \ 1.522 + self.sectors[tail+self.sector_size-38:tail+self.sector_size-35] 1.523 + 1.524 + dir_title = \ 1.525 + self.sectors[tail+self.sector_size-35:tail+self.sector_size-16] 1.526 + 1.527 + if head == self.root_dir_address: 1.528 + dir_name = '$' 1.529 + 1.530 + endseq = self.sectors[tail+self.sector_size-6] 1.531 + if endseq != dir_seq: 1.532 + 1.533 + if self.verify: 1.534 + 1.535 + self.verify_log.append( 1.536 + ( WARNING, 1.537 + 'Broken directory: %s at [%x, %x]' % \ 1.538 + (dir_title, head, tail) ) 1.539 + ) 1.540 + 1.541 + return dir_name, files 1.542 + 1.543 + return dir_name, files 1.544 + 1.545 + def _read_new_address(self, s): 1.546 + 1.547 + # From the three character string passed, determine the address on the 1.548 + # disc. 1.549 + value = self._str2num(3, s) 1.550 + 1.551 + # This is a SIN (System Internal Number) 1.552 + # The bottom 8 bits are the sector offset + 1 1.553 + offset = value & 0xff 1.554 + if offset != 0: 1.555 + address = (offset - 1) * self.sector_size 1.556 + else: 1.557 + address = 0 1.558 + 1.559 + # The top 16 bits are the file number 1.560 + file_no = value >> 8 1.561 + 1.562 + # The pieces of the object are returned as a list of pairs of 1.563 + # addresses. 1.564 + pieces = self._find_in_new_map(file_no) 1.565 + 1.566 + if pieces == []: 1.567 + 1.568 + return -1 1.569 + 1.570 + # Ensure that the first piece of data is read from the appropriate 1.571 + # point in the relevant sector. 1.572 + 1.573 + pieces[0][0] = pieces[0][0] + address 1.574 + 1.575 + return pieces 1.576 + 1.577 + def _find_in_new_map(self, file_no): 1.578 + 1.579 + try: 1.580 + 1.581 + return self.disc_map[file_no] 1.582 + 1.583 + except KeyError: 1.584 + 1.585 + return [] 1.586 + 1.587 + def find_address_from_map(self, addr, begin, entry): 1.588 + 1.589 + return ((addr - begin) * self.sector_size) 1.590 + 1.591 +class ADFSbigNewMap(ADFSnewMap): 1.592 + 1.593 + dir_markers = ('Nick',) 1.594 + root_dir_address = 0xc8800 1.595 + 1.596 + def find_address_from_map(self, addr, begin, entry): 1.597 + 1.598 + # I can't remember where the rationale for this calculation 1.599 + # came from or where the necessary information was obtained. 1.600 + # It probably came from one of the WSS files, such as 1.601 + # Formats.htm or Formats2.htm which imply that the F format 1.602 + # uses 512 byte sectors (see the 0x200 value below) and 1.603 + # indicate that F format uses 4 zones rather than 1. 1.604 + 1.605 + upper = (entry & 0x7f00) >> 8 1.606 + 1.607 + if upper > 1: 1.608 + upper = upper - 1 1.609 + if upper > 3: 1.610 + upper = 3 1.611 + 1.612 + return ((addr - begin) - (upper * 0xc8)) * 0x200 1.613 + 1.614 +class ADFSoldMap(ADFSmap): 1.615 + 1.616 + pass 1.617 + 1.618 +class ADFSdisc(Utilities): 1.619 1.620 """disc = ADFSdisc(file_handle, verify = 0) 1.621 1.622 @@ -244,7 +847,7 @@ 1.623 1.624 # Find the root directory name and all the files and directories 1.625 # contained within it. 1.626 - self.root_name, self.files = self._read_new_catalogue(2*self.sector_size) 1.627 + self.root_name, self.files = self.disc_map.read_catalogue(2*self.sector_size) 1.628 1.629 elif self.disc_type == 'adEbig': 1.630 1.631 @@ -253,7 +856,7 @@ 1.632 1.633 # Find the root directory name and all the files and directories 1.634 # contained within it. The 1.635 - self.root_name, self.files = self._read_new_catalogue((self.ntracks * self.nsectors/2 + 2) * self.sector_size) 1.636 + self.root_name, self.files = self.disc_map.read_catalogue((self.ntracks * self.nsectors/2 + 2) * self.sector_size) 1.637 1.638 else: 1.639 1.640 @@ -261,64 +864,6 @@ 1.641 # contained within it. 1.642 self.root_name, self.files = self._read_old_catalogue(2*self.sector_size) 1.643 1.644 - 1.645 - # Little endian reading 1.646 - 1.647 - def _read_signed_word(self, s): 1.648 - 1.649 - return struct.unpack("<i", s)[0] 1.650 - 1.651 - def _read_unsigned_word(self, s): 1.652 - 1.653 - return struct.unpack("<I", s)[0] 1.654 - 1.655 - def _read_signed_byte(self, s): 1.656 - 1.657 - return struct.unpack("<b", s)[0] 1.658 - 1.659 - def _read_unsigned_byte(self, s): 1.660 - 1.661 - return struct.unpack("<B", s)[0] 1.662 - 1.663 - def _read_unsigned_half_word(self, s): 1.664 - 1.665 - return struct.unpack("<H", s)[0] 1.666 - 1.667 - def _read_signed_half_word(self, s): 1.668 - 1.669 - return struct.unpack("<h", s)[0] 1.670 - 1.671 - def _str2num(self, size, s): 1.672 - 1.673 - i = 0 1.674 - n = 0 1.675 - while i < size: 1.676 - 1.677 - n = n | (ord(s[i]) << (i*8)) 1.678 - i = i + 1 1.679 - 1.680 - return n 1.681 - 1.682 - 1.683 - def _binary(self, size, n): 1.684 - 1.685 - new = "" 1.686 - while (n != 0) & (size > 0): 1.687 - 1.688 - if (n & 1)==1: 1.689 - new = "1" + new 1.690 - else: 1.691 - new = "0" + new 1.692 - 1.693 - n = n >> 1 1.694 - size = size - 1 1.695 - 1.696 - if size > 0: 1.697 - new = ("0"*size) + new 1.698 - 1.699 - return new 1.700 - 1.701 - 1.702 def _identify_format(self, adf): 1.703 1.704 # Look for a valid disc record when determining whether the disc 1.705 @@ -353,7 +898,6 @@ 1.706 # for the length of the image file. 1.707 checklist["Length field matches image length"] = 1 1.708 1.709 - 1.710 # Check the sector size of the disc. 1.711 1.712 if record["sector size"] == 1024: 1.713 @@ -361,7 +905,6 @@ 1.714 # These should be equal if the disc record is valid. 1.715 checklist["Expected sector size (1024 bytes)"] = 1 1.716 1.717 - 1.718 # Check the density of the disc. 1.719 1.720 if record["density"] == "double": 1.721 @@ -369,7 +912,6 @@ 1.722 # This should be a double density disc if the disc record is valid. 1.723 checklist["Expected density (double)"] = 1 1.724 1.725 - 1.726 # Check the data at the root directory location. 1.727 1.728 adf.seek((record["root dir"] * record["sector size"]) + 1, 0) 1.729 @@ -460,7 +1002,6 @@ 1.730 1.731 return '?' 1.732 1.733 - 1.734 def _read_disc_record(self, offset): 1.735 1.736 # Total sectors per track (sectors * heads) 1.737 @@ -524,7 +1065,6 @@ 1.738 'disc size': disc_size, 'disc ID': disc_id, 1.739 'disc name': disc_name, 'zones': zones, 'root dir': root } 1.740 1.741 - 1.742 def _read_disc_info(self): 1.743 1.744 checksum = ord(self.sectors[0]) 1.745 @@ -538,14 +1078,11 @@ 1.746 1.747 self.map_header = 0 1.748 self.map_start, self.map_end = 0x40, 0x400 1.749 - self.free_space = self._read_free_space( 1.750 - self.map_header, self.map_start, self.map_end 1.751 - ) 1.752 - self.disc_map = self._read_new_map( 1.753 - self.map_header, self.map_start, self.map_end 1.754 - ) 1.755 + self.disc_map = ADFSnewMap(self.map_header, self.map_start, 1.756 + self.map_end, self.sectors, 1.757 + self.sector_size) 1.758 1.759 - return self.record['disc name'] #, map 1.760 + return self.record['disc name'] 1.761 1.762 elif self.disc_type == 'adEbig': 1.763 1.764 @@ -555,278 +1092,15 @@ 1.765 1.766 self.map_header = 0xc6800 1.767 self.map_start, self.map_end = 0xc6840, 0xc7800 1.768 - self.free_space = self._read_free_space( 1.769 - self.map_header, self.map_start, self.map_end 1.770 - ) 1.771 - self.disc_map = self._read_new_map( 1.772 - self.map_header, self.map_start, self.map_end 1.773 - ) 1.774 + self.disc_map = ADFSbigNewMap(self.map_header, self.map_start, 1.775 + self.map_end, self.sectors, 1.776 + self.sector_size) 1.777 1.778 - return self.record['disc name'] #, map 1.779 + return self.record['disc name'] 1.780 1.781 else: 1.782 return 'Unknown' 1.783 1.784 - # zone_size = 819200 / record['zones'] 1.785 - # ids_per_zone = zone_size / 1.786 - 1.787 - def _read_new_map(self, header, begin, end): 1.788 - 1.789 - disc_map = {} 1.790 - 1.791 - a = begin 1.792 - 1.793 - current_piece = None 1.794 - current_start = 0 1.795 - 1.796 - next_zone = header + self.sector_size 1.797 - 1.798 - # Copy the free space map. 1.799 - free_space = self.free_space[:] 1.800 - 1.801 - while a < end: 1.802 - 1.803 - # The next entry to be read will occur one byte after this one 1.804 - # unless one of the following checks override this behaviour. 1.805 - next = a + 1 1.806 - 1.807 - if (a % self.sector_size) < 4: 1.808 - 1.809 - # In a zone header. Not the first zone header as this 1.810 - # was already skipped when we started reading. 1.811 - next = a + 4 - (a % self.sector_size) 1.812 - 1.813 - # Set the next zone offset. 1.814 - next_zone = next_zone + self.sector_size 1.815 - 1.816 - # Reset the current piece and starting offset. 1.817 - current_piece = None 1.818 - current_start = 0 1.819 - 1.820 - elif free_space != [] and a >= free_space[0][0]: 1.821 - 1.822 - # In the next free space entry. Go to the entry following 1.823 - # it and discard this free space entry. 1.824 - next = free_space[0][1] 1.825 - 1.826 - free_space.pop(0) 1.827 - 1.828 - # Reset the current piece and starting offset. 1.829 - current_piece = None 1.830 - current_start = 0 1.831 - 1.832 - elif current_piece is None and (next_zone - a) >= 2: 1.833 - 1.834 - # If there is enough space left in this zone to allow 1.835 - # further fragments then read the next two bytes. 1.836 - value = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.837 - 1.838 - entry = value & 0x7fff 1.839 - 1.840 - # See ADFS/EAddrs.htm document for restriction on 1.841 - # the disc address and hence the file number. 1.842 - # i.e.the top bit of the file number cannot be set. 1.843 - 1.844 - if entry >= 1: 1.845 - 1.846 - # Defects (1), files or directories (greater than 1) 1.847 - next = a + 2 1.848 - 1.849 - # Define a new entry. 1.850 - #print "Begin:", hex(entry), hex(a) 1.851 - 1.852 - if not disc_map.has_key(entry): 1.853 - 1.854 - # Create a new map entry if none exists. 1.855 - disc_map[entry] = [] 1.856 - 1.857 - if (value & 0x8000) == 0: 1.858 - 1.859 - # Record the file number and start of this fragment. 1.860 - current_piece = entry 1.861 - current_start = a 1.862 - 1.863 - else: 1.864 - 1.865 - # For an immediately terminated fragment, add the 1.866 - # extents of the block to the list of pieces found 1.867 - # and implicitly finish reading this fragment 1.868 - # (current_piece remains None). 1.869 - 1.870 - start_addr = self._find_address_from_map( 1.871 - a, begin, entry 1.872 - ) 1.873 - end_addr = self._find_address_from_map( 1.874 - next, begin, entry 1.875 - ) 1.876 - 1.877 - if [start_addr, end_addr] not in disc_map[entry]: 1.878 - 1.879 - disc_map[entry].append( [start_addr, end_addr] ) 1.880 - 1.881 - else: 1.882 - 1.883 - # Search for a valid file number. 1.884 - # Should probably stop looking in this zone. 1.885 - next = a + 1 1.886 - 1.887 - elif current_piece is not None: 1.888 - 1.889 - # In a piece being read. 1.890 - 1.891 - value = ord(self.sectors[a]) 1.892 - 1.893 - if value == 0: 1.894 - 1.895 - # Still in the block. 1.896 - next = a + 1 1.897 - 1.898 - elif value == 0x80: 1.899 - 1.900 - # At the end of the block. 1.901 - next = a + 1 1.902 - 1.903 - # For relevant entries add the block to the list of 1.904 - # pieces found. 1.905 - start_addr = self._find_address_from_map( 1.906 - current_start, begin, current_piece 1.907 - ) 1.908 - end_addr = self._find_address_from_map( 1.909 - next, begin, current_piece 1.910 - ) 1.911 - 1.912 - if [start_addr, end_addr] not in disc_map[current_piece]: 1.913 - 1.914 - disc_map[current_piece].append( 1.915 - [ start_addr, end_addr] 1.916 - ) 1.917 - 1.918 - # Look for a new fragment. 1.919 - current_piece = None 1.920 - 1.921 - else: 1.922 - 1.923 - # The byte found was unexpected - backtrack to the 1.924 - # byte after the start of this block and try again. 1.925 - #print "Backtrack from %s to %s" % (hex(a), hex(current_start+1)) 1.926 - 1.927 - next = current_start + 1 1.928 - current_piece = None 1.929 - 1.930 - # Move to the next relevant byte. 1.931 - a = next 1.932 - 1.933 - return disc_map 1.934 - 1.935 - def _read_free_space(self, header, begin, end): 1.936 - 1.937 - free_space = [] 1.938 - 1.939 - a = header 1.940 - 1.941 - while a < end: 1.942 - 1.943 - # The next zone starts a sector after this one. 1.944 - next_zone = a + self.sector_size 1.945 - 1.946 - a = a + 1 1.947 - 1.948 - # Start by reading the offset in bits from the start of the header 1.949 - # of the first item of free space in the map. 1.950 - offset = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.951 - 1.952 - # The top bit is apparently always set, so mask it off and convert 1.953 - # the result into bytes. * Not sure if this is the case for 1.954 - # entries in the map. * 1.955 - next = ((offset & 0x7fff) >> 3) 1.956 - 1.957 - if next == 0: 1.958 - 1.959 - # No more free space in this zone. Look at the free 1.960 - # space field in the next zone. 1.961 - a = next_zone 1.962 - continue 1.963 - 1.964 - # Update the offset to point to the free space in this zone. 1.965 - a = a + next 1.966 - 1.967 - while a < next_zone: 1.968 - 1.969 - # Read the offset to the next free fragment in this zone. 1.970 - offset = self._read_unsigned_half_word(self.sectors[a:a+2]) 1.971 - 1.972 - # Convert this to a byte offset. 1.973 - next = ((offset & 0x7fff) >> 3) 1.974 - 1.975 - # Find the end of the free space. 1.976 - b = a + 1 1.977 - 1.978 - while b < next_zone: 1.979 - 1.980 - c = b + 1 1.981 - 1.982 - value = self._read_unsigned_byte(self.sectors[b]) 1.983 - 1.984 - if (value & 0x80) != 0: 1.985 - 1.986 - break 1.987 - 1.988 - b = c 1.989 - 1.990 - # Record the offset into the map of this item of free space 1.991 - # and the offset of the byte after it ends. 1.992 - free_space.append( (a, c) ) 1.993 - 1.994 - if next == 0: 1.995 - 1.996 - break 1.997 - 1.998 - # Move to the next free space entry. 1.999 - a = a + next 1.1000 - 1.1001 - # Whether we are at the end of the zone or not, move to the 1.1002 - # beginning of the next zone. 1.1003 - a = next_zone 1.1004 - 1.1005 - # Return the free space list. 1.1006 - return free_space 1.1007 - 1.1008 - def _find_address_from_map(self, addr, begin, entry): 1.1009 - 1.1010 - if self.disc_type == 'adE': 1.1011 - 1.1012 - address = ((addr - begin) * self.sector_size) 1.1013 - 1.1014 - elif self.disc_type == 'adEbig': 1.1015 - 1.1016 - # I can't remember where the rationale for this calculation 1.1017 - # came from or where the necessary information was obtained. 1.1018 - # It probably came from one of the WSS files, such as 1.1019 - # Formats.htm or Formats2.htm which imply that the F format 1.1020 - # uses 512 byte sectors (see the 0x200 value below) and 1.1021 - # indicate that F format uses 4 zones rather than 1. 1.1022 - 1.1023 - upper = (entry & 0x7f00) >> 8 1.1024 - 1.1025 - if upper > 1: 1.1026 - upper = upper - 1 1.1027 - if upper > 3: 1.1028 - upper = 3 1.1029 - 1.1030 - address = ((addr - begin) - (upper * 0xc8)) * 0x200 1.1031 - 1.1032 - return address 1.1033 - 1.1034 - def _find_in_new_map(self, file_no): 1.1035 - 1.1036 - try: 1.1037 - 1.1038 - return self.disc_map[file_no] 1.1039 - 1.1040 - except KeyError: 1.1041 - 1.1042 - return [] 1.1043 - 1.1044 def _read_tracks(self, f, inter): 1.1045 1.1046 t = "" 1.1047 @@ -871,7 +1145,6 @@ 1.1048 1.1049 return t 1.1050 1.1051 - 1.1052 def _read_sectors(self, adf): 1.1053 1.1054 s = [] 1.1055 @@ -894,69 +1167,6 @@ 1.1056 1.1057 return s 1.1058 1.1059 - 1.1060 - def _safe(self, s, with_space = 0): 1.1061 - 1.1062 - new = "" 1.1063 - if with_space == 1: 1.1064 - lower = 31 1.1065 - else: 1.1066 - lower = 32 1.1067 - 1.1068 - for i in s: 1.1069 - 1.1070 - if ord(i) <= lower: 1.1071 - break 1.1072 - 1.1073 - if ord(i) >= 128: 1.1074 - c = ord(i)^128 1.1075 - if c > 32: 1.1076 - new = new + chr(c) 1.1077 - else: 1.1078 - new = new + i 1.1079 - 1.1080 - return new 1.1081 - 1.1082 - 1.1083 - def _read_freespace(self): 1.1084 - 1.1085 - # Currently unused 1.1086 - 1.1087 - base = 0 1.1088 - 1.1089 - free = [] 1.1090 - p = 0 1.1091 - while self.sectors[base+p] != 0: 1.1092 - 1.1093 - free.append(self._str2num(3, self.sectors[base+p:base+p+3])) 1.1094 - 1.1095 - name = self.sectors[self.sector_size-9:self.sector_size-4] 1.1096 - 1.1097 - disc_size = self._str2num( 1.1098 - 3, self.sectors[self.sector_size-4:self.sector_size-1] 1.1099 - ) 1.1100 - 1.1101 - checksum0 = self._read_unsigned_byte(self.sectors[self.sector_size-1]) 1.1102 - 1.1103 - base = self.sector_size 1.1104 - 1.1105 - p = 0 1.1106 - while self.sectors[base+p] != 0: 1.1107 - 1.1108 - free.append(self._str2num(3, self.sectors[base+p:base+p+3])) 1.1109 - 1.1110 - name = name + \ 1.1111 - self.sectors[base+self.sector_size-10:base+self.sector_size-5] 1.1112 - 1.1113 - disc_id = self._str2num( 1.1114 - 2, self.sectors[base+self.sector_size-5:base+self.sector_size-3] 1.1115 - ) 1.1116 - 1.1117 - boot = self._read_unsigned_byte(self.sectors[base+self.sector_size-3]) 1.1118 - 1.1119 - checksum1 = self._read_unsigned_byte(self.sectors[base+self.sector_size-1]) 1.1120 - 1.1121 - 1.1122 def _read_old_catalogue(self, base): 1.1123 1.1124 head = base 1.1125 @@ -1115,213 +1325,6 @@ 1.1126 1.1127 return dir_name, files 1.1128 1.1129 - 1.1130 - def _read_new_address(self, s): 1.1131 - 1.1132 - # From the three character string passed, determine the address on the 1.1133 - # disc. 1.1134 - value = self._str2num(3, s) 1.1135 - 1.1136 - # This is a SIN (System Internal Number) 1.1137 - # The bottom 8 bits are the sector offset + 1 1.1138 - offset = value & 0xff 1.1139 - if offset != 0: 1.1140 - address = (offset - 1) * self.sector_size 1.1141 - else: 1.1142 - address = 0 1.1143 - 1.1144 - # The top 16 bits are the file number 1.1145 - file_no = value >> 8 1.1146 - 1.1147 - # The pieces of the object are returned as a list of pairs of 1.1148 - # addresses. 1.1149 - pieces = self._find_in_new_map(file_no) 1.1150 - 1.1151 - if pieces == []: 1.1152 - 1.1153 - return -1 1.1154 - 1.1155 - # Ensure that the first piece of data is read from the appropriate 1.1156 - # point in the relevant sector. 1.1157 - 1.1158 - pieces[0][0] = pieces[0][0] + address 1.1159 - 1.1160 - return pieces 1.1161 - 1.1162 - 1.1163 - def _read_new_catalogue(self, base): 1.1164 - 1.1165 - head = base 1.1166 - p = 0 1.1167 - 1.1168 - dir_seq = self.sectors[head + p] 1.1169 - dir_start = self.sectors[head+p+1:head+p+5] 1.1170 - if dir_start not in self.dir_markers: 1.1171 - 1.1172 - if self.verify: 1.1173 - 1.1174 - self.verify_log.append( 1.1175 - (WARNING, 'Not a directory: %s' % hex(head)) 1.1176 - ) 1.1177 - 1.1178 - return '', [] 1.1179 - 1.1180 - p = p + 5 1.1181 - 1.1182 - files = [] 1.1183 - 1.1184 - while ord(self.sectors[head+p]) != 0: 1.1185 - 1.1186 - old_name = self.sectors[head+p:head+p+10] 1.1187 - top_set = 0 1.1188 - counter = 1 1.1189 - for i in old_name: 1.1190 - if (ord(i) & 128) != 0: 1.1191 - top_set = counter 1.1192 - counter = counter + 1 1.1193 - 1.1194 - name = self._safe(self.sectors[head+p:head+p+10]) 1.1195 - 1.1196 - load = self._read_unsigned_word(self.sectors[head+p+10:head+p+14]) 1.1197 - exe = self._read_unsigned_word(self.sectors[head+p+14:head+p+18]) 1.1198 - length = self._read_unsigned_word(self.sectors[head+p+18:head+p+22]) 1.1199 - 1.1200 - inddiscadd = self._read_new_address( 1.1201 - self.sectors[head+p+22:head+p+25] 1.1202 - ) 1.1203 - newdiratts = self._read_unsigned_byte(self.sectors[head+p+25]) 1.1204 - 1.1205 - if inddiscadd == -1: 1.1206 - 1.1207 - if (newdiratts & 0x8) != 0: 1.1208 - 1.1209 - if self.verify: 1.1210 - 1.1211 - self.verify_log.append( 1.1212 - (WARNING, "Couldn't find directory: %s" % name) 1.1213 - ) 1.1214 - self.verify_log.append( 1.1215 - (WARNING, " at: %x" % (head+p+22)) 1.1216 - ) 1.1217 - self.verify_log.append( ( 1.1218 - WARNING, " file details: %x" % \ 1.1219 - self._str2num(3, self.sectors[head+p+22:head+p+25]) 1.1220 - ) ) 1.1221 - self.verify_log.append( 1.1222 - (WARNING, " atts: %x" % newdiratts) 1.1223 - ) 1.1224 - 1.1225 - elif length != 0: 1.1226 - 1.1227 - if self.verify: 1.1228 - 1.1229 - self.verify_log.append( 1.1230 - (WARNING, "Couldn't find file: %s" % name) 1.1231 - ) 1.1232 - self.verify_log.append( 1.1233 - (WARNING, " at: %x" % (head+p+22)) 1.1234 - ) 1.1235 - self.verify_log.append( ( 1.1236 - WARNING, 1.1237 - " file details: %x" % \ 1.1238 - self._str2num(3, self.sectors[head+p+22:head+p+25]) 1.1239 - ) ) 1.1240 - self.verify_log.append( 1.1241 - (WARNING, " atts: %x" % newdiratts) 1.1242 - ) 1.1243 - 1.1244 - else: 1.1245 - 1.1246 - # Store a zero length file. This appears to be the 1.1247 - # standard behaviour for storing empty files. 1.1248 - files.append(ADFSfile(name, "", load, exe, length)) 1.1249 - 1.1250 - else: 1.1251 - 1.1252 - if (newdiratts & 0x8) != 0: 1.1253 - 1.1254 - # Remember that inddiscadd will be a sequence of 1.1255 - # pairs of addresses. 1.1256 - 1.1257 - for start, end in inddiscadd: 1.1258 - 1.1259 - # Try to interpret the data at the referenced address 1.1260 - # as a directory. 1.1261 - 1.1262 - lower_dir_name, lower_files = \ 1.1263 - self._read_new_catalogue(start) 1.1264 - 1.1265 - # Store the directory name and file found therein. 1.1266 - files.append(ADFSdirectory(name, lower_files)) 1.1267 - 1.1268 - else: 1.1269 - 1.1270 - # Remember that inddiscadd will be a sequence of 1.1271 - # pairs of addresses. 1.1272 - 1.1273 - file = "" 1.1274 - remaining = length 1.1275 - 1.1276 - for start, end in inddiscadd: 1.1277 - 1.1278 - amount = min(remaining, end - start) 1.1279 - file = file + self.sectors[start : (start + amount)] 1.1280 - remaining = remaining - amount 1.1281 - 1.1282 - files.append(ADFSfile(name, file, load, exe, length)) 1.1283 - 1.1284 - p = p + 26 1.1285 - 1.1286 - 1.1287 - # Go to tail of directory structure (0x800 -- 0xc00) 1.1288 - 1.1289 - tail = head + self.sector_size 1.1290 - 1.1291 - dir_end = self.sectors[tail+self.sector_size-5:tail+self.sector_size-1] 1.1292 - 1.1293 - if dir_end not in self.dir_markers: 1.1294 - 1.1295 - if self.verify: 1.1296 - 1.1297 - self.verify_log.append( 1.1298 - ( WARNING, 1.1299 - 'Discrepancy in directory structure: [%x, %x]' % \ 1.1300 - ( head, tail ) ) 1.1301 - ) 1.1302 - 1.1303 - return '', files 1.1304 - 1.1305 - dir_name = self._safe( 1.1306 - self.sectors[tail+self.sector_size-16:tail+self.sector_size-6] 1.1307 - ) 1.1308 - 1.1309 - parent = \ 1.1310 - self.sectors[tail+self.sector_size-38:tail+self.sector_size-35] 1.1311 - 1.1312 - dir_title = \ 1.1313 - self.sectors[tail+self.sector_size-35:tail+self.sector_size-16] 1.1314 - 1.1315 - if head == 0x800 and self.disc_type == 'adE': 1.1316 - dir_name = '$' 1.1317 - if head == 0xc8800 and self.disc_type == 'adEbig': 1.1318 - dir_name = '$' 1.1319 - 1.1320 - endseq = self.sectors[tail+self.sector_size-6] 1.1321 - if endseq != dir_seq: 1.1322 - 1.1323 - if self.verify: 1.1324 - 1.1325 - self.verify_log.append( 1.1326 - ( WARNING, 1.1327 - 'Broken directory: %s at [%x, %x]' % \ 1.1328 - (dir_title, head, tail) ) 1.1329 - ) 1.1330 - 1.1331 - return dir_name, files 1.1332 - 1.1333 - return dir_name, files 1.1334 - 1.1335 - 1.1336 def print_catalogue(self, files = None, path = "$", filetypes = 0): 1.1337 1.1338 """Prints the contents of the disc catalogue to standard output. 1.1339 @@ -1393,7 +1396,6 @@ 1.1340 1.1341 self.print_catalogue(obj.files, path + "." + name, filetypes) 1.1342 1.1343 - 1.1344 def _convert_name(self, old_name, convert_dict): 1.1345 1.1346 # Use the conversion dictionary to convert any forbidden 1.1347 @@ -1486,7 +1488,6 @@ 1.1348 obj.files, new_path, filetypes, separator, convert_dict 1.1349 ) 1.1350 1.1351 - 1.1352 def _extract_new_files(self, objects, path, filetypes = 0, separator = ",", 1.1353 convert_dict = {}, with_time_stamps = False): 1.1354 1.1355 @@ -1555,7 +1556,6 @@ 1.1356 obj.files, new_path, filetypes, separator, convert_dict 1.1357 ) 1.1358 1.1359 - 1.1360 def extract_files(self, out_path, files = None, filetypes = 0, 1.1361 separator = ",", convert_dict = {}, 1.1362 with_time_stamps = False):
