|
|
1.1 root 1: /*
1.1.1.3 root 2: Hatari - msa.c
3:
1.1.1.12 root 4: This file is distributed under the GNU General Public License, version 2
5: or at your option any later version. Read the file gpl.txt for details.
1.1 root 6:
1.1.1.5 root 7: MSA Disk support
1.1 root 8: */
1.1.1.9 root 9: const char MSA_fileid[] = "Hatari msa.c : " __DATE__ " " __TIME__;
1.1.1.3 root 10:
11: #include <SDL_endian.h>
1.1 root 12:
13: #include "main.h"
14: #include "file.h"
15: #include "floppy.h"
1.1.1.3 root 16: #include "msa.h"
17:
1.1.1.7 root 18: #include "sysdeps.h"
19: #include "maccess.h"
1.1.1.3 root 20:
1.1 root 21:
22: #define SAVE_TO_MSA_IMAGES
23:
1.1.1.3 root 24:
1.1 root 25: /*
26: .MSA FILE FORMAT
27: --================------------------------------------------------------------
28:
29: For those interested, an MSA file is made up as follows:
30:
31: Header:
32:
33: Word ID marker, should be $0E0F
34: Word Sectors per track
35: Word Sides (0 or 1; add 1 to this to get correct number of sides)
36: Word Starting track (0-based)
37: Word Ending track (0-based)
38:
39: Individual tracks follow the header in alternating side order, e.g. a double
40: sided disk is stored as:
41:
42: TRACK 0, SIDE 0
43: TRACK 0, SIDE 1
44: TRACK 1, SIDE 0
45: TRACK 1, SIDE 1
46: TRACK 2, SIDE 0
47: TRACK 2, SIDE 1
48:
49: ...and so on. Track blocks are made up as follows:
50:
51: Word Data length
52: Bytes Data
53:
54: If the data length is equal to 512 x the sectors per track value, it is an
55: uncompressed track and you can merely copy the data to the appropriate track
56: of the disk. However, if the data length value is less than 512 x the sectors
57: per track value it is a compressed track.
58:
59: Compressed tracks use simple a Run Length Encoding (RLE) compression method.
60: You can directly copy any data bytes until you find an $E5 byte. This signals
61: a compressed run, and is made up as follows:
62:
63: Byte Marker - $E5
64: Byte Data byte
65: Word Run length
66:
67: So, if MSA found six $AA bytes in a row it would encode it as:
68:
69: $E5AA0006
70:
71: What happens if there's an actual $E5 byte on the disk? Well, logically
72: enough, it is encoded as:
73:
74: $E5E50001
75:
76: This is obviously bad news if a disk consists of lots of data like
77: $E500E500E500E500... but if MSA makes a track bigger when attempting to
78: compress it, it just stores the uncompressed version instead.
79:
80: MSA only compresses runs of at least 4 identical bytes (after all, it would be
81: wasteful to store 4 bytes for a run of only 3 identical bytes!). There is one
82: exception to this rule: if a run of 2 or 3 $E5 bytes is found, that is stored
83: appropriately enough as a run. Again, it would be wasteful to store 4 bytes
84: for every single $E5 byte.
85:
86: The hacked release of MSA that enables the user to turn off compression
87: completely simply stops MSA from trying this compression and produces MSA
88: images that are completely uncompressed. This is okay because it is possible
89: for MSA to produce such an image anyway, and such images are therefore 100%
90: compatible with normal MSA versions (and MSA-to-ST of course).
91: */
92:
1.1.1.7 root 93: typedef struct
94: {
1.1.1.13 root 95: Uint16 ID; /* Word : ID marker, should be $0E0F */
96: Uint16 SectorsPerTrack; /* Word : Sectors per track */
97: Uint16 Sides; /* Word : Sides (0 or 1; add 1 to this to get correct number of sides) */
98: Uint16 StartingTrack; /* Word : Starting track (0-based) */
99: Uint16 EndingTrack; /* Word : Ending track (0-based) */
1.1 root 100: } MSAHEADERSTRUCT;
101:
102: #define MSA_WORKSPACE_SIZE (1024*1024) /* Size of workspace to use when saving MSA files */
103:
104:
1.1.1.2 root 105: /*-----------------------------------------------------------------------*/
1.1.1.7 root 106: /**
1.1.1.10 root 107: * Does filename end with a .MSA extension? If so, return true
1.1.1.7 root 108: */
1.1.1.11 root 109: bool MSA_FileNameIsMSA(const char *pszFileName, bool bAllowGZ)
1.1.1.3 root 110: {
1.1.1.7 root 111: return(File_DoesFileExtensionMatch(pszFileName,".msa")
112: || (bAllowGZ && File_DoesFileExtensionMatch(pszFileName,".msa.gz")));
1.1.1.3 root 113: }
114:
115:
116: /*-----------------------------------------------------------------------*/
1.1.1.7 root 117: /**
118: * Uncompress .MSA data into a new buffer.
119: */
1.1.1.14! root 120: Uint8 *MSA_UnCompress(Uint8 *pMSAFile, long *pImageSize, long nBytesLeft)
1.1 root 121: {
1.1.1.7 root 122: MSAHEADERSTRUCT *pMSAHeader;
123: Uint8 *pMSAImageBuffer, *pImageBuffer;
124: Uint8 Byte,Data;
125: int i,Track,Side,DataLength,NumBytesUnCompressed,RunLength;
126: Uint8 *pBuffer = NULL;
127:
128: *pImageSize = 0;
129:
130: pMSAHeader = (MSAHEADERSTRUCT *)pMSAFile;
1.1.1.14! root 131: /* First swap 'header' words around to PC format - easier later on */
! 132: pMSAHeader->ID = SDL_SwapBE16(pMSAHeader->ID);
! 133: pMSAHeader->SectorsPerTrack = SDL_SwapBE16(pMSAHeader->SectorsPerTrack);
! 134: pMSAHeader->Sides = SDL_SwapBE16(pMSAHeader->Sides);
! 135: pMSAHeader->StartingTrack = SDL_SwapBE16(pMSAHeader->StartingTrack);
! 136: pMSAHeader->EndingTrack = SDL_SwapBE16(pMSAHeader->EndingTrack);
! 137:
! 138: /* Is it really an '.msa' file? Check header */
! 139: if (pMSAHeader->ID != 0x0E0F || pMSAHeader->EndingTrack > 86
! 140: || pMSAHeader->StartingTrack > pMSAHeader->EndingTrack
! 141: || pMSAHeader->SectorsPerTrack > 56|| pMSAHeader->Sides > 1
! 142: || nBytesLeft <= (long)sizeof(MSAHEADERSTRUCT))
1.1.1.7 root 143: {
1.1.1.14! root 144: fprintf(stderr, "MSA image has a bad header!\n");
! 145: return NULL;
! 146: }
1.1.1.7 root 147:
1.1.1.14! root 148: /* Create buffer */
! 149: pBuffer = malloc((pMSAHeader->EndingTrack - pMSAHeader->StartingTrack + 1)
! 150: * pMSAHeader->SectorsPerTrack * (pMSAHeader->Sides + 1)
! 151: * NUMBYTESPERSECTOR);
! 152: if (!pBuffer)
! 153: {
! 154: perror("MSA_UnCompress");
! 155: return NULL;
! 156: }
! 157:
! 158: /* Set pointers */
! 159: pImageBuffer = (Uint8 *)pBuffer;
! 160: pMSAImageBuffer = pMSAFile + sizeof(MSAHEADERSTRUCT);
! 161: nBytesLeft -= sizeof(MSAHEADERSTRUCT);
! 162:
! 163: /* Uncompress to memory as '.ST' disk image - NOTE: assumes 512 bytes
! 164: * per sector (use NUMBYTESPERSECTOR define)!!! */
! 165: for (Track = pMSAHeader->StartingTrack; Track <= pMSAHeader->EndingTrack; Track++)
! 166: {
! 167: for (Side = 0; Side < (pMSAHeader->Sides+1); Side++)
1.1.1.7 root 168: {
1.1.1.14! root 169: int nBytesPerTrack = NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack;
1.1.1.7 root 170:
1.1.1.14! root 171: nBytesLeft -= sizeof(Uint16);
! 172: if (nBytesLeft < 0)
! 173: goto out;
! 174: /* Uncompress MSA Track, first check if is not compressed */
! 175: DataLength = do_get_mem_word(pMSAImageBuffer);
! 176: pMSAImageBuffer += sizeof(Uint16);
! 177: if (DataLength == nBytesPerTrack)
! 178: {
! 179: nBytesLeft -= DataLength;
! 180: if (nBytesLeft < 0)
! 181: goto out;
! 182: /* No compression on track, simply copy and continue */
! 183: memcpy(pImageBuffer, pMSAImageBuffer, nBytesPerTrack);
! 184: pImageBuffer += nBytesPerTrack;
! 185: pMSAImageBuffer += DataLength;
! 186: continue;
! 187: }
! 188: /* Uncompress track */
! 189: NumBytesUnCompressed = 0;
! 190: while (NumBytesUnCompressed < nBytesPerTrack)
! 191: {
! 192: if (--nBytesLeft < 0)
! 193: goto out;
! 194: Byte = *pMSAImageBuffer++;
! 195: if (Byte != 0xE5) /* Compressed header? */
1.1.1.7 root 196: {
1.1.1.14! root 197: *pImageBuffer++ = Byte; /* No, just copy byte */
! 198: NumBytesUnCompressed++;
1.1.1.7 root 199: }
200: else
201: {
1.1.1.14! root 202: nBytesLeft -= 3;
! 203: if (nBytesLeft < 0)
! 204: goto out;
! 205: Data = *pMSAImageBuffer++; /* Byte to copy */
! 206: RunLength = do_get_mem_word(pMSAImageBuffer); /* For length */
! 207: /* Limit length to size of track, incorrect images may overflow */
! 208: if (RunLength+NumBytesUnCompressed > nBytesPerTrack)
1.1.1.7 root 209: {
1.1.1.14! root 210: fprintf(stderr, "MSA_UnCompress: Illegal run length -> corrupted disk image?\n");
! 211: RunLength = nBytesPerTrack - NumBytesUnCompressed;
1.1.1.7 root 212: }
1.1.1.14! root 213: pMSAImageBuffer += sizeof(Uint16);
! 214: for (i = 0; i < RunLength; i++)
! 215: *pImageBuffer++ = Data; /* Copy byte */
! 216: NumBytesUnCompressed += RunLength;
1.1.1.7 root 217: }
218: }
219: }
1.1.1.14! root 220: }
! 221: out:
! 222: if (nBytesLeft < 0)
! 223: {
! 224: fprintf(stderr, "MSA error: Premature end of file!\n");
! 225: free(pBuffer);
! 226: pBuffer = NULL;
! 227: }
! 228: else
! 229: {
1.1.1.7 root 230: /* Set size of loaded image */
1.1.1.13 root 231: *pImageSize = pImageBuffer-pBuffer;
1.1.1.7 root 232: }
1.1 root 233:
1.1.1.7 root 234: /* Return pointer to buffer, NULL if failed */
1.1.1.14! root 235: return pBuffer;
1.1 root 236: }
237:
1.1.1.2 root 238:
239: /*-----------------------------------------------------------------------*/
1.1.1.7 root 240: /**
241: * Uncompress .MSA file into memory, set number bytes of the disk image and
242: * return a pointer to the buffer.
243: */
1.1.1.13 root 244: Uint8 *MSA_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType)
1.1 root 245: {
1.1.1.7 root 246: Uint8 *pMsaFile;
247: Uint8 *pDiskBuffer = NULL;
1.1.1.14! root 248: long nFileSize;
1.1.1.3 root 249:
1.1.1.7 root 250: *pImageSize = 0;
1.1 root 251:
1.1.1.7 root 252: /* Read in file */
1.1.1.14! root 253: pMsaFile = File_Read(pszFileName, &nFileSize, NULL);
1.1.1.7 root 254: if (pMsaFile)
255: {
256: /* Uncompress into disk buffer */
1.1.1.14! root 257: pDiskBuffer = MSA_UnCompress(pMsaFile, pImageSize, nFileSize);
1.1.1.7 root 258:
259: /* Free MSA file we loaded */
260: free(pMsaFile);
261: }
1.1 root 262:
1.1.1.13 root 263: if ( ( pMsaFile == NULL ) || ( pDiskBuffer == NULL ) )
264: return NULL;
265:
266: *pImageType = FLOPPY_IMAGE_TYPE_MSA;
1.1.1.7 root 267: /* Return pointer to buffer, NULL if failed */
268: return pDiskBuffer;
1.1 root 269: }
270:
1.1.1.2 root 271:
272: /*-----------------------------------------------------------------------*/
1.1.1.7 root 273: /**
274: * Return number of bytes of the same byte in the passed buffer
275: * If we return '0' this means no run (or end of buffer)
276: */
1.1.1.10 root 277: static int MSA_FindRunOfBytes(Uint8 *pBuffer, int nBytesInBuffer)
1.1 root 278: {
1.1.1.10 root 279: Uint8 ScannedByte;
1.1.1.7 root 280: int nTotalRun;
1.1.1.8 root 281: bool bMarker;
1.1.1.7 root 282: int i;
283:
284: /* Is this the marker? If so, this is at least a run of one. */
285: bMarker = (*pBuffer == 0xE5);
286:
287: /* Do we enough for a run? */
288: if (nBytesInBuffer < 2)
289: {
290: if (nBytesInBuffer == 1 && bMarker)
291: return 1;
292: else
293: return 0;
294: }
295:
296: /* OK, scan for run */
297: nTotalRun = 1;
298: ScannedByte = *pBuffer++;
299:
300: for (i = 1; i < nBytesInBuffer; i++)
301: {
302: if (*pBuffer++ == ScannedByte)
303: nTotalRun++;
304: else
305: break;
306: }
307:
308: /* Was this enough of a run to make a difference? */
309: if (nTotalRun < 4 && !bMarker)
310: nTotalRun = 0; /* Just store uncompressed */
1.1 root 311:
1.1.1.7 root 312: return nTotalRun;
1.1 root 313: }
314:
1.1.1.2 root 315:
316: /*-----------------------------------------------------------------------*/
1.1.1.7 root 317: /**
1.1.1.10 root 318: * Save compressed .MSA file from memory buffer. Returns true is all OK
1.1.1.7 root 319: */
1.1.1.13 root 320: bool MSA_WriteDisk(int Drive, const char *pszFileName, Uint8 *pBuffer, int ImageSize)
1.1 root 321: {
322: #ifdef SAVE_TO_MSA_IMAGES
323:
1.1.1.7 root 324: MSAHEADERSTRUCT *pMSAHeader;
1.1.1.13 root 325: Uint16 *pMSADataLength;
1.1.1.7 root 326: Uint8 *pMSAImageBuffer, *pMSABuffer, *pImageBuffer;
1.1.1.10 root 327: Uint16 nSectorsPerTrack, nSides, nCompressedBytes, nBytesPerTrack;
1.1.1.8 root 328: bool nRet;
1.1.1.7 root 329: int nTracks,nBytesToGo,nBytesRun;
330: int Track,Side;
331:
332: /* Allocate workspace for compressed image */
333: pMSAImageBuffer = (Uint8 *)malloc(MSA_WORKSPACE_SIZE);
334: if (!pMSAImageBuffer)
335: {
336: perror("MSA_WriteDisk");
1.1.1.10 root 337: return false;
1.1.1.7 root 338: }
339:
340: /* Store header */
341: pMSAHeader = (MSAHEADERSTRUCT *)pMSAImageBuffer;
342: pMSAHeader->ID = SDL_SwapBE16(0x0E0F);
343: Floppy_FindDiskDetails(pBuffer,ImageSize, &nSectorsPerTrack, &nSides);
344: pMSAHeader->SectorsPerTrack = SDL_SwapBE16(nSectorsPerTrack);
345: pMSAHeader->Sides = SDL_SwapBE16(nSides-1);
346: pMSAHeader->StartingTrack = SDL_SwapBE16(0);
347: nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides;
348: pMSAHeader->EndingTrack = SDL_SwapBE16(nTracks-1);
349:
350: /* Compress image */
351: pMSABuffer = pMSAImageBuffer + sizeof(MSAHEADERSTRUCT);
352: for (Track = 0; Track < nTracks; Track++)
353: {
354: for (Side = 0; Side < nSides; Side++)
355: {
356: /* Get track data pointer */
357: nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack;
358: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
359:
360: /* Skip data length (fill in later) */
361: pMSADataLength = (Uint16 *)pMSABuffer;
362: pMSABuffer += sizeof(Uint16);
363:
364: /* Compress track */
365: nBytesToGo = nBytesPerTrack;
366: nCompressedBytes = 0;
367: while (nBytesToGo > 0)
368: {
369: nBytesRun = MSA_FindRunOfBytes(pImageBuffer,nBytesToGo);
370: if (nBytesRun == 0)
371: {
372: /* Just copy byte */
373: *pMSABuffer++ = *pImageBuffer++;
374: nCompressedBytes++;
375: nBytesRun = 1;
376: }
377: else
378: {
379: /* Store run! */
380: *pMSABuffer++ = 0xE5; /* Marker */
381: *pMSABuffer++ = *pImageBuffer; /* Byte, and follow with 16-bit length */
382: do_put_mem_word(pMSABuffer, nBytesRun);
383: pMSABuffer += sizeof(Uint16);
384: pImageBuffer += nBytesRun;
385: nCompressedBytes += 4;
386: }
387: nBytesToGo -= nBytesRun;
388: }
389:
390: /* Is compressed track smaller than the original? */
391: if (nCompressedBytes < nBytesPerTrack)
392: {
393: /* Yes, store size */
394: do_put_mem_word(pMSADataLength, nCompressedBytes);
395: }
396: else
397: {
398: /* No, just store uncompressed track */
399: do_put_mem_word(pMSADataLength, nBytesPerTrack);
400: pMSABuffer = ((Uint8 *)pMSADataLength) + 2;
401: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
402: memcpy(pMSABuffer,pImageBuffer, nBytesPerTrack);
403: pMSABuffer += nBytesPerTrack;
404: }
405: }
406: }
1.1 root 407:
1.1.1.7 root 408: /* And save to file! */
1.1.1.10 root 409: nRet = File_Save(pszFileName,pMSAImageBuffer, pMSABuffer-pMSAImageBuffer, false);
1.1 root 410:
1.1.1.7 root 411: /* Free workspace */
412: free(pMSAImageBuffer);
1.1 root 413:
1.1.1.7 root 414: return nRet;
1.1 root 415:
1.1.1.2 root 416: #else /*SAVE_TO_MSA_IMAGES*/
1.1 root 417:
1.1.1.7 root 418: /* Oops, cannot save */
1.1.1.10 root 419: return false;
1.1 root 420:
1.1.1.2 root 421: #endif /*SAVE_TO_MSA_IMAGES*/
1.1 root 422: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.