castleraider

changeset 306:8d31adbe9148

Added modules to help with writing to disk images. Added options to the build script to allow disk images to be created. Added an instruction in the loader to ensure that the panel copying routine is performed correctly after switching from DFS to tape.
author David Boddie <david@boddie.org.uk>
date Wed Apr 23 00:20:53 2014 +0200
parents 8b49c7f39945
children c9449b95afbc
files build.py loader.oph tools/__init__.py tools/makeadf.py tools/makedfs.py
diffstat 5 files changed, 413 insertions(+), 269 deletions(-) [+]
line diff
     1.1 --- a/build.py	Tue Apr 08 00:03:35 2014 +0200
     1.2 +++ b/build.py	Wed Apr 23 00:20:53 2014 +0200
     1.3 @@ -20,7 +20,7 @@
     1.4  import os, shutil, stat, struct, sys
     1.5  import UEFfile
     1.6  
     1.7 -from tools import makelevels, makesprites
     1.8 +from tools import makeadf, makedfs, makelevels, makesprites
     1.9  
    1.10  version = "0.1"
    1.11  
    1.12 @@ -133,7 +133,7 @@
    1.13  
    1.14      if not 3 <= len(sys.argv) <= 4:
    1.15      
    1.16 -        sys.stderr.write("Usage: %s -e|-b <new UEF file> [level file]\n" % sys.argv[0])
    1.17 +        sys.stderr.write("Usage: %s -e|-b -t|-a|-d <new UEF or ADF file> [level file]\n" % sys.argv[0])
    1.18          sys.exit(1)
    1.19      
    1.20      machine_type = sys.argv[1]
    1.21 @@ -141,12 +141,15 @@
    1.22          sys.stderr.write("Please specify a valid machine type.\n")
    1.23          sys.exit(1)
    1.24      
    1.25 -    out_uef_file = sys.argv[2]
    1.26 +    make_tape_image = sys.argv[2] == "-t"
    1.27 +    make_adfs_image = sys.argv[2] == "-a"
    1.28 +    make_dfs_image = sys.argv[2] == "-d"
    1.29 +    out_file = sys.argv[3]
    1.30      
    1.31 -    if len(sys.argv) == 3:
    1.32 +    if len(sys.argv) == 4:
    1.33          level_file = "levels/default.txt"
    1.34      else:
    1.35 -        level_file = sys.argv[3]
    1.36 +        level_file = sys.argv[4]
    1.37      
    1.38      # Encode the in-game title data.
    1.39      title_data_oph = "title_data:\n"
    1.40 @@ -765,34 +768,72 @@
    1.41      #    sys.stderr.write("SPRITES overruns following data by %i bytes.\n" % (char_area_finish - panel_address))
    1.42      #    sys.exit(1)
    1.43      
    1.44 -    u = UEFfile.UEFfile(creator = 'build.py '+version)
    1.45 -    u.minor = 6
    1.46 -    u.target_machine = "Electron"
    1.47 +    if make_tape_image:
    1.48      
    1.49 -    u.import_files(0, files)
    1.50 +        u = UEFfile.UEFfile(creator = 'build.py '+version)
    1.51 +        u.minor = 6
    1.52 +        u.target_machine = "Electron"
    1.53 +        
    1.54 +        u.import_files(0, files)
    1.55 +        
    1.56 +        # Insert a gap before each file.
    1.57 +        offset = 0
    1.58 +        for f in u.contents:
    1.59 +        
    1.60 +            # Insert a gap and some padding before the file.
    1.61 +            gap_padding = [(0x112, "\xdc\x05"), (0x110, "\xdc\x05"), (0x100, "\xdc")]
    1.62 +            u.chunks = u.chunks[:f["position"] + offset] + \
    1.63 +                       gap_padding + u.chunks[f["position"] + offset:]
    1.64      
    1.65 -    # Insert a gap before each file.
    1.66 -    offset = 0
    1.67 -    for f in u.contents:
    1.68 +            # Each time we insert a gap, the position of the file changes, so we
    1.69 +            # need to update its position and last position. This isn't really
    1.70 +            # necessary because we won't read the contents list again.
    1.71 +            offset += len(gap_padding)
    1.72 +            f["position"] += offset
    1.73 +            f["last position"] += offset
    1.74 +        
    1.75 +        # Write the new UEF file.
    1.76 +        try:
    1.77 +            u.write(out_file, write_emulator_info = False)
    1.78 +        except UEFfile.UEFfile_error:
    1.79 +            sys.stderr.write("Couldn't write the new executable to %s.\n" % out_file)
    1.80 +            sys.exit(1)
    1.81      
    1.82 -        # Insert a gap and some padding before the file.
    1.83 -        gap_padding = [(0x112, "\xdc\x05"), (0x110, "\xdc\x05"), (0x100, "\xdc")]
    1.84 -        u.chunks = u.chunks[:f["position"] + offset] + \
    1.85 -                   gap_padding + u.chunks[f["position"] + offset:]
    1.86 -
    1.87 -        # Each time we insert a gap, the position of the file changes, so we
    1.88 -        # need to update its position and last position. This isn't really
    1.89 -        # necessary because we won't read the contents list again.
    1.90 -        offset += len(gap_padding)
    1.91 -        f["position"] += offset
    1.92 -        f["last position"] += offset
    1.93 +    elif make_adfs_image:
    1.94      
    1.95 -    # Write the new UEF file.
    1.96 -    try:
    1.97 -        u.write(out_uef_file, write_emulator_info = False)
    1.98 -    except UEFfile.UEFfile_error:
    1.99 -        sys.stderr.write("Couldn't write the new executable to %s.\n" % out_uef_file)
   1.100 -        sys.exit(1)
   1.101 -
   1.102 +        disk = makeadf.Disk("M")
   1.103 +        disk.new()
   1.104 +        
   1.105 +        catalogue = disk.catalogue()
   1.106 +        
   1.107 +        disk_files = []
   1.108 +        for name, load, exec_, data in files:
   1.109 +            disk_files.append(makeadf.File(name, data, load, exec_, len(data)))
   1.110 +        
   1.111 +        dir_address = catalogue.sector_size * 2
   1.112 +        catalogue.write("$", "Castle Raider", disk_files, dir_address, dir_address)
   1.113 +        catalogue.write_free_space()
   1.114 +        
   1.115 +        disk.file.seek(0, 0)
   1.116 +        disk_data = disk.file.read()
   1.117 +        open(out_file, "w").write(disk_data)
   1.118 +    
   1.119 +    elif make_dfs_image:
   1.120 +    
   1.121 +        disk = makedfs.Disk()
   1.122 +        disk.new()
   1.123 +        
   1.124 +        catalogue = disk.catalogue()
   1.125 +        
   1.126 +        disk_files = []
   1.127 +        for name, load, exec_, data in files:
   1.128 +            disk_files.append(makedfs.File("$." + name, data, load, exec_, len(data)))
   1.129 +        
   1.130 +        catalogue.write("Castle Raider", disk_files)
   1.131 +        
   1.132 +        disk.file.seek(0, 0)
   1.133 +        disk_data = disk.file.read()
   1.134 +        open(out_file, "w").write(disk_data)
   1.135 +    
   1.136      # Exit
   1.137      sys.exit()
     2.1 --- a/loader.oph	Tue Apr 08 00:03:35 2014 +0200
     2.2 +++ b/loader.oph	Wed Apr 23 00:20:53 2014 +0200
     2.3 @@ -182,6 +182,7 @@
     2.4  
     2.5      lda #140
     2.6      jsr $fff4   ; *TAPE (reclaim any workspace used on high PAGE systems)
     2.7 +    clc         ; This is apparently needed if we switch from DFS to tape.
     2.8  
     2.9      ; Copy the panel onto bank 2.
    2.10      jsr copy_panel
     3.1 --- a/tools/__init__.py	Tue Apr 08 00:03:35 2014 +0200
     3.2 +++ b/tools/__init__.py	Wed Apr 23 00:20:53 2014 +0200
     3.3 @@ -1,1 +1,1 @@
     3.4 -__all__ = ["compress", "makeadf", "makelevels", "makesprites"]
     3.5 +__all__ = ["compress", "diskutils", "makeadf", "makedfs", "makelevels", "makesprites"]
     4.1 --- a/tools/makeadf.py	Tue Apr 08 00:03:35 2014 +0200
     4.2 +++ b/tools/makeadf.py	Wed Apr 23 00:20:53 2014 +0200
     4.3 @@ -20,172 +20,8 @@
     4.4  __version__ = "0.1"
     4.5  __license__ = "GNU General Public License (version 3 or later)"
     4.6  
     4.7 -import StringIO, struct, time
     4.8 -
     4.9 -class DiskError(Exception):
    4.10 -    pass
    4.11 -
    4.12 -
    4.13 -class Utilities:
    4.14 -
    4.15 -    # Little endian reading
    4.16 -    
    4.17 -    def _read_signed_word(self, s):
    4.18 -    
    4.19 -        return struct.unpack("<i", s)[0]
    4.20 -    
    4.21 -    def _read_unsigned_word(self, s):
    4.22 -    
    4.23 -        return struct.unpack("<I", s)[0]
    4.24 -    
    4.25 -    def _read_signed_byte(self, s):
    4.26 -    
    4.27 -        return struct.unpack("<b", s)[0]
    4.28 -    
    4.29 -    def _read_unsigned_byte(self, s):
    4.30 -    
    4.31 -        return struct.unpack("<B", s)[0]
    4.32 -    
    4.33 -    def _read_unsigned_half_word(self, s):
    4.34 -    
    4.35 -        return struct.unpack("<H", s)[0]
    4.36 -    
    4.37 -    def _read_signed_half_word(self, s):
    4.38 -    
    4.39 -        return struct.unpack("<h", s)[0]
    4.40 -    
    4.41 -    def _read(self, offset, length = 1):
    4.42 -    
    4.43 -        self.file.seek(offset, 0)
    4.44 -        return self.file.read(length)
    4.45 -    
    4.46 -    def _write(self, offset, data):
    4.47 -    
    4.48 -        self.file.seek(offset, 0)
    4.49 -        self.file.write(data)
    4.50 -    
    4.51 -    def _str2num(self, s):
    4.52 -    
    4.53 -        i = 0
    4.54 -        n = 0
    4.55 -        while i < len(s):
    4.56 -        
    4.57 -            n = n | (ord(s[i]) << (i*8))
    4.58 -            i = i + 1
    4.59 -        
    4.60 -        return n
    4.61 -    
    4.62 -    def _binary(self, size, n):
    4.63 -    
    4.64 -        new = ""
    4.65 -        while (n != 0) & (size > 0):
    4.66 -        
    4.67 -            if (n & 1)==1:
    4.68 -                new = "1" + new
    4.69 -            else:
    4.70 -                new = "0" + new
    4.71 -            
    4.72 -            n = n >> 1
    4.73 -            size = size - 1
    4.74 -        
    4.75 -        if size > 0:
    4.76 -            new = ("0"*size) + new
    4.77 -        
    4.78 -        return new
    4.79 -    
    4.80 -    def _safe(self, s, with_space = 0):
    4.81 -    
    4.82 -        new = ""
    4.83 -        if with_space == 1:
    4.84 -            lower = 31
    4.85 -        else:
    4.86 -            lower = 32
    4.87 -        
    4.88 -        for c in s:
    4.89 -        
    4.90 -            if ord(c) >= 128:
    4.91 -                i = ord(c) ^ 128
    4.92 -                c = chr(i)
    4.93 -            
    4.94 -            if ord(c) <= lower:
    4.95 -                break
    4.96 -            
    4.97 -            new = new + c
    4.98 -        
    4.99 -        return new
   4.100 -
   4.101 -
   4.102 -class Directory:
   4.103 -
   4.104 -    """directory = Directory(name, address)
   4.105 -    
   4.106 -    The directory created contains name and files attributes containing the
   4.107 -    directory name and the objects it contains.
   4.108 -    """
   4.109 -    
   4.110 -    def __init__(self, name, files):
   4.111 -    
   4.112 -        self.name = name
   4.113 -        self.files = files
   4.114 -    
   4.115 -    def __repr__(self):
   4.116 -    
   4.117 -        return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self))
   4.118 -
   4.119 -
   4.120 -class File:
   4.121 -
   4.122 -    """file = File(name, data, load_address, execution_address, length)
   4.123 -    """
   4.124 -    
   4.125 -    def __init__(self, name, data, load_address, execution_address, length):
   4.126 -    
   4.127 -        self.name = name
   4.128 -        self.data = data
   4.129 -        self.load_address = load_address
   4.130 -        self.execution_address = execution_address
   4.131 -        self.length = length
   4.132 -    
   4.133 -    def __repr__(self):
   4.134 -    
   4.135 -        return '<%s instance, "%s", at %x>' % (self.__class__, self.name, id(self))
   4.136 -    
   4.137 -    def has_filetype(self):
   4.138 -    
   4.139 -        """Returns True if the file's meta-data contains filetype information."""
   4.140 -        return self.load_address & 0xfff00000 == 0xfff00000
   4.141 -    
   4.142 -    def filetype(self):
   4.143 -    
   4.144 -        """Returns the meta-data containing the filetype information.
   4.145 -        
   4.146 -        Note that a filetype can be obtained for all files, though it may not
   4.147 -        necessarily be valid. Use has_filetype() to determine whether the file
   4.148 -        is likely to have a valid filetype."""
   4.149 -        
   4.150 -        return "%03x" % ((self.load_address >> 8) & 0xfff)
   4.151 -    
   4.152 -    def time_stamp(self):
   4.153 -    
   4.154 -        """Returns the time stamp for the file as a tuple of values containing
   4.155 -        the local time, or an empty tuple if the file does not have a time stamp."""
   4.156 -        
   4.157 -        # RISC OS time is given as a five byte block containing the
   4.158 -        # number of centiseconds since 1900 (presumably 1st January 1900).
   4.159 -        
   4.160 -        # Convert the time to the time elapsed since the Epoch (assuming
   4.161 -        # 1970 for this value).
   4.162 -        date_num = struct.unpack("<Q",
   4.163 -            struct.pack("<IBxxx", self.execution_address, self.load_address & 0xff))[0]
   4.164 -        
   4.165 -        centiseconds = date_num - between_epochs
   4.166 -        
   4.167 -        # Convert this to a value in seconds and return a time tuple.
   4.168 -        try:
   4.169 -            return time.localtime(centiseconds / 100.0)
   4.170 -        except ValueError:
   4.171 -            return ()
   4.172 -
   4.173 +import StringIO
   4.174 +from diskutils import Directory, DiskError, File, Utilities
   4.175  
   4.176  class Catalogue(Utilities):
   4.177  
   4.178 @@ -195,42 +31,97 @@
   4.179      
   4.180          self.file = file
   4.181          self.sector_size = 256
   4.182 +        
   4.183 +        # The free space map initially contains all the space after the map
   4.184 +        # itself and the first directory catalogue.
   4.185 +        self.free_space = [(7, 1273)]
   4.186 +        self.level3_sector = 0
   4.187 +        self.disc_size = 1280
   4.188 +        self.disc_id = 0
   4.189 +        self.boot_option = 0
   4.190      
   4.191      def read_free_space(self):
   4.192      
   4.193 -        # Currently unused
   4.194 +        # Using notes from http://mdfs.net/Docs/Comp/Disk/Format/ADFS
   4.195          
   4.196 +        self.free_space = []
   4.197          base = 0
   4.198 -        free_space = []
   4.199          p = 0
   4.200          while self._read(base + p) != chr(0):
   4.201          
   4.202 -            free_space.append(self._str2num(self._read(base + p, 3)))
   4.203 +            self.free_space.append([self._str2num(self._read(base + p, 3))])
   4.204              p += 3
   4.205          
   4.206 -        name = self._read(self.sector_size - 9, 5)
   4.207 +        # Level 3 partition sector
   4.208 +        level3_sector = self._read(self.sector_size - 10, 2)
   4.209          
   4.210 -        disc_size = self._str2num(self._read(self.sector_size - 4, 3))
   4.211 +        self.disc_size = self._str2num(self._read(self.sector_size - 4, 3))
   4.212          
   4.213 -        checksum0 = self._read_unsigned_byte(self._read(self.sector_size-1))
   4.214 +        self.checksum0 = self._read_unsigned_byte(self._read(self.sector_size - 1))
   4.215          
   4.216          base = self.sector_size
   4.217          
   4.218          p = 0
   4.219 +        i = 0
   4.220          while self._read(base + p) != chr(0):
   4.221          
   4.222 -            free_space.append(self._str2num(self._read(base + p, 3)))
   4.223 +            self.free_space[i].append(self._str2num(self._read(base + p, 3)))
   4.224 +            p += 3
   4.225 +            i += 1
   4.226 +        
   4.227 +        self.disc_id = self._str2num(self._read(base + self.sector_size - 5, 2))
   4.228 +        
   4.229 +        self.boot_option = self._read_unsigned_byte(self._read(base + self.sector_size - 3))
   4.230 +        
   4.231 +        self.checksum1 = self._read_unsigned_byte(self._read(base + self.sector_size - 1))
   4.232 +    
   4.233 +    def write_free_space(self):
   4.234 +    
   4.235 +        base = 0
   4.236 +        
   4.237 +        p = 0
   4.238 +        for sector, length in self.free_space:
   4.239 +        
   4.240 +            if p == 0xf6:
   4.241 +                raise DiskError, "Too many free space entries."
   4.242 +            
   4.243 +            self._write(base + p, self._num2str(3, sector))
   4.244              p += 3
   4.245          
   4.246 -        name = name + self._read(base + self.sector_size - 10, 5)
   4.247 +        while p < 0xf6:
   4.248 +            self._write(base + p, self._num2str(3, 0))
   4.249 +            p += 3
   4.250          
   4.251 -        disc_id = self._str2num(self._read(base + self.sector_size - 5, 2))
   4.252 +        # Level 3 partition sector
   4.253 +        self._write(self.sector_size - 10, self._num2str(2, self.level3_sector))
   4.254          
   4.255 -        boot = self._read_unsigned_byte(self._read(base + self.sector_size - 3))
   4.256 +        self._write(self.sector_size - 4, self._num2str(3, self.disc_size))
   4.257          
   4.258 -        checksum1 = self._read_unsigned_byte(self._read(base + self.sector_size - 1))
   4.259 +        self.checksum0 = self._checksum(0)
   4.260 +        self._write(self.sector_size - 1, chr(self.checksum0))
   4.261          
   4.262 -        return free_space
   4.263 +        base = self.sector_size
   4.264 +        
   4.265 +        p = 0
   4.266 +        for sector, length in self.free_space:
   4.267 +        
   4.268 +            self._write(base + p, self._num2str(3, length))
   4.269 +            p += 3
   4.270 +        
   4.271 +        while p < 0xf6:
   4.272 +            self._write(base + p, self._num2str(3, 0))
   4.273 +            p += 3
   4.274 +        
   4.275 +        self._write(base + self.sector_size - 5, self._num2str(2, self.disc_id))
   4.276 +        
   4.277 +        # Boot option
   4.278 +        self._write(base + self.sector_size - 3, chr(self.boot_option))
   4.279 +        
   4.280 +        # Length of free space list
   4.281 +        self._write(base + self.sector_size - 2, chr(3 * len(self.free_space)))
   4.282 +        
   4.283 +        self.checksum1 = self._checksum(1)
   4.284 +        self._write(base + self.sector_size - 1, chr(self.checksum0))
   4.285      
   4.286      def read(self, offset = 512):
   4.287      
   4.288 @@ -257,24 +148,22 @@
   4.289              
   4.290              inddiscadd = self.sector_size * self._str2num(self._read(head + p + 22, 3))
   4.291              
   4.292 -            print hex(head + p), name, map(hex, (load, exe, length, inddiscadd))
   4.293 -            
   4.294              olddirobseq = self._read_unsigned_byte(self._read(head + p + 25))
   4.295              
   4.296              # [Needs more accurate check for directories.]
   4.297 -            if length == (self.sector_size * 5):
   4.298 +            if load == exe == 0 and length == (self.sector_size * 5):
   4.299              
   4.300                  # A directory has been found.
   4.301                  lower_dir_name, lower_files = self.read(inddiscadd)
   4.302 -                print "Entering", lower_dir_name
   4.303                  
   4.304                  files.append(Directory(name, lower_files))
   4.305              
   4.306              else:
   4.307              
   4.308 -                # A file has been found.
   4.309 +                # A file has been found. Treat it as unlocked for now.
   4.310                  data = self._read(inddiscadd, length)
   4.311 -                files.append(File(name, data, load, exe, length))
   4.312 +                files.append(File(name, data, load, exe, length, False,
   4.313 +                                  inddiscadd))
   4.314              
   4.315              p = p + 26
   4.316          
   4.317 @@ -303,8 +192,11 @@
   4.318          
   4.319          return dir_name, files
   4.320      
   4.321 -    def write(self, files, offset = 512):
   4.322 +    def write(self, dir_name, dir_title, files, offset, parent_address):
   4.323      
   4.324 +        if len(files) > 47:
   4.325 +            raise DiskError, "Too many entries to write."
   4.326 +        
   4.327          head = offset
   4.328          p = 0
   4.329          
   4.330 @@ -318,46 +210,36 @@
   4.331              self._write(head + p + 1, dir_start)
   4.332          
   4.333          p = p + 5
   4.334 +        i = 1
   4.335          
   4.336 -        files = []
   4.337 +        for file in files:
   4.338          
   4.339 -        while ord(self._read(head + p)) != 0:
   4.340 -        
   4.341 -            old_name = self.data[head+p:head+p+10]
   4.342 -            top_set = 0
   4.343 -            counter = 1
   4.344 -            for i in old_name:
   4.345 -                if (ord(i) & 128) != 0:
   4.346 -                    top_set = counter
   4.347 -                counter = counter + 1
   4.348 +            name = self._pad(file.name, 10, " ")
   4.349 +            self._write(head + p, name)
   4.350              
   4.351 -            name = self._safe(self.data[head+p:head+p+10])
   4.352 +            if isinstance(file, File):
   4.353 +                load = file.load_address
   4.354 +                exe = file.execution_address
   4.355 +                length = file.length
   4.356 +            else:
   4.357 +                load = exe = 0
   4.358 +                length = 5 * self.sector_size
   4.359              
   4.360 -            load = self._read_unsigned_word(self.data[head+p+10:head+p+14])
   4.361 -            exe = self._read_unsigned_word(self.data[head+p+14:head+p+18])
   4.362 -            length = self._read_unsigned_word(self.data[head+p+18:head+p+22])
   4.363 +            self._write(head + p + 10, self._write_unsigned_word(load))
   4.364 +            self._write(head + p + 14, self._write_unsigned_word(exe))
   4.365 +            self._write(head + p + 18, self._write_unsigned_word(length))
   4.366              
   4.367 -            inddiscadd = self.sector_size * self._str2num(
   4.368 -                3, self.data[head+p+22:head+p+25]
   4.369 -                )
   4.370 +            disc_address = self._find_space(file)
   4.371 +            inddiscadd = disc_address / self.sector_size
   4.372 +            self._write(head + p + 22, self._num2str(3, inddiscadd))
   4.373              
   4.374 -            olddirobseq = self._read_unsigned_byte(self.data[head+p+25])
   4.375 +            self._write(disc_address, file.data)
   4.376              
   4.377 -            # [Needs more accurate check for directories.]
   4.378 -            if (load == 0 and exe == 0 and top_set > 2) or \
   4.379 -                (top_set > 0 and length == (self.sector_size * 5)):
   4.380 +            olddirobseq = i
   4.381 +            self._write(head + p + 25, chr(olddirobseq))
   4.382              
   4.383 -                # A directory has been found.
   4.384 -                lower_dir_name, lower_files = \
   4.385 -                    self._read_old_catalogue(inddiscadd)
   4.386 -                
   4.387 -                files.append(Directory(name, lower_files))
   4.388 -            
   4.389 -            else:
   4.390 -            
   4.391 -                # A file has been found.
   4.392 -                data = self.data[inddiscadd:inddiscadd+length]
   4.393 -                files.append(File(name, data, load, exe, length))
   4.394 +            if isinstance(file, Directory):
   4.395 +                self.write(file.files, disc_address)
   4.396              
   4.397              p = p + 26
   4.398          
   4.399 @@ -365,32 +247,60 @@
   4.400          
   4.401          tail = head + (self.sector_size*4)
   4.402          
   4.403 -        dir_end = self.data[tail+self.sector_size-5:tail+self.sector_size-1]
   4.404 +        dir_end = self._read(tail + self.sector_size - 5, 4)
   4.405          
   4.406          if dir_end not in self.DirMarkers:
   4.407 -            raise DiskError, 'Discrepancy in directory structure: [%x, %x]' % (head, tail)
   4.408 +            dir_end = "Hugo"
   4.409 +            self._write(tail + self.sector_size - 5, dir_end)
   4.410          
   4.411 -        # Read the directory name, its parent and any title given.
   4.412 +        # Write the directory name, its parent and any title given.
   4.413          
   4.414 -        dir_name = self._safe(
   4.415 -            self.data[tail+self.sector_size-52:tail+self.sector_size-42]
   4.416 -            )
   4.417 +        dir_name = self._pad(self._safe(dir_name), 10, " ")
   4.418 +        self._write(tail + self.sector_size - 52, dir_name)
   4.419          
   4.420 -        parent = self.sector_size*self._str2num(
   4.421 -            3,
   4.422 -            self.data[tail+self.sector_size-42:tail+self.sector_size-39]
   4.423 -            )
   4.424 +        self._write(tail + self.sector_size - 42,
   4.425 +                    self._num2str(3, parent_address / self.sector_size))
   4.426          
   4.427 -        dir_title = self._safe(
   4.428 -            self.data[tail+self.sector_size-39:tail+self.sector_size-20]
   4.429 -            )
   4.430 +        dir_title = self._pad(self._safe(dir_title), 19, " ")
   4.431 +        self._write(tail + self.sector_size - 39, dir_title)
   4.432          
   4.433 -        endseq = self.data[tail+self.sector_size-6]
   4.434 +        endseq = dir_seq
   4.435 +        self._write(tail + self.sector_size - 6, chr(endseq))
   4.436 +    
   4.437 +    def _find_space(self, file):
   4.438 +    
   4.439 +        for i in range(len(self.free_space)):
   4.440          
   4.441 -        if endseq != dir_seq:
   4.442 -            raise DiskError, 'Broken directory: %s at [%x, %x]' % (dir_title, head, tail)
   4.443 +            sector, length = self.free_space[i]
   4.444 +            file_length = file.length/self.sector_size
   4.445 +            
   4.446 +            if file.length % self.sector_size != 0:
   4.447 +                file_length += 1
   4.448 +            
   4.449 +            if length >= file_length:
   4.450 +            
   4.451 +                if length > file_length:
   4.452 +                    # Update the free space entry to contain the remaining space.
   4.453 +                    self.free_space[i] = (sector + file_length, length - file_length)
   4.454 +                else:
   4.455 +                    # Remove the free space entry.
   4.456 +                    del self.free_space[i]
   4.457 +                
   4.458 +                return sector * self.sector_size
   4.459          
   4.460 -        return dir_name, files
   4.461 +        raise DiskError, "Failed to find space for file: %s" % file.name
   4.462 +    
   4.463 +    def _checksum(self, sector):
   4.464 +    
   4.465 +        v = 255
   4.466 +        i = 254
   4.467 +        while i >= 0:
   4.468 +            if v > 255:
   4.469 +                v = (v + 1) % 255
   4.470 +            v += ord(self._read((sector * self.sector_size) + i))
   4.471 +            i -= 1
   4.472 +        
   4.473 +        return v % 255
   4.474  
   4.475  
   4.476  class Disk:
   4.477 @@ -406,7 +316,7 @@
   4.478      def new(self):
   4.479      
   4.480          self.size = self.DiskSizes[self.format]
   4.481 -        self.data = "\x00" * size
   4.482 +        self.data = "\x00" * self.size
   4.483          self.file = StringIO.StringIO(self.data)
   4.484      
   4.485      def open(self, file_object):
   4.486 @@ -414,8 +324,7 @@
   4.487          self.size = self.DiskSizes[self.format]
   4.488          self.file = file_object
   4.489      
   4.490 -    def read_catalogue(self):
   4.491 +    def catalogue(self):
   4.492      
   4.493          sector_size = self.SectorSizes[self.format]
   4.494 -        catalogue = self.Catalogues[self.format](self.file)
   4.495 -        return catalogue.read()
   4.496 +        return self.Catalogues[self.format](self.file)
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/tools/makedfs.py	Wed Apr 23 00:20:53 2014 +0200
     5.3 @@ -0,0 +1,193 @@
     5.4 +"""
     5.5 +Copyright (C) 2014 David Boddie <david@boddie.org.uk>
     5.6 +
     5.7 +This program is free software: you can redistribute it and/or modify
     5.8 +it under the terms of the GNU General Public License as published by
     5.9 +the Free Software Foundation, either version 3 of the License, or
    5.10 +(at your option) any later version.
    5.11 +
    5.12 +This program is distributed in the hope that it will be useful,
    5.13 +but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.14 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.15 +GNU General Public License for more details.
    5.16 +
    5.17 +You should have received a copy of the GNU General Public License
    5.18 +along with this program.  If not, see <http://www.gnu.org/licenses/>.
    5.19 +"""
    5.20 +
    5.21 +__author__ = "David Boddie <david@boddie.org.uk>"
    5.22 +__date__ = "2014-04-22"
    5.23 +__version__ = "0.1"
    5.24 +__license__ = "GNU General Public License (version 3 or later)"
    5.25 +
    5.26 +import StringIO
    5.27 +from diskutils import Directory, DiskError, File, Utilities
    5.28 +
    5.29 +class Catalogue(Utilities):
    5.30 +
    5.31 +    def __init__(self, file):
    5.32 +    
    5.33 +        self.file = file
    5.34 +        self.sector_size = 256
    5.35 +        
    5.36 +        # The free space map initially contains all the space after the
    5.37 +        # catalogue.
    5.38 +        self.free_space = [(2, 798)]
    5.39 +        self.sectors = 800
    5.40 +        self.disk_cycle = 0
    5.41 +        self.boot_option = 0
    5.42 +    
    5.43 +    def read_free_space(self):
    5.44 +    
    5.45 +        # Using notes from http://mdfs.net/Docs/Comp/Disk/Format/DFS
    5.46 +        
    5.47 +        self.free_space = []
    5.48 +    
    5.49 +    def read(self):
    5.50 +    
    5.51 +        disk_title = self._read(0, 8) + self._read(0x100, 4)
    5.52 +        self.disk_cycle = self._read_unsigned_byte(self._read(0x104, 1))
    5.53 +        last_entry = self._read_unsigned_byte(self._read(0x105, 1))
    5.54 +        extra = self._read_unsigned_byte(self._read(0x106, 1))
    5.55 +        sectors = self._read_unsigned_byte(self._read(0x107, 1))
    5.56 +        self.sectors = sectors | ((extra & 0x03) << 8)
    5.57 +        self.boot_option = (extra & 0x30) >> 4
    5.58 +        
    5.59 +        files = []
    5.60 +        p = 8
    5.61 +        
    5.62 +        while p <= last_entry:
    5.63 +        
    5.64 +            name = self._read(p, 7)
    5.65 +            if name[0] == "\x00":
    5.66 +                break
    5.67 +            
    5.68 +            name = name.strip()
    5.69 +            extra = self._read_unsigned_byte(self._read(p + 7))
    5.70 +            prefix = chr(extra & 0x7f)
    5.71 +            locked = (extra & 0x80) != 0
    5.72 +            
    5.73 +            load = self._read_unsigned_half_word(self._read(0x100 + p, 2))
    5.74 +            exec_ = self._read_unsigned_half_word(self._read(0x100 + p + 2, 2))
    5.75 +            length = self._read_unsigned_half_word(self._read(0x100 + p + 4, 2))
    5.76 +            
    5.77 +            extra = self._read_unsigned_byte(self._read(0x100 + p + 6))
    5.78 +            load = load | ((extra & 0x0c) << 14)
    5.79 +            exec_ = exec_ | ((extra & 0x30) << 12)
    5.80 +            length = length | ((extra & 0xc0) << 10)
    5.81 +            
    5.82 +            file_start_sector = self._read_unsigned_byte(self._read(0x100 + p + 7))
    5.83 +            file_start_sector = file_start_sector | ((extra & 0x03) << 8)
    5.84 +            
    5.85 +            data = self._read(file_start_sector * self.sector_size, length)
    5.86 +            
    5.87 +            files.append(File(prefix + "." + name, data, load, exec_, length, locked,
    5.88 +                              file_start_sector * self.sector_size))
    5.89 +            
    5.90 +            p += 8
    5.91 +        
    5.92 +        return disk_title, files
    5.93 +    
    5.94 +    def write(self, disk_title, files):
    5.95 +    
    5.96 +        if len(files) > 31:
    5.97 +            raise DiskError, "Too many entries to write."
    5.98 +        
    5.99 +        disk_name = self._pad(self._safe(disk_title), 12, " ")
   5.100 +        self._write(0, disk_title[:8])
   5.101 +        self._write(0x100, disk_title[8:12])
   5.102 +        
   5.103 +        # Write the number of files and the disk cycle.
   5.104 +        self.disk_cycle += 1
   5.105 +        self._write(0x104, self._write_unsigned_byte(self.disk_cycle))
   5.106 +        self._write(0x105, len(files) * 8)
   5.107 +        
   5.108 +        extra = (self.sectors >> 8) & 0x03
   5.109 +        extra = extra | (self.boot_option << 4)
   5.110 +        self._write(0x106, self._write_unsigned_byte(extra))
   5.111 +        self._write(0x107, self._write_unsigned_byte(self.sectors & 0xff))
   5.112 +        
   5.113 +        p = 8
   5.114 +        for file in files:
   5.115 +        
   5.116 +            prefix, name = file.name.split(".")
   5.117 +            name = self._pad(name, 7, " ")
   5.118 +            self._write(p, name)
   5.119 +            
   5.120 +            extra = ord(prefix)
   5.121 +            if file.locked:
   5.122 +                extra = extra | 128
   5.123 +            
   5.124 +            self._write(p + 7, self._write_unsigned_byte(extra))
   5.125 +            
   5.126 +            load = file.load_address
   5.127 +            exec_ = file.execution_address
   5.128 +            length = file.length
   5.129 +            
   5.130 +            self._write(0x100 + p, self._write_unsigned_half_word(load & 0xffff))
   5.131 +            self._write(0x100 + p + 2, self._write_unsigned_half_word(exec_ & 0xffff))
   5.132 +            self._write(0x100 + p + 4, self._write_unsigned_half_word(length & 0xffff))
   5.133 +            
   5.134 +            disk_address = self._find_space(file)
   5.135 +            file_start_sector = disk_address / self.sector_size
   5.136 +            self._write(disk_address, file.data)
   5.137 +            
   5.138 +            extra = ((file_start_sector >> 8) & 0x03)
   5.139 +            extra = extra | ((load >> 14) & 0x0c)
   5.140 +            extra = extra | ((exec_ >> 12) & 0x30)
   5.141 +            extra = extra | ((length >> 10) & 0xc0)
   5.142 +            
   5.143 +            self._write(0x100 + p + 6, self._write_unsigned_byte(extra))
   5.144 +            self._write(0x100 + p + 7, self._write_unsigned_byte(file_start_sector & 0xff))
   5.145 +            
   5.146 +            p += 8
   5.147 +    
   5.148 +    def _find_space(self, file):
   5.149 +    
   5.150 +        for i in range(len(self.free_space)):
   5.151 +        
   5.152 +            sector, length = self.free_space[i]
   5.153 +            file_length = file.length/self.sector_size
   5.154 +            
   5.155 +            if file.length % self.sector_size != 0:
   5.156 +                file_length += 1
   5.157 +            
   5.158 +            if length >= file_length:
   5.159 +            
   5.160 +                if length > file_length:
   5.161 +                    # Update the free space entry to contain the remaining space.
   5.162 +                    self.free_space[i] = (sector + file_length, length - file_length)
   5.163 +                else:
   5.164 +                    # Remove the free space entry.
   5.165 +                    del self.free_space[i]
   5.166 +                
   5.167 +                return sector * self.sector_size
   5.168 +        
   5.169 +        raise DiskError, "Failed to find space for file: %s" % file.name
   5.170 +
   5.171 +
   5.172 +class Disk:
   5.173 +
   5.174 +    DiskSizes = {None: 200 * 1024}
   5.175 +    SectorSizes = {None: 256}
   5.176 +    Catalogues = {None: Catalogue}
   5.177 +    
   5.178 +    def __init__(self, format = None):
   5.179 +    
   5.180 +        self.format = format
   5.181 +    
   5.182 +    def new(self):
   5.183 +    
   5.184 +        self.size = self.DiskSizes[self.format]
   5.185 +        self.data = "\x00" * self.size
   5.186 +        self.file = StringIO.StringIO(self.data)
   5.187 +    
   5.188 +    def open(self, file_object):
   5.189 +    
   5.190 +        self.size = self.DiskSizes[self.format]
   5.191 +        self.file = file_object
   5.192 +    
   5.193 +    def catalogue(self):
   5.194 +    
   5.195 +        sector_size = self.SectorSizes[self.format]
   5.196 +        return self.Catalogues[self.format](self.file)