HatariWii/src/msa.c

423 lines
12 KiB
C

/*
Hatari - msa.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
MSA Disk support
*/
const char MSA_fileid[] = "Hatari msa.c : " __DATE__ " " __TIME__;
#include <SDL_endian.h>
#include "main.h"
#include "file.h"
#include "floppy.h"
#include "msa.h"
#include "sysdeps.h"
#include "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
{
Uint16 ID; /* Word : ID marker, should be $0E0F */
Uint16 SectorsPerTrack; /* Word : Sectors per track */
Uint16 Sides; /* Word : Sides (0 or 1; add 1 to this to get correct number of sides) */
Uint16 StartingTrack; /* Word : Starting track (0-based) */
Uint16 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(const 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, long nBytesLeft)
{
MSAHEADERSTRUCT *pMSAHeader;
Uint8 *pMSAImageBuffer, *pImageBuffer;
Uint8 Byte,Data;
int i,Track,Side,DataLength,NumBytesUnCompressed,RunLength;
Uint8 *pBuffer = NULL;
*pImageSize = 0;
pMSAHeader = (MSAHEADERSTRUCT *)pMSAFile;
/* First swap 'header' words around to PC format - easier later on */
pMSAHeader->ID = SDL_SwapBE16(pMSAHeader->ID);
pMSAHeader->SectorsPerTrack = SDL_SwapBE16(pMSAHeader->SectorsPerTrack);
pMSAHeader->Sides = SDL_SwapBE16(pMSAHeader->Sides);
pMSAHeader->StartingTrack = SDL_SwapBE16(pMSAHeader->StartingTrack);
pMSAHeader->EndingTrack = SDL_SwapBE16(pMSAHeader->EndingTrack);
/* Is it really an '.msa' file? Check header */
if (pMSAHeader->ID != 0x0E0F || pMSAHeader->EndingTrack > 86
|| pMSAHeader->StartingTrack > pMSAHeader->EndingTrack
|| pMSAHeader->SectorsPerTrack > 56|| pMSAHeader->Sides > 1
|| nBytesLeft <= (long)sizeof(MSAHEADERSTRUCT))
{
fprintf(stderr, "MSA image has a bad header!\n");
return NULL;
}
/* 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 = (Uint8 *)pBuffer;
pMSAImageBuffer = pMSAFile + sizeof(MSAHEADERSTRUCT);
nBytesLeft -= 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++)
{
int nBytesPerTrack = NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack;
nBytesLeft -= sizeof(Uint16);
if (nBytesLeft < 0)
goto out;
/* Uncompress MSA Track, first check if is not compressed */
DataLength = do_get_mem_word(pMSAImageBuffer);
pMSAImageBuffer += sizeof(Uint16);
if (DataLength == nBytesPerTrack)
{
nBytesLeft -= DataLength;
if (nBytesLeft < 0)
goto out;
/* No compression on track, simply copy and continue */
memcpy(pImageBuffer, pMSAImageBuffer, nBytesPerTrack);
pImageBuffer += nBytesPerTrack;
pMSAImageBuffer += DataLength;
continue;
}
/* Uncompress track */
NumBytesUnCompressed = 0;
while (NumBytesUnCompressed < nBytesPerTrack)
{
if (--nBytesLeft < 0)
goto out;
Byte = *pMSAImageBuffer++;
if (Byte != 0xE5) /* Compressed header? */
{
*pImageBuffer++ = Byte; /* No, just copy byte */
NumBytesUnCompressed++;
}
else
{
nBytesLeft -= 3;
if (nBytesLeft < 0)
goto out;
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 > nBytesPerTrack)
{
fprintf(stderr, "MSA_UnCompress: Illegal run length -> corrupted disk image?\n");
RunLength = nBytesPerTrack - NumBytesUnCompressed;
}
pMSAImageBuffer += sizeof(Uint16);
for (i = 0; i < RunLength; i++)
*pImageBuffer++ = Data; /* Copy byte */
NumBytesUnCompressed += RunLength;
}
}
}
}
out:
if (nBytesLeft < 0)
{
fprintf(stderr, "MSA error: Premature end of file!\n");
free(pBuffer);
pBuffer = NULL;
}
else
{
/* Set size of loaded image */
*pImageSize = pImageBuffer-pBuffer;
}
/* Return pointer to buffer, NULL if failed */
return pBuffer;
}
/*-----------------------------------------------------------------------*/
/**
* Uncompress .MSA file into memory, set number bytes of the disk image and
* return a pointer to the buffer.
*/
Uint8 *MSA_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType)
{
Uint8 *pMsaFile;
Uint8 *pDiskBuffer = NULL;
long nFileSize;
*pImageSize = 0;
/* Read in file */
pMsaFile = File_Read(pszFileName, &nFileSize, NULL);
if (pMsaFile)
{
/* Uncompress into disk buffer */
pDiskBuffer = MSA_UnCompress(pMsaFile, pImageSize, nFileSize);
/* Free MSA file we loaded */
free(pMsaFile);
}
if ( ( pMsaFile == NULL ) || ( pDiskBuffer == NULL ) )
return NULL;
*pImageType = FLOPPY_IMAGE_TYPE_MSA;
/* Return pointer to buffer, NULL if failed */
return pDiskBuffer;
}
/*-----------------------------------------------------------------------*/
/**
* Return number of bytes of the same byte in the passed buffer
* If we return '0' this means no run (or end of buffer)
*/
static int MSA_FindRunOfBytes(Uint8 *pBuffer, int nBytesInBuffer)
{
Uint8 ScannedByte;
int nTotalRun;
bool bMarker;
int i;
/* Is this the marker? If so, this is at least a run of one. */
bMarker = (*pBuffer == 0xE5);
/* Do we enough for a run? */
if (nBytesInBuffer < 2)
{
if (nBytesInBuffer == 1 && bMarker)
return 1;
else
return 0;
}
/* OK, scan for run */
nTotalRun = 1;
ScannedByte = *pBuffer++;
for (i = 1; i < nBytesInBuffer; i++)
{
if (*pBuffer++ == ScannedByte)
nTotalRun++;
else
break;
}
/* Was this enough of a run to make a difference? */
if (nTotalRun < 4 && !bMarker)
nTotalRun = 0; /* Just store uncompressed */
return nTotalRun;
}
/*-----------------------------------------------------------------------*/
/**
* Save compressed .MSA file from memory buffer. Returns true is all OK
*/
bool MSA_WriteDisk(int Drive, const char *pszFileName, Uint8 *pBuffer, int ImageSize)
{
#ifdef SAVE_TO_MSA_IMAGES
MSAHEADERSTRUCT *pMSAHeader;
Uint16 *pMSADataLength;
Uint8 *pMSAImageBuffer, *pMSABuffer, *pImageBuffer;
Uint16 nSectorsPerTrack, nSides, nCompressedBytes, nBytesPerTrack;
bool nRet;
int nTracks,nBytesToGo,nBytesRun;
int Track,Side;
/* Allocate workspace for compressed image */
pMSAImageBuffer = (Uint8 *)malloc(MSA_WORKSPACE_SIZE);
if (!pMSAImageBuffer)
{
perror("MSA_WriteDisk");
return false;
}
/* Store header */
pMSAHeader = (MSAHEADERSTRUCT *)pMSAImageBuffer;
pMSAHeader->ID = 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; Track < nTracks; Track++)
{
for (Side = 0; Side < nSides; Side++)
{
/* Get track data pointer */
nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack;
pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
/* Skip data length (fill in later) */
pMSADataLength = (Uint16 *)pMSABuffer;
pMSABuffer += sizeof(Uint16);
/* Compress track */
nBytesToGo = nBytesPerTrack;
nCompressedBytes = 0;
while (nBytesToGo > 0)
{
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(Uint16);
pImageBuffer += nBytesRun;
nCompressedBytes += 4;
}
nBytesToGo -= nBytesRun;
}
/* Is compressed track smaller than the original? */
if (nCompressedBytes < nBytesPerTrack)
{
/* Yes, store size */
do_put_mem_word(pMSADataLength, nCompressedBytes);
}
else
{
/* No, just store uncompressed track */
do_put_mem_word(pMSADataLength, nBytesPerTrack);
pMSABuffer = ((Uint8 *)pMSADataLength) + 2;
pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
memcpy(pMSABuffer,pImageBuffer, nBytesPerTrack);
pMSABuffer += nBytesPerTrack;
}
}
}
/* 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*/
}