lib/DOSDisk.rb in dsktool-0.2.1 vs lib/DOSDisk.rb in dsktool-0.4.1

- old
+ new

@@ -82,36 +82,72 @@ # 10-FF Up to 120 more track and sector pairs class DOSDisk < DSK - def dump_catalog - files.each_value { |file| - puts "#{file.locked ? '*':' '}#{file.file_type} #{sprintf('%03X',file.sector_count)} #{file.filename}" - } + def dump_catalog + s="" + files.keys.sort.each { |file_name| + file=files[file_name] + s<< "#{file.locked ? '*':' '}#{file.file_type} #{sprintf('%04d',file.contents.length)} #{file.filename}\n" + } + s end + + def file_system + :dos + end - def initialize(file_bytes) - super(file_bytes) + def initialize(file_bytes,sector_order) + super(file_bytes,sector_order) self.read_vtoc end - #reads the VTOC, and populate the "files" array with files + + #default file type is TextFile + #Tokenisation not currently implemented + def make_file(filename,contents,file_options={}) + raise "Tokenisation not currently supported for DOS files" if (file_options[:tokenise]) + file_type = case file_options[:filetype] + when nil then 0x00 + when 'T' then 0x00 + when 'I' then 0x01 + when 'A' then 0x02 + when 'B' then 0x04 + else file_options[:filetype].tr("$","").hex + end + if file_type==4 && !(file_options[:base].nil?) then + base=file_options[:base].tr("$","").hex + s="\0\0\0\0" + s[0]=base%256 + s[1]=base/256 + s[2]=contents.length%256 + s[3]=contents.length/256 + contents=s+contents + end + new_file=DOSFile.new(filename,contents,false,file_type) + return new_file +end + + #reads the VTOC, and populate the "files" array with files def read_vtoc + @files={} vtoc_sector=get_sector(17,0) catalog_sector=get_sector(vtoc_sector[01],vtoc_sector[02]) - done=false + done=false + visited_sectors={} while !done break if catalog_sector.nil? (0..6).each {|file_number| file_descriptive_entry_start=11+file_number*35 file_descriptive_entry=catalog_sector[file_descriptive_entry_start..file_descriptive_entry_start+35] break if (file_descriptive_entry[0]==0xFF) # skip deleted files filename="" - file_descriptive_entry[3..32].to_s.each_byte{|b| filename+=(b.%128).chr} - filename.sub!(/ *$/,"") #strip off trailing spaces + file_descriptive_entry[3..32].to_s.each_byte{|b| filename+=(b.%128).chr} + filename.gsub!(/ *$/,"") #strip off trailing spaces + filename.tr!("\x00-\x1f","\x40-\x5f") #convert non-printable chars to corresponding uppercase letter locked=(file_descriptive_entry[2]>=0x80) sector_count=file_descriptive_entry[0x21]+file_descriptive_entry[0x22]*256 file_type_code=file_descriptive_entry[2]%0x80 @@ -133,33 +169,227 @@ end } end if contents.length>0 then @files[filename]= case file_type_code - when 0x00 then TextFile.new(filename,locked,sector_count,contents) - when 0x01 then SCAsmFile.can_be_scasm_file?(contents)? SCAsmFile.new(filename,locked,sector_count,contents): IntegerBasicFile.new(filename,locked,sector_count,contents) - when 0x02 then AppleSoftFile.new(filename,locked,sector_count,contents) - when 0x04 then BinaryFile.new(filename,locked,sector_count,contents) + when 0x00 then TextFile.new(filename,contents,locked) + when 0x01 then SCAsmFile.can_be_scasm_file?(contents)? SCAsmFile.new(filename,contents,locked): IntegerBasicFile.new(filename,contents,locked) + when 0x02 then AppleSoftFile.new(filename,contents,locked) + when 0x04 then BinaryFile.new(filename,contents,locked) # when 0x08 then "S" #S type file # when 0x10 then "R" #RELOCATABLE object module file # when 0x20 then "a" #?? # when 0x40 then "b" #?? - else DOSFile.new(filename,locked,sector_count,contents,sprintf("$%02X",file_type_code)) + else DOSFile.new(filename,contents,locked,file_type_code) end end end } next_track=catalog_sector[1] - next_sector=catalog_sector[2] + next_sector=catalog_sector[2]%0x10 if (next_track==0) &&( next_sector==0) then done=true - else - catalog_sector=get_sector(next_track,next_sector) + else + #check we haven't got into an endless loop + s="#{next_track}/#{next_sector}" + if (!visited_sectors[s].nil?) then + done=true + end + visited_sectors[s]=true + catalog_sector=get_sector(next_track,next_sector) end end end +#iterate through the CATALOG to find either the named file or (if nil is passed in) an empty slot +def find_catalog_slot(filename) + vtoc_sector=get_sector(17,0) + catalog_filename=DOSFile.catalog_filename(filename.upcase) unless filename.nil? + catalog_track_no=vtoc_sector[01] + catalog_sector_no=vtoc_sector[02] + + while (catalog_track_no+catalog_sector_no>0) do + catalog=get_sector(catalog_track_no,catalog_sector_no) + (0..7).each do |slot_no| + slot_start=slot_no*0x23+0x0B + if filename.nil? && (catalog[slot_start]==0x00)|| (catalog[slot_start]==0xFF) then + return DSKTrackSector.new(catalog_track_no,catalog_sector_no,slot_start) + end + if (!filename.nil?) && (catalog[slot_start+0x03..slot_start+0x20]==catalog_filename) then + return DSKTrackSector.new(catalog_track_no,catalog_sector_no,slot_start) + end + end + catalog_track_no=catalog[01] + catalog_sector_no=catalog[02] + end + nil +end + +#iterate through the sector usage bitmap, return a list of [track,sector] for sectors marked available +def free_sector_list + end_of_sector_usage_bitmap=(track_count*4+0x38)-1 + sector_usage_bitmap=get_sector(0x11,0)[0x38..end_of_sector_usage_bitmap] + free_sectors=[] + #skip track 0 - even if sectors there are unused, we can't include them in a catalog or track/sector list + (1..track_count-1).each do |track| + track_bitmap_lo=sector_usage_bitmap[track*4+1] + track_bitmap_hi=sector_usage_bitmap[track*4] + (0..7).each do |sector| + if ((track_bitmap_lo & (2**(sector)))!=0) then + free_sectors<<DSKTrackSector.new(track,sector) + end + if ((track_bitmap_hi & (2**(sector)))!=0) then + free_sectors<<DSKTrackSector.new(track,sector+8) + end + end + end + free_sectors.sort +end + + + #given a track and sector, treat it as a track/sector list and return an array containing track/sector pairs + def get_track_sector_list(ts_list_track_no,ts_list_sector_no) + ts_list_sector=get_sector(ts_list_track_no,ts_list_sector_no) + ts_list=[] + for entry_number in 0..121 + data_track_no=ts_list_sector[entry_number*2+0x0C] + data_sector_no=ts_list_sector[entry_number*2+0x0D] + if( (data_track_no!=0 || data_sector_no!=0) && data_track_no<track_count && data_sector_no<=0x0f) then + ts_list<<DSKTrackSector.new(data_track_no,data_sector_no) + end + end + ts_list + end + + def delete_file(filename) + this_files_catalog_slot=find_catalog_slot(filename) + #if file not in catalog, do nothing + return if this_files_catalog_slot.nil? + file_descriptive_entry=get_sector(this_files_catalog_slot.track_no,this_files_catalog_slot.sector_no)[this_files_catalog_slot.offset..this_files_catalog_slot.offset+0x22] + + #mark sector as free in sector usage list + sector_usage_bitmap_sector=get_sector(0x11,0) + sectors_to_mark_available=get_track_sector_list(file_descriptive_entry[0x00],file_descriptive_entry[0x01]) + sectors_to_mark_available<<DSKTrackSector.new(file_descriptive_entry[0x01],file_descriptive_entry[0x00]) + + sectors_to_mark_available.each do |ts| + offset_of_byte_containing_this_sector=0x38+(ts.track_no*4) + if ts.sector_no<8 then + offset_of_byte_containing_this_sector+=1 + end + byte_containing_this_sector=sector_usage_bitmap_sector[offset_of_byte_containing_this_sector] + byte_containing_this_sector=byte_containing_this_sector|(2**(ts.sector_no%8)) + sector_usage_bitmap_sector[offset_of_byte_containing_this_sector]=byte_containing_this_sector + end + set_sector(0x11,0,sector_usage_bitmap_sector) + + #mark slot as available in catalog + catalog_sector=get_sector(this_files_catalog_slot.track_no,this_files_catalog_slot.sector_no) + catalog_sector[this_files_catalog_slot.offset+0x20]=catalog_sector[this_files_catalog_slot.offset] #save the current "first track no" in last byte of filename + catalog_sector[this_files_catalog_slot.offset]=0xFF + set_sector(this_files_catalog_slot.track_no,this_files_catalog_slot.sector_no,catalog_sector) + end + + + def set_sector(track,sector,contents) + super(track,sector,contents) + + #now mark sector as used in sector usage list + if ((track!=0x11) || (sector!=00)) then #don't bother marking the VTOC sectors used + sector_usage_bitmap_sector=get_sector(0x11,0) + offset_of_byte_containing_this_sector=0x38+(track*4) + if sector<8 then + offset_of_byte_containing_this_sector+=1 + end + byte_containing_this_sector=sector_usage_bitmap_sector[offset_of_byte_containing_this_sector] + byte_containing_this_sector=byte_containing_this_sector&(0xff-(2**(sector%8))) + sector_usage_bitmap_sector[offset_of_byte_containing_this_sector]=byte_containing_this_sector + set_sector(0x11,0,sector_usage_bitmap_sector) + end +end + + +def add_file(file) + raise "only DOSFiles may be added to DOS format disks!" unless file.kind_of?(DOSFile) + + #if this file exists, delete it first + delete_file(file.filename) unless files[file.filename].nil? + catalog_slot=find_catalog_slot(nil) + raise "CATALOG IS FULL!" if catalog_slot.nil? + free_sectors=free_sector_list + sectors_needed=1+file.length_in_sectors + raise "not enough free space - #{sectors_needed} sectors needed, #{free_sector_list.length} available " unless sectors_needed<=free_sectors.length + + #TODO - allow files of more than 122 sectors + raise "only files up to 122 sectors currently supported " if sectors_needed>122 + +#TRACK/SECTOR LIST FORMAT (from Beneath Apple DOS p 4-6) + # 00 Not used + # 01 Track number of next T/S list of one is needed or zero if no more t/s list + # 02 Sector number of next T/S list (if one is present) + # 03-04 Not used + # 05-06 Sector offset in file of the first sector described by this list + # 07-oB Not used + # 0C-0D Track and sector of first data sector or zeros + # 0E-0F Track and sector of second data sector or zeros + # 10-FF Up to 120 more track and sector pairs + track_sector_list="\0"*256 + track_sector_list_sector=free_sectors[0] + + (0..sectors_needed-2).each do |sector_in_file| + sector_to_use=free_sectors[sector_in_file+1] + track_sector_list[(sector_in_file*2)+0x0C]=sector_to_use.track_no + track_sector_list[(sector_in_file*2)+0X0D]=sector_to_use.sector_no + sector_contents=file.contents[(sector_in_file*256)..(sector_in_file*256)+255] || "" + set_sector(sector_to_use.track_no,sector_to_use.sector_no,sector_contents) + end + #write the track/sector list + set_sector(track_sector_list_sector.track_no,track_sector_list_sector.sector_no,track_sector_list) + + #update the catalog file descriptive entry + #FILE DESCRIPTIVE ENTRY (from Beneath Apple DOS p 4-6) + # 00 Track of first track/sector list sector, if this is a deleted file this contains FF + # and the original track number is copied to the last byte of the file name (BYTE 20) + # If this byte contains a 00, the entry is assumed to never have been used and is + # available for use. (This means track 0 can never be used for data even if the DOS image + # is 'wiped' from the disk) + # + # 01 Sector of first track/sector list sector + # 02 File type and flags: + # 80+file type - file is locked + # 00+file type - file is not locked + # + # 00 - TEXT file + # 01 - INTEGER BASIC file + # 02 - APPLESOFT BASIC file + # 04 - BINARY file + # 08 - S type file + # 10 - RELOCATABLE object module file + # 20 - a type file + # 40 - b type file + # + # 03-20 File Name (30 characters) + # 21-22 Length of file in sectors (LO/HI format) + + catalog_sector=get_sector(catalog_slot.track_no,catalog_slot.sector_no) + file_descriptive_entry="\0"*0x23 + file_descriptive_entry[0]=track_sector_list_sector.track_no + file_descriptive_entry[1]=track_sector_list_sector.sector_no + file_descriptive_entry[2]=file.file_type_byte + file_descriptive_entry[3..0x20]=file.catalog_filename + file_descriptive_entry[0x21]=(sectors_needed-1)%256 + file_descriptive_entry[0x22]=(sectors_needed-1)/256 + + catalog_sector[catalog_slot.offset..catalog_slot.offset+0x22]=file_descriptive_entry + + set_sector(catalog_slot.track_no,catalog_slot.sector_no,catalog_sector) + + raise "catalog not updated correctly!" if find_catalog_slot(file.filename).nil? + #reread the catalog to populate the files list + read_vtoc + + end end # == Author # Jonno Downes (jonno@jamtronix.com) # # == Copyright