|
|
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.3 root 120: Uint8 *MSA_UnCompress(Uint8 *pMSAFile, long *pImageSize)
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: /* Is an '.msa' file?? Check header */
131: pMSAHeader = (MSAHEADERSTRUCT *)pMSAFile;
132: if (pMSAHeader->ID == SDL_SwapBE16(0x0E0F))
133: {
134: /* First swap 'header' words around to PC format - easier later on */
135: pMSAHeader->SectorsPerTrack = SDL_SwapBE16(pMSAHeader->SectorsPerTrack);
136: pMSAHeader->Sides = SDL_SwapBE16(pMSAHeader->Sides);
137: pMSAHeader->StartingTrack = SDL_SwapBE16(pMSAHeader->StartingTrack);
138: pMSAHeader->EndingTrack = SDL_SwapBE16(pMSAHeader->EndingTrack);
139:
140: /* Create buffer */
141: pBuffer = malloc((pMSAHeader->EndingTrack - pMSAHeader->StartingTrack + 1)
142: * pMSAHeader->SectorsPerTrack * (pMSAHeader->Sides + 1)
143: * NUMBYTESPERSECTOR);
144: if (!pBuffer)
145: {
146: perror("MSA_UnCompress");
147: return NULL;
148: }
149:
150: /* Set pointers */
151: pImageBuffer = (Uint8 *)pBuffer;
1.1.1.13! root 152: pMSAImageBuffer = pMSAFile + sizeof(MSAHEADERSTRUCT);
1.1.1.7 root 153:
154: /* Uncompress to memory as '.ST' disk image - NOTE: assumes 512 bytes
155: * per sector (use NUMBYTESPERSECTOR define)!!! */
156: for (Track = pMSAHeader->StartingTrack; Track <= pMSAHeader->EndingTrack; Track++)
157: {
158: for (Side = 0; Side < (pMSAHeader->Sides+1); Side++)
159: {
160: int nBytesPerTrack = NUMBYTESPERSECTOR*pMSAHeader->SectorsPerTrack;
161:
162: /* Uncompress MSA Track, first check if is not compressed */
163: DataLength = do_get_mem_word(pMSAImageBuffer);
1.1.1.13! root 164: pMSAImageBuffer += sizeof(Uint16);
1.1.1.7 root 165: if (DataLength == nBytesPerTrack)
166: {
167: /* No compression on track, simply copy and continue */
168: memcpy(pImageBuffer, pMSAImageBuffer, nBytesPerTrack);
169: pImageBuffer += nBytesPerTrack;
170: pMSAImageBuffer += DataLength;
171: }
172: else
173: {
174: /* Uncompress track */
175: NumBytesUnCompressed = 0;
176: while (NumBytesUnCompressed < nBytesPerTrack)
177: {
178: Byte = *pMSAImageBuffer++;
179: if (Byte != 0xE5) /* Compressed header?? */
180: {
181: *pImageBuffer++ = Byte; /* No, just copy byte */
182: NumBytesUnCompressed++;
183: }
184: else
185: {
186: Data = *pMSAImageBuffer++; /* Byte to copy */
187: RunLength = do_get_mem_word(pMSAImageBuffer); /* For length */
188: /* Limit length to size of track, incorrect images may overflow */
189: if (RunLength+NumBytesUnCompressed > nBytesPerTrack)
190: {
191: fprintf(stderr, "MSA_UnCompress: Illegal run length -> corrupted disk image?\n");
192: RunLength = nBytesPerTrack - NumBytesUnCompressed;
193: }
1.1.1.13! root 194: pMSAImageBuffer += sizeof(Uint16);
1.1.1.7 root 195: for (i = 0; i < RunLength; i++)
196: *pImageBuffer++ = Data; /* Copy byte */
197: NumBytesUnCompressed += RunLength;
198: }
199: }
200: }
201: }
202: }
203:
204: /* Set size of loaded image */
1.1.1.13! root 205: *pImageSize = pImageBuffer-pBuffer;
1.1.1.7 root 206: }
1.1 root 207:
1.1.1.7 root 208: /* Return pointer to buffer, NULL if failed */
209: return(pBuffer);
1.1 root 210: }
211:
1.1.1.2 root 212:
213: /*-----------------------------------------------------------------------*/
1.1.1.7 root 214: /**
215: * Uncompress .MSA file into memory, set number bytes of the disk image and
216: * return a pointer to the buffer.
217: */
1.1.1.13! root 218: Uint8 *MSA_ReadDisk(int Drive, const char *pszFileName, long *pImageSize, int *pImageType)
1.1 root 219: {
1.1.1.7 root 220: Uint8 *pMsaFile;
221: Uint8 *pDiskBuffer = NULL;
1.1.1.3 root 222:
1.1.1.7 root 223: *pImageSize = 0;
1.1 root 224:
1.1.1.7 root 225: /* Read in file */
226: pMsaFile = File_Read(pszFileName, NULL, NULL);
227: if (pMsaFile)
228: {
229: /* Uncompress into disk buffer */
230: pDiskBuffer = MSA_UnCompress(pMsaFile, pImageSize);
231:
232: /* Free MSA file we loaded */
233: free(pMsaFile);
234: }
1.1 root 235:
1.1.1.13! root 236: if ( ( pMsaFile == NULL ) || ( pDiskBuffer == NULL ) )
! 237: return NULL;
! 238:
! 239: *pImageType = FLOPPY_IMAGE_TYPE_MSA;
1.1.1.7 root 240: /* Return pointer to buffer, NULL if failed */
241: return pDiskBuffer;
1.1 root 242: }
243:
1.1.1.2 root 244:
245: /*-----------------------------------------------------------------------*/
1.1.1.7 root 246: /**
247: * Return number of bytes of the same byte in the passed buffer
248: * If we return '0' this means no run (or end of buffer)
249: */
1.1.1.10 root 250: static int MSA_FindRunOfBytes(Uint8 *pBuffer, int nBytesInBuffer)
1.1 root 251: {
1.1.1.10 root 252: Uint8 ScannedByte;
1.1.1.7 root 253: int nTotalRun;
1.1.1.8 root 254: bool bMarker;
1.1.1.7 root 255: int i;
256:
257: /* Is this the marker? If so, this is at least a run of one. */
258: bMarker = (*pBuffer == 0xE5);
259:
260: /* Do we enough for a run? */
261: if (nBytesInBuffer < 2)
262: {
263: if (nBytesInBuffer == 1 && bMarker)
264: return 1;
265: else
266: return 0;
267: }
268:
269: /* OK, scan for run */
270: nTotalRun = 1;
271: ScannedByte = *pBuffer++;
272:
273: for (i = 1; i < nBytesInBuffer; i++)
274: {
275: if (*pBuffer++ == ScannedByte)
276: nTotalRun++;
277: else
278: break;
279: }
280:
281: /* Was this enough of a run to make a difference? */
282: if (nTotalRun < 4 && !bMarker)
283: nTotalRun = 0; /* Just store uncompressed */
1.1 root 284:
1.1.1.7 root 285: return nTotalRun;
1.1 root 286: }
287:
1.1.1.2 root 288:
289: /*-----------------------------------------------------------------------*/
1.1.1.7 root 290: /**
1.1.1.10 root 291: * Save compressed .MSA file from memory buffer. Returns true is all OK
1.1.1.7 root 292: */
1.1.1.13! root 293: bool MSA_WriteDisk(int Drive, const char *pszFileName, Uint8 *pBuffer, int ImageSize)
1.1 root 294: {
295: #ifdef SAVE_TO_MSA_IMAGES
296:
1.1.1.7 root 297: MSAHEADERSTRUCT *pMSAHeader;
1.1.1.13! root 298: Uint16 *pMSADataLength;
1.1.1.7 root 299: Uint8 *pMSAImageBuffer, *pMSABuffer, *pImageBuffer;
1.1.1.10 root 300: Uint16 nSectorsPerTrack, nSides, nCompressedBytes, nBytesPerTrack;
1.1.1.8 root 301: bool nRet;
1.1.1.7 root 302: int nTracks,nBytesToGo,nBytesRun;
303: int Track,Side;
304:
305: /* Allocate workspace for compressed image */
306: pMSAImageBuffer = (Uint8 *)malloc(MSA_WORKSPACE_SIZE);
307: if (!pMSAImageBuffer)
308: {
309: perror("MSA_WriteDisk");
1.1.1.10 root 310: return false;
1.1.1.7 root 311: }
312:
313: /* Store header */
314: pMSAHeader = (MSAHEADERSTRUCT *)pMSAImageBuffer;
315: pMSAHeader->ID = SDL_SwapBE16(0x0E0F);
316: Floppy_FindDiskDetails(pBuffer,ImageSize, &nSectorsPerTrack, &nSides);
317: pMSAHeader->SectorsPerTrack = SDL_SwapBE16(nSectorsPerTrack);
318: pMSAHeader->Sides = SDL_SwapBE16(nSides-1);
319: pMSAHeader->StartingTrack = SDL_SwapBE16(0);
320: nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides;
321: pMSAHeader->EndingTrack = SDL_SwapBE16(nTracks-1);
322:
323: /* Compress image */
324: pMSABuffer = pMSAImageBuffer + sizeof(MSAHEADERSTRUCT);
325: for (Track = 0; Track < nTracks; Track++)
326: {
327: for (Side = 0; Side < nSides; Side++)
328: {
329: /* Get track data pointer */
330: nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack;
331: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
332:
333: /* Skip data length (fill in later) */
334: pMSADataLength = (Uint16 *)pMSABuffer;
335: pMSABuffer += sizeof(Uint16);
336:
337: /* Compress track */
338: nBytesToGo = nBytesPerTrack;
339: nCompressedBytes = 0;
340: while (nBytesToGo > 0)
341: {
342: nBytesRun = MSA_FindRunOfBytes(pImageBuffer,nBytesToGo);
343: if (nBytesRun == 0)
344: {
345: /* Just copy byte */
346: *pMSABuffer++ = *pImageBuffer++;
347: nCompressedBytes++;
348: nBytesRun = 1;
349: }
350: else
351: {
352: /* Store run! */
353: *pMSABuffer++ = 0xE5; /* Marker */
354: *pMSABuffer++ = *pImageBuffer; /* Byte, and follow with 16-bit length */
355: do_put_mem_word(pMSABuffer, nBytesRun);
356: pMSABuffer += sizeof(Uint16);
357: pImageBuffer += nBytesRun;
358: nCompressedBytes += 4;
359: }
360: nBytesToGo -= nBytesRun;
361: }
362:
363: /* Is compressed track smaller than the original? */
364: if (nCompressedBytes < nBytesPerTrack)
365: {
366: /* Yes, store size */
367: do_put_mem_word(pMSADataLength, nCompressedBytes);
368: }
369: else
370: {
371: /* No, just store uncompressed track */
372: do_put_mem_word(pMSADataLength, nBytesPerTrack);
373: pMSABuffer = ((Uint8 *)pMSADataLength) + 2;
374: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
375: memcpy(pMSABuffer,pImageBuffer, nBytesPerTrack);
376: pMSABuffer += nBytesPerTrack;
377: }
378: }
379: }
1.1 root 380:
1.1.1.7 root 381: /* And save to file! */
1.1.1.10 root 382: nRet = File_Save(pszFileName,pMSAImageBuffer, pMSABuffer-pMSAImageBuffer, false);
1.1 root 383:
1.1.1.7 root 384: /* Free workspace */
385: free(pMSAImageBuffer);
1.1 root 386:
1.1.1.7 root 387: return nRet;
1.1 root 388:
1.1.1.2 root 389: #else /*SAVE_TO_MSA_IMAGES*/
1.1 root 390:
1.1.1.7 root 391: /* Oops, cannot save */
1.1.1.10 root 392: return false;
1.1 root 393:
1.1.1.2 root 394: #endif /*SAVE_TO_MSA_IMAGES*/
1.1 root 395: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.