|
|
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.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: {
95: short int ID; /* Word ID marker, should be $0E0F */
96: short int SectorsPerTrack; /* Word Sectors per track */
97: short int Sides; /* Word Sides (0 or 1; add 1 to this to get correct number of sides) */
98: short int StartingTrack; /* Word Starting track (0-based) */
99: short int 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: /**
107: * Does filename end with a .MSA extension? If so, return TRUE
108: */
1.1.1.8 root 109: bool MSA_FileNameIsMSA(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;
152: pMSAImageBuffer = (Uint8 *)((unsigned long)pMSAFile + sizeof(MSAHEADERSTRUCT));
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);
164: pMSAImageBuffer += sizeof(short int);
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: }
194: pMSAImageBuffer += sizeof(short int);
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 */
205: *pImageSize = (unsigned long)pImageBuffer-(unsigned long)pBuffer;
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.5 root 218: Uint8 *MSA_ReadDisk(char *pszFileName, long *pImageSize)
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.7 root 236: /* Return pointer to buffer, NULL if failed */
237: return pDiskBuffer;
1.1 root 238: }
239:
1.1.1.2 root 240:
241: /*-----------------------------------------------------------------------*/
1.1.1.7 root 242: /**
243: * Return number of bytes of the same byte in the passed buffer
244: * If we return '0' this means no run (or end of buffer)
245: */
1.1.1.3 root 246: static int MSA_FindRunOfBytes(unsigned char *pBuffer, int nBytesInBuffer)
1.1 root 247: {
1.1.1.7 root 248: unsigned char ScannedByte;
249: int nTotalRun;
1.1.1.8 root 250: bool bMarker;
1.1.1.7 root 251: int i;
252:
253: /* Is this the marker? If so, this is at least a run of one. */
254: bMarker = (*pBuffer == 0xE5);
255:
256: /* Do we enough for a run? */
257: if (nBytesInBuffer < 2)
258: {
259: if (nBytesInBuffer == 1 && bMarker)
260: return 1;
261: else
262: return 0;
263: }
264:
265: /* OK, scan for run */
266: nTotalRun = 1;
267: ScannedByte = *pBuffer++;
268:
269: for (i = 1; i < nBytesInBuffer; i++)
270: {
271: if (*pBuffer++ == ScannedByte)
272: nTotalRun++;
273: else
274: break;
275: }
276:
277: /* Was this enough of a run to make a difference? */
278: if (nTotalRun < 4 && !bMarker)
279: nTotalRun = 0; /* Just store uncompressed */
1.1 root 280:
1.1.1.7 root 281: return nTotalRun;
1.1 root 282: }
283:
1.1.1.2 root 284:
285: /*-----------------------------------------------------------------------*/
1.1.1.7 root 286: /**
287: * Save compressed .MSA file from memory buffer. Returns TRUE is all OK
288: */
1.1.1.8 root 289: bool MSA_WriteDisk(char *pszFileName, Uint8 *pBuffer, int ImageSize)
1.1 root 290: {
291: #ifdef SAVE_TO_MSA_IMAGES
292:
1.1.1.7 root 293: MSAHEADERSTRUCT *pMSAHeader;
294: unsigned short int *pMSADataLength;
295: Uint8 *pMSAImageBuffer, *pMSABuffer, *pImageBuffer;
296: unsigned short int nSectorsPerTrack, nSides, nCompressedBytes, nBytesPerTrack;
1.1.1.8 root 297: bool nRet;
1.1.1.7 root 298: int nTracks,nBytesToGo,nBytesRun;
299: int Track,Side;
300:
301: /* Allocate workspace for compressed image */
302: pMSAImageBuffer = (Uint8 *)malloc(MSA_WORKSPACE_SIZE);
303: if (!pMSAImageBuffer)
304: {
305: perror("MSA_WriteDisk");
306: return FALSE;
307: }
308:
309: /* Store header */
310: pMSAHeader = (MSAHEADERSTRUCT *)pMSAImageBuffer;
311: pMSAHeader->ID = SDL_SwapBE16(0x0E0F);
312: Floppy_FindDiskDetails(pBuffer,ImageSize, &nSectorsPerTrack, &nSides);
313: pMSAHeader->SectorsPerTrack = SDL_SwapBE16(nSectorsPerTrack);
314: pMSAHeader->Sides = SDL_SwapBE16(nSides-1);
315: pMSAHeader->StartingTrack = SDL_SwapBE16(0);
316: nTracks = ((ImageSize / NUMBYTESPERSECTOR) / nSectorsPerTrack) / nSides;
317: pMSAHeader->EndingTrack = SDL_SwapBE16(nTracks-1);
318:
319: /* Compress image */
320: pMSABuffer = pMSAImageBuffer + sizeof(MSAHEADERSTRUCT);
321: for (Track = 0; Track < nTracks; Track++)
322: {
323: for (Side = 0; Side < nSides; Side++)
324: {
325: /* Get track data pointer */
326: nBytesPerTrack = NUMBYTESPERSECTOR*nSectorsPerTrack;
327: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
328:
329: /* Skip data length (fill in later) */
330: pMSADataLength = (Uint16 *)pMSABuffer;
331: pMSABuffer += sizeof(Uint16);
332:
333: /* Compress track */
334: nBytesToGo = nBytesPerTrack;
335: nCompressedBytes = 0;
336: while (nBytesToGo > 0)
337: {
338: nBytesRun = MSA_FindRunOfBytes(pImageBuffer,nBytesToGo);
339: if (nBytesRun == 0)
340: {
341: /* Just copy byte */
342: *pMSABuffer++ = *pImageBuffer++;
343: nCompressedBytes++;
344: nBytesRun = 1;
345: }
346: else
347: {
348: /* Store run! */
349: *pMSABuffer++ = 0xE5; /* Marker */
350: *pMSABuffer++ = *pImageBuffer; /* Byte, and follow with 16-bit length */
351: do_put_mem_word(pMSABuffer, nBytesRun);
352: pMSABuffer += sizeof(Uint16);
353: pImageBuffer += nBytesRun;
354: nCompressedBytes += 4;
355: }
356: nBytesToGo -= nBytesRun;
357: }
358:
359: /* Is compressed track smaller than the original? */
360: if (nCompressedBytes < nBytesPerTrack)
361: {
362: /* Yes, store size */
363: do_put_mem_word(pMSADataLength, nCompressedBytes);
364: }
365: else
366: {
367: /* No, just store uncompressed track */
368: do_put_mem_word(pMSADataLength, nBytesPerTrack);
369: pMSABuffer = ((Uint8 *)pMSADataLength) + 2;
370: pImageBuffer = pBuffer + (nBytesPerTrack*Side) + ((nBytesPerTrack*nSides)*Track);
371: memcpy(pMSABuffer,pImageBuffer, nBytesPerTrack);
372: pMSABuffer += nBytesPerTrack;
373: }
374: }
375: }
1.1 root 376:
1.1.1.7 root 377: /* And save to file! */
378: nRet = File_Save(pszFileName,pMSAImageBuffer, pMSABuffer-pMSAImageBuffer, FALSE);
1.1 root 379:
1.1.1.7 root 380: /* Free workspace */
381: free(pMSAImageBuffer);
1.1 root 382:
1.1.1.7 root 383: return nRet;
1.1 root 384:
1.1.1.2 root 385: #else /*SAVE_TO_MSA_IMAGES*/
1.1 root 386:
1.1.1.7 root 387: /* Oops, cannot save */
388: return FALSE;
1.1 root 389:
1.1.1.2 root 390: #endif /*SAVE_TO_MSA_IMAGES*/
1.1 root 391: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.