summaryrefslogblamecommitdiffstatshomepage
path: root/src/tools/imgtool/modules/prodos.cpp
blob: f5b275376712d3ae2c699e640488b29e4be1f332 (plain) (tree)
1
2
3
4
5
                       
                                  

                                                                             
              






























































































































                                                                              

                                   
 
                      
 

                                                                                         



                                     

  
                     
 


                                       

  
                    

                          



                                
                     

                                   

                   





                               

                    




                                   

  
                      



                    
  


 
                                                                
 










                                                                              



 
                                                   

                    
                            




                                                     




                                                          




                      
                                     







                                      
                                                    






                                                                      
                                                          





                                                                  
                                                            





                                                                  
                                                   





                                                                
                                                              









                                                                             
                                                                   
                                                                              
 
                                        














                                                               
                                                                


                                
                                               




                                                                               
                                                           




                                                                     
                                                             







                                                  
                                                                


                                      
                                               




                                                                               
                                                                                                                 




                                                                      
                                                                                                                     







                                                  
                                                      










                                                                             
                                                                          
                                                          

















                                                                                                
                                                               



                                
                                     













                                                                                              
                                                               



                                      
                                     




                                                                         
                                                                                                                                               







                                                  
                                                     










                                                                             
                                                            








                                                          
                                                            








                                                          
                                                                        
 
                                   







                                                                             
                                                                

                         
                                   
                            
                           
















                                                                          



                                                                      














                                                                  
                                                                                                  






                                            
                                                                                                 








                                                                             
                                                                                      


                            

                                  



                                                                                     
                                                                    



























                                                                           
                                                                                           


                            
                                  
















                                                                                     
                                                                                    
 
                     









                                      
                                                                              
 
                     






                                        

                                                                              


                                              

                                     
















































                                                                                      
                                                                                                 

                         



                                                         
 



                                             





























                                                                                             
                                                                                                                                   






                                                    
                                                                                                                                  








                                                                             
                                                           
                                                                  

                         
                                   


















                                                                              
                                                                  


                                          
                        























                                                                                          
                                                                                        



                            
                        










                                                 
                                           






                                          
                                                                 
                                                      



                            



                                        


                                    
                                     






                                                        

















                                                                                        

                                                                                   
                           
                                                                                            
 
                                                                           




                                                             


                                                                                                      








                                                                                  
                                                           





                                                                                                     

                                                      
                                                            




                                                                                                         







                                                                         


                                                                                            





















                                                                          
                                                                                                   

                         

                                   



















                                                              
                                                            



                                                      
                        
                            
                                   
                     

                                 


                          



























































                                                                                                      
                                                           


                                                                 
                                                            





















































                                                                                             
                                                                               



                                                                              
                                       
                             





                                   















                                                                 
                                                                           





















































































































                                                                                                   


                                                                            




                                

                                             






                                                                           
                             






                                                                  
                              



                                 
                                            










                                                                                
                                                   










                                                                                   
                                     





























                                                                                                                                  
                                                                                               
                                                                                   


                                


                                   





































                                                                                                

                                                           

























                                                                          
                                                                                                             























                                                                               
                                                                                                    























                                                                          
                                                                                        
                                                            

                                              

                                            



































                                                                                                                 
                                                                        
 
                                  

















                                                          
                                                                                                 

                         
                                                   

                                  
                                       
 
                                                                 
























                                                                               
                                                                                                   

                         
                                                   

                                  
                              
 
                                                                 


          
                                                                       









                                                             
                            


                                          



                                                                             
 
                           
         
                                                  

                                                                                       
                                                
                 

                                                    






                                  

                                                                                    


                            

                                   

















                                                                                       
                                          














                                                                                                               
                                                                             
                                                    






                                            

                                                                                     


                            

                                   


































                                                                                                                  
                                                                            
                                                    










                                                              
                                                                                             

                         
                                                 
                            

                               





















                                                             
                                                                                                                                            

                         
                                                 
                          
                             


























                                                                                                 
                                                      





                                  
                                                                                                                                                                              

                         
                                                 

                               
                           

                            
                                   



























                                                                                               
                                                                                                

                         
                                                 




























                                                                           
                                                                                                                                        

                         
                                                 

                               







                                                                           

                                                                               


                                                          

                                                                                           

         




                                  
                                                                                               

                         
                                                 















                                                                          
                                                                                                              



                            


                                   





























                                                                                                       
                                                                                               

                         
                                                 

                               
                                      




































                                                                              
                                                                                                           
                                                                                  



                            

                                   





































                                                                                                                               
                                                                                                                                                

                         
                                                 













































                                                                       
                                                                                               

                                                           
                                                                                                   








                                      
                                                                                                                                                      

                         
                                                 































































                                                                                       
                                                                                                                                                                          

                         
                                                 


















                                                                               
                                                                                                                                          

                         
                                                 


















































                                                                                                        
                                                                                                           



                                                                                               







                                                                                                                

                                                                                                


                                                                                                                                 

                                                                                                      













                                                                                                                                     




         
                                                                                                



                                                                                                
                                                                                                                              

                                                                                                      


                                                                                                                      
 
                                                                                                                          




         
                                                                                               



                                                                                                
                                                                                                                             

                                                                                                      


                                                                                                                         
 
                                                                                                                          

         
// license:BSD-3-Clause
// copyright-holders:Raphael Nabet
/****************************************************************************

    prodos.cpp

    Apple II ProDOS disk images

*****************************************************************************

  Notes:
  - ProDOS disks are split into 512 byte blocks.

  ProDOS directory structure:

  Offset  Length  Description
  ------  ------  -----------
       0       2  ???
       2       2  Next block (0 if end)
       4      39  Directory Entry
      43      39  Directory Entry
                  ...
     472      39  Directory Entry
     511       1  ???


  ProDOS directory entry structure:

  Offset  Length  Description
  ------  ------  -----------
       0       1  Storage type (bits 7-4)
                      10 - Seedling File (1 block)
                      20 - Sapling File (2-256 blocks)
                      30 - Tree File (257-32768 blocks)
                      40 - Pascal Areas on ProFile HDs (???)
                      50 - GS/OS Extended File (data and rsrc fork)
                      E0 - Subdirectory Header
                      F0 - Volume Header
       1      15  File name (NUL padded)
      16       1  File type
      17       2  Key pointer
      19       2  Blocks used
      21       3  File size
      24       4  Creation date
      28       1  ProDOS version that created the file
      29       1  Minimum ProDOS version needed to read the file
      30       1  Access byte
      31       2  Auxilary file type
      33       4  Last modified date
      37       2  Header pointer


  In "seedling files", the key pointer points to a single block that is the
  whole file.  In "sapling files", the key pointer points to an index block
  that contains 256 2-byte index pointers that point to the actual blocks of
  the files.  These 2-byte values are not contiguous; the low order byte is
  in the first half of the block, and the high order byte is in the second
  half of the block.  In "tree files", the key pointer points to an index
  block of index blocks.

  ProDOS dates are 32-bit little endian values

    bits  0- 4  Day
    bits  5- 8  Month (0-11)
    bits  9-15  Year (0-49 is 2000-2049, 50-99 is 1950-1999)
    bits 16-21  Minute
    bits 24-28  Hour


  ProDOS directory and volume headers have this information:

  Offset  Length  Description
  ------  ------  -----------
      31       1  Length of the entry; generally is 39
      32       1  Number of entries per block; generally is 13
      33       2  Active entry count in directory
      35       2  Volume bitmap block number
      37       2  Total blocks on volume

  GS/OS Extended Files (storage type $5) point to an extended key block that
  contains information about the two forks.  The first half of the extended
  key block contains info about the data form, and the second half the
  resource fork.  Both sides have the following format:

  Offset  Length  Description
  ------  ------  -----------
       0       1  Storage type (bits 3-0, unlike the directory entry)
       1       2  Key pointer
       3       2  Blocks used
       5       3  File size
       8       1  Size of secondary info #1 (must be 18 to be valid)
       9       1  Secondary info #1 type (1=FInfo 2=xFInfo)
      10      16  FInfo or xFInfo
      26       1  Size of secondary info #2 (must be 18 to be valid)
      27       1  Secondary info #2 type (1=FInfo 2=xFInfo)
      28      16  FInfo or xFInfo

  FInfo format:

  Offset  Length  Description
  ------  ------  -----------
       0       4  File type
       4       4  File creator
       8       2  Finder flags
      10       2  X Coordinate
      12       2  Y Coordinate
      14       2  Finder folder

  xFInfo format:

  Offset  Length  Description
  ------  ------  -----------
       0       2  Icon ID
       2       6  Reserved
       8       1  Script Code
       9       1  Extended flags
      10       2  Comment ID
      12       4  Put Away Directory


  For more info, consult ProDOS technical note #25
    (http://web.pdx.edu/~heiss/technotes/pdos/tn.pdos.25.html)

*****************************************************************************/

#include "imgtool.h"
#include "formats/imageutl.h"
#include "formats/ap2_dsk.h"
#include "formats/ap_dsk35.h"
#include "iflopimg.h"
#include "macutil.h"

#define ROOTDIR_BLOCK           2
#define BLOCK_SIZE              512

struct prodos_diskinfo
{
	imgtoolerr_t (*load_block)(imgtool::image &image, int block, void *buffer);
	imgtoolerr_t (*save_block)(imgtool::image &image, int block, const void *buffer);
	uint8_t dirent_size;
	uint8_t dirents_per_block;
	uint16_t volume_bitmap_block;
	uint16_t total_blocks;
};

struct prodos_direnum
{
	uint32_t block;
	uint32_t index;
	uint8_t block_data[BLOCK_SIZE];
};

struct prodos_dirent
{
	char filename[16];
	uint8_t storage_type;
	uint16_t extkey_pointer;
	uint16_t key_pointer[2];
	uint32_t filesize[2];
	int depth[2];
	uint32_t lastmodified_time;
	uint32_t creation_time;

	/* FInfo */
	uint32_t file_type;
	uint32_t file_creator;
	uint16_t finder_flags;
	uint16_t coord_x;
	uint16_t coord_y;
	uint16_t finder_folder;

	/* xFInfo */
	uint16_t icon_id;
	uint8_t script_code;
	uint8_t extended_flags;
	uint16_t comment_id;
	uint32_t putaway_directory;
};

enum creation_policy_t
{
	CREATE_NONE,
	CREATE_FILE,
	CREATE_DIR
};



static imgtool::datetime prodos_crack_time(uint32_t prodos_time)
{
	util::arbitrary_datetime dt;
	dt.second		= 0;
	dt.minute		= ((prodos_time >> 16) & 0x3F);
	dt.hour			= ((prodos_time >> 24) & 0x1F);
	dt.day_of_month	= ((prodos_time >> 0) & 0x1F);
	dt.month		= ((prodos_time >> 5) & 0x0F) + 1;
	dt.year			= ((prodos_time >> 9) & 0x7F) + 1900;
	if (dt.year <= 1949)
		dt.year += 100;

	return imgtool::datetime(imgtool::datetime::datetime_type::LOCAL, dt);
}



static uint32_t prodos_setup_time(time_t ansi_time)
{
	struct tm t;
	uint32_t result = 0;

	t = *localtime(&ansi_time);
	if ((t.tm_year >= 100) && (t.tm_year <= 149))
		t.tm_year -= 100;

	result |= (((uint32_t) t.tm_min)  & 0x003F) << 16;
	result |= (((uint32_t) t.tm_hour) & 0x001F) << 24;
	result |= (((uint32_t) t.tm_mday) & 0x001F) <<  0;
	result |= (((uint32_t) t.tm_mon)  & 0x000F) <<  5;
	result |= (((uint32_t) t.tm_year) & 0x007F) <<  9;
	return result;
}



static uint32_t prodos_time_now(void)
{
	time_t now;
	time(&now);
	return prodos_setup_time(now);
}



static int is_file_storagetype(uint8_t storage_type)
{
	return ((storage_type >= 0x10) && (storage_type <= 0x3F))
		|| ((storage_type >= 0x50) && (storage_type <= 0x5F));
}



static int is_normalfile_storagetype(uint8_t storage_type)
{
	return ((storage_type >= 0x10) && (storage_type <= 0x3F));
}



static int is_extendedfile_storagetype(uint8_t storage_type)
{
	return ((storage_type >= 0x50) && (storage_type <= 0x5F));
}



static int is_dir_storagetype(uint8_t storage_type)
{
	return (storage_type >= 0xE0) && (storage_type <= 0xEF);
}



static prodos_diskinfo *get_prodos_info(imgtool::image &image)
{
	prodos_diskinfo *info;
	info = (prodos_diskinfo *) imgtool_floppy_extrabytes(image);
	return info;
}



/* ----------------------------------------------------------------------- */

static void prodos_find_block_525(imgtool::image &image, int block,
	uint32_t *track, uint32_t *head, uint32_t *sector1, uint32_t *sector2)
{
	static const uint8_t skewing[] =
	{
		0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
		0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F
	};

	block *= 2;

	*track = block / APPLE2_SECTOR_COUNT;
	*head = 0;
	*sector1 = skewing[block % APPLE2_SECTOR_COUNT + 0];
	*sector2 = skewing[block % APPLE2_SECTOR_COUNT + 1];
}



static imgtoolerr_t prodos_load_block_525(imgtool::image &image,
	int block, void *buffer)
{
	floperr_t ferr;
	uint32_t track, head, sector1, sector2;

	prodos_find_block_525(image, block, &track, &head, &sector1, &sector2);

	/* read first sector */
	ferr = floppy_read_sector(imgtool_floppy(image), head, track,
		sector1, 0, ((uint8_t *) buffer) + 0, 256);
	if (ferr)
		return imgtool_floppy_error(ferr);

	/* read second sector */
	ferr = floppy_read_sector(imgtool_floppy(image), head, track,
		sector2, 0, ((uint8_t *) buffer) + 256, 256);
	if (ferr)
		return imgtool_floppy_error(ferr);

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_save_block_525(imgtool::image &image,
	int block, const void *buffer)
{
	floperr_t ferr;
	uint32_t track, head, sector1, sector2;

	prodos_find_block_525(image, block, &track, &head, &sector1, &sector2);

	/* read first sector */
	ferr = floppy_write_sector(imgtool_floppy(image), head, track,
		sector1, 0, ((const uint8_t *) buffer) + 0, 256, 0);  /* TODO: pass ddam argument from imgtool */
	if (ferr)
		return imgtool_floppy_error(ferr);

	/* read second sector */
	ferr = floppy_write_sector(imgtool_floppy(image), head, track,
		sector2, 0, ((const uint8_t *) buffer) + 256, 256, 0);    /* TODO: pass ddam argument from imgtool */
	if (ferr)
		return imgtool_floppy_error(ferr);

	return IMGTOOLERR_SUCCESS;
}



static void prodos_setprocs_525(imgtool::image &image)
{
	prodos_diskinfo *info;
	info = get_prodos_info(image);
	info->load_block = prodos_load_block_525;
	info->save_block = prodos_save_block_525;
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_find_block_35(imgtool::image &image, int block,
	uint32_t *track, uint32_t *head, uint32_t *sector)
{
	int sides = 2;

	*track = 0;
	while(block >= (apple35_sectors_per_track(imgtool_floppy(image), *track) * sides))
	{
		block -= (apple35_sectors_per_track(imgtool_floppy(image), (*track)++) * sides);
		if (*track >= 80)
			return IMGTOOLERR_SEEKERROR;
	}

	*head = block / apple35_sectors_per_track(imgtool_floppy(image), *track);
	*sector = block % apple35_sectors_per_track(imgtool_floppy(image), *track);
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_load_block_35(imgtool::image &image,
	int block, void *buffer)
{
	imgtoolerr_t err;
	floperr_t ferr;
	uint32_t track, head, sector;

	err = prodos_find_block_35(image, block, &track, &head, &sector);
	if (err)
		return err;

	ferr = floppy_read_sector(imgtool_floppy(image), head, track, sector, 0, buffer, 512);
	if (ferr)
		return imgtool_floppy_error(ferr);

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_save_block_35(imgtool::image &image,
	int block, const void *buffer)
{
	imgtoolerr_t err;
	floperr_t ferr;
	uint32_t track, head, sector;

	err = prodos_find_block_35(image, block, &track, &head, &sector);
	if (err)
		return err;

	ferr = floppy_write_sector(imgtool_floppy(image), head, track, sector, 0, buffer, 512, 0);  /* TODO: pass ddam argument from imgtool */
	if (ferr)
		return imgtool_floppy_error(ferr);

	return IMGTOOLERR_SUCCESS;
}



static void prodos_setprocs_35(imgtool::image &image)
{
	prodos_diskinfo *info;
	info = get_prodos_info(image);
	info->load_block = prodos_load_block_35;
	info->save_block = prodos_save_block_35;
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_load_block(imgtool::image &image,
	int block, void *buffer)
{
	prodos_diskinfo *diskinfo;
	diskinfo = get_prodos_info(image);
	return diskinfo->load_block(image, block, buffer);
}



static imgtoolerr_t prodos_save_block(imgtool::image &image,
	int block, const void *buffer)
{
	prodos_diskinfo *diskinfo;
	diskinfo = get_prodos_info(image);
	return diskinfo->save_block(image, block, buffer);
}



static imgtoolerr_t prodos_clear_block(imgtool::image &image, int block)
{
	uint8_t buffer[BLOCK_SIZE];
	memset(buffer, 0, sizeof(buffer));
	return prodos_save_block(image, block, buffer);
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_diskimage_open(imgtool::image &image)
{
	imgtoolerr_t err;
	uint8_t buffer[BLOCK_SIZE];
	prodos_diskinfo *di;
	const uint8_t *ent;

	di = get_prodos_info(image);

	/* specify defaults */
	di->dirent_size = 39;
	di->dirents_per_block = 13;

	/* load the first block, hoping that the volume header is first */
	err = prodos_load_block(image, ROOTDIR_BLOCK, buffer);
	if (err)
		return err;

	ent = &buffer[4];

	/* did we find the volume header? */
	if ((ent[0] & 0xF0) == 0xF0)
	{
		di->dirent_size         = pick_integer_le(ent, 31, 1);
		di->dirents_per_block   = pick_integer_le(ent, 32, 1);
		di->volume_bitmap_block = pick_integer_le(ent, 35, 2);
		di->total_blocks        = pick_integer_le(ent, 37, 2);
	}

	/* sanity check these values */
	if (di->dirent_size < 39)
		return IMGTOOLERR_CORRUPTIMAGE;
	if (di->dirents_per_block * di->dirent_size >= BLOCK_SIZE)
		return IMGTOOLERR_CORRUPTIMAGE;
	if (di->volume_bitmap_block >= di->total_blocks)
		return IMGTOOLERR_CORRUPTIMAGE;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_open_525(imgtool::image &image, imgtool::stream::ptr &&dummy)
{
	prodos_setprocs_525(image);
	return prodos_diskimage_open(image);
}



static imgtoolerr_t prodos_diskimage_open_35(imgtool::image &image, imgtool::stream::ptr &&dummy)
{
	prodos_setprocs_35(image);
	return prodos_diskimage_open(image);
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_load_volume_bitmap(imgtool::image &image, uint8_t **bitmap)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	uint8_t *alloc_bitmap;
	uint32_t bitmap_blocks, i;

	di = get_prodos_info(image);

	bitmap_blocks = (di->total_blocks + (BLOCK_SIZE * 8) - 1) / (BLOCK_SIZE * 8);
	alloc_bitmap = (uint8_t*)malloc(bitmap_blocks * BLOCK_SIZE);
	if (!alloc_bitmap)
	{
		err = IMGTOOLERR_OUTOFMEMORY;
		goto done;
	}

	for (i = 0; i < bitmap_blocks; i++)
	{
		err = prodos_load_block(image, di->volume_bitmap_block + i,
			&alloc_bitmap[i * BLOCK_SIZE]);
		if (err)
			goto done;
	}

	err = IMGTOOLERR_SUCCESS;

done:
	if (err && alloc_bitmap)
	{
		free(alloc_bitmap);
		alloc_bitmap = NULL;
	}
	*bitmap = alloc_bitmap;
	return err;
}



static imgtoolerr_t prodos_save_volume_bitmap(imgtool::image &image, const uint8_t *bitmap)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	uint32_t bitmap_blocks, i;

	di = get_prodos_info(image);

	bitmap_blocks = (di->total_blocks + (BLOCK_SIZE * 8) - 1) / (BLOCK_SIZE * 8);

	for (i = 0; i < bitmap_blocks; i++)
	{
		err = prodos_save_block(image, di->volume_bitmap_block + i,
			&bitmap[i * BLOCK_SIZE]);
		if (err)
			return err;
	}
	return IMGTOOLERR_SUCCESS;
}



static void prodos_set_volume_bitmap_bit(uint8_t *buffer, uint16_t block, int value)
{
	uint8_t mask;
	buffer += block / 8;
	mask = 1 << (7 - (block % 8));
	if (value)
		*buffer |= mask;
	else
		*buffer &= ~mask;
}



static int prodos_get_volume_bitmap_bit(const uint8_t *buffer, uint16_t block)
{
	uint8_t mask;
	buffer += block / 8;
	mask = 1 << (7 - (block % 8));
	return (*buffer & mask) ? 1 : 0;
}



static imgtoolerr_t prodos_alloc_block(imgtool::image &image, uint8_t *bitmap,
	uint16_t *block)
{
	imgtoolerr_t err = IMGTOOLERR_SUCCESS;
	prodos_diskinfo *di;
	uint16_t bitmap_blocks, i;
	uint8_t *alloc_bitmap = NULL;

	di = get_prodos_info(image);
	*block = 0;
	bitmap_blocks = (di->total_blocks + (BLOCK_SIZE * 8) - 1) / (BLOCK_SIZE * 8);

	if (!bitmap)
	{
		err = prodos_load_volume_bitmap(image, &alloc_bitmap);
		if (err)
			goto done;
		bitmap = alloc_bitmap;
	}

	for (i = (di->volume_bitmap_block + bitmap_blocks); i < di->total_blocks; i++)
	{
		if (!prodos_get_volume_bitmap_bit(bitmap, i))
		{
			prodos_set_volume_bitmap_bit(bitmap, i, 1);
			*block = i;
			break;
		}
	}

	if (*block > 0)
	{
		if (alloc_bitmap)
		{
			err = prodos_save_volume_bitmap(image, bitmap);
			if (err)
				goto done;
		}
	}
	else
	{
		err = IMGTOOLERR_NOSPACE;
	}

done:
	if (err)
		*block = 0;
	if (alloc_bitmap)
		free(alloc_bitmap);
	return err;
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_diskimage_create(imgtool::image &image, util::option_resolution *opts)
{
	imgtoolerr_t err;
	uint32_t heads, tracks, sectors, sector_bytes;
	uint32_t dirent_size, volume_bitmap_block, i;
	uint32_t volume_bitmap_block_count, total_blocks;
	uint8_t buffer[BLOCK_SIZE];

	heads = opts->lookup_int('H');
	tracks = opts->lookup_int('T');
	sectors = opts->lookup_int('S');
	sector_bytes = opts->lookup_int('L');

	dirent_size = 39;
	volume_bitmap_block = 6;
	total_blocks = tracks * heads * sectors * sector_bytes / BLOCK_SIZE;
	volume_bitmap_block_count = (total_blocks + (BLOCK_SIZE * 8) - 1) / (BLOCK_SIZE * 8);

	/* prepare initial dir block */
	memset(buffer, 0, sizeof(buffer));
	place_integer_le(buffer, 4 +  0, 1, 0xF0);
	place_integer_le(buffer, 4 + 31, 1, dirent_size);
	place_integer_le(buffer, 4 + 32, 1, BLOCK_SIZE / dirent_size);
	place_integer_le(buffer, 4 + 35, 2, volume_bitmap_block);
	place_integer_le(buffer, 4 + 37, 2, total_blocks);

	err = prodos_save_block(image, ROOTDIR_BLOCK, buffer);
	if (err)
		return err;

	/* setup volume bitmap */
	memset(buffer, 0, sizeof(buffer));
	for (i = 0; i < (volume_bitmap_block + volume_bitmap_block_count); i++)
		prodos_set_volume_bitmap_bit(buffer, i, 1);
	prodos_save_block(image, volume_bitmap_block, buffer);

	/* and finally open the image */
	return prodos_diskimage_open(image);
}



static imgtoolerr_t prodos_diskimage_create_525(imgtool::image &image, imgtool::stream::ptr &&dummy, util::option_resolution *opts)
{
	prodos_setprocs_525(image);
	return prodos_diskimage_create(image, opts);
}



static imgtoolerr_t prodos_diskimage_create_35(imgtool::image &image, imgtool::stream::ptr &&dummy, util::option_resolution *opts)
{
	prodos_setprocs_35(image);
	return prodos_diskimage_create(image, opts);
}



/* ----------------------------------------------------------------------- */

static imgtoolerr_t prodos_enum_seek(imgtool::image &image,
	prodos_direnum *appleenum, uint32_t block, uint32_t index)
{
	imgtoolerr_t err;
	uint8_t buffer[BLOCK_SIZE];

	if (appleenum->block != block)
	{
		if (block != 0)
		{
			err = prodos_load_block(image, block, buffer);
			if (err)
				return err;
			memcpy(appleenum->block_data, buffer, sizeof(buffer));
		}
		appleenum->block = block;
	}

	appleenum->index = index;
	return IMGTOOLERR_SUCCESS;
}



static uint8_t *next_info_block(uint8_t *buffer, size_t *position)
{
	size_t side = *position & 0x100;
	size_t subpos = *position & 0x0FF;
	uint8_t *result;

	if (subpos < 8)
	{
		subpos = 8;
		*position = side + subpos;
	}

	while((buffer[side + subpos] == 0x00) || (subpos + buffer[side + subpos] > 0x100))
	{
		if (side)
			return NULL;

		side = 0x100;
		subpos = 8;
	}

	result = &buffer[side + subpos];
	subpos += *result;
	*position = side + subpos;
	return result;
}



static uint8_t *alloc_info_block(uint8_t *buffer, size_t block_size, uint8_t block_type)
{
	size_t position = 0;
	size_t side;
	size_t subpos;
	uint8_t *result;

	while(next_info_block(buffer, &position))
		;

	side = position & 0x100;
	subpos = position & 0x0FF;

	if ((subpos + block_size) > 0x100)
		return NULL;

	result = &buffer[side + subpos];
	*(result++) = (uint8_t) block_size;
	*(result++) = block_type;
	memset(result, 0, block_size - 2);
	return result;
}



static imgtoolerr_t prodos_get_next_dirent(imgtool::image &image,
	prodos_direnum *appleenum, prodos_dirent &ent)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	size_t finfo_offset;
	uint32_t next_block, next_index;
	uint32_t offset;
	uint8_t buffer[BLOCK_SIZE];
	const uint8_t *info_ptr;
	int fork_num;

	di = get_prodos_info(image);
	memset(&ent, 0, sizeof(ent));

	/* have we hit the end of the file? */
	if (appleenum->block == 0)
		return IMGTOOLERR_SUCCESS;

	/* populate the resulting dirent */
	offset = appleenum->index * di->dirent_size + 4;
	ent.storage_type = appleenum->block_data[offset + 0];
	memcpy(ent.filename, &appleenum->block_data[offset + 1], 15);
	ent.filename[15] = '\0';
	ent.creation_time      = pick_integer_le(appleenum->block_data, offset + 24, 4);
	ent.lastmodified_time  = pick_integer_le(appleenum->block_data, offset + 33, 4);
	ent.file_type = 0x3F3F3F3F;
	ent.file_creator = 0x3F3F3F3F;
	ent.finder_flags  = 0;
	ent.coord_x = 0;
	ent.coord_y = 0;
	ent.finder_folder = 0;
	ent.icon_id = 0;
	ent.script_code = 0;
	ent.extended_flags = 0;
	ent.comment_id = 0;
	ent.putaway_directory = 0;

	if (is_extendedfile_storagetype(ent.storage_type))
	{
		/* this is a ProDOS extended file; we need to get the extended info
		 * block */
		ent.extkey_pointer = pick_integer_le(appleenum->block_data, offset + 17, 2);

		err = prodos_load_block(image, ent.extkey_pointer, buffer);
		if (err)
			return err;

		for (fork_num = 0; fork_num <= 1; fork_num++)
		{
			ent.key_pointer[fork_num]  = pick_integer_le(buffer, 1 + (fork_num * 256), 2);
			ent.filesize[fork_num]     = pick_integer_le(buffer, 5 + (fork_num * 256), 3);
			ent.depth[fork_num]        = buffer[fork_num * 256] & 0x0F;
		}

		finfo_offset = 0;
		while((info_ptr = next_info_block(buffer, &finfo_offset)) != NULL)
		{
			if (*(info_ptr++) == 18)
			{
				switch(*(info_ptr++))
				{
					case 1: /* FInfo */
						ent.file_type     = pick_integer_be(info_ptr,  0, 4);
						ent.file_creator  = pick_integer_be(info_ptr,  4, 4);
						ent.finder_flags  = pick_integer_be(info_ptr,  8, 2);
						ent.coord_x       = pick_integer_be(info_ptr, 10, 2);
						ent.coord_y       = pick_integer_be(info_ptr, 12, 2);
						ent.finder_folder = pick_integer_be(info_ptr, 14, 4);
						break;

					case 2: /* xFInfo */
						ent.icon_id           = pick_integer_be(info_ptr,  0, 2);
						ent.script_code       = pick_integer_be(info_ptr,  8, 1);
						ent.extended_flags    = pick_integer_be(info_ptr,  9, 1);
						ent.comment_id        = pick_integer_be(info_ptr, 10, 2);
						ent.putaway_directory = pick_integer_be(info_ptr, 12, 4);
						break;
				}
			}
		}
	}
	else
	{
		/* normal ProDOS files have all of the info right here */
		ent.key_pointer[0] = pick_integer_le(appleenum->block_data, offset + 17, 2);
		ent.filesize[0]    = pick_integer_le(appleenum->block_data, offset + 21, 3);
		ent.depth[0]       = ent.storage_type >> 4;
	}

	/* identify next entry */
	next_block = appleenum->block;
	next_index = appleenum->index + 1;
	if (next_index >= di->dirents_per_block)
	{
		next_block = pick_integer_le(appleenum->block_data, 2, 2);
		next_index = 0;
	}

	/* seek next block */
	err = prodos_enum_seek(image, appleenum, next_block, next_index);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



/* changes a normal file to a ProDOS extended file */
static imgtoolerr_t prodos_promote_file(imgtool::image &image, uint8_t *bitmap, prodos_dirent *ent)
{
	imgtoolerr_t err;
	uint16_t new_block;
	uint8_t buffer[BLOCK_SIZE];

	assert(is_normalfile_storagetype(ent->storage_type));

	err = prodos_alloc_block(image, bitmap, &new_block);
	if (err)
		return err;

	/* create raw extended info block */
	memset(buffer, 0, sizeof(buffer));
	err = prodos_save_block(image, new_block, buffer);
	if (err)
		return err;

	ent->storage_type = (ent->storage_type & 0x0F) | 0x50;
	ent->extkey_pointer = new_block;
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_put_dirent(imgtool::image &image,
	prodos_direnum *appleenum, prodos_dirent *ent)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	uint32_t offset;
	size_t finfo_offset;
	uint8_t buffer[BLOCK_SIZE];
	int fork_num;
	int needs_finfo = false;
	int needs_xfinfo = false;
	uint8_t *info_ptr;
	uint8_t *finfo;
	uint8_t *xfinfo;

	di = get_prodos_info(image);
	offset = appleenum->index * di->dirent_size + 4;

	/* determine whether we need FInfo and/or xFInfo */
	if (is_normalfile_storagetype(ent->storage_type))
	{
		needs_finfo = (ent->file_type != 0x3F3F3F3F) ||
			(ent->file_creator != 0x3F3F3F3F) ||
			(ent->finder_flags != 0) ||
			(ent->coord_x != 0) ||
			(ent->coord_y != 0) ||
			(ent->finder_folder != 0);

		needs_xfinfo = (ent->icon_id != 0) ||
			(ent->script_code != 0) ||
			(ent->extended_flags != 0) ||
			(ent->comment_id != 0) ||
			(ent->putaway_directory != 0);
	}

	/* do we need to promote this file to an extended file? */
	if (!is_extendedfile_storagetype(ent->storage_type)
		&& (needs_finfo || needs_xfinfo))
	{
		err = prodos_promote_file(image, NULL, ent);
		if (err)
			return err;
	}

	/* write out the storage type, filename, creation and lastmodified times */
	appleenum->block_data[offset + 0] = ent->storage_type;
	memcpy(&appleenum->block_data[offset + 1], ent->filename, 15);
	place_integer_le(appleenum->block_data, offset + 24, 4, ent->creation_time);
	place_integer_le(appleenum->block_data, offset + 33, 4, ent->lastmodified_time);

	if (is_extendedfile_storagetype(ent->storage_type))
	{
		/* ProDOS extended file */
		err = prodos_load_block(image, ent->extkey_pointer, buffer);
		if (err)
			return err;

		finfo = NULL;
		xfinfo = NULL;

		for (fork_num = 0; fork_num <= 1; fork_num++)
		{
			place_integer_le(buffer, 1 + (fork_num * 256), 2, ent->key_pointer[fork_num]);
			place_integer_le(buffer, 5 + (fork_num * 256), 3, ent->filesize[fork_num]);
			buffer[fork_num * 256] = ent->depth[fork_num];
		}

		finfo_offset = 0;
		while((info_ptr = next_info_block(buffer, &finfo_offset)) != NULL)
		{
			if (*(info_ptr++) == 18)
			{
				switch(*(info_ptr++))
				{
					case 1: /* FInfo */
						finfo = info_ptr;
						break;

					case 2: /* xFInfo */
						xfinfo = info_ptr;
						break;
				}
			}
		}

		/* allocate the finfo and/or xinfo blocks, if we need them */
		if (needs_finfo && !finfo)
			finfo = alloc_info_block(buffer, 18, 1);
		if (needs_xfinfo && !xfinfo)
			xfinfo = alloc_info_block(buffer, 18, 2);

		if (finfo)
		{
			place_integer_be(finfo,  0, 4, ent->file_type);
			place_integer_be(finfo,  4, 4, ent->file_creator);
			place_integer_be(finfo,  8, 2, ent->finder_flags);
			place_integer_be(finfo, 10, 2, ent->coord_x);
			place_integer_be(finfo, 12, 2, ent->coord_y);
			place_integer_be(finfo, 14, 4, ent->finder_folder);
		}

		if (xfinfo)
		{
			place_integer_be(xfinfo,  0, 2, ent->icon_id);
			place_integer_be(xfinfo,  8, 1, ent->script_code);
			place_integer_be(xfinfo,  9, 1, ent->extended_flags);
			place_integer_be(xfinfo, 10, 2, ent->comment_id);
			place_integer_be(xfinfo, 12, 4, ent->putaway_directory);
		}

		err = prodos_save_block(image, ent->extkey_pointer, buffer);
		if (err)
			return err;

		place_integer_le(appleenum->block_data, offset + 17, 2, ent->extkey_pointer);
		place_integer_le(appleenum->block_data, offset + 21, 3, BLOCK_SIZE);
	}
	else
	{
		/* normal file */
		place_integer_le(appleenum->block_data, offset + 17, 2, ent->key_pointer[0]);
		place_integer_le(appleenum->block_data, offset + 21, 3, ent->filesize[0]);
	}

	err = prodos_save_block(image, appleenum->block, appleenum->block_data);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_lookup_path(imgtool::image &image, const char *path,
	creation_policy_t create, prodos_direnum *direnum, prodos_dirent *ent)
{
	imgtoolerr_t err;
	prodos_direnum my_direnum;
	uint32_t block = ROOTDIR_BLOCK;
	const char *old_path;
	uint16_t this_block;
	uint32_t this_index;
	uint16_t free_block = 0;
	uint32_t free_index = 0;
	uint16_t new_file_block;
	uint8_t buffer[BLOCK_SIZE];

	if (!direnum)
		direnum = &my_direnum;

	while(*path)
	{
		memset(direnum, 0, sizeof(*direnum));
		err = prodos_enum_seek(image, direnum, block, 0);
		if (err)
			goto done;

		do
		{
			this_block = direnum->block;
			this_index = direnum->index;

			err = prodos_get_next_dirent(image, direnum, *ent);
			if (err)
				goto done;

			/* if we need to create a file entry and this is free, track it */
			if (create && this_block && !free_block && !ent->storage_type)
			{
				free_block = this_block;
				free_index = this_index;
			}
		}
		while(direnum->block && (strcmp(path, ent->filename) || (
			!is_file_storagetype(ent->storage_type) &&
			!is_dir_storagetype(ent->storage_type))));

		old_path = path;
		path += strlen(path) + 1;
		if (*path)
		{
			/* we have found part of the path; we are not finished yet */
			if (!is_dir_storagetype(ent->storage_type))
			{
				err = IMGTOOLERR_FILENOTFOUND;
				goto done;
			}
			block = ent->key_pointer[0];
		}
		else if (!direnum->block)
		{
			/* did not find file; maybe we need to create it */
			if (create == CREATE_NONE)
			{
				err = IMGTOOLERR_FILENOTFOUND;
				goto done;
			}

			/* do we need to expand the directory? */
			if (!free_block)
			{
				if (this_block == 0)
				{
					err = IMGTOOLERR_CORRUPTFILE;
					goto done;
				}

				err = prodos_load_block(image, this_block, buffer);
				if (err)
					goto done;

				/* allocate a block */
				err = prodos_alloc_block(image, NULL, &free_block);
				if (err)
					goto done;

				/* clear out this new block */
				err = prodos_clear_block(image, free_block);
				if (err)
					goto done;

				/* save this link */
				place_integer_le(buffer, 2, 2, free_block);
				err = prodos_save_block(image, this_block, buffer);
				if (err)
					goto done;

				free_index = 0;
			}

			/* seek back to the free space */
			err = prodos_enum_seek(image, direnum, free_block, free_index);
			if (err)
				goto done;

			new_file_block = 0;
			if (create == CREATE_DIR)
			{
				/* if we are creating a directory, we need to create a new block */
				err = prodos_alloc_block(image, NULL, &new_file_block);
				if (err)
					goto done;

				err = prodos_clear_block(image, new_file_block);
				if (err)
					goto done;
			}

			/* prepare the dirent */
			memset(ent, 0, sizeof(*ent));
			ent->storage_type = (create == CREATE_DIR) ? 0xe0 : 0x10;
			ent->creation_time = ent->lastmodified_time = prodos_time_now();
			ent->key_pointer[0] = new_file_block;
			ent->file_type = 0x3F3F3F3F;
			ent->file_creator = 0x3F3F3F3F;
			strncpy(ent->filename, old_path, ARRAY_LENGTH(ent->filename));

			/* and place it */
			err = prodos_put_dirent(image, direnum, ent);
			if (err)
				goto done;

			this_block = free_block;
			this_index = free_index;
		}
		else
		{
			/* we've found the file; seek that dirent */
			err = prodos_enum_seek(image, direnum, this_block, this_index);
			if (err)
				goto done;
		}
	}

	err = IMGTOOLERR_SUCCESS;
done:
	return err;
}



static imgtoolerr_t prodos_fill_file(imgtool::image &image, uint8_t *bitmap,
	uint16_t key_block, int key_block_allocated,
	int depth, uint32_t blockcount, uint32_t block_index)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	int dirty;
	int sub_block_allocated;
	uint16_t i, sub_block, new_sub_block;
	uint8_t buffer[BLOCK_SIZE];

	di = get_prodos_info(image);

	if (key_block_allocated)
	{
		/* we are on a recently allocated key block; start fresh */
		memset(buffer, 0, sizeof(buffer));
		dirty = true;
	}
	else
	{
		/* this is a preexisting key block */
		err = prodos_load_block(image, key_block, buffer);
		if (err)
			return err;
		dirty = false;
	}

	for (i = 0; i < 256; i++)
	{
		sub_block_allocated = false;

		sub_block = buffer[i + 256];
		sub_block <<= 8;
		sub_block += buffer[i + 0];

		new_sub_block = sub_block;
		if ((block_index < blockcount) && (sub_block == 0))
		{
			err = prodos_alloc_block(image, bitmap, &new_sub_block);
			if (err)
				return err;
			sub_block_allocated = true;
		}
		else if ((block_index >= blockcount) && (sub_block != 0))
		{
			new_sub_block = 0;
			if (sub_block < di->total_blocks)
				prodos_set_volume_bitmap_bit(bitmap, sub_block, 0);
		}

		/* did we change the block? */
		if (new_sub_block != sub_block)
		{
			dirty = true;
			buffer[i + 0] = new_sub_block >> 0;
			buffer[i + 256] = new_sub_block >> 8;
			if (sub_block == 0)
				sub_block = new_sub_block;
		}

		/* call recursive function */
		if (depth > 2)
		{
			err = prodos_fill_file(image, bitmap, sub_block, sub_block_allocated, depth - 1, blockcount, block_index);
			if (err)
				return err;
		}

		/* increment index */
		block_index += 1 << ((depth - 2) * 8);
	}

	/* if we changed anything, then save the block */
	if (dirty)
	{
		err = prodos_save_block(image, key_block, buffer);
		if (err)
			return err;
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_set_file_block_count(imgtool::image &image, prodos_direnum *direnum,
	prodos_dirent *ent, uint8_t *bitmap, int fork_num, uint32_t new_blockcount)
{
	imgtoolerr_t err;
	int depth, new_depth, i;
	uint16_t new_block, block;
	uint8_t buffer[BLOCK_SIZE];
	uint16_t key_pointer;

	if (fork_num && (new_blockcount > 0) && !is_extendedfile_storagetype(ent->storage_type))
	{
		/* need to change a normal file to an extended file */
		err = prodos_promote_file(image, bitmap, ent);
		if (err)
			return err;
	}

	key_pointer = ent->key_pointer[fork_num];
	depth = ent->depth[fork_num];

	/* determine the new tree depth */
	if (new_blockcount <= 1)
		new_depth = 1;
	else if (new_blockcount <= 256)
		new_depth = 2;
	else
		new_depth = 3;

	/* are we zero length, and do we have to create a block? */
	if ((new_blockcount >= 1) && (key_pointer == 0))
	{
		err = prodos_alloc_block(image, bitmap, &new_block);
		if (err)
			return err;
		key_pointer = new_block;
	}

	/* do we have to grow the tree? */
	while(new_depth > depth)
	{
		err = prodos_alloc_block(image, bitmap, &new_block);
		if (err)
			return err;

		/* create this new key block, with a link to the previous one */
		memset(buffer, 0, sizeof(buffer));
		buffer[0] = (uint8_t) (key_pointer >> 0);
		buffer[256] = (uint8_t) (key_pointer >> 8);
		err = prodos_save_block(image, new_block, buffer);
		if (err)
			return err;

		depth++;
		key_pointer = new_block;
	}

	/* do we have to shrink the tree? */
	while(new_depth < depth)
	{
		err = prodos_load_block(image, key_pointer, buffer);
		if (err)
			return err;

		for (i = 1; i < 256; i++)
		{
			block = buffer[i + 256];
			block <<= 8;
			block |= buffer[i + 0];

			if (block > 0)
			{
				if (depth > 2)
				{
					/* remove this block's children */
					err = prodos_fill_file(image, bitmap, block, false, depth - 1, 0, 0);
					if (err)
						return err;
				}

				/* and remove this block */
				prodos_set_volume_bitmap_bit(bitmap, block, 0);
			}
		}

		/* remove this key block */
		prodos_set_volume_bitmap_bit(bitmap, key_pointer, 0);

		/* set the new key pointer */
		block = buffer[256];
		block <<= 8;
		block |= buffer[0];
		key_pointer = block;

		depth--;
	}

	if (new_blockcount > 0)
	{
		/* fill out the file tree */
		err = prodos_fill_file(image, bitmap, key_pointer, false, depth, new_blockcount, 0);
		if (err)
			return err;
	}
	else if (key_pointer != 0)
	{
		/* we are now zero length, and don't need a key pointer */
		prodos_set_volume_bitmap_bit(bitmap, key_pointer, 0);
		key_pointer = 0;
	}

	/* change the depth if we are not an extended file */
	if (is_normalfile_storagetype(ent->storage_type))
	{
		ent->storage_type &= ~0xF0;
		ent->storage_type |= depth * 0x10;
	}

	ent->key_pointer[fork_num] = key_pointer;
	ent->depth[fork_num] = depth;
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_set_file_size(imgtool::image &image, prodos_direnum *direnum,
	prodos_dirent *ent, int fork_num, uint32_t new_size)
{
	imgtoolerr_t err = IMGTOOLERR_SUCCESS;
	uint32_t blockcount, new_blockcount;
	uint8_t *bitmap = NULL;

	if (ent->filesize[fork_num] != new_size)
	{
		blockcount = (ent->filesize[fork_num] + BLOCK_SIZE - 1) / BLOCK_SIZE;
		new_blockcount = (new_size + BLOCK_SIZE - 1) / BLOCK_SIZE;

		/* do we need to change the block chain? */
		if (new_blockcount != blockcount)
		{
			err = prodos_load_volume_bitmap(image, &bitmap);
			if (err)
				goto done;

			err = prodos_set_file_block_count(image, direnum, ent, bitmap, fork_num, new_blockcount);
			if (err)
				goto done;

			err = prodos_save_volume_bitmap(image, bitmap);
			if (err)
				goto done;
		}

		ent->filesize[fork_num] = new_size;
		err = prodos_put_dirent(image, direnum, ent);
		if (err)
			goto done;
	}

done:
	if (bitmap)
		free(bitmap);
	return err;
}



static uint32_t prodos_get_storagetype_maxfilesize(uint8_t storage_type)
{
	uint32_t max_filesize = 0;
	switch(storage_type & 0xF0)
	{
		case 0x10:
			max_filesize = BLOCK_SIZE * 1;
			break;
		case 0x20:
			max_filesize = BLOCK_SIZE * 256;
			break;
		case 0x30:
		case 0x50:
			max_filesize = BLOCK_SIZE * 32768;
			break;
	}
	return max_filesize;
}



static imgtoolerr_t prodos_diskimage_beginenum(imgtool::directory &enumeration, const char *path)
{
	imgtoolerr_t err;
	imgtool::image &image(enumeration.image());
	prodos_direnum *appleenum;
	prodos_dirent ent;
	uint16_t block = ROOTDIR_BLOCK;

	appleenum = (prodos_direnum *) enumeration.extra_bytes();

	/* find subdirectory, if appropriate */
	if (*path)
	{
		err = prodos_lookup_path(image, path, CREATE_NONE, NULL, &ent);
		if (err)
			return err;

		/* only work on directories */
		if (!is_dir_storagetype(ent.storage_type))
			return IMGTOOLERR_FILENOTFOUND;

		block = ent.key_pointer[0];
	}

	/* seek initial block */
	err = prodos_enum_seek(image, appleenum, block, 0);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent)
{
	imgtoolerr_t err;
	imgtool::image &image(enumeration.image());
	prodos_direnum *appleenum;
	prodos_dirent pd_ent;
	uint32_t max_filesize;

	appleenum = (prodos_direnum *) enumeration.extra_bytes();

	do
	{
		err = prodos_get_next_dirent(image, appleenum, pd_ent);
		if (err)
			return err;
	}
	while(appleenum->block
		&& !is_file_storagetype(pd_ent.storage_type)
		&& !is_dir_storagetype(pd_ent.storage_type));

	/* end of file? */
	if (pd_ent.storage_type == 0x00)
	{
		ent.eof = 1;
		return IMGTOOLERR_SUCCESS;
	}

	strcpy(ent.filename, pd_ent.filename);
	ent.directory          = is_dir_storagetype(pd_ent.storage_type);
	ent.creation_time      = prodos_crack_time(pd_ent.creation_time);
	ent.lastmodified_time  = prodos_crack_time(pd_ent.lastmodified_time);

	if (!ent.directory)
	{
		ent.filesize = pd_ent.filesize[0];

		max_filesize = prodos_get_storagetype_maxfilesize(pd_ent.storage_type);
		if (ent.filesize > max_filesize)
		{
			ent.corrupt = 1;
			ent.filesize = max_filesize;
		}
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_read_file_tree(imgtool::image &image, uint32_t *filesize,
	uint32_t block, int nest_level, imgtool::stream &destf)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	uint8_t buffer[BLOCK_SIZE];
	uint16_t sub_block;
	size_t bytes_to_write;
	int i;

	/* check bounds */
	di = get_prodos_info(image);
	if (block >= di->total_blocks)
		return IMGTOOLERR_CORRUPTFILE;

	err = prodos_load_block(image, block, buffer);
	if (err)
		return err;

	if (nest_level > 0)
	{
		/* this is an index block */
		for (i = 0; i < 256; i++)
		{
			/* retrieve the block pointer; the two bytes are on either half
			 * of the block */
			sub_block = buffer[i + 256];
			sub_block <<= 8;
			sub_block |= buffer[i + 0];

			if (sub_block != 0)
			{
				err = prodos_read_file_tree(image, filesize, sub_block, nest_level - 1, destf);
				if (err)
					return err;
			}
		}
	}
	else
	{
		/* this is a leaf block */
		bytes_to_write = std::min(size_t(*filesize), sizeof(buffer));
		destf.write(buffer, bytes_to_write);
		*filesize -= bytes_to_write;
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_write_file_tree(imgtool::image &image, uint32_t *filesize,
	uint32_t block, int nest_level, imgtool::stream &sourcef)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	uint8_t buffer[BLOCK_SIZE];
	uint16_t sub_block;
	size_t bytes_to_read;
	int i;

	/* nothing more to read? bail */
	if (*filesize == 0)
		return IMGTOOLERR_SUCCESS;

	/* check bounds */
	di = get_prodos_info(image);
	if (block >= di->total_blocks)
		return IMGTOOLERR_CORRUPTFILE;

	err = prodos_load_block(image, block, buffer);
	if (err)
		return err;

	if (nest_level > 0)
	{
		for (i = 0; i < 256; i++)
		{
			sub_block = buffer[i + 256];
			sub_block <<= 8;
			sub_block |= buffer[i + 0];

			if (sub_block != 0)
			{
				err = prodos_write_file_tree(image, filesize, sub_block, nest_level - 1, sourcef);
				if (err)
					return err;
			}
		}
	}
	else
	{
		/* this is a leaf block */
		bytes_to_read = std::min(size_t(*filesize), sizeof(buffer));
		sourcef.read(buffer, bytes_to_read);
		*filesize -= bytes_to_read;

		err = prodos_save_block(image, block, buffer);
		if (err)
			return err;
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_freespace(imgtool::partition &partition, uint64_t *size)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_diskinfo *di;
	uint8_t *bitmap = NULL;
	uint16_t i;

	di = get_prodos_info(image);
	*size = 0;

	err = prodos_load_volume_bitmap(image, &bitmap);
	if (err)
		goto done;

	for (i = 0; i < di->total_blocks; i++)
	{
		if (!prodos_get_volume_bitmap_bit(bitmap, i))
			*size += BLOCK_SIZE;
	}

done:
	if (bitmap)
		free(bitmap);
	return err;
}



static imgtoolerr_t prodos_diskimage_readfile(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &destf)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	uint16_t key_pointer;
	int nest_level;
	mac_fork_t fork_num;

	err = prodos_lookup_path(image, filename, CREATE_NONE, NULL, &ent);
	if (err)
		return err;

	if (is_dir_storagetype(ent.storage_type))
		return IMGTOOLERR_FILENOTFOUND;

	err = mac_identify_fork(fork, &fork_num);
	if (err)
		return err;

	key_pointer = ent.key_pointer[fork_num];
	nest_level = ent.depth[fork_num] - 1;

	if (key_pointer != 0)
	{
		err = prodos_read_file_tree(image, &ent.filesize[fork_num], key_pointer,
			nest_level, destf);
		if (err)
			return err;
	}

	/* have we not actually received the correct amount of bytes? if not, fill in the rest */
	if (ent.filesize[fork_num] > 0)
		destf.fill(0, ent.filesize[fork_num]);

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_writefile(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *opts)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;
	uint64_t file_size;
	mac_fork_t fork_num;

	file_size = sourcef.size();

	err = prodos_lookup_path(image, filename, CREATE_FILE, &direnum, &ent);
	if (err)
		return err;

	/* only work on files */
	if (is_dir_storagetype(ent.storage_type))
		return IMGTOOLERR_FILENOTFOUND;

	err = mac_identify_fork(fork, &fork_num);
	if (err)
		return err;

	/* set the file size */
	err = prodos_set_file_size(image, &direnum, &ent, fork_num, file_size);
	if (err)
		return err;

	err = prodos_write_file_tree(image, &ent.filesize[fork_num], ent.key_pointer[fork_num],
		ent.depth[fork_num] - 1, sourcef);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_deletefile(imgtool::partition &partition, const char *path)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;

	err = prodos_lookup_path(image, path, CREATE_NONE, &direnum, &ent);
	if (err)
		return err;

	/* only work on files */
	if (is_dir_storagetype(ent.storage_type))
		return IMGTOOLERR_FILENOTFOUND;

	/* empty out both forks */
	err = prodos_set_file_size(image, &direnum, &ent, 0, 0);
	if (err)
		return err;
	err = prodos_set_file_size(image, &direnum, &ent, 1, 0);
	if (err)
		return err;

	memset(&ent, 0, sizeof(ent));
	err = prodos_put_dirent(image, &direnum, &ent);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_listforks(imgtool::partition &partition, const char *path, std::vector<imgtool::fork_entry> &forks)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;

	err = prodos_lookup_path(image, path, CREATE_NONE, &direnum, &ent);
	if (err)
		return err;

	if (is_dir_storagetype(ent.storage_type))
		return IMGTOOLERR_FILENOTFOUND;

	// specify data fork
	forks.emplace_back(ent.filesize[0], imgtool::fork_entry::type_t::DATA);

	if (is_extendedfile_storagetype(ent.storage_type))
	{
		// specify the resource fork
		forks.emplace_back(ent.filesize[1], imgtool::fork_entry::type_t::RESOURCE);
	}

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_createdir(imgtool::partition &partition, const char *path)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;

	err = prodos_lookup_path(image, path, CREATE_DIR, &direnum, &ent);
	if (err)
		return err;

	/* only work on directories */
	if (!is_dir_storagetype(ent.storage_type))
		return IMGTOOLERR_FILENOTFOUND;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_free_directory(imgtool::image &image, uint8_t *volume_bitmap, uint16_t key_pointer)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	int i;
	uint16_t next_block;
	uint32_t offset;
	uint8_t buffer[BLOCK_SIZE];

	di = get_prodos_info(image);

	if (key_pointer != 0)
	{
		err = prodos_load_block(image, key_pointer, buffer);
		if (err)
			return err;

		for (i = 0; i < di->dirents_per_block; i++)
		{
			offset = i * di->dirent_size + 4;

			if (is_file_storagetype(buffer[offset]) || is_file_storagetype(buffer[offset]))
				return IMGTOOLERR_DIRNOTEMPTY;
		}

		next_block = pick_integer_le(buffer, 2, 2);

		err = prodos_free_directory(image, volume_bitmap, next_block);
		if (err)
			return err;

		prodos_set_volume_bitmap_bit(volume_bitmap, key_pointer, 0);
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_deletedir(imgtool::partition &partition, const char *path)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;
	uint8_t *volume_bitmap = NULL;

	err = prodos_lookup_path(image, path, CREATE_NONE, &direnum, &ent);
	if (err)
		goto done;

	/* only work on directories */
	if (!is_dir_storagetype(ent.storage_type))
	{
		err = IMGTOOLERR_FILENOTFOUND;
		goto done;
	}

	err = prodos_load_volume_bitmap(image, &volume_bitmap);
	if (err)
		goto done;

	err = prodos_free_directory(image, volume_bitmap, ent.key_pointer[0]);
	if (err)
		goto done;

	err = prodos_save_volume_bitmap(image, volume_bitmap);
	if (err)
		goto done;

	memset(&ent, 0, sizeof(ent));
	err = prodos_put_dirent(image, &direnum, &ent);
	if (err)
		goto done;

done:
	if (volume_bitmap)
		free(volume_bitmap);
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_get_file_tree(imgtool::image &image, imgtool_chainent *chain, size_t chain_size,
	size_t *chain_pos, uint16_t block, uint8_t total_depth, uint8_t cur_depth)
{
	imgtoolerr_t err;
	prodos_diskinfo *di;
	int i;
	uint16_t sub_block;
	uint8_t buffer[BLOCK_SIZE];

	if (block == 0)
		return IMGTOOLERR_SUCCESS;
	if (*chain_pos >= chain_size)
		return IMGTOOLERR_SUCCESS;

	/* check bounds */
	di = get_prodos_info(image);
	if (block >= di->total_blocks)
		return IMGTOOLERR_CORRUPTFILE;

	chain[*chain_pos].level = cur_depth;
	chain[*chain_pos].block = block;
	(*chain_pos)++;

	/* must we recurse into the tree? */
	if (cur_depth < total_depth)
	{
		err = prodos_load_block(image, block, buffer);
		if (err)
			return err;

		for (i = 0; i < 256; i++)
		{
			sub_block = buffer[i + 256];
			sub_block <<= 8;
			sub_block |= buffer[i + 0];

			err = prodos_get_file_tree(image, chain, chain_size, chain_pos, sub_block, total_depth, cur_depth + 1);
			if (err)
				return err;
		}
	}
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_getattrs(imgtool::partition &partition, const char *path, const uint32_t *attrs, imgtool_attribute *values)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	int i;

	err = prodos_lookup_path(image, path, CREATE_NONE, NULL, &ent);
	if (err)
		return err;

	for (i = 0; attrs[i]; i++)
	{
		switch(attrs[i])
		{
			case IMGTOOLATTR_INT_MAC_TYPE:
				values[i].i = ent.file_type;
				break;
			case IMGTOOLATTR_INT_MAC_CREATOR:
				values[i].i = ent.file_creator;
				break;
			case IMGTOOLATTR_INT_MAC_FINDERFLAGS:
				values[i].i = ent.finder_flags;
				break;
			case IMGTOOLATTR_INT_MAC_COORDX:
				values[i].i = ent.coord_x;
				break;
			case IMGTOOLATTR_INT_MAC_COORDY:
				values[i].i = ent.coord_y;
				break;
			case IMGTOOLATTR_INT_MAC_FINDERFOLDER:
				values[i].i = ent.finder_folder;
				break;
			case IMGTOOLATTR_INT_MAC_ICONID:
				values[i].i = ent.icon_id;
				break;
			case IMGTOOLATTR_INT_MAC_SCRIPTCODE:
				values[i].i = ent.script_code;
				break;
			case IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS:
				values[i].i = ent.extended_flags;
				break;
			case IMGTOOLATTR_INT_MAC_COMMENTID:
				values[i].i = ent.comment_id;
				break;
			case IMGTOOLATTR_INT_MAC_PUTAWAYDIRECTORY:
				values[i].i = ent.putaway_directory;
				break;

			case IMGTOOLATTR_TIME_CREATED:
				values[i].t = prodos_crack_time(ent.creation_time).to_time_t();
				break;
			case IMGTOOLATTR_TIME_LASTMODIFIED:
				values[i].t = prodos_crack_time(ent.lastmodified_time).to_time_t();
				break;
		}
	}

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_setattrs(imgtool::partition &partition, const char *path, const uint32_t *attrs, const imgtool_attribute *values)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	prodos_direnum direnum;
	int i;

	err = prodos_lookup_path(image, path, CREATE_NONE, &direnum, &ent);
	if (err)
		return err;

	for (i = 0; attrs[i]; i++)
	{
		switch(attrs[i])
		{
			case IMGTOOLATTR_INT_MAC_TYPE:
				ent.file_type = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_CREATOR:
				ent.file_creator = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_FINDERFLAGS:
				ent.finder_flags = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_COORDX:
				ent.coord_x = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_COORDY:
				ent.coord_y = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_FINDERFOLDER:
				ent.finder_folder = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_ICONID:
				ent.icon_id = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_SCRIPTCODE:
				ent.script_code = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS:
				ent.extended_flags = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_COMMENTID:
				ent.comment_id = values[i].i;
				break;
			case IMGTOOLATTR_INT_MAC_PUTAWAYDIRECTORY:
				ent.putaway_directory = values[i].i;
				break;

			case IMGTOOLATTR_TIME_CREATED:
				ent.creation_time = prodos_setup_time(values[i].t);
				break;
			case IMGTOOLATTR_TIME_LASTMODIFIED:
				ent.lastmodified_time = prodos_setup_time(values[i].t);
				break;
		}
	}

	err = prodos_put_dirent(image, &direnum, &ent);
	if (err)
		return err;

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_suggesttransfer(imgtool::partition &partition, const char *path, imgtool_transfer_suggestion *suggestions, size_t suggestions_length)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	mac_filecategory_t file_category = MAC_FILECATEGORY_DATA;

	if (path)
	{
		err = prodos_lookup_path(image, path, CREATE_NONE, NULL, &ent);
		if (err)
			return err;

		file_category = is_extendedfile_storagetype(ent.storage_type)
			? MAC_FILECATEGORY_FORKED : MAC_FILECATEGORY_DATA;
	}

	mac_suggest_transfer(file_category, suggestions, suggestions_length);
	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t prodos_diskimage_getchain(imgtool::partition &partition, const char *path, imgtool_chainent *chain, size_t chain_size)
{
	imgtoolerr_t err;
	imgtool::image &image(partition.image());
	prodos_dirent ent;
	size_t chain_pos = 0;
	int fork_num;

	err = prodos_lookup_path(image, path, CREATE_NONE, NULL, &ent);
	if (err)
		return err;

	switch(ent.storage_type & 0xF0)
	{
		case 0x10:
		case 0x20:
		case 0x30:
			/* normal ProDOS file */
			err = prodos_get_file_tree(image, chain, chain_size, &chain_pos,
				ent.key_pointer[0], ent.depth[0] - 1, 0);
			if (err)
				return err;
			break;

		case 0x50:
			/* extended ProDOS file */
			chain[chain_pos].level = 0;
			chain[chain_pos].block = ent.extkey_pointer;
			chain_pos++;

			for (fork_num = 0; fork_num <= 1; fork_num++)
			{
				if (ent.key_pointer[fork_num])
				{
					err = prodos_get_file_tree(image, chain, chain_size, &chain_pos,
						ent.key_pointer[fork_num], ent.depth[fork_num] - 1, 1);
					if (err)
						return err;
				}
			}
			break;

		case 0xE0:
			/* directory */
			return IMGTOOLERR_UNIMPLEMENTED;

		default:
			return IMGTOOLERR_UNEXPECTED;
	}

	return IMGTOOLERR_SUCCESS;
}



static void generic_prodos_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
{
	switch(state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case IMGTOOLINFO_INT_INITIAL_PATH_SEPARATOR:        info->i = 1; break;
		case IMGTOOLINFO_INT_OPEN_IS_STRICT:                info->i = 1; break;
		case IMGTOOLINFO_INT_SUPPORTS_CREATION_TIME:        info->i = 1; break;
		case IMGTOOLINFO_INT_SUPPORTS_LASTMODIFIED_TIME:    info->i = 1; break;
		case IMGTOOLINFO_INT_WRITING_UNTESTED:              info->i = 1; break;
		case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES:             info->i = sizeof(prodos_diskinfo); break;
		case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES:             info->i = sizeof(prodos_direnum); break;
		case IMGTOOLINFO_INT_PATH_SEPARATOR:                info->i = '/'; break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case IMGTOOLINFO_STR_DESCRIPTION:                   strcpy(info->s = imgtool_temp_str(), "ProDOS format"); break;
		case IMGTOOLINFO_STR_FILE:                          strcpy(info->s = imgtool_temp_str(), __FILE__); break;
		case IMGTOOLINFO_STR_EOLN:                          strcpy(info->s = imgtool_temp_str(), "\r"); break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case IMGTOOLINFO_PTR_MAKE_CLASS:                    info->make_class = imgtool_floppy_make_class; break;
		case IMGTOOLINFO_PTR_BEGIN_ENUM:                    info->begin_enum = prodos_diskimage_beginenum; break;
		case IMGTOOLINFO_PTR_NEXT_ENUM:                     info->next_enum = prodos_diskimage_nextenum; break;
		case IMGTOOLINFO_PTR_FREE_SPACE:                    info->free_space = prodos_diskimage_freespace; break;
		case IMGTOOLINFO_PTR_READ_FILE:                     info->read_file = prodos_diskimage_readfile; break;
		case IMGTOOLINFO_PTR_WRITE_FILE:                    info->write_file = prodos_diskimage_writefile; break;
		case IMGTOOLINFO_PTR_DELETE_FILE:                   info->delete_file = prodos_diskimage_deletefile; break;
		case IMGTOOLINFO_PTR_LIST_FORKS:                    info->list_forks = prodos_diskimage_listforks; break;
		case IMGTOOLINFO_PTR_CREATE_DIR:                    info->create_dir = prodos_diskimage_createdir; break;
		case IMGTOOLINFO_PTR_DELETE_DIR:                    info->delete_dir = prodos_diskimage_deletedir; break;
		case IMGTOOLINFO_PTR_GET_ATTRS:                     info->get_attrs = prodos_diskimage_getattrs; break;
		case IMGTOOLINFO_PTR_SET_ATTRS:                     info->set_attrs = prodos_diskimage_setattrs; break;
		case IMGTOOLINFO_PTR_SUGGEST_TRANSFER:              info->suggest_transfer = prodos_diskimage_suggesttransfer; break;
		case IMGTOOLINFO_PTR_GET_CHAIN:                     info->get_chain = prodos_diskimage_getchain; break;
	}
}



void prodos_525_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
{
	switch(state)
	{
		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case IMGTOOLINFO_STR_NAME:                          strcpy(info->s = imgtool_temp_str(), "prodos_525"); break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case IMGTOOLINFO_PTR_FLOPPY_CREATE:                 info->create = prodos_diskimage_create_525; break;
		case IMGTOOLINFO_PTR_FLOPPY_OPEN:                   info->open = prodos_diskimage_open_525; break;
		case IMGTOOLINFO_PTR_FLOPPY_FORMAT:                 info->p = (void *) floppyoptions_apple2; break;

		default:                                            generic_prodos_get_info(imgclass, state, info); break;
	}
}



void prodos_35_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
{
	switch(state)
	{
		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case IMGTOOLINFO_STR_NAME:                          strcpy(info->s = imgtool_temp_str(), "prodos_35"); break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case IMGTOOLINFO_PTR_FLOPPY_CREATE:                 info->create = prodos_diskimage_create_35; break;
		case IMGTOOLINFO_PTR_FLOPPY_OPEN:                   info->open = prodos_diskimage_open_35; break;
		case IMGTOOLINFO_PTR_FLOPPY_FORMAT:                 info->p = (void *) floppyoptions_apple35_iigs; break;

		default:                                            generic_prodos_get_info(imgclass, state, info); break;
	}
}