castleraider
changeset 305:8b49c7f39945
Added a module based on ADFSlib specifically for creating ADFS disk
images for the 8-bit machines.
| author | David Boddie <david@boddie.org.uk> |
|---|---|
| date | Tue Apr 08 00:03:35 2014 +0200 |
| parents | 2ab5cb93b22a |
| children | 8d31adbe9148 |
| files | tools/__init__.py tools/makeadf.py |
| diffstat | 2 files changed, 422 insertions(+), 1 deletions(-) [+] |
line diff
1.1 --- a/tools/__init__.py Mon Apr 07 01:13:08 2014 +0200 1.2 +++ b/tools/__init__.py Tue Apr 08 00:03:35 2014 +0200 1.3 @@ -1,1 +1,1 @@ 1.4 -__all__ = ["compress", "makelevels", "makesprites"] 1.5 +__all__ = ["compress", "makeadf", "makelevels", "makesprites"]
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/tools/makeadf.py Tue Apr 08 00:03:35 2014 +0200 2.3 @@ -0,0 +1,421 @@ 2.4 +""" 2.5 +Copyright (C) 2014 David Boddie <david@boddie.org.uk> 2.6 + 2.7 +This program is free software: you can redistribute it and/or modify 2.8 +it under the terms of the GNU General Public License as published by 2.9 +the Free Software Foundation, either version 3 of the License, or 2.10 +(at your option) any later version. 2.11 + 2.12 +This program is distributed in the hope that it will be useful, 2.13 +but WITHOUT ANY WARRANTY; without even the implied warranty of 2.14 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 2.15 +GNU General Public License for more details. 2.16 + 2.17 +You should have received a copy of the GNU General Public License 2.18 +along with this program. If not, see <http://www.gnu.org/licenses/>. 2.19 +""" 2.20 + 2.21 +__author__ = "David Boddie <david@boddie.org.uk>" 2.22 +__date__ = "2014-04-06" 2.23 +__version__ = "0.1" 2.24 +__license__ = "GNU General Public License (version 3 or later)" 2.25 + 2.26 +import StringIO, struct, time 2.27 + 2.28 +class DiskError(Exception): 2.29 + pass 2.30 + 2.31 + 2.32 +class Utilities: 2.33 + 2.34 + # Little endian reading 2.35 + 2.36 + def _read_signed_word(self, s): 2.37 + 2.38 + return struct.unpack("<i", s)[0] 2.39 + 2.40 + def _read_unsigned_word(self, s): 2.41 + 2.42 + return struct.unpack("<I", s)[0] 2.43 + 2.44 + def _read_signed_byte(self, s): 2.45 + 2.46 + return struct.unpack("<b", s)[0] 2.47 + 2.48 + def _read_unsigned_byte(self, s): 2.49 + 2.50 + return struct.unpack("<B", s)[0] 2.51 + 2.52 + def _read_unsigned_half_word(self, s): 2.53 + 2.54 + return struct.unpack("<H", s)[0] 2.55 + 2.56 + def _read_signed_half_word(self, s): 2.57 + 2.58 + return struct.unpack("<h", s)[0] 2.59 + 2.60 + def _read(self, offset, length = 1): 2.61 + 2.62 + self.file.seek(offset, 0) 2.63 + return self.file.read(length) 2.64 + 2.65 + def _write(self, offset, data): 2.66 + 2.67 + self.file.seek(offset, 0) 2.68 + self.file.write(data) 2.69 + 2.70 + def _str2num(self, s): 2.71 + 2.72 + i = 0 2.73 + n = 0 2.74 + while i < len(s): 2.75 + 2.76 + n = n | (ord(s[i]) << (i*8)) 2.77 + i = i + 1 2.78 + 2.79 + return n 2.80 + 2.81 + def _binary(self, size, n): 2.82 + 2.83 + new = "" 2.84 + while (n != 0) & (size > 0): 2.85 + 2.86 + if (n & 1)==1: 2.87 + new = "1" + new 2.88 + else: 2.89 + new = "0" + new 2.90 + 2.91 + n = n >> 1 2.92 + size = size - 1 2.93 + 2.94 + if size > 0: 2.95 + new = ("0"*size) + new 2.96 + 2.97 + return new 2.98 + 2.99 + def _safe(self, s, with_space = 0): 2.100 + 2.101 + new = "" 2.102 + if with_space == 1: 2.103 + lower = 31 2.104 + else: 2.105 + lower = 32 2.106 + 2.107 + for c in s: 2.108 + 2.109 + if ord(c) >= 128: 2.110 + i = ord(c) ^ 128 2.111 + c = chr(i) 2.112 + 2.113 + if ord(c) <= lower: 2.114 + break 2.115 + 2.116 + new = new + c 2.117 + 2.118 + return new 2.119 + 2.120 + 2.121 +class Directory: 2.122 + 2.123 + """directory = Directory(name, address) 2.124 + 2.125 + The directory created contains name and files attributes containing the 2.126 + directory name and the objects it contains. 2.127 + """ 2.128 + 2.129 + def __init__(self, name, files): 2.130 + 2.131 + self.name = name 2.132 + self.files = files 2.133 + 2.134 + def __repr__(self): 2.135 + 2.136 + return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self)) 2.137 + 2.138 + 2.139 +class File: 2.140 + 2.141 + """file = File(name, data, load_address, execution_address, length) 2.142 + """ 2.143 + 2.144 + def __init__(self, name, data, load_address, execution_address, length): 2.145 + 2.146 + self.name = name 2.147 + self.data = data 2.148 + self.load_address = load_address 2.149 + self.execution_address = execution_address 2.150 + self.length = length 2.151 + 2.152 + def __repr__(self): 2.153 + 2.154 + return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self)) 2.155 + 2.156 + def has_filetype(self): 2.157 + 2.158 + """Returns True if the file's meta-data contains filetype information.""" 2.159 + return self.load_address & 0xfff00000 == 0xfff00000 2.160 + 2.161 + def filetype(self): 2.162 + 2.163 + """Returns the meta-data containing the filetype information. 2.164 + 2.165 + Note that a filetype can be obtained for all files, though it may not 2.166 + necessarily be valid. Use has_filetype() to determine whether the file 2.167 + is likely to have a valid filetype.""" 2.168 + 2.169 + return "%03x" % ((self.load_address >> 8) & 0xfff) 2.170 + 2.171 + def time_stamp(self): 2.172 + 2.173 + """Returns the time stamp for the file as a tuple of values containing 2.174 + the local time, or an empty tuple if the file does not have a time stamp.""" 2.175 + 2.176 + # RISC OS time is given as a five byte block containing the 2.177 + # number of centiseconds since 1900 (presumably 1st January 1900). 2.178 + 2.179 + # Convert the time to the time elapsed since the Epoch (assuming 2.180 + # 1970 for this value). 2.181 + date_num = struct.unpack("<Q", 2.182 + struct.pack("<IBxxx", self.execution_address, self.load_address & 0xff))[0] 2.183 + 2.184 + centiseconds = date_num - between_epochs 2.185 + 2.186 + # Convert this to a value in seconds and return a time tuple. 2.187 + try: 2.188 + return time.localtime(centiseconds / 100.0) 2.189 + except ValueError: 2.190 + return () 2.191 + 2.192 + 2.193 +class Catalogue(Utilities): 2.194 + 2.195 + DirMarkers = ('Hugo',) 2.196 + 2.197 + def __init__(self, file): 2.198 + 2.199 + self.file = file 2.200 + self.sector_size = 256 2.201 + 2.202 + def read_free_space(self): 2.203 + 2.204 + # Currently unused 2.205 + 2.206 + base = 0 2.207 + free_space = [] 2.208 + p = 0 2.209 + while self._read(base + p) != chr(0): 2.210 + 2.211 + free_space.append(self._str2num(self._read(base + p, 3))) 2.212 + p += 3 2.213 + 2.214 + name = self._read(self.sector_size - 9, 5) 2.215 + 2.216 + disc_size = self._str2num(self._read(self.sector_size - 4, 3)) 2.217 + 2.218 + checksum0 = self._read_unsigned_byte(self._read(self.sector_size-1)) 2.219 + 2.220 + base = self.sector_size 2.221 + 2.222 + p = 0 2.223 + while self._read(base + p) != chr(0): 2.224 + 2.225 + free_space.append(self._str2num(self._read(base + p, 3))) 2.226 + p += 3 2.227 + 2.228 + name = name + self._read(base + self.sector_size - 10, 5) 2.229 + 2.230 + disc_id = self._str2num(self._read(base + self.sector_size - 5, 2)) 2.231 + 2.232 + boot = self._read_unsigned_byte(self._read(base + self.sector_size - 3)) 2.233 + 2.234 + checksum1 = self._read_unsigned_byte(self._read(base + self.sector_size - 1)) 2.235 + 2.236 + return free_space 2.237 + 2.238 + def read(self, offset = 512): 2.239 + 2.240 + head = offset 2.241 + p = 0 2.242 + 2.243 + dir_seq = self._read(head + p) 2.244 + dir_start = self._read(head + p + 1, 4) 2.245 + 2.246 + if dir_start not in self.DirMarkers: 2.247 + raise DiskError, "Not a directory at 0x%x" % (head + p) 2.248 + 2.249 + p = p + 5 2.250 + 2.251 + files = [] 2.252 + 2.253 + while ord(self._read(head + p)) != 0: 2.254 + 2.255 + name = self._safe(self._read(head + p, 10)) 2.256 + 2.257 + load = self._read_unsigned_word(self._read(head + p + 10, 4)) 2.258 + exe = self._read_unsigned_word(self._read(head + p + 14, 4)) 2.259 + length = self._read_unsigned_word(self._read(head + p + 18, 4)) 2.260 + 2.261 + inddiscadd = self.sector_size * self._str2num(self._read(head + p + 22, 3)) 2.262 + 2.263 + print hex(head + p), name, map(hex, (load, exe, length, inddiscadd)) 2.264 + 2.265 + olddirobseq = self._read_unsigned_byte(self._read(head + p + 25)) 2.266 + 2.267 + # [Needs more accurate check for directories.] 2.268 + if length == (self.sector_size * 5): 2.269 + 2.270 + # A directory has been found. 2.271 + lower_dir_name, lower_files = self.read(inddiscadd) 2.272 + print "Entering", lower_dir_name 2.273 + 2.274 + files.append(Directory(name, lower_files)) 2.275 + 2.276 + else: 2.277 + 2.278 + # A file has been found. 2.279 + data = self._read(inddiscadd, length) 2.280 + files.append(File(name, data, load, exe, length)) 2.281 + 2.282 + p = p + 26 2.283 + 2.284 + # Go to the tail of the directory structure (0x400 - 0x500). 2.285 + 2.286 + tail = head + (self.sector_size*4) 2.287 + 2.288 + dir_end = self._read(tail + self.sector_size-5, 4) 2.289 + 2.290 + if dir_end not in self.DirMarkers: 2.291 + raise DiskError, 'Discrepancy in directory structure: [%x, %x]' % (head, tail) 2.292 + 2.293 + # Read the directory name, its parent and any title given. 2.294 + 2.295 + dir_name = self._safe(self._read(tail + self.sector_size - 52, 10)) 2.296 + 2.297 + parent = self.sector_size * \ 2.298 + self._str2num(self._read(tail + self.sector_size - 42, 3)) 2.299 + 2.300 + dir_title = self._safe(self._read(tail + self.sector_size - 39, 19)) 2.301 + 2.302 + endseq = self._read(tail + self.sector_size - 6) 2.303 + 2.304 + if endseq != dir_seq: 2.305 + raise DiskError, 'Broken directory: %s at [%x, %x]' % (dir_title, head, tail) 2.306 + 2.307 + return dir_name, files 2.308 + 2.309 + def write(self, files, offset = 512): 2.310 + 2.311 + head = offset 2.312 + p = 0 2.313 + 2.314 + dir_seq = (self._read_unsigned_byte(self._read(head + p)) + 1) % 256 2.315 + self._write(head + p, chr(dir_seq)) 2.316 + 2.317 + dir_start = self._read(head + p + 1, 4) 2.318 + 2.319 + if dir_start not in self.DirMarkers: 2.320 + dir_start = "Hugo" 2.321 + self._write(head + p + 1, dir_start) 2.322 + 2.323 + p = p + 5 2.324 + 2.325 + files = [] 2.326 + 2.327 + while ord(self._read(head + p)) != 0: 2.328 + 2.329 + old_name = self.data[head+p:head+p+10] 2.330 + top_set = 0 2.331 + counter = 1 2.332 + for i in old_name: 2.333 + if (ord(i) & 128) != 0: 2.334 + top_set = counter 2.335 + counter = counter + 1 2.336 + 2.337 + name = self._safe(self.data[head+p:head+p+10]) 2.338 + 2.339 + load = self._read_unsigned_word(self.data[head+p+10:head+p+14]) 2.340 + exe = self._read_unsigned_word(self.data[head+p+14:head+p+18]) 2.341 + length = self._read_unsigned_word(self.data[head+p+18:head+p+22]) 2.342 + 2.343 + inddiscadd = self.sector_size * self._str2num( 2.344 + 3, self.data[head+p+22:head+p+25] 2.345 + ) 2.346 + 2.347 + olddirobseq = self._read_unsigned_byte(self.data[head+p+25]) 2.348 + 2.349 + # [Needs more accurate check for directories.] 2.350 + if (load == 0 and exe == 0 and top_set > 2) or \ 2.351 + (top_set > 0 and length == (self.sector_size * 5)): 2.352 + 2.353 + # A directory has been found. 2.354 + lower_dir_name, lower_files = \ 2.355 + self._read_old_catalogue(inddiscadd) 2.356 + 2.357 + files.append(Directory(name, lower_files)) 2.358 + 2.359 + else: 2.360 + 2.361 + # A file has been found. 2.362 + data = self.data[inddiscadd:inddiscadd+length] 2.363 + files.append(File(name, data, load, exe, length)) 2.364 + 2.365 + p = p + 26 2.366 + 2.367 + # Go to the tail of the directory structure (0x400 - 0x500). 2.368 + 2.369 + tail = head + (self.sector_size*4) 2.370 + 2.371 + dir_end = self.data[tail+self.sector_size-5:tail+self.sector_size-1] 2.372 + 2.373 + if dir_end not in self.DirMarkers: 2.374 + raise DiskError, 'Discrepancy in directory structure: [%x, %x]' % (head, tail) 2.375 + 2.376 + # Read the directory name, its parent and any title given. 2.377 + 2.378 + dir_name = self._safe( 2.379 + self.data[tail+self.sector_size-52:tail+self.sector_size-42] 2.380 + ) 2.381 + 2.382 + parent = self.sector_size*self._str2num( 2.383 + 3, 2.384 + self.data[tail+self.sector_size-42:tail+self.sector_size-39] 2.385 + ) 2.386 + 2.387 + dir_title = self._safe( 2.388 + self.data[tail+self.sector_size-39:tail+self.sector_size-20] 2.389 + ) 2.390 + 2.391 + endseq = self.data[tail+self.sector_size-6] 2.392 + 2.393 + if endseq != dir_seq: 2.394 + raise DiskError, 'Broken directory: %s at [%x, %x]' % (dir_title, head, tail) 2.395 + 2.396 + return dir_name, files 2.397 + 2.398 + 2.399 +class Disk: 2.400 + 2.401 + DiskSizes = {"M": 327680} 2.402 + SectorSizes = {"M": 256} 2.403 + Catalogues = {"M": Catalogue} 2.404 + 2.405 + def __init__(self, format): 2.406 + 2.407 + self.format = format 2.408 + 2.409 + def new(self): 2.410 + 2.411 + self.size = self.DiskSizes[self.format] 2.412 + self.data = "\x00" * size 2.413 + self.file = StringIO.StringIO(self.data) 2.414 + 2.415 + def open(self, file_object): 2.416 + 2.417 + self.size = self.DiskSizes[self.format] 2.418 + self.file = file_object 2.419 + 2.420 + def read_catalogue(self): 2.421 + 2.422 + sector_size = self.SectorSizes[self.format] 2.423 + catalogue = self.Catalogues[self.format](self.file) 2.424 + return catalogue.read()
