lib/DSK.rb in dsktool-0.4.2 vs lib/DSK.rb in dsktool-0.5.1

- old
+ new

@@ -8,25 +8,26 @@ # For manipulating DSK files, as created by ADT (http://adt.berlios.de) and ADTPRo (http://adtpro.sourceforge.net) # used by many Apple 2 emulators. # class DSK - FILE_SYSTEMS=[:prodos,:dos33,:nadol,:pascal,:unknown,:none] + FILE_SYSTEMS=[:prodos,:dos33,:nadol,:cpm,:pascal,:modified_dos,:unknown,:none] SECTOR_ORDERS=[:physical,:dos] - DSK_IMAGE_EXTENSIONS=[".dsk",".po",".do",".hdv"] + DSK_IMAGE_EXTENSIONS=[".dsk",".po",".do",".hdv",".nib"] INTERLEAVES={ :physical=> [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F], - :dos=>[0x00,0x0E,0x0D,0x0C,0x0B,0x0A,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x0F] + :dos=>[0x00,0x0E,0x0D,0x0C,0x0B,0x0A,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x0F], } #does this filename have a suitable extension? def DSK.is_dsk_file?(filename) extension=File.extname(File.basename(filename,".gz")).downcase DSK_IMAGE_EXTENSIONS.include?(extension) end DSK_FILE_LENGTH=143360 - attr_accessor :file_bytes,:sector_order,:track_count + NIB_FILE_LENGTH=232960 + attr_accessor :file_bytes,:sector_order,:track_count,:source_filename def file_system :unknown end @@ -61,16 +62,42 @@ # see if these are reasonable values vtoc_sector=get_sector(0x11,0) (vtoc_sector[01]<=34) && (vtoc_sector[02]<=15) && (vtoc_sector[0x27]==0x7A) && (vtoc_sector[0x35]==0x10) end + def is_modified_dos?(vtoc_track_no,vtoc_sector_no) + vtoc_sector=get_sector(vtoc_track_no,vtoc_sector_no) + (vtoc_sector[01]<=34) && (vtoc_sector[02]<=15) && (vtoc_sector[0x27]==0x7A) && (vtoc_sector[0x35]==0x10) + end + def is_nadol?(sector_order) #currently ignores sector order # track $00, sector $02 , bytes $11 - "NADOL" (@file_bytes[0x00211..0x00215]=="NADOL") end + def is_cpm?(sector_order) + #currently ignores sector order + #look for a valid looking CPM directory on track 3. + #go through each sector in order, for each sector, look at every 32nd byte, and see if it is a valid 'user number' (i.e. a number from 00..0F). Stop looking when you see an 'E5'. + #if an invalid user number is found before an E5, then the disk is NOT a CPM disk + found_0xE5_byte=false + [0x0,0x6,0xC,0x3,0x9,0xF,0xE,0x5].each do |sector_no| + sector=get_sector(3,INTERLEAVES[sector_order][sector_no]) + [0x00,0x20,0x40,0x60,0x80,0xA0,0xC0,0xE0].each do |byte_number| + if (sector[byte_number]>0x0F && sector[byte_number]!=0xe5 && sector[byte_number]!=0x1F) then + # puts "found #{sprintf '%02x',sector[byte_number]} at #{sprintf '%02x', byte_number} sector #{sprintf '%02x', sector_no}" + return false + end + if (sector[byte_number]==0xe5) then + found_0xE5_byte=true + end + end + end + return found_0xE5_byte #if we've only seen 00 bytes, then it's not really a CPM + end + def is_prodos?(sector_order) #block $02 - bytes $00/$01 are both $00, byte $04 is $F?, #bytes $29-$2A = sectors ( 0x118 on a 35 track 5.25" disk) first_sector_in_block_2=INTERLEAVES[sector_order][4] first_sector_in_block_2=get_sector(0,4,sector_order) @@ -93,13 +120,14 @@ raise "DSK files must be #{DSK_FILE_LENGTH} bytes long (was #{file_bytes.length} bytes)" end @file_bytes=file_bytes @files={} @sector_order=sector_order - @track_count=file_bytes.length/4096 + @track_count=file_bytes.length/4096 + @source_filename="(unknown)" end - + #write out DSK to file def save_as(filename) if !(filename=~/\.gz$/).nil? then require 'zlib' f=Zlib::GzipWriter.new(open(filename,"wb")) @@ -117,78 +145,100 @@ filesystem=:dos33 if filesystem==:dos case filesystem when :none return DSK.new() + when :cpm + require 'CPMDisk' + return CPMDisk.new("\xe5"*DSK_FILE_LENGTH,:physical) when :nadol return DSK.read(File.dirname(__FILE__)+"/nadol_blank.po.gz") when :dos33 return DSK.read(File.dirname(__FILE__)+"/dos33_blank.dsk.gz") else raise "initialisation of #{filesystem} file system not currently supported" end end - - - #read in an existing DSK file (must exist) - def DSK.read(filename) - #is the file extension .gz? - if !(filename=~/\.gz$/).nil? then - require 'zlib' - file_bytes=Zlib::GzipReader.new(open(filename,"rb")).read - else - file_bytes=open(filename,"rb").read - end - - - dsk=DSK.new(file_bytes) - SECTOR_ORDERS.each do |sector_order| - begin + + #for a generic DSK,return an instance of subclass representing the the best match file system + def best_subclass + SECTOR_ORDERS.each do |sector_order| + begin candidate_filesystem="unknown" - if (dsk.is_dos33?(sector_order)) - require 'DOSDisk' + if (self.is_dos33?(sector_order)) + require 'DOSDisk' candidate_filesystem="DOS 3.3" - dsk=DOSDisk.new(file_bytes,sector_order) - break + return DOSDisk.new(self.file_bytes,sector_order) end - if (dsk.is_nadol?(sector_order)) - require 'NADOLDisk' + if (self.is_nadol?(sector_order)) + require 'NADOLDisk' candidate_filesystem="NADOL" - dsk=NADOLDisk.new(file_bytes,sector_order) - break + return NADOLDisk.new(self.file_bytes,sector_order) end - - if (dsk.is_prodos?(sector_order)) - require 'ProDOSDisk' - candidate_filesystem="ProDOS" - dsk=ProDOSDisk.new(file_bytes,sector_order) - break + + if (self.is_prodos?(sector_order)) + require 'ProDOSDisk' + candidate_filesystem="ProDOS" + return ProDOSDisk.new(self.file_bytes,sector_order) end - if (dsk.is_pascal?(sector_order)) - require 'PascalDisk' + if (self.is_pascal?(sector_order)) + require 'PascalDisk' candidate_filesystem="Pascal" - dsk=PascalDisk.new(file_bytes,sector_order) - break + return PascalDisk.new(self.file_bytes,sector_order) end - - rescue Exception=>e - STDERR<<"error while parsing #{filename} as #{candidate_filesystem} (sector order #{sector_order}\n" - STDERR<<"#{e}\n" - STDERR<<e.backtrace.join("\n") - end + + if (self.is_cpm?(sector_order)) + require 'CPMDisk' + candidate_filesystem="CP/M" + return CPMDisk.new(self.file_bytes,sector_order) + end + rescue Exception=>e + STDERR<<"error while parsing #{self.source_filename} as #{candidate_filesystem} (sector order #{sector_order}\n" + STDERR<<"#{e}\n" + STDERR<<e.backtrace.join("\n") + end end - dsk + + #if none of the above matched, look for a DOS image with a VTOC in the wrong spot + 0.upto(0x22) do |track| + if (self.is_modified_dos?(track,0)) + require 'DOSDisk' + candidate_filesystem="MODIFIED DOS" + return DOSDisk.new(self.file_bytes,:physical,track,0) + end + end + #if we didn't find a better match, return self + self + end + #read in an existing DSK file (must exist) + def DSK.read(filename) + #is the file extension .gz? + if !(filename=~/\.gz$/).nil? then + require 'zlib' + file_bytes=Zlib::GzipReader.new(open(filename,"rb")).read + else + file_bytes=open(filename,"rb").read + end + + if (file_bytes.length-NIB_FILE_LENGTH).abs<=1 then + require 'Nibbles' + dsk=Nibbles.make_dsk_from_nibbles(file_bytes) + else + dsk=DSK.new(file_bytes) + end + dsk.source_filename=filename + dsk.best_subclass end def get_sector(track,requested_sector,sector_order=@sector_order) raise "bad sector #{requested_sector}" unless requested_sector.between?(0,0x0F) raise "bad sector_order #{sector_order}" if INTERLEAVES[sector_order].nil? physical_sector=INTERLEAVES[sector_order][requested_sector] start_byte=track*16*256+physical_sector*256 - @file_bytes[start_byte..start_byte+255] + @file_bytes[start_byte,256] end def set_sector(track,sector,contents) physical_sector=INTERLEAVES[@sector_order][sector] start_byte=track*16*256+physical_sector*256 @@ -203,10 +253,10 @@ def set_boot_track(contents) sectors_needed=(contents.length / 256)+1 raise "boot code can't exceed 16 sectors" if sectors_needed>16 s=sectors_needed.chr+contents for sector in 0..sectors_needed-1 - sector_data=s[sector*256..255+sector*256] + sector_data=s[sector*256,256] set_sector(0,sector,sector_data) end end def get_block(block_no)