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()