1 /*
2  * /home/ms/files/source/libsidtune/RCS/IconInfo.cpp,v
3  *
4  * Amiga icon tooltype PlaySID file format (.info) support.
5  *
6  * This is a derived work, courtesy of Peter Kunath, who has
7  * provided an examplary source code to examine an Amiga icon file.
8  *
9  * It has been ported and heavily modified to suit certain
10  * requirements. This replaces the old code, which was simply
11  * scanning input data for a first, presumedly constant, Id string.
12  * This code does not require the default tool to serve as a
13  * constant Id by containing "SID:PlaySID".
14  *
15  *  This program is free software; you can redistribute it and/or modify
16  *  it under the terms of the GNU General Public License as published by
17  *  the Free Software Foundation; either version 2 of the License, or
18  *  (at your option) any later version.
19  *
20  *  This program is distributed in the hope that it will be useful,
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  *  GNU General Public License for more details.
24  *
25  *  You should have received a copy of the GNU General Public License
26  *  along with this program; if not, write to the Free Software
27  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28  */
29 
30 #include "config.h"
31 #include "SidTune.h"
32 #include "SmartPtr.h"
33 #include "SidTuneTools.h"
34 #include "sidendian.h"
35 
36 #ifdef HAVE_EXCEPTIONS
37 #   include <new>
38 #endif
39 #include <string.h>
40 
41 // Amiga Workbench specific structures.
42 
43 struct Border
44 {
45     uint8_t LeftEdge[2];          // uint_least16_t; initial offsets from the origin
46     uint8_t TopEdge[2];           // uint_least16_t
47     uint8_t FrontPen, BackPen;    // pens numbers for rendering
48     uint8_t DrawMode;             // mode for rendering
49     uint8_t Count;                // number of XY pairs
50     uint8_t pXY[4];               // int_least16_t *XY; vector coordinate pairs rel to LeftTop
51     uint8_t pNextBorder[4];       // Border *NextBorder; pointer to any other Border too
52 };
53 
54 struct Image
55 {
56     uint8_t LeftEdge[2];          // uint_least16_t; starting offset relative to some origin
57     uint8_t TopEdge[2];           // uint_least16_t; starting offsets relative to some origin
58     uint8_t Width[2];             // uint_least16_t; pixel size (though data is word-aligned)
59     uint8_t Height[2];            // uint_least16_t
60     uint8_t Depth[2];             // uint_least16_t; >= 0, for images you create
61     uint8_t pImageData[4];        // uint_least16_t *ImageData; pointer to the actual word-aligned bits
62     uint8_t PlanePick, PlaneOnOff;
63     uint8_t pNextImage[4];        // Image *NextImage;
64 };
65 
66 struct Gadget
67 {
68     uint8_t pNextGadget[4];       // Gadget *NextGadget; next gadget in the list
69     uint8_t LeftEdge[2];          // uint_least16_t; "hit box" of gadget
70     uint8_t TopEdge[2];           // uint_least16_t
71     uint8_t Width[2];             // uint_least16_t; "hit box" of gadget
72     uint8_t Height[2];            // uint_least16_t
73     uint8_t Flags[2];             // uint_least16_t; see below for list of defines
74     uint8_t Activation[2];        // uint_least16_t
75     uint8_t GadgetType[2];        // uint_least16_t; see below for defines
76     uint8_t pGadgetRender[4];     // Image *GadgetRender;
77     uint8_t pSelectRender[4];     // Image *SelectRender;
78     uint8_t pGadgetText[4];       // void *GadgetText;
79     uint8_t MutualExclude[4];     // udword
80     uint8_t pSpecialInfo[4];      // void *SpecialInfo;
81     uint8_t GadgetID[2];          // uint_least16_t
82     uint8_t UserData[4];          // udword; ptr to general purpose User data
83 };
84 
85 struct DiskObject
86 {
87     uint8_t Magic[2];             // uint_least16_t; a magic num at the start of the file
88     uint8_t Version[2];           // uint_least16_t; a version number, so we can change it
89     struct Gadget Gadget;         // a copy of in core gadget
90     uint8_t Type;
91     uint8_t PAD_BYTE;             // Pad it out to the next word boundry
92     uint8_t pDefaultTool[4];      // char *DefaultTool;
93     uint8_t ppToolTypes[4];       // char **ToolTypes;
94     uint8_t CurrentX[4];          // udword
95     uint8_t CurrentY[4];          // udword
96     uint8_t pDrawerData[4];       // char *DrawerData;
97     uint8_t pToolWindow[4];       // char *ToolWindow; only applies to tools
98     uint8_t StackSize[4];         // udword; only applies to tools
99 };
100 
101 
102 // A magic number, not easily impersonated.
103 #define WB_DISKMAGIC 0xE310
104 // Our current version number.
105 #define WB_DISKVERSION  1
106 // Our current revision number.
107 #define WB_DISKREVISION 1
108 // I only use the lower 8 bits of Gadget.UserData for the revision #.
109 #define WB_DISKREVISIONMASK 0xFF
110 
111 // The Workbench object types.
112 #define    WB_DISK       1
113 #define    WB_DRAWER     2
114 #define    WB_TOOL       3
115 #define    WB_PROJECT    4
116 #define    WB_GARBAGE    5
117 #define    WB_DEVICE     6
118 #define    WB_KICK       7
119 #define    WB_APPICON    8
120 
121 // --- Gadget.Flags values    ---
122 // Combinations in these bits describe the highlight technique to be used.
123 #define GFLG_GADGHIGHBITS 0x0003
124 // Complement the select box.
125 #define GFLG_GADGHCOMP    0x0000
126 // Draw a box around the image.
127 #define GFLG_GADGHBOX     0x0001
128 // Blast in this alternate image.
129 #define GFLG_GADGHIMAGE   0x0002
130 // Don't highlight.
131 #define GFLG_GADGHNONE    0x0003
132 // Set if GadgetRender and SelectRender point to an Image structure,
133 // clear if they point to Border structures.
134 #define GFLG_GADGIMAGE    0x0004
135 
136 const char _sidtune_txt_format[] = "Raw plus PlaySID icon tooltype file (INFO)";
137 
138 const char _sidtune_keyword_id[] = "SID:PLAYSID";
139 const char _sidtune_keyword_address[] = "ADDRESS=";
140 const char _sidtune_keyword_songs[] = "SONGS=";
141 const char _sidtune_keyword_speed[] = "SPEED=";
142 const char _sidtune_keyword_name[] = "NAME=";
143 const char _sidtune_keyword_author[] = "AUTHOR=";
144 const char _sidtune_keyword_copyright[] = "COPYRIGHT=";
145 const char _sidtune_keyword_musPlayer[] = "SIDSONG=YES";
146 
147 const char _sidtune_txt_noMemError[] = "ERROR: Not enough free memory";
148 const char _sidtune_txt_corruptError[] = "ERROR: Info file is incomplete or corrupt";
149 const char _sidtune_txt_noStringsError[] = "ERROR: Info file does not contain required strings";
150 const char _sidtune_txt_dataCorruptError[] = "ERROR: C64 data file is corrupt";
151 #if defined(SIDTUNE_REJECT_UNKNOWN_FIELDS)
152 const char _sidtune_txt_chunkError[] = "ERROR: Invalid tooltype information in icon file";
153 #endif
154 
155 const uint_least16_t safeBufferSize = 64;  // for string comparison, stream parsing
156 
157 
INFO_fileSupport(const void * dataBuffer,uint_least32_t dataLength,const void * infoBuffer,uint_least32_t infoLength)158 bool SidTune::INFO_fileSupport(const void* dataBuffer, uint_least32_t dataLength,
159                                const void* infoBuffer, uint_least32_t infoLength)
160 {
161     // Require a first minimum safety size.
162     uint_least32_t minSize = 1+sizeof(struct DiskObject);
163     if (infoLength < minSize)
164         return( false );
165 
166     const DiskObject *dobject = (const DiskObject *)infoBuffer;
167 
168     // Require Magic_Id in the first two bytes of the file.
169     if ( endian_16(dobject->Magic[0],dobject->Magic[1]) != WB_DISKMAGIC )
170         return false;
171 
172     // Only version 1.x supported.
173     if ( endian_16(dobject->Version[0],dobject->Version[1]) != WB_DISKVERSION )
174         return false;
175 
176     // A PlaySID icon must be of type project.
177     if ( dobject->Type != WB_PROJECT )
178         return false;
179 
180     uint i;  // general purpose index variable
181 
182     // We want to skip a possible Gadget Image item.
183     const char *icon = (const char*)infoBuffer + sizeof(DiskObject);
184 
185     if ( (endian_16(dobject->Gadget.Flags[0],dobject->Gadget.Flags[1]) & GFLG_GADGIMAGE) == 0)
186     {
187         // Calculate size of gadget borders (vector image).
188 
189         if (dobject->Gadget.pGadgetRender[0] |
190             dobject->Gadget.pGadgetRender[1] |
191             dobject->Gadget.pGadgetRender[2] |
192             dobject->Gadget.pGadgetRender[3])  // border present?
193         {
194             // Require another minimum safety size.
195             minSize += sizeof(struct Border);
196             if (infoLength < minSize)
197                 return( false );
198 
199             const Border *brd = (const Border *)icon;
200             icon += sizeof(Border);
201             icon += brd->Count * (2+2);           // pair of uint_least16_t
202         }
203 
204         if (dobject->Gadget.pSelectRender[0] |
205             dobject->Gadget.pSelectRender[1] |
206             dobject->Gadget.pSelectRender[2] |
207             dobject->Gadget.pSelectRender[3])  // alternate border present?
208         {
209             // Require another minimum safety size.
210             minSize += sizeof(Border);
211             if (infoLength < minSize)
212                 return( false );
213 
214             const Border *brd = (const Border *)icon;
215             icon += sizeof(Border);
216             icon += brd->Count * (2+2);           // pair of uint_least16_t
217         }
218     }
219     else
220     {
221         // Calculate size of gadget images (bitmap image).
222 
223         if (dobject->Gadget.pGadgetRender[0] |
224             dobject->Gadget.pGadgetRender[1] |
225             dobject->Gadget.pGadgetRender[2] |
226             dobject->Gadget.pGadgetRender[3])  // image present?
227         {
228             // Require another minimum safety size.
229             minSize += sizeof(Image);
230             if (infoLength < minSize)
231                 return( false );
232 
233             const Image *img = (const Image *)icon;
234             icon += sizeof(Image);
235 
236             uint_least32_t imgsize = 0;
237             for(i=0;i<endian_16(img->Depth[0],img->Depth[1]);i++)
238             {
239                 if ( (img->PlanePick & (1<<i)) != 0)
240                 {
241                     // NOTE: Intuition relies on PlanePick to know how many planes
242                     // of data are found in ImageData. There should be no more
243                     // '1'-bits in PlanePick than there are planes in ImageData.
244                     imgsize++;
245                 }
246             }
247 
248             imgsize *= ((endian_16(img->Width[0],img->Width[1])+15)/16)*2;  // bytes per line
249             imgsize *= endian_16(img->Height[0],img->Height[1]);            // bytes per plane
250 
251             icon += imgsize;
252         }
253 
254         if (dobject->Gadget.pSelectRender[0] |
255             dobject->Gadget.pSelectRender[1] |
256             dobject->Gadget.pSelectRender[2] |
257             dobject->Gadget.pSelectRender[3])  // alternate image present?
258         {
259             // Require another minimum safety size.
260             minSize += sizeof(Image);
261             if (infoLength < minSize)
262                 return( false );
263 
264             const Image *img = (const Image *)icon;
265             icon += sizeof(Image);
266 
267             uint_least32_t imgsize = 0;
268             for(i=0;i<endian_16(img->Depth[0],img->Depth[1]);i++)
269             {
270                 if ( (img->PlanePick & (1<<i)) != 0)
271                 {
272                     // NOTE: Intuition relies on PlanePick to know how many planes
273                     // of data are found in ImageData. There should be no more
274                     // '1'-bits in PlanePick than there are planes in ImageData.
275                     imgsize++;
276                 }
277             }
278 
279             imgsize *= ((endian_16(img->Width[0],img->Width[1])+15)/16)*2;  // bytes per line
280             imgsize *= endian_16(img->Height[0],img->Height[1]);            // bytes per plane
281             icon += imgsize;
282         }
283     }
284 
285     // Here use a smart pointer to prevent access violation errors.
286     SmartPtr_sidtt<const char> spTool((const char*)icon,infoLength-(uint_least32_t)(icon-(const char*)infoBuffer));
287     if ( !spTool )
288     {
289         info.formatString = _sidtune_txt_corruptError;
290         return false;
291     }
292 
293     // A separate safe buffer is used for each tooltype string.
294 #ifdef HAVE_EXCEPTIONS
295     SmartPtr_sidtt<char> spCmpBuf(new(std::nothrow) char[safeBufferSize],safeBufferSize,true);
296 #else
297     SmartPtr_sidtt<char> spCmpBuf(new char[safeBufferSize],safeBufferSize,true);
298 #endif
299     if ( !spCmpBuf )
300     {
301         info.formatString = _sidtune_txt_noMemError;
302         return false;
303     }
304 
305 #ifndef SID_HAVE_BAD_COMPILER
306     char* cmpBuf = spCmpBuf.tellBegin();
307 #else
308     // This should not be necessary, but for some reason
309     // Microsoft Visual C++ says spCmpBuf is const...
310     char* cmpBuf = (char*) spCmpBuf.tellBegin();
311 #endif
312 
313     // Skip default tool.
314     spTool += endian_32(spTool[0],spTool[1],spTool[2],spTool[3]) + 4;
315 
316     // Defaults.
317     fileOffset = 0;                   // no header in separate data file
318     info.sidChipBase1 = 0xd400;
319     info.sidChipBase2 = 0;
320     info.musPlayer = false;
321     info.numberOfInfoStrings = 0;
322     uint_least32_t oldStyleSpeed = 0;
323 
324     // Flags for required entries.
325     bool hasAddress = false,
326     hasName = false,
327     hasAuthor = false,
328     hasCopyright = false,
329     hasSongs = false,
330     hasSpeed = false,
331     hasUnknownChunk = false;
332 
333     // Calculate number of tooltype strings.
334     i = (endian_32(spTool[0],spTool[1],spTool[2],spTool[3])/4) - 1;
335     spTool += 4;  // skip size info
336 
337     while( i-- > 0 )
338     {
339         // Get length of this tool.
340         uint_least32_t toolLen = endian_32(spTool[0],spTool[1],spTool[2],spTool[3]);
341         spTool += 4;  // skip tool length
342         // Copy item to safe buffer.
343         for ( uint ci = 0; ci < toolLen; ci++ )
344         {
345 #ifndef SID_HAVE_BAD_COMPILER
346             spCmpBuf[ci] = spTool[ci];
347 #else
348             // This should not be necessary, but for some reason
349             // Microsoft Visual C++ says spCmpBuf is const...
350             (*((char*) (&spCmpBuf[ci]))) = (char) spTool[ci];
351 #endif
352         }
353         if ( !(spTool&&spCmpBuf) )
354         {
355             return false;
356         }
357 
358         // Now check all possible keywords.
359         if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_address) == 0 )
360         {
361             const char *addrIn= cmpBuf + strlen(_sidtune_keyword_address);
362             int len = toolLen - strlen(_sidtune_keyword_address);
363             int pos = 0;
364             info.loadAddr = (uint_least16_t)SidTuneTools::readHex( addrIn, len, pos );
365             info.initAddr = (uint_least16_t)SidTuneTools::readHex( addrIn, len, pos );
366             info.playAddr = (uint_least16_t)SidTuneTools::readHex( addrIn, len, pos );
367             if ( pos >= len )
368             {
369                 return false;
370             }
371             hasAddress = true;
372         }
373         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_songs) == 0 )
374         {
375             const char *numIn = cmpBuf + strlen(_sidtune_keyword_songs);
376             int len = toolLen - strlen(_sidtune_keyword_songs);
377             int pos = 0;
378             if ( !pos >= len )
379             {
380                 return false;
381             }
382             info.songs = (uint_least16_t)SidTuneTools::readDec( numIn, len, pos );
383             info.startSong = (uint_least16_t)SidTuneTools::readDec( numIn, len, pos );
384             hasSongs = true;
385         }
386         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_speed) == 0 )
387         {
388             const char *speedIn = cmpBuf + strlen(_sidtune_keyword_speed);
389             int len = toolLen - strlen(_sidtune_keyword_speed);
390             int pos = 0;
391             if ( pos >= len )
392             {
393                 return false;
394             }
395             oldStyleSpeed = SidTuneTools::readHex(speedIn, len, pos);
396             hasSpeed = true;
397         }
398         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_name) == 0 )
399         {
400             strncpy( &infoString[0][0], cmpBuf + strlen(_sidtune_keyword_name), 31 );
401             info.infoString[0] = &infoString[0][0];
402             hasName = true;
403         }
404         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_author) == 0 )
405         {
406             strncpy( &infoString[1][0], cmpBuf + strlen(_sidtune_keyword_author), 31 );
407             info.infoString[1] = &infoString[1][0];
408             hasAuthor = true;
409         }
410         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_copyright) == 0 )
411         {
412             strncpy( &infoString[2][0], cmpBuf + strlen(_sidtune_keyword_copyright), 31 );
413             info.infoString[2] = &infoString[2][0];
414             hasCopyright = true;
415         }
416         else if ( SidTuneTools::myStrNcaseCmp(cmpBuf,_sidtune_keyword_musPlayer) == 0 )
417         {
418             info.musPlayer = true;
419         }
420         else
421         {
422             hasUnknownChunk = true;
423 #if defined(SIDTUNE_REJECT_UNKNOWN_FIELDS)
424             info.formatString = _sidtune_txt_chunkError;
425             return false;
426 #endif
427         }
428         // Skip to next tool.
429         spTool += toolLen;
430     }
431 
432     // Collected ``required'' information complete ?
433     if ( hasAddress && hasName && hasAuthor && hasCopyright && hasSongs && hasSpeed )
434     {
435         // Create the speed/clock setting table.
436         convertOldStyleSpeedToTables(oldStyleSpeed);
437         if (( info.loadAddr == 0 ) && ( dataLength != 0 ))
438         {
439             SmartPtr_sidtt<const uint_least8_t> spDataBuf((const uint_least8_t*)dataBuffer,dataLength);
440             spDataBuf += fileOffset;
441             info.loadAddr = endian_16(spDataBuf[1],spDataBuf[0]);
442             if ( !spDataBuf )
443             {
444                 info.formatString = _sidtune_txt_dataCorruptError;
445                 return false;
446             }
447             fileOffset += 2;
448         }
449         if ( info.initAddr == 0 )
450         {
451             info.initAddr = info.loadAddr;
452         }
453         info.numberOfInfoStrings = 3;
454         // We finally accept the input data.
455         info.formatString = _sidtune_txt_format;
456         return true;
457     }
458     else if ( hasAddress || hasName || hasAuthor || hasCopyright || hasSongs || hasSpeed )
459     {
460         // Something is missing (or damaged?).
461         info.formatString = _sidtune_txt_corruptError;
462         return false;
463     }
464     else
465     {
466         // No PlaySID conform info strings.
467         info.formatString = _sidtune_txt_noStringsError;
468         return false;
469     }
470 }
471