Class: DOSDisk

Inherits:
DSK
  • Object
show all
Defined in:
lib/DOSDisk.rb

Constant Summary

Constants inherited from DSK

DSK::DSK_FILE_LENGTH, DSK::DSK_IMAGE_EXTENSIONS, DSK::FILE_SYSTEMS, DSK::INTERLEAVES, DSK::NIB_FILE_LENGTH, DSK::SECTOR_ORDERS

Instance Attribute Summary collapse

Attributes inherited from DSK

#file_bytes, #sector_order, #source_filename, #track_count

Instance Method Summary collapse

Methods inherited from DSK

#best_subclass, create_new, #disassemble_sector, #dump_sector, #files, #get_block, #get_sector, #hex_dump, #is_cpm?, #is_dos33?, is_dsk_file?, #is_modified_dos?, #is_nadol?, #is_pascal?, #is_prodos?, read, #save_as, #set_boot_track

Constructor Details

#initialize(file_bytes, sector_order, vtoc_track_no = 0x11, vtoc_sector_no = 0) ⇒ DOSDisk

Returns a new instance of DOSDisk.



103
104
105
106
107
108
# File 'lib/DOSDisk.rb', line 103

def initialize(file_bytes,sector_order,vtoc_track_no=0x11,vtoc_sector_no=0)
	super(file_bytes,sector_order)
   @vtoc_track_no=vtoc_track_no
   @vtoc_sector_no=vtoc_sector_no
	self.read_vtoc
end

Instance Attribute Details

#vtoc_sector_noObject

Returns the value of attribute vtoc_sector_no.



87
88
89
# File 'lib/DOSDisk.rb', line 87

def vtoc_sector_no
  @vtoc_sector_no
end

#vtoc_track_noObject

Returns the value of attribute vtoc_track_no.



87
88
89
# File 'lib/DOSDisk.rb', line 87

def vtoc_track_no
  @vtoc_track_no
end

Instance Method Details

#add_file(file) ⇒ Object



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/DOSDisk.rb', line 316

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),256] || ""
  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

#delete_file(filename) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/DOSDisk.rb', line 268

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(vtoc_track_no,vtoc_sector_no)
  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(vtoc_track_no,vtoc_sector_no,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

#dump_catalogObject



88
89
90
91
92
93
94
95
# File 'lib/DOSDisk.rb', line 88

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

#file_systemObject



97
98
99
100
# File 'lib/DOSDisk.rb', line 97

def file_system
	return :dos if (vtoc_track_no==0x11) && (vtoc_sector_no==0)
   :modified_dos
end

#find_catalog_slot(filename) ⇒ Object

iterate through the CATALOG to find either the named file or (if nil is passed in) an empty slot



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/DOSDisk.rb', line 208

def find_catalog_slot(filename)
  vtoc_sector=get_sector(vtoc_track_no,vtoc_sector_no)
  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

#free_sector_listObject

iterate through the sector usage bitmap, return a list of [track,sector] for sectors marked available



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/DOSDisk.rb', line 233

def free_sector_list
  end_of_sector_usage_bitmap=(track_count*4+0x38)-1
  sector_usage_bitmap=get_sector(vtoc_track_no,vtoc_sector_no)[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

#get_track_sector_list(ts_list_track_no, ts_list_sector_no) ⇒ Object

given a track and sector, treat it as a track/sector list and return an array containing track/sector pairs



255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/DOSDisk.rb', line 255

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

#make_file(filename, contents, file_options = {}) ⇒ Object

default file type is TextFile Tokenisation not currently implemented



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/DOSDisk.rb', line 112

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

#read_vtocObject

reads the VTOC, and populate the “files” array with files



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/DOSDisk.rb', line 137

def read_vtoc
   @files={}
	vtoc_sector=get_sector(vtoc_track_no,vtoc_sector_no)
	catalog_sector=get_sector(vtoc_sector[01],vtoc_sector[02])
	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,36]					
			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.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
			
			
			if (sector_count>0) then
				contents=""
				ts_list_track_no=file_descriptive_entry[0]
				ts_list_sector_no=file_descriptive_entry[1]
				while (ts_list_track_no>0) && (ts_list_track_no<=0X22) && (ts_list_sector_no<=0x0f)
					ts_list_sector=get_sector(ts_list_track_no,ts_list_sector_no)
					ts_list_track_no=ts_list_sector[1]
					ts_list_sector_no=ts_list_sector[2]

					0x0C.step(0xff,2) {|i|						
						data_track_no=ts_list_sector[i]
						data_sector_no=ts_list_sector[i+1]
						if (data_track_no>0) && (data_track_no<=0X22) && (data_sector_no<=0x0f) then
							contents+=get_sector(data_track_no,data_sector_no)
						end
					}
				end
				if contents.length>0 then
					@files[filename]= case file_type_code
						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,contents,locked,file_type_code)
					end
				end
			end
		}
		next_track=catalog_sector[1]		
		next_sector=catalog_sector[2]%0x10
		if (next_track==0) &&( next_sector==0) then
			done=true
		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

#set_sector(track, sector, contents) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/DOSDisk.rb', line 298

def set_sector(track,sector,contents)
  super(track,sector,contents)  
  
  #now mark sector as used in sector usage list  
  if ((track!=vtoc_track_no) || (sector!=vtoc_sector_no)) then #don't bother marking the VTOC sectors used
    sector_usage_bitmap_sector=get_sector(vtoc_track_no,vtoc_sector_no)  
    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(vtoc_track_no,vtoc_sector_no,sector_usage_bitmap_sector)
  end
end