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)
