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