summaryrefslogblamecommitdiffstatshomepage
path: root/src/lib/formats/ti99_dsk.cpp
blob: b248c9dd5942b22f8f0ce16104d249418e083023 (plain) (tree)
1
2
3

                                 
                                                                      





































                                                                                     

                   
                 
                   
                  
 
                                               


                     

                       













                                                          
 


                                                       
 
                                                   
 
                                                                       

 


                                                                           
                                                                                        
 


                             
                                
                                            
 



                                    
 


                                                                                 
 

                                                             
 









                                                                                              
 




                                                                                
 



                                                                                


                                                             
                                                                    
 
                                                                                                                                                          
 


                                                                              
         


                                                                                                                                  
         
 
                                                                              

                                                                     
 

                                                                             
 

                                                                                                                          
 
                    
                         



                                           
                                              
         
                                                                   
                 


                                                                                                    
 
                                                                                                                     
 
                                        
                         
                                                      
                                 












                                                                                                                                                                 


                                    




                                                                                                                                                             
                                 


                            


                                                                                                                
                                                

                                                                                                                                                            
                                    
                                                                                                                                                             
                         
                                                

                 
 
                    

 


                                      
                                                                  
 
                               
 
                                    
                                                        
 
                                                     
 

                                                                                         

                                                             
 
                                               
            
         

                                                                           
         
 

                        
                         
 





                                                                   
         
                                                                 
                 
                                                                                                               
                                                                                                                
 

                                                                                                       
 


                                                                                                                                       
 

                                                                                                                                 
                         
                                                                         
                                 
















                                                                                                                                         
 







                                                                                                                            
                                         

                                                                                                                                 
                                         
                                            
                                         



                                                                                                       
                                         

                                                  


                                    

                                                                                  
                                         

                                                                                                           
                                         
                                            
                                         
                                                                                                                       

                                         
                         


                                                                                                
                 
                       
         


                    








                                                                                                                                                                                     
 
                                     
 
                                                         
 


                                                   
 




                                                                                         
 
                         
 







                                                           
 





                                                           
         

                                                                                 
                 

                                                                     


                    






























                                                                           
                         

                                                                                       
                         






                                                                           


                 



                                                           
                                                                  

 
                                                                                                                                                                                      
 



                                                
                                     
 


                                                               
 

                             
 
                              
         

                                                           
         









                                                                                         
                         



                                                            
 

                                                  
 









                                                            
         

                                                                                 
                 

                                                                     


                    





































                                                                            
                         

                                                                                        
                         






                                                                            

                 


                                                            
 
                                                                  

 



                                    


                
                
                 

               


               



               
  


                                                                            
  
                                                                                                                                                                  
 
                       
                        

                         
                               




                             
 

                                                                
 


                                                                    
 
                                   
         
                                                                             
                                                      
                                                                       
                                                          





                                                          
                              
 
                              
                 

                                                
                         

                                                                                                 
                                               

                                                 

                              
                             

                                                








                                                        
                                 
                                                                                     
                                 





                                                                                                                            
                         



                                                   
                         




                                                                                                                                                  
                                    




                                                       

                              
                            



                                                                                      
                         
                                                                                                  
 
                                            
                                 



















































                                                                                                                                                                                
                                 

                                                 

                              
                           












                                                                                                                          

                                                                       



























                                                                                                                
                                 
                                                                                 
                                 





                                                                                                                                

                              
 
                            
                                                   
                         
                                                     
                                 


                                                                                                                                  

                                              
 
                                                                         
                                 

                                                                                                                                                      
                                 








                                                                          



                              
                                                   
                         



                                                                      

                                    
                                                      
                                 

                                                 

                              
                 









































                                                                                                                                            
         






                                                                                               

 
 
                                                                





















































                                                                              
                                                                   
 
                                                 



                                                                              

                                                         

                                                            
                          
         











                                 

         







                                                                         
                 
                                                                                            
                                   

                    
                 
                                                                                                        
                 
         
                                                                               

                    
 
                                                                                                                 
 
                                                 







                              


                                                         


















                                                                                     
                                                                                                                                                                                                                                                          
                                
                                           

         
                                                                                           
                                                                             
 































                                                                   
                                                                                                                    




                                                                                                         
                                                                                                                                                 



                 
                                                                           




                                             
                                                     

                                          

 
                                                                                                                                                        
 
                                                                          

                                                                         
 
                                                                           
 



                                                   
 
                                           
 
                                                 
 
                                        

                                           








                                                               
                 
                                                                 
                 

                                                               
                 
                                                                    
                 
                                           
         




                                                            
                                                                  


                                        
                                                                                          


         

















                                                                                     

                                                                         
                                                                              
  
                                                                                                                                           
 
                     
 


                                                                          
 




                                                                                                                          
         

 
 

































                                                                                                


                         





















                                                                                         
                                                                   
 
                     









                                                                    

                                                                                                 
                              



                          
















                                                                                  
 












                                                                                                                                                   
 



                                                                                                                         

                    

                                                                                           

                 
                                                                               



                    
                                                  
  
                                                                                                                 
 
                                                 
                                                             
                    
                                                                                                





































                                      


  



                                                                           
  
                                                                                                                                                        
 



















































































                                                                                                                                   


  




                                                                            
  
                                                                                                                                           
 




















































                                                                                        

 
                                                     
 










                             

                 

                                                                                                
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/*********************************************************************

  TI-99 family disk images

  used by TI-99/4, TI-99/4A, TI-99/8, SGCPU ("TI-99/4P"), and Geneve

  Sector Dump Format, aka "v9t9" format
     customized wd177x_format
     no track data

  Track Dump Format, aka "pc99" format
     contains all track information, but no clock patterns

  Both formats allow for a broad range of medium sizes. All sectors are 256
  bytes long. The most common formats are 9 sectors per track, single-sided,
  40 tracks, which yields 90 KiB of sector data (known as SSSD), and 18
  sectors per track, double-sided, and 40 tracks, which is 360 KiB (known as
  DSDD). There are rare occurances of 16 sectors/track
  (prototypical TI double-density controller) and 35 track media. Newer
  controllers and ROMs allow for up to 36 sectors per track and 80 tracks on
  both sides, which is 1,44 MiB (DSHD80).

  Double stepping
  --------------
  When using a 40-track disk in an 80-track drive, it seems as if each
  track appears twice in sequence (0,0,1,1,2,2,...,39,39).
  The system will write to each second track and leave the others untouched.
  When we write back that image, there is no simple way to know that
  the 80 tracks are in fact doubled 40 tracks. We will actually write
  all 80 tracks, doubling the size of the image file. This disk image will
  now become unusable in a 40-track drive - which is exactly what happens in reality.

  Michael Zapf, July 2015
  Updated August 2018

   TODO:
   - Support for 77 track images for HX5102

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

#include <string.h>
#include <time.h>
#include <assert.h>
#include <iomanip>

#include "emu.h" // osd_printf_* (in osdcore.h)
#include "imageutl.h"
#include "ti99_dsk.h"

#define SECTOR_SIZE 256

#undef LOG_OUTPUT_FUNC
#define LOG_OUTPUT_FUNC osd_printf_info

#define LOG_WARN       (1U<<1)   // Warnings
#define LOG_HEADER     (1U<<2)   // Header
#define LOG_SHIFT      (1U<<3)   // Shift register
#define LOG_INTERLEAVE (1U<<4)   // Interleave information
#define LOG_INFO       (1U<<5)   // Disk info
#define LOG_DETAIL     (1U<<6)   // Details
#define LOG_TRACK      (1U<<7)   // Track output

#define VERBOSE ( LOG_WARN )

#include "logmacro.h"

// ====================================================
//  Common methods for both formats.
// ====================================================

int ti99_floppy_format::get_encoding(int cell_size)
{
	return (cell_size==4000)? floppy_image::FM : floppy_image::MFM;
}

/*
    Load the image from disk and convert it into a sequence of flux levels.
*/
bool ti99_floppy_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
{
	int cell_size = 0;
	int sector_count = 0;
	int heads = 0;
	int log_track_count = 0;
	int file_size = io_generic_size(io);

	bool img_high_tpi = false;
	bool drive_high_tpi = false;
	bool skip_track = false;
	int track_count, head_count;

	// Get information about cell size, sector count, and logical track count
	// (the track count as defined by the file system)
	determine_sizes(io, cell_size, sector_count, heads, log_track_count);

	image->get_maximal_geometry(track_count, head_count);
	drive_high_tpi = (track_count > 44);

	// Depends on the image format (SDF or TDF)
	int track_size = get_track_size(sector_count);

	// Adding support for a variant of the sector image format which adds 768 bytes
	// as a bad sector map. Only applies for SDF; TDF will not yield that result
	if (((file_size - 768) % (SECTOR_SIZE*360)) == 0)
	{
		LOGMASKED(LOG_INFO, "[ti99_dsk] Stripping map of bad sectors at image end\n");
		file_size -= 768;
	}

	// Problem: If the disk is improperly formatted, the track count will be
	// wrong. For instance, a disk could be reformatted to single-side.
	// We assume there is no disk with single side format beyond 40 tracks.
	if ((heads==1) && (file_size > track_size*40))
		heads = 2;

	// TI formats are known as having a constant track size, so the physical
	// track count on the medium can easily be calculated.
	int img_track_count = file_size / (track_size*heads);
	img_high_tpi = (img_track_count > 44);

	// Some disks are known to have an incomplete header.
	// PASCAL disks have a track count of 0.
	if (log_track_count == 0) log_track_count = img_track_count;

	LOGMASKED(LOG_INFO, "[ti99_dsk] logical tracks = %d, image tracks = %d, drive track_count = %d\n", log_track_count, img_track_count, track_count);

	// If the floppy disk has 80 tracks but the drive has 40 tracks, force
	// the floppy to 40 tracks (will make it unreadable)
	if (img_high_tpi && !drive_high_tpi)
	{
		LOGMASKED(LOG_WARN, "[ti99_dsk] Floppy disk has too many tracks for this drive, will skip every second track.\n");
		img_high_tpi = false;
		skip_track = true; // only read the even numbered tracks
	}

	// Is this the first time that this disk is read in an 80-track drive?
	bool double_step = (drive_high_tpi && log_track_count <= 44);
	bool first_time_double = (drive_high_tpi && !img_high_tpi);

	if (double_step)
		LOGMASKED(LOG_INFO, "[ti99_dsk] Applying double stepping\n");

	if (first_time_double)
		LOGMASKED(LOG_WARN, "[ti99_dsk] 40-track image in an 80-track drive. On save, image size will double.\n");

	int trackid;
	// Read the image
	uint8_t sectordata[36*SECTOR_SIZE];
	int sectornmb[36];
	int sectoroff[36];

	for (int head=0; head < heads; head++)
	{
		for (int track=0; track < img_track_count; track++)
		{
			LOGMASKED(LOG_TRACK, "[ti99_dsk] Reading track %d, head %d\n", track, head);
			if (double_step && !first_time_double) trackid = track/2;
			else trackid = track;

			load_track(io, sectordata, sectornmb, sectoroff, head, track, sector_count, img_track_count);

			if (double_step)
			{
				if (first_time_double)
				{
					// Create two tracks from each medium track. This reflects the
					// fact that the drive head will find the same data after
					// a single step
					if (sector_count < 10)
					{
						generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2, trackid, head);
						generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2+1, trackid, head);
					}
					else
					{
						generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2, trackid, head);
						generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2+1, trackid, head);
					}
				}
				else
				{
					// Reading 80 track image for double stepping
					if (sector_count < 10)
						generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track, trackid, head);
					else
						generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track, trackid, head);
				}
			}
			else
			{
				// No double stepping now, but maybe the image was created under double stepping
				int drivetrack = (skip_track)? track / 2 : track;

				// Normal tracks
				if (sector_count < 10)
					generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, drivetrack, drivetrack, head);
				else
					generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, drivetrack, drivetrack, head);
			}
			if (skip_track) track++;
		}
	}

	return true;
}

/*
    Save all tracks to the image file.
*/
bool ti99_floppy_format::save(io_generic *io, floppy_image *image)
{
	int act_track_size = 0;

	uint8_t bitstream[500000/8];
	uint8_t sectordata[9216];   // max size (36*256)

	int cellsizes[] = { 2000, 4000, 1000, 2000 };

	// Do we use double-stepping?
	// If our image was loaded into a 80-track drive, we will always write 80 tracks.
	int track_count, head_count;
	image->get_maximal_geometry(track_count, head_count);

	if (track_count > 80) track_count = 80;
	else
	{
		if (track_count > 35 && track_count < 80) track_count = 40;
		else track_count = 35;
	}

	int attempt = 0;
	int sector[36];
	int seccount = 0;

	int head = 0;
	int cell_size = cellsizes[0];
	int encoding = get_encoding(cell_size);
	int expected_sectors = (encoding==floppy_image::MFM)? 16:9;

	while (head < 2)
	{
		for (int track = 0; track < track_count; track++)
		{
			// Retrieve the cells from the flux sequence. This also delivers the actual track size.
			generate_bitstream_from_track(track, head, cell_size, bitstream, act_track_size, image);

			// Maybe the track has become longer due to moving splices
			if (act_track_size > 200000000/cell_size) act_track_size = 200000000/cell_size;

			LOGMASKED(LOG_DETAIL, "[ti99_dsk] Getting sectors from track %d, head %d\n", track, head);
			seccount = get_sectors(bitstream, act_track_size, encoding, track, head, expected_sectors, sectordata, sector);
			LOGMASKED(LOG_DETAIL, "[ti99_dsk] Seccount = %d\n", seccount);

			// We may have more sectors in MFM (18). This is OK in track 0; otherwise we need to restart the process.
			if (track==0 && head==0)
			{
				if (seccount==18 && expected_sectors==16)
				{
					LOGMASKED(LOG_INFO, "[ti99_dsk] Using full 18 sectors/track format for MFM\n");
					expected_sectors=18;
				}
			}
			else
			{
				if (seccount==18 && expected_sectors==16)
				{
					// This happens when we assumed 16 sectors/track up to here
					// and now the track uses 18 tracks. We need to start over,
					// assuming 18 sectors/track, and adding empty sectors
					// where necessary
					head = -1;
					LOGMASKED(LOG_WARN, "[ti99_dsk] Tracks with variable sector count; assuming 18 sectors/track\n");
					break;
				}
			}

			if (seccount < 3 && track == 0)
			{
				if (head == 0)
				{
					// Bad cell size.
					LOGMASKED(LOG_DETAIL, "[ti99_dsk] Decoding with cell size %d failed.\n", cell_size);
					attempt++;
					if (attempt == 4)
					{
						LOGMASKED(LOG_INFO, "[ti99_dsk] Unsupported cell size or unformatted medium.\n");
						return false;
					}
					else
					{
						// Pick a new cell size
						cell_size = cellsizes[attempt];
						encoding = get_encoding(cell_size);
						expected_sectors = (encoding==floppy_image::MFM)? 16:9;
					}
					head = -1;
					break;
				}
				else
				{
					// head 0 already done, but head 1 failing
					if (min_heads()==1)
					{
						LOGMASKED(LOG_INFO, "[ti99_dsk] Saving as single-sided\n");
						break;
					}
					else
					{
						LOGMASKED(LOG_WARN, "[ti99_dsk] Saving empty tracks on second side\n");
					}
				}
			}

			// Save to the file
			write_track(io, sectordata, sector, track, head, seccount, track_count);
		}
		head++;
	}
	return true;
}

enum
{
	GAP0 = 0, GAP1, GAP2, GAP3, GAPBYTE, SYNC
};

/*
    Build a new track from sector contents.
*/
void ti99_floppy_format::generate_fm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sectornmb, int *secoffset, int track, int trackid, int head)
{
	std::vector<uint32_t> buffer;

	const int param[6] = { 10, 26, 11, 28, 0xff, 6 };

	// cell size in ns
	const int cell_size = 4000;
	const int cell_count = 200000000/cell_size;

	int sectorpartlen = 2*param[SYNC] + 10 + param[GAP2] + SECTOR_SIZE + param[GAP3];
	int tracklen = param[GAP1] + sector_count * sectorpartlen;
	if (param[GAP0]!=0) tracklen += param[GAP0] + param[SYNC] + 1;
	// The remaining bytes are filled with the gap byte as gap 4.
	int gap4 = cell_count/16 - tracklen;

	uint16_t crc = 0;

	// Create Gap 0 and Index AM
	for (int i=0; i < param[GAP0]; i++)
		fm_w(buffer, 8, param[GAPBYTE], cell_size);

	for (int i=0; i < param[SYNC]; i++)
		fm_w(buffer, 8, 0x00, cell_size);

	raw_w(buffer, 16, 0xf77a, cell_size);  // IXAM

	// and Gap 1
	for (int i=0; i < param[GAP1]; i++)
		fm_w(buffer, 8, param[GAPBYTE], cell_size);

	// For each sector
	for (int j=0; j < sector_count; j++)
	{
		// If the sector is not available, create an empty space of zeros
		if (sectornmb[j]==-1)
		{
			for (int i=0; i < sectorpartlen; i++)
				raw_w(buffer, 16, 0x0000, cell_size);
		}
		else
		{
			// Sync
			for (int i=0; i < param[SYNC]; i++)
				fm_w(buffer, 8, 0x00, cell_size);
			raw_w(buffer, 16, 0xf57e, cell_size);  // IDAM

			crc = ccitt_crc16_one(0xffff, 0xfe);

			// Header
			fm_w(buffer, 8, trackid, cell_size);
			crc = ccitt_crc16_one(crc, trackid);
			fm_w(buffer, 8, head, cell_size);
			crc = ccitt_crc16_one(crc, head);
			fm_w(buffer, 8, sectornmb[j], cell_size);
			crc = ccitt_crc16_one(crc, sectornmb[j]);
			fm_w(buffer, 8, 0x01, cell_size);
			crc = ccitt_crc16_one(crc, 0x01);
			// CRC
			fm_w(buffer, 8, (crc>>8)&0xff, cell_size);
			fm_w(buffer, 8, crc&0xff, cell_size);

			// Gap 2
			for (int i=0; i < param[GAP2]; i++)
				fm_w(buffer, 8, param[GAPBYTE], cell_size);
			// Sync
			for (int i=0; i < param[SYNC]; i++)
				fm_w(buffer, 8, 0x00, cell_size);
			raw_w(buffer, 16, 0xf56f, cell_size);  // DAM
			crc = ccitt_crc16_one(0xffff, 0xfb);

			// Sector contents
			for (int k=0; k < SECTOR_SIZE; k++)
			{
				fm_w(buffer, 8, sectordata[secoffset[j]+k], cell_size);
				crc = ccitt_crc16_one(crc, sectordata[secoffset[j]+k]);
			}
			// CRC
			fm_w(buffer, 8, (crc>>8)&0xff, cell_size);
			fm_w(buffer, 8, crc&0xff, cell_size);

			// Gap 3
			for (int i=0; i < param[GAP3]; i++)
				fm_w(buffer, 8, param[GAPBYTE], cell_size);
		}
	}

	// and fill the remaining bytes with gap bytes
	for (int i=0; i < gap4; i++)
		fm_w(buffer, 8, param[GAPBYTE], cell_size);

	generate_track_from_levels(track, head, buffer, 0, image);
}

void ti99_floppy_format::generate_mfm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sectornmb, int *secoffset, int track, int trackid, int head)
{
	// MFM16:  (80,50,22,50,4e,12)
	// MFM18/36:  (10,30,22,21,4e,12)
	// A1: 0x4489   01 00 01 00 10 *00 10 01
	// C2: 0x5224   01 01 00 10 *00 10 01 00
	std::vector<uint32_t> buffer;

	const int mfm16param[7] = { 80, 50, 22, 50, 0x4e, 12 };
	const int mfm18param[7] = { 10, 30, 22, 21, 0x4e, 12 };
	const int *param = mfm18param;

	// cell size in ns
	int cell_size = 2000;

	if (sector_count < 30)
	{
		cell_size = 2000;
		if (sector_count == 16) param = mfm16param;
	}
	else cell_size = 1000;

	int cell_count = 200000000/cell_size;

	int sectorpartlen = 2*param[SYNC] + 16 + param[GAP2] + SECTOR_SIZE + param[GAP3];
	int tracklen = param[GAP1] + sector_count * sectorpartlen;
	if (param[GAP0]!=0) tracklen += param[GAP0] + param[SYNC] + 4;
	// The remaining bytes are filled with the gap byte as gap 4.
	int gap4 = cell_count/16 - tracklen;

	uint16_t crc = 0;

	// Create Gap 0 and Index AM
	for (int i=0; i < param[GAP0]; i++)
		mfm_w(buffer, 8, param[GAPBYTE], cell_size);

	for (int i=0; i < param[SYNC]; i++)
		mfm_w(buffer, 8, 0x00, cell_size);

	for (int i=0; i < 3; i++)
		raw_w(buffer, 16, 0x5224, cell_size);
	mfm_w(buffer, 8, 0xfc, cell_size);

	// and Gap 1
	for (int i=0; i < param[GAP1]; i++)
		mfm_w(buffer, 8, param[GAPBYTE], cell_size);

	// For each sector
	for (int j=0; j < sector_count; j++)
	{
		// If the sector is not available, create an empty space of zeros
		if (sectornmb[j]==-1)
		{
			for (int i=0; i < sectorpartlen; i++)
				raw_w(buffer, 16, 0x0000, cell_size);
		}
		else
		{
			// Sync
			for (int i=0; i < param[SYNC]; i++)
				mfm_w(buffer, 8, 0x00, cell_size);
			// IDAM
			for (int i=0; i < 3; i++)
				raw_w(buffer, 16, 0x4489, cell_size);
			mfm_w(buffer, 8, 0xfe, cell_size);

			crc = 0xb230; // pre-init including IDAM

			// Header
			mfm_w(buffer, 8, trackid, cell_size);
			crc = ccitt_crc16_one(crc, trackid);
			mfm_w(buffer, 8, head, cell_size);
			crc = ccitt_crc16_one(crc, head);
			mfm_w(buffer, 8, sectornmb[j], cell_size);
			crc = ccitt_crc16_one(crc, sectornmb[j]);
			mfm_w(buffer, 8, 0x01, cell_size);
			crc = ccitt_crc16_one(crc, 0x01);
			// CRC
			mfm_w(buffer, 8, (crc>>8)&0xff, cell_size);
			mfm_w(buffer, 8, crc&0xff, cell_size);

			// Gap 2
			for (int i=0; i < param[GAP2]; i++)
				mfm_w(buffer, 8, param[GAPBYTE], cell_size);
			// Sync
			for (int i=0; i < param[SYNC]; i++)
				mfm_w(buffer, 8, 0x00, cell_size);

			// DAM
			for (int i=0; i < 3; i++)
				raw_w(buffer, 16, 0x4489, cell_size);
			mfm_w(buffer, 8, 0xfb, cell_size);
			crc = 0xe295; // pre-init including DAM

			// Sector contents
			for (int k=0; k < SECTOR_SIZE; k++)
			{
				mfm_w(buffer, 8, sectordata[secoffset[j]+k], cell_size);
				crc = ccitt_crc16_one(crc, sectordata[secoffset[j]+k]);
			}
			// CRC
			mfm_w(buffer, 8, (crc>>8)&0xff, cell_size);
			mfm_w(buffer, 8, crc&0xff, cell_size);

			// Gap 3
			for (int i=0; i < param[GAP3]; i++)
				mfm_w(buffer, 8, param[GAPBYTE], cell_size);
		}
	}
	// and fill the remaining bytes with gap bytes
	for (int i=0; i < gap4; i++)
		mfm_w(buffer, 8, param[GAPBYTE], cell_size);

	generate_track_from_levels(track, head, buffer, 0, image);
}

// States for decoding the bitstream
enum
{
	FMIDAM,
	A1IDAM1,
	A1IDAM2,
	A1IDAM3,
	MFMIDAM,
	MFMIDENT,
	HEADER,
	FMDAM,
	A1DAM1,
	A1DAM2,
	A1DAM3,
	MFMDAM,
	DATA
};

/*
    Retrieve all sectors from the track, given as a bitstream.
    The sectors are assumed to have a length of 256 byte, and their sequence
    is stored in the secnumber array.
*/
int ti99_floppy_format::get_sectors(const uint8_t *bitstream, int cell_count, int encoding, int track, int head, int sectors, uint8_t *sectordata, int *secnumber)
{
	int bitpos = 0;
	int lastpos = 0;
	int spos = 0;
	int seccount = 0;
	uint16_t shift_reg = 0;
	int bytepos = 0;
	uint8_t databyte = 0;
	uint8_t curbyte = 0;
	int rep = 0;
	int cursect = 0;

	// An array of flags (max 32), set for each found sector
	uint32_t foundsectors = 0;

	int maxsect = (encoding==floppy_image::MFM)? 18 : 9;
	int first = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
	int state = first;

	while (bitpos < cell_count)
	{
		LOGMASKED(LOG_SHIFT, "[ti99_dsk] shift = %04x\n", shift_reg);
		shift_reg = (shift_reg << 1) & 0xffff;
		if ((bitpos & 0x07)==0) curbyte = bitstream[bytepos++];
		if ((curbyte & 0x80) != 0) shift_reg |= 1;

		if (((bitpos - lastpos) & 1)==0)
		{
			databyte = (databyte << 1) & 0xff;
			databyte |= shift_reg & 1;
		}
		curbyte <<= 1;

		switch (state)
		{
		case FMIDAM:
			if (shift_reg == 0xf57e)
			{
				// IDAM found
				LOGMASKED(LOG_DETAIL, "[ti99_dsk] IDAM found at %05x\n", bitpos);
				state = HEADER;
				lastpos = bitpos;
				rep = 0;
			}
			break;
		case A1IDAM1:
			if (shift_reg == 0x4489)
			{
				state = A1IDAM2;
				lastpos = bitpos;
			}
			break;
		case A1IDAM2:
		case A1IDAM3:
			if (bitpos - lastpos == 16)
			{
				if (shift_reg == 0x4489)
				{
					state = (state==A1IDAM2)? A1IDAM3 : MFMIDENT;
				}
				else
				{
					LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Missing A1, not a header at %05x\n", bitpos);
					state = A1IDAM1;
				}
				lastpos = bitpos;
			}
			break;

		case MFMIDENT:
			if (bitpos - lastpos == 16)
			{
				if (databyte != 0xfe)
				{
					LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Ident (found) = %02x, expected = fe; skipping header\n", databyte);
					state = A1IDAM1;
				}
				else
				{
					rep = 0;
					state = HEADER;
				}
				lastpos = bitpos;
			}
			break;
		case HEADER:
			// Get the header, store the sector number, and report any
			// unexpected contents in the other fields. When invalid, skip
			// that header.
			if (bitpos - lastpos == 16)
			{
				LOGMASKED(LOG_HEADER, "[ti99_dsk] Header byte: %02x\n", databyte);

				switch (rep)
				{
				case 0:
					if (databyte > 79)
					{
						// Illegal cylinder, ignore this header.
						LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Track (found) = %d, must be less than 80; skipping header.\n", databyte);
						state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
						break;
					}
					// Consider double stepping
					if (databyte != track && databyte != track/2)
						LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Track (found) = %d, assuming %d\n", databyte, track);
					break;
				case 1:
					if (databyte > 2)
					{
						// Illegal head, ignore this header.
						LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Head (found) = %d, must be less than 2; skipping header.\n", databyte);
						state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
						break;
					}
					if (databyte != head)
						LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Head (found) = %d, assuming = %d\n", databyte, head);
					break;
				case 2:
					if (databyte >= maxsect)
					{
						// Illegal sector, ignore this header.
						LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector number (found) = %d, must be less than %d; skipping header\n", databyte, maxsect);
						state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
					}
					else cursect = databyte;

					if ((foundsectors & (1 << cursect)) != 0)
					{
						// Sector already appeared; discard it
						LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector number (found) = %d already appeared; skipping header\n", databyte);
						state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
					}
					break;
				case 3:
					if (databyte != 1)
					{
						// Illegal sector, ignore this header.
						LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector size (found) = %d, must be 1; skipping header\n", databyte);
						state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM;
					}
					else
					{
						state = (encoding==floppy_image::MFM)? A1DAM1 : FMDAM;
					}
					rep = -1;
					break;
				}
				rep++;
				lastpos = bitpos;
			}
			break;
		case FMDAM:
			if (bitpos - lastpos > 500)
			{
				LOGMASKED(LOG_WARN, "[ti99_dsk] Error: DAM too far away, skipping header\n");
				state = FMIDAM;
				break;
			}
			if (shift_reg == 0xf57e)
			{
				LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Expected DAM, found next IDAM; skipping header\n");
				state = HEADER;  // continue with reading the header
				lastpos = bitpos;
				break;
			}
			if (shift_reg == 0xf56a || shift_reg == 0xf56f)
			{
				// DAM found
				LOGMASKED(LOG_DETAIL, "[ti99_dsk] DAM found: %04x\n", bitpos);
				state = DATA;
				rep = 0;
				lastpos = bitpos;
				secnumber[seccount++] = cursect;
				// Keep track of the found sectors
				foundsectors |= (1 << cursect);
			}
			break;
		case A1DAM1:
			if (bitpos - lastpos > 1000)
			{
				LOGMASKED(LOG_WARN, "[ti99_dsk] Error: A1/DAM too far away, skipping header\n");
				state = A1IDAM1;
				break;
			}
			if (shift_reg == 0x4489)
			{
				state = A1DAM2;
				lastpos = bitpos;
			}
			break;
		case A1DAM2:
		case A1DAM3:
			if (bitpos - lastpos == 16)
			{
				if (shift_reg == 0x4489)
				{
					state = (state==A1DAM2)? A1DAM3 : MFMDAM;
				}
				else
				{
					LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Missing A1, not a data field at %05x\n", bitpos);
					state = A1IDAM1;
				}
				lastpos = bitpos;
			}
			break;

		case MFMDAM:
			if (bitpos - lastpos == 16)
			{
				if (databyte == 0xfe)
				{
					LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Expected DAM, found next IDAM; skipping header\n");
					state = HEADER;  // continue with reading the header
					lastpos = bitpos;
					break;
				}

				if (databyte != 0xf8 && databyte != 0xfb)
				{
					LOGMASKED(LOG_WARN, "[ti99_dsk] Error: DAM (found) = %02x, expected = f8 or fb; skipping header\n", databyte);
					state = HEADER;  // continue with reading the header
				}
				else
				{
					state = DATA;
					secnumber[seccount++] = cursect;
					// Keep track of the found sectors
					foundsectors |= (1 << cursect);
				}
				rep = 0;
				lastpos = bitpos;
			}
			break;

		case DATA:
			if (bitpos - lastpos == 16)
			{
				if (rep < 256)
				{
					sectordata[spos++] = databyte;
				}
				else
				{
					state = first;
				}
				rep++;
				lastpos = bitpos;
			}
			break;
		}
		bitpos++;
	}

	// We start with 9 sectors (FM) and 16 sectors (MFM)
	// If we have 18 sectors in MFM, and we have track 0/head 0, then accept
	// it, and switch to expected = 18.
	// If we have expected=16 and find sectors past sector 15,
	// and track!=0 or head!=0, then stop
	// the creation and restart with expected=18.

	// We now add all remaining, unfound sectors to complete the track
	// If all sectors were found, this loop is skipped
	int j = 0;
	int actual_sectors = seccount;
	LOGMASKED(LOG_DETAIL, "[ti99_dsk] Sector map: %08x\n", foundsectors);

	// Don't try to fill the first track
	if (track==0 && head==0 && seccount<3)
		return seccount;

	if (seccount < sectors)
	{
		LOGMASKED(LOG_INFO, "[ti99_dsk] %d missing sectors in track %d, head %d, adding as empty\n", sectors-seccount, track, head);
	}

	for (int i = seccount; i < sectors; i++)
	{
		// Search the next missing sector number. All sectors that were
		// found have a 1 at their bit position in foundsectors.
		while ((foundsectors & 1)!=0 && j < sectors)
		{
			foundsectors >>= 1;
			j++;
		}
		LOGMASKED(LOG_INFO, "[ti99_dsk] add empty sector %d\n", j);
		// Add the sector
		secnumber[seccount++] = j;
		foundsectors >>= 1;
		j++;
		// Create an empty sector
		for (int k=0; k < 256; k++)
			sectordata[spos++] = 0xe5;
	}

	LOGMASKED(LOG_INTERLEAVE, "[ti99_dsk] End of track reached, sectors = %d\n", seccount);
	LOGMASKED(LOG_INTERLEAVE, "[ti99_dsk] Sector sequence: ");
	for (int i=0; i < seccount; i++)
		LOGMASKED(LOG_INTERLEAVE, "%d ", secnumber[i]);
	LOGMASKED(LOG_INTERLEAVE, "\n");
	return actual_sectors;
}


uint8_t ti99_floppy_format::get_data_from_encoding(uint16_t raw)
{
	return (raw & 0x4000 ? 0x80 : 0x00) |
			(raw & 0x1000 ? 0x40 : 0x00) |
			(raw & 0x0400 ? 0x20 : 0x00) |
			(raw & 0x0100 ? 0x10 : 0x00) |
			(raw & 0x0040 ? 0x08 : 0x00) |
			(raw & 0x0010 ? 0x04 : 0x00) |
			(raw & 0x0004 ? 0x02 : 0x00) |
			(raw & 0x0001 ? 0x01 : 0x00);
}

/*
    Sector Dump Format
    ------------------
    The Sector Dump Format is also known as v9t9 format (named after the first
    TI emulator to use this format). It is a contiguous sequence of sector
    contents without track data. The first sector of the disk is located at
    the start of the image, while the last sector is at its end. The sectors
    are ordered by their logical number as used in the TI file system.

    In this implementation, the sector dump format is just a kind of
    wd177x_format with minor customizations, which allows us to keep the code
    small. The difference is the logical ordering of tracks and sectors.

    The TI file system orders all tracks on side 0 as going inwards,
    and then all tracks on side 1 going outwards.

        00 01 02 03 ... 38 39     side 0
        79 78 77 76 ... 41 40     side 1

    The SDF format stores the tracks and their sectors in logical order
        00 01 02 03 ... 38 39 [40 41 ... 79]

    There is also a variant of the SDF which adds three sectors at the end
    containing a map of bad sectors. This was introduced by a tool to read
    real TI floppy disks on a PC. As other emulators tolerate this additional
    bad sector map, we just check whether there are 3 more sectors and ignore
    them.
*/
const char *ti99_sdf_format::name() const
{
	return "sdf";
}

const char *ti99_sdf_format::description() const
{
	return "TI99 sector dump floppy disk image";
}

const char *ti99_sdf_format::extensions() const
{
	return "dsk";
}

int ti99_sdf_format::identify(io_generic *io, uint32_t form_factor)
{
	uint64_t file_size = io_generic_size(io);
	int vote = 0;

	// Adding support for another sector image format which adds 768 bytes
	// as a bad sector map
	if (((file_size - 768) % (SECTOR_SIZE*360)) == 0)
		file_size -= 768;

	// All formats apply for 5.25" and 3.5" form factors
	switch (file_size)
	{
	case 92160:     // SSSD
	case 163840:    // SSDD16
	case 184320:    // DSSD
	case 327680:    // DSDD16
	case 368640:    // DSDD
	case 737280:    // DSDD80
	case 1474560:   // DSQD
		vote = 50;
		break;
	default:
		vote = 0;
		break;
	}

	if (vote > 0)
	{
		// Read first sector (Volume Information Block)
		ti99vib vib;
		io_generic_read(io, &vib, 0, sizeof(ti99vib));

		// Check from contents
		if ((vib.id[0]=='D')&&(vib.id[1]=='S')&&(vib.id[2]=='K'))
		{
			LOGMASKED(LOG_INFO, "[ti99_dsk] Found formatted SDF disk medium\n");
			vote = 100;
		}
		else
		{
			LOGMASKED(LOG_INFO, "[ti99_dsk] No valid VIB found; disk may be unformatted\n");
		}
	}
	else LOGMASKED(LOG_INFO, "[ti99_dsk] Disk image is not a SDF image\n");
	return vote;
}

void ti99_sdf_format::determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks)
{
	uint64_t file_size = io_generic_size(io);
	ti99vib vib;

	cell_size = 0;
	sector_count = 0;
	heads = 2;

	bool have_vib = false;

	// Remove the bad sector map
	if (((file_size - 768) % (SECTOR_SIZE*360)) == 0)
		file_size -= 768;

	// Read first sector
	io_generic_read(io, &vib, 0, sizeof(ti99vib));

	// Check from contents
	if ((vib.id[0]=='D')&&(vib.id[1]=='S')&&(vib.id[2]=='K'))
	{
		sector_count = vib.secspertrack;
		heads = vib.sides;

		// Find out more about the density. SSDD happens to be the same size
		// as DSSD in the sector dump format, so we need to ask the
		// VIB if available. Otherwise, we assume that we have a DSSD medium.
		if (vib.density < 2) cell_size = 4000;
		else
		{
			if (vib.density < 4) cell_size = 2000;
			else cell_size = 1000;
		}
		LOGMASKED(LOG_INFO, "[ti99_dsk] VIB says that this disk is %s density with %d sectors per track, %d tracks, and %d heads\n", (cell_size==4000)? "single": ((cell_size==2000)? "double" : "high"), sector_count, vib.tracksperside, heads);
		have_vib = true;
		tracks = vib.tracksperside;
	}

	// Do we have a broken VIB? The Pascal disks are known to have such incomplete VIBs
	if (tracks == 0 || heads == 0 || sector_count == 0) have_vib = false;

	// We're also checking the size of the image
	int cell_size1 = 0;
	int sector_count1 = 0;

	// 90 KiB -> SSSD, 9 sect, FM
	// 160 KiB -> SSDD, 16 sect, MFM
	// 180 KiB -> DSSD, 9 sect, FM
	// 320 KiB -> DSDD, 16 sect, MFM
	// 360 KiB -> DSDD, 18 sect, MFM
	// 720 KiB -> DSDD, 18 sect, MFM, 80 tracks
	// 1440 KiB-> DSQD, 36 sect, MFM, 80 tracks

	if ((file_size == 163840) || (file_size == 327680))
	{
		cell_size1 = 2000;
		sector_count1 = 16;
	}
	else
	{
		if (file_size < 300000) cell_size1 = 4000;
		else
		{
			if (file_size < 1000000) cell_size1 = 2000;
			else cell_size1 = 1000;
		}
		sector_count1 = 36000 / cell_size1;
	}

	if (have_vib)
	{
		if (sector_count == 16 && sector_count1 == 18)
		{
			LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Invalid 16-sector format. Assuming 18 sectors.\n");
			sector_count = 18;
		}
		else
		{
			if (heads == 2 && ((cell_size1 != cell_size) || (sector_count1 != sector_count)))
				LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Disk image size does not correspond with format information in VIB.\n");
		}
	}
	else
	{
		heads = (file_size < 180000)? 1 : 2;    // for SSSD, SSDD16
		cell_size = cell_size1;
		sector_count = sector_count1;
	}
}

int ti99_sdf_format::get_track_size(int sector_count)
{
	return sector_count * SECTOR_SIZE;
}

void ti99_sdf_format::load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount)
{
	// Calculate the track offset from the beginning of the image file
	int logicaltrack = (head==0)? track : (2*trackcount - track - 1);
	int position = logicaltrack * get_track_size(sectorcount);

	io_generic_read(io, sectordata, position, sectorcount*SECTOR_SIZE);

	// Interleave and skew
	int interleave = 7;
	if (sectorcount == 18) interleave = 11;
	else if (sectorcount == 16) interleave = 9;

	int skew = (sectorcount<10)? 6 : 0;

	int secno = (track * skew) % sectorcount;

	// Construct the sector sequence
	for (int i=0; i < sectorcount; i++)
	{
		sector[i] = secno;
		secoffset[i] = secno*SECTOR_SIZE;
		secno = (secno + interleave) % sectorcount;
	}
	if (track==0 && head==0)
	{
		dumpbytes(sectordata, sectorcount*SECTOR_SIZE);
		LOGMASKED(LOG_TRACK, "[ti99_dsk] Sectors = ");
		for (int i=0; i < sectorcount; i++)
		{
			LOGMASKED(LOG_TRACK, "%02d ", sector[i]);
		}
		LOGMASKED(LOG_TRACK, "\n[ti99_dsk] Offset = ");
		for (int i=0; i < sectorcount; i++)
		{
			LOGMASKED(LOG_TRACK, "%04x ", secoffset[i]);
		}
		LOGMASKED(LOG_TRACK, "\n");
	}
}

/*
    For debugging. Outputs the byte array in a xxd-like way.
*/
void ti99_floppy_format::dumpbytes(uint8_t* trackdata, int length)
{
	for (int i=0; i < length; i+=16)
	{
		LOGMASKED(LOG_TRACK, "[ti99_dsk] %s\n", dumpline(trackdata+i, i).c_str());
	}
}

std::string ti99_floppy_format::dumpline(uint8_t* line, int address) const
{
	std::ostringstream stream;
	stream << std::hex << std::setfill('0') << std::setw(6) << unsigned(address);

	for (int i=0; i < 16; i++)
	{
		stream << " "  << std::setw(2) << (int)line[i];
	}
	stream << "   " ;
	for (int i=0; i < 16; i++)
	{
		char ch = (line[i] >= 32 && line[i] < 127)? (char)line[i] : '.';
		stream << std::setw(1) << ch;
	}
	return stream.str();
}

/*
    Write the data to the disk. We have a list of sector positions, so we
    just need to go through that list and save each sector in the sector data.
*/
void ti99_sdf_format::write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count)
{
	uint8_t* buf;

	int logicaltrack = head * track_count;
	logicaltrack += ((head&1)==0)?  track : (track_count - 1 - track);
	int trackoffset = logicaltrack * sector_count * SECTOR_SIZE;

	for (int i=0; i < sector_count; i++)
	{
		buf = sectordata + i * SECTOR_SIZE;
		LOGMASKED(LOG_DETAIL, "[ti99_dsk] Writing sector %d (offset %06x)\n", sector[i], sector[i] * SECTOR_SIZE);
		io_generic_write(io, buf, trackoffset + sector[i] * SECTOR_SIZE, SECTOR_SIZE);
	}
}


const floppy_format_type FLOPPY_TI99_SDF_FORMAT = &floppy_image_format_creator<ti99_sdf_format>;

/*
    Track Dump Format
    -----------------
    The Track Dump Format is also known as pc99 (again, named after the first
    TI emulator to use this format). It is a contiguous sequence of track
    contents, containing all information including the data values of the
    address marks and CRC, but it does not contain clock signals.
    Therefore, the format requires that the address marks be at the same
    positions within each track.

    Different to earlier implementations of the TDF format, we stay much
    closer to the specification. Deviations (like different gap sizes) are
    automatically rectified. It does not make sense to have a format that
    pretends to be an almost precise track image and then fail to load in
    other emulations because of small deviations.
    For precise track images there are better suited formats.

    For FM recording, each track is exactly 3253 bytes long in this format.
    For MFM recording, track length is 6872 bytes.

    Accordingly, we get a multiple of these values as the image length.
    There are no single-sided images (when single-sided, the upper half of
    the image is empty).

    DSSD: 260240 bytes
    DSDD: 549760 bytes

    We do not support other geometries in this format. One exception: We accept
    images of double size which may be generated when a 40-track disk is
    modified in an 80-track drive. This will automatically cause the creation
    of an 80-track image.

    DSSD80: 520480 bytes
    DSDD80: 1099520 bytes

    Tracks are stored from outside to inside of head 0, then outside to inside of head 1:

    (Head,Track): (0,0) (0,1) (0,2) ... (0,38) (0,39) (1,0) (1,1) ... (1,39)
*/
const char *ti99_tdf_format::name() const
{
	return "tdf";
}

const char *ti99_tdf_format::description() const
{
	return "TI99 track dump floppy disk image";
}

const char *ti99_tdf_format::extensions() const
{
	return "dsk,dtk";
}

/*
    Determine whether the image file can be interpreted as a track dump
*/
int ti99_tdf_format::identify(io_generic *io, uint32_t form_factor)
{
	int vote = 0;
	uint8_t fulltrack[6872];

	int cell_size = 0;
	int sector_count = 0;
	int heads = 0;
	int tracks = 0;
	int sectorinterval = 0;
	int headsectoff = 0;
	int pos = 0;
	determine_sizes(io, cell_size, sector_count, heads, tracks);

	// Do we have a plausible image size? From the size alone we only give a 50 percent vote.
	if (sector_count != 0)
		vote = 50;

	if (vote > 0)
	{
		LOGMASKED(LOG_INFO, "[ti99_dsk] Image file length matches TDF\n");

		// Fetch track 0
		io_generic_read(io, fulltrack, 0, get_track_size(sector_count));

		if (sector_count == 9)
		{
			headsectoff = 24;
			sectorinterval = 334;
		}
		else
		{
			headsectoff = 44;
			sectorinterval = (sector_count==16)? 368 : 340;
		}

		while (fulltrack[pos] != 0xfe && pos < 70) pos++;

		bool failed = false;
		// Find the IDAMs and DAMs on track 0
		for (int i=0; i < sector_count; i++)
		{
			int idam = fulltrack[pos+i*sectorinterval];
			int dam = fulltrack[pos+headsectoff+i*sectorinterval];
			if ((idam != 0xfe) || ((dam != 0xfb) && (dam != 0xf8)))
			{
				LOGMASKED(LOG_WARN, "[ti99_dsk] Failed at %04x or %04x\n", pos+i*sectorinterval, pos+headsectoff+i*sectorinterval);
				failed = true;
				break;
			}
		}

		if (failed)
		{
			LOGMASKED(LOG_WARN, "[ti99_dsk] Image does not comply with TDF; may be broken or unformatted\n");
		}
		else
		{
			LOGMASKED(LOG_INFO, "[ti99_dsk] Image format complies with TDF\n");
			vote = 100;
		}
	}
	else LOGMASKED(LOG_INFO, "[ti99_dsk] Disk image is not a TDF image\n");
	return vote;
}

/*
    Find the proper format for a given image file.
*/
void ti99_tdf_format::determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks)
{
	uint64_t file_size = io_generic_size(io);
	heads = 2;  // TDF only supports two-sided recordings
	tracks = 40;
	// LOGMASKED(LOG_INFO, "[ti99_dsk] Image size = %ld\n", file_size);   // doesn't compile
	switch (file_size)
	{
	case 260240:   // used in PC99
		cell_size = 4000;
		sector_count = 9;
		break;
	case 491520:   // 320K HX5102
		cell_size = 2000;
		sector_count = 16;
		break;
	case 549760:   // used in PC99
		cell_size = 2000;
		sector_count = 18;
		break;
	case 520480:
		cell_size = 4000;
		sector_count = 9;
		break;
	case 983040:
		cell_size = 2000;
		sector_count = 16;
		tracks = 80;
		break;
	case 1099520:
		cell_size = 2000;
		sector_count = 18;
		tracks = 80;
		break;
	case 2007040:
		cell_size = 1000;
		sector_count = 18;
		tracks = 80;
		break;
	default:
		cell_size = 0;
		sector_count = 0;
		break;
	}
}

/*
    Load a track. Although TDF provides us with a near-precise image of the
    track, we just copy the sectors, store the sequence, and recreate the
    track from scratch. TDF is not as flexible as it suggests, it does not
    allow different gap lengths and so on.
*/
void ti99_tdf_format::load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount)
{
	uint8_t fulltrack[6872]; // space for a full TDF track

	// Read beginning of track 0. We need this to get the first gap, according
	// to the format
	io_generic_read(io, fulltrack, 0, 100);

	int offset = 0;
	int tracksize = get_track_size(sectorcount);
	int sectorinterval = 0;
	int headsectoff = 0;
	int pos = 0;
	int img_sect = 0;

	if (sectorcount == 9)
	{
		headsectoff = 25;
		sectorinterval = 334;
	}
	else
	{
		headsectoff = 45;
		sectorinterval = (sectorcount==16)? 368 : 340;
	}

	// Initialize the sequence lists
	for (int i=0; i < sectorcount; i++)
	{
		sector[i] = -1;
		secoffset[0] = 0;
	}

	// We already verified that this is TDF, but the disk may be unformatted
	while (fulltrack[pos] != 0xfe && pos < tracksize) pos++;

	if (pos==tracksize)
	{
		// No sectors found
		LOGMASKED(LOG_WARN, "[ti99_dsk] No sectors on track %d, head %d\n", track, head);
		return;
	}

	offset = pos;

	int base = (head * trackcount + track) * tracksize;
	int position = 0;
	io_generic_read(io, fulltrack, base, tracksize);

	for (int i=0; i < sectorcount; i++)
	{
		position = i*sectorinterval + offset;
		if (position >= 6872)
		{
			LOGMASKED(LOG_WARN, "[ti99_dsk] No more sectors on track %d, head %d\n", track, head);
			break;                // no more sectors
		}
		if (fulltrack[position] != 0xfe)
		{
			LOGMASKED(LOG_WARN, "[ti99_dsk] Invalid header (no FE) on track %d, head %d\n", track, head);
			continue;  // no valid header
		}
		img_sect = fulltrack[position+3];
		if (img_sect >= 18)
		{
			LOGMASKED(LOG_WARN, "[ti99_dsk] Invalid sector number (%d) on track %d, head %d\n", img_sect, track, head);
			continue;               // invalid sector number
		}
		sector[i] = img_sect;
		secoffset[i] = i*SECTOR_SIZE;
		position += headsectoff;
		memcpy(sectordata + i*SECTOR_SIZE, fulltrack+position, SECTOR_SIZE);
	}

	// dumpbytes(sectordata, sectorcount*SECTOR_SIZE);
	LOGMASKED(LOG_TRACK, "[ti99_dsk] Sectors = ");
	for (int i=0; i < sectorcount; i++)
	{
		LOGMASKED(LOG_TRACK, "%02d ", sector[i]);
	}
	LOGMASKED(LOG_TRACK, "\n[ti99_dsk] Offset = ");
	for (int i=0; i < sectorcount; i++)
	{
		LOGMASKED(LOG_TRACK, "%04x ", secoffset[i]);
	}
	LOGMASKED(LOG_TRACK, "\n");
}

/*
    Rebuild TDF track image
    The TDF format is a bit more restrictive than it could be. The gaps need
    to be of the exact size and contain the expected bytes. So we only
    need the sector contents and the sector sequence, which are passed via
    sectordata, sector, and sector_count.
*/
void ti99_tdf_format::write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count)
{
	uint8_t trackdata[14000];
	int offset = ((track_count * head) + track) * get_track_size(sector_count);
	int a1 = 0;

	enum
	{
		WGAP1 = 0, WGAP2, WGAP3, WGAP4, WGAP1BYTE, WGAPBYTE, WSYNC1, WSYNC2
	};

	const int fm9param[8]   = { 16, 11, 45, 231, 0x00, 0xff,  6,  6 };
	const int mfm16param[8] = { 50, 22, 50, 206, 0x4e, 0x4e, 12, 12 };
	const int mfm18param[8] = { 40, 22, 24, 712, 0x4e, 0x4e, 10, 12 };
	const int mfm36param[8] = { 40, 22, 24, 264, 0x4e, 0x4e, 10, 12 };

	const int *param = fm9param;

	if (sector_count == 16)
		param = mfm16param;
	else if (sector_count == 18)
		param = mfm18param;
	else if (sector_count == 36)
		param = mfm36param;

	LOGMASKED(LOG_TRACK, "[ti99_dsk] Writing track %d, head %d\n", track, head);

	if (sector_count >= 16) a1 = 3;

	// Create track
	int pos = 0;
	for (int i=0; i < param[WGAP1]; i++) trackdata[pos++] = param[WGAP1BYTE];
	for (int j=0; j < sector_count; j++)
	{
		for (int i=0; i < param[WSYNC1]; i++) trackdata[pos++] = 0x00;
		for (int i=0; i < a1; i++) trackdata[pos++] = 0xa1;
		trackdata[pos++] = 0xfe;
		trackdata[pos++] = track;
		trackdata[pos++] = head;
		trackdata[pos++] = sector[j];
		trackdata[pos++] = 0x01;
		trackdata[pos++] = 0xf7;    // PC99 does not accept real CRC values
		trackdata[pos++] = 0xf7;
		for (int i=0; i < param[WGAP2]; i++) trackdata[pos++] = param[WGAPBYTE];
		for (int i=0; i < param[WSYNC2]; i++) trackdata[pos++] = 0x00;
		for (int i=0; i < a1; i++) trackdata[pos++] = 0xa1;
		trackdata[pos++] = 0xfb;
		memcpy(trackdata + pos, sectordata + j * SECTOR_SIZE, SECTOR_SIZE);
		pos +=256;
		trackdata[pos++] = 0xf7;
		trackdata[pos++] = 0xf7;
		for (int i=0; i < param[WGAP3]; i++) trackdata[pos++] = param[WGAPBYTE];
	}
	for (int i=0; i < param[WGAP4]; i++) trackdata[pos++] = param[WGAPBYTE];
	io_generic_write(io, trackdata, offset, get_track_size(sector_count));
}

int ti99_tdf_format::get_track_size(int sector_count)
{
	switch (sector_count)
	{
	case 9:
		return 3253;
	case 16:
		return 6144;
	case 18:
		return 6872;
	case 36:
		return 12544;
	}
	return 0;
}

const floppy_format_type FLOPPY_TI99_TDF_FORMAT = &floppy_image_format_creator<ti99_tdf_format>;