/* Hatari - msa.c This file is distributed under the GNU Public License, version 2 or at your option any later version. Read the file gpl.txt for details. MSA Disk support */ const char MSA_rcsid[] = "Hatari $Id: msa.c,v 1.11 2007/01/16 18:42:59 thothy Exp $"; #include #include "main.h" #include "file.h" #include "floppy.h" #include "misc.h" #include "msa.h" #include "uae-cpu/sysdeps.h" #include "uae-cpu/maccess.h" #define SAVE_TO_MSA_IMAGES /* .MSA FILE FORMAT --================------------------------------------------------------------ For those interested, an MSA file is made up as follows: Header: Word ID marker, should be $0E0F Word Sectors per track Word Sides (0 or 1; add 1 to this to get correct number of sides) Word Starting track (0-based) Word Ending track (0-based) Individual tracks follow the header in alternating side order, e.g. a double sided disk is stored as: TRACK 0, SIDE 0 TRACK 0, SIDE 1 TRACK 1, SIDE 0 TRACK 1, SIDE 1 TRACK 2, SIDE 0 TRACK 2, SIDE 1 ...and so on. Track blocks are made up as follows: Word Data length Bytes Data If the data length is equal to 512 x the sectors per track value, it is an uncompressed track and you can merely copy the data to the appropriate track of the disk. However, if the data length value is less than 512 x the sectors per track value it is a compressed track. Compressed tracks use simple a Run Length Encoding (RLE) compression method. You can directly copy any data bytes until you find an $E5 byte. This signals a compressed run, and is made up as follows: Byte Marker - $E5 Byte Data byte Word Run length So, if MSA found six $AA bytes in a row it would encode it as: $E5AA0006 What happens if there's an actual $E5 byte on the disk? Well, logically enough, it is encoded as: $E5E50001 This is obviously bad news if a disk consists of lots of data like $E500E500E500E500... but if MSA makes a track bigger when attempting to compress it, it just stores the uncompressed version instead. MSA only compresses runs of at least 4 identical bytes (after all, it would be wasteful to store 4 bytes for a run of only 3 identical bytes!). There is one exception to this rule: if a run of 2 or 3 $E5 bytes is found, that is stored appropriately enough as a run. Again, it would be wasteful to store 4 bytes for every single $E5 byte. The hacked release of MSA that enables the user to turn off compression completely simply stops MSA from trying this compression and produces MSA images that are completely uncompressed. This is okay because it is possible for MSA to produce such an image anyway, and such images are therefore 100% compatible with normal MSA versions (and MSA-to-ST of course). */ typedef struct { short int ID; /* Word ID marker, should be $0E0F */ short int SectorsPerTrack; /* Word Sectors per track */ short int Sides; /* Word Sides (0 or 1; add 1 to this to get correct number of sides) */ short int StartingTrack; /* Word Starting track (0-based) */ short int EndingTrack; /* Word Ending track (0-based) */ } MSAHEADERSTRUCT; #define MSA_WORKSPACE_SIZE (1024*1024) /* Size of workspace to use when saving MSA files */ /*-----------------------------------------------------------------------*/ /** * Does filename end with a .MSA extension? If so, return TRUE */ BOOL MSA_FileNameIsMSA(char *pszFileName, BOOL bAllowGZ) { return(File_DoesFileExtensionMatch(pszFileName,".msa") || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".msa.gz"))); } /*-----------------------------------------------------------------------*/ /** * Uncompress .MSA data into a new buffer. */ Uint8 *MSA_UnCompress(Uint8 *pMSAFile, long *pImageSize) { MSAHEADERSTRUCT *pMSAHeader; unsigned char *pMSAImageBuffer,*pImageBuffer; unsigned char Byte,Data; int i,Track,Side,DataLength,NumBytesUnCompressed,RunLength; Uint8 *pBuffer = NULL; *pImageSize = 0; /* Is an '.msa' file?? Check header */ pMSAHeader = (MSAHEADERSTRUCT *)pMSAFile; if (pMSAHeader->ID == SDL_SwapBE16(0x0E0F)) { /* First swap 'header' words around to PC format - easier later on */ pMSAHeader->SectorsPerTrack = SDL_SwapBE16(pMSAHeader->SectorsPerTrack); pMSAHeader->Sides = SDL_SwapBE16(pMSAHeader->Sides); pMSAHeader->StartingTrack = SDL_SwapBE16(pMSAHeader->StartingTrack); pMSAHeader->EndingTrack = SDL_SwapBE16(pMSAHeader->EndingTrack); /* Create buffer */ pBuffer = malloc((pMSAHeader->EndingTrack - pMSAHeader->StartingTrack + 1) * pMSAHeader->SectorsPerTrack * (pMSAHeader->Sides + 1) * NUMBYTESPERSECTOR); if (!pBuffer) { perror("MSA_UnCompress"); return NULL; } /* Set pointers */ pImageBuffer = (unsigned char *)pBuffer; pMSAImageBuffer = (unsigned char *)((unsigned long)pMSAFile+sizeof(MSAHEADERSTRUCT)); /* Uncompress to memory as '.ST' disk image - NOTE: assumes 512 bytes per sector (use NUMBYTESPERSECTOR define)!!! */ for(Track=pMSAHeader->StartingTrack; Track <= pMSAHeader->EndingTrack; Track++) { for(Side=0; Side<(pMSAHeader->Sides+1); Side++) { /* Uncompress MSA Track, first check if is not compressed */ DataLength = do_get_mem_word(pMSAImageBuffer); pMSAImageBuffer += sizeof(short int); if (DataLength==(NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack)) { /* No compression on track, simply copy and continue */ memcpy(pImageBuffer,pMSAImageBuffer,NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack); pImageBuffer += NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack; pMSAImageBuffer += DataLength; } else { /* Uncompress track */ NumBytesUnCompressed = 0; while(NumBytesUnCompressed<(NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack)) { Byte = *pMSAImageBuffer++; if (Byte!=0xE5) { /* Compressed header?? */ *pImageBuffer++ = Byte; /* No, just copy byte */ NumBytesUnCompressed++; } else { Data = *pMSAImageBuffer++; /* Byte to copy */ RunLength = do_get_mem_word(pMSAImageBuffer); /* For length */ /* Limit length to size of track, incorrect images may overflow */ if ( (RunLength+NumBytesUnCompressed)>(NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack) ) { fprintf(stderr, "MSA_UnCompress: Illegal run length -> corrupted disk image?\n"); RunLength = (NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack)-NumBytesUnCompressed; } pMSAImageBuffer += sizeof(short int); for(i=0; iID = SDL_SwapBE16(0x0E0F); Floppy_FindDiskDetails(pBuffer,ImageSize,&nSectorsPerTrack,&nSides); pMSAHeader->SectorsPerTrack = SDL_SwapBE16(nSectorsPerTrack); pMSAHeader->Sides = SDL_SwapBE16(nSides-1); pMSAHeader->StartingTrack = SDL_SwapBE16(0); nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides; pMSAHeader->EndingTrack = SDL_SwapBE16(nTracks-1); /* Compress image */ pMSABuffer = pMSAImageBuffer + sizeof(MSAHEADERSTRUCT); for(Track=0; Track0) { nBytesRun = MSA_FindRunOfBytes(pImageBuffer,nBytesToGo); if (nBytesRun==0) { /* Just copy byte */ *pMSABuffer++ = *pImageBuffer++; nCompressedBytes++; nBytesRun = 1; } else { /* Store run! */ *pMSABuffer++ = 0xE5; /* Marker */ *pMSABuffer++ = *pImageBuffer; /* Byte, and follow with 16-bit length */ do_put_mem_word(pMSABuffer, nBytesRun); pMSABuffer += sizeof(short int); pImageBuffer += nBytesRun; nCompressedBytes += 4; } nBytesToGo -= nBytesRun; } /* Is compressed track smaller than the original? */ if (nCompressedBytes<(NUMBYTESPERSECTOR*nSectorsPerTrack)) { /* Yes, store size */ do_put_mem_word(pMSADataLength, nCompressedBytes); } else { /* No, just store uncompressed track */ do_put_mem_word(pMSADataLength, NUMBYTESPERSECTOR*nSectorsPerTrack); pMSABuffer = ((unsigned char *)pMSADataLength) + 2; pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track); memcpy(pMSABuffer,pImageBuffer,(NUMBYTESPERSECTOR*nSectorsPerTrack)); pMSABuffer += (NUMBYTESPERSECTOR*nSectorsPerTrack); } } } /* And save to file! */ nRet = File_Save(pszFileName,pMSAImageBuffer,pMSABuffer-pMSAImageBuffer,FALSE); /* Free workspace */ free(pMSAImageBuffer); return(nRet); #else /*SAVE_TO_MSA_IMAGES*/ /* Oops, cannot save */ return(FALSE); #endif /*SAVE_TO_MSA_IMAGES*/ }