1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2011-2021 Leandro Nini <drfiemost@users.sourceforge.net>
5  * Copyright 2007-2010 Antti Lankila
6  * Copyright 2000 Simon White
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21  */
22 
23 #include "SidTuneBase.h"
24 
25 #include <cstring>
26 #include <climits>
27 #include <iostream>
28 #include <iomanip>
29 #include <algorithm>
30 #include <iterator>
31 #include <fstream>
32 
33 #include "SmartPtr.h"
34 #include "SidTuneTools.h"
35 #include "SidTuneInfoImpl.h"
36 #include "sidendian.h"
37 #include "sidmemory.h"
38 #include "stringutils.h"
39 
40 #include "MUS.h"
41 #include "p00.h"
42 #include "prg.h"
43 #include "PSID.h"
44 
45 namespace libsidplayfp
46 {
47 
48 // Error and status message strings.
49 const char ERR_EMPTY[]               = "SIDTUNE ERROR: No data to load";
50 const char ERR_UNRECOGNIZED_FORMAT[] = "SIDTUNE ERROR: Could not determine file format";
51 const char ERR_CANT_LOAD_FILE[]      = "SIDTUNE ERROR: Could not load input file";
52 const char ERR_CANT_OPEN_FILE[]      = "SIDTUNE ERROR: Could not open file for binary input";
53 const char ERR_FILE_TOO_LONG[]       = "SIDTUNE ERROR: Input data too long";
54 const char ERR_DATA_TOO_LONG[]       = "SIDTUNE ERROR: Size of music data exceeds C64 memory";
55 const char ERR_BAD_ADDR[]            = "SIDTUNE ERROR: Bad address data";
56 const char ERR_BAD_RELOC[]           = "SIDTUNE ERROR: Bad reloc data";
57 const char ERR_CORRUPT[]             = "SIDTUNE ERROR: File is incomplete or corrupt";
58 //const char ERR_NOT_ENOUGH_MEMORY[]   = "SIDTUNE ERROR: Not enough free memory";
59 
60 const char SidTuneBase::ERR_TRUNCATED[] = "SIDTUNE ERROR: File is most likely truncated";
61 const char SidTuneBase::ERR_INVALID[]   = "SIDTUNE ERROR: File contains invalid data";
62 
63 /**
64  * Petscii to Ascii conversion table (0x01 = no output).
65  */
66 const char CHR_tab[256] =
67 {
68   0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0d,0x01,0x01,
69   0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
70   0x20,0x21,0x01,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
71   0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
72   0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
73   0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x24,0x5d,0x20,0x20,
74   // alternative: CHR$(92=0x5c) => ISO Latin-1(0xa3)
75   0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
76   0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
77   // 0x80-0xFF
78   0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
79   0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
80   0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
81   0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23,
82   0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
83   0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
84   0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
85   0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23
86 };
87 
88 /// The Commodore 64 memory size
89 const uint_least32_t MAX_MEMORY = 65536;
90 
91 /// C64KB + LOAD + PSID
92 const uint_least32_t MAX_FILELEN = MAX_MEMORY + 2 + 0x7C;
93 
94 /// Minimum load address for real c64 only tunes
95 const uint_least16_t SIDTUNE_R64_MIN_LOAD_ADDR = 0x07e8;
96 
load(const char * fileName,const char ** fileNameExt,bool separatorIsSlash)97 SidTuneBase* SidTuneBase::load(const char* fileName, const char **fileNameExt,
98                  bool separatorIsSlash)
99 {
100     return load(nullptr, fileName, fileNameExt, separatorIsSlash);
101 }
102 
load(LoaderFunc loader,const char * fileName,const char ** fileNameExt,bool separatorIsSlash)103 SidTuneBase* SidTuneBase::load(LoaderFunc loader, const char* fileName,
104                  const char **fileNameExt, bool separatorIsSlash)
105 {
106     if (fileName == nullptr)
107         return nullptr;
108 
109 #if !defined(SIDTUNE_NO_STDIN_LOADER)
110     // Filename "-" is used as a synonym for standard input.
111     if (strcmp(fileName, "-") == 0)
112         return getFromStdIn();
113 #endif
114     return getFromFiles(loader, fileName, fileNameExt, separatorIsSlash);
115 }
116 
read(const uint_least8_t * sourceBuffer,uint_least32_t bufferLen)117 SidTuneBase* SidTuneBase::read(const uint_least8_t* sourceBuffer, uint_least32_t bufferLen)
118 {
119     return getFromBuffer(sourceBuffer, bufferLen);
120 }
121 
getInfo() const122 const SidTuneInfo* SidTuneBase::getInfo() const
123 {
124     return info.get();
125 }
126 
getInfo(unsigned int songNum)127 const SidTuneInfo* SidTuneBase::getInfo(unsigned int songNum)
128 {
129     selectSong(songNum);
130     return info.get();
131 }
132 
selectSong(unsigned int selectedSong)133 unsigned int SidTuneBase::selectSong(unsigned int selectedSong)
134 {
135     // Check whether selected song is valid, use start song if not
136     const unsigned int song = (selectedSong == 0 || selectedSong > info->m_songs) ? info->m_startSong : selectedSong;
137 
138     // Copy any song-specific variable information
139     // such a speed/clock setting to the info structure.
140     info->m_currentSong = song;
141 
142     // Retrieve song speed definition.
143     switch (info->m_compatibility)
144     {
145     case SidTuneInfo::COMPATIBILITY_R64:
146         info->m_songSpeed = SidTuneInfo::SPEED_CIA_1A;
147         break;
148     case SidTuneInfo::COMPATIBILITY_PSID:
149         // This does not take into account the PlaySID bug upon evaluating the
150         // SPEED field. It would most likely break compatibility to lots of
151         // sidtunes, which have been converted from .SID format and vice versa.
152         // The .SID format does the bit-wise/song-wise evaluation of the SPEED
153         // value correctly, like it is described in the PlaySID documentation.
154         info->m_songSpeed = songSpeed[(song - 1) & 31];
155         break;
156     default:
157         info->m_songSpeed = songSpeed[song - 1];
158         break;
159     }
160 
161     info->m_clockSpeed = clockSpeed[song - 1];
162 
163     return info->m_currentSong;
164 }
165 
166 // ------------------------------------------------- private member functions
167 
placeSidTuneInC64mem(sidmemory & mem)168 void SidTuneBase::placeSidTuneInC64mem(sidmemory& mem)
169 {
170     // The Basic ROM sets these values on loading a file.
171     // Program end address
172     const uint_least16_t start = info->m_loadAddr;
173     const uint_least16_t end   = start + info->m_c64dataLen;
174     mem.writeMemWord(0x2d, end); // Variables start
175     mem.writeMemWord(0x2f, end); // Arrays start
176     mem.writeMemWord(0x31, end); // Strings start
177     mem.writeMemWord(0xac, start);
178     mem.writeMemWord(0xae, end);
179 
180     // Copy data from cache to the correct destination.
181     mem.fillRam(info->m_loadAddr, &cache[fileOffset], info->m_c64dataLen);
182 }
183 
loadFile(const char * fileName,buffer_t & bufferRef)184 void SidTuneBase::loadFile(const char* fileName, buffer_t& bufferRef)
185 {
186     std::ifstream inFile(fileName, std::ifstream::binary);
187 
188     if (!inFile.is_open())
189     {
190         throw loadError(ERR_CANT_OPEN_FILE);
191     }
192 
193     inFile.seekg(0, inFile.end);
194     const int fileLen = inFile.tellg();
195 
196     if (fileLen <= 0)
197     {
198         throw loadError(ERR_EMPTY);
199     }
200 
201     inFile.seekg(0, inFile.beg);
202 
203     buffer_t fileBuf;
204     fileBuf.reserve(fileLen);
205 
206     try
207     {
208         fileBuf.assign(std::istreambuf_iterator<char>(inFile), std::istreambuf_iterator<char>());
209     }
210     catch (std::exception &ex)
211     {
212         throw loadError(ex.what());
213     }
214 
215     if (inFile.bad())
216     {
217         throw loadError(ERR_CANT_LOAD_FILE);
218     }
219 
220     inFile.close();
221 
222     bufferRef.swap(fileBuf);
223 }
224 
SidTuneBase()225 SidTuneBase::SidTuneBase() :
226     info(new SidTuneInfoImpl()),
227     fileOffset(0)
228 {
229     // Initialize the object with some safe defaults.
230     for (unsigned int si = 0; si < MAX_SONGS; si++)
231     {
232         songSpeed[si] = info->m_songSpeed;
233         clockSpeed[si] = info->m_clockSpeed;
234     }
235 }
236 
237 #if !defined(SIDTUNE_NO_STDIN_LOADER)
238 
getFromStdIn()239 SidTuneBase* SidTuneBase::getFromStdIn()
240 {
241     buffer_t fileBuf;
242 
243     // We only read as much as fits in the buffer.
244     // This way we avoid choking on huge data.
245     char datb;
246     while (std::cin.get(datb) && fileBuf.size() < MAX_FILELEN)
247     {
248         fileBuf.push_back((uint_least8_t)datb);
249     }
250 
251     return getFromBuffer(&fileBuf.front(), fileBuf.size());
252 }
253 
254 #endif
255 
getFromBuffer(const uint_least8_t * const buffer,uint_least32_t bufferLen)256 SidTuneBase* SidTuneBase::getFromBuffer(const uint_least8_t* const buffer, uint_least32_t bufferLen)
257 {
258     if (buffer == nullptr || bufferLen == 0)
259     {
260         throw loadError(ERR_EMPTY);
261     }
262 
263     if (bufferLen > MAX_FILELEN)
264     {
265         throw loadError(ERR_FILE_TOO_LONG);
266     }
267 
268     buffer_t buf1(buffer, buffer + bufferLen);
269 
270     // Here test for the possible single file formats.
271     std::unique_ptr<SidTuneBase> s(PSID::load(buf1));
272     if (s.get() == nullptr) s.reset(MUS::load(buf1, true));
273     if (s.get() == nullptr) throw loadError(ERR_UNRECOGNIZED_FORMAT);
274 
275     s->acceptSidTune("-", "-", buf1, false);
276     return s.release();
277 }
278 
acceptSidTune(const char * dataFileName,const char * infoFileName,buffer_t & buf,bool isSlashedFileName)279 void SidTuneBase::acceptSidTune(const char* dataFileName, const char* infoFileName,
280                             buffer_t& buf, bool isSlashedFileName)
281 {
282     // Make a copy of the data file name and path, if available.
283     if (dataFileName != nullptr)
284     {
285         const size_t fileNamePos = isSlashedFileName ?
286             SidTuneTools::slashedFileNameWithoutPath(dataFileName) :
287             SidTuneTools::fileNameWithoutPath(dataFileName);
288         info->m_path = std::string(dataFileName, fileNamePos);
289         info->m_dataFileName = std::string(dataFileName + fileNamePos);
290     }
291 
292     // Make a copy of the info file name, if available.
293     if (infoFileName != nullptr)
294     {
295         const size_t fileNamePos = isSlashedFileName ?
296             SidTuneTools::slashedFileNameWithoutPath(infoFileName) :
297             SidTuneTools::fileNameWithoutPath(infoFileName);
298         info->m_infoFileName = std::string(infoFileName + fileNamePos);
299     }
300 
301     // Fix bad sidtune set up.
302     if (info->m_songs > MAX_SONGS)
303     {
304         info->m_songs = MAX_SONGS;
305     }
306     else if (info->m_songs == 0)
307     {
308         info->m_songs = 1;
309     }
310 
311     if (info->m_startSong == 0
312         || info->m_startSong > info->m_songs)
313     {
314         info->m_startSong = 1;
315     }
316 
317     info->m_dataFileLen = buf.size();
318     info->m_c64dataLen = buf.size() - fileOffset;
319 
320     // Calculate any remaining addresses and then
321     // confirm all the file details are correct
322     resolveAddrs(&buf[fileOffset]);
323 
324     if (checkRelocInfo() == false)
325     {
326         throw loadError(ERR_BAD_RELOC);
327     }
328     if (checkCompatibility() == false)
329     {
330          throw loadError(ERR_BAD_ADDR);
331     }
332 
333     if (info->m_dataFileLen >= 2)
334     {
335         // We only detect an offset of two. Some position independent
336         // sidtunes contain a load address of 0xE000, but are loaded
337         // to 0x0FFE and call player at 0x1000.
338         info->m_fixLoad = (endian_little16(&buf[fileOffset])==(info->m_loadAddr+2));
339     }
340 
341     // Check the size of the data.
342     if (info->m_c64dataLen > MAX_MEMORY)
343     {
344         throw loadError(ERR_DATA_TOO_LONG);
345     }
346     else if (info->m_c64dataLen == 0)
347     {
348         throw loadError(ERR_EMPTY);
349     }
350 
351     cache.swap(buf);
352 }
353 
createNewFileName(std::string & destString,const char * sourceName,const char * sourceExt)354 void SidTuneBase::createNewFileName(std::string& destString,
355                                 const char* sourceName,
356                                 const char* sourceExt)
357 {
358     destString.assign(sourceName);
359     destString.erase(destString.find_last_of('.'));
360     destString.append(sourceExt);
361 }
362 
363 // Initializing the object based upon what we find in the specified file.
364 
getFromFiles(const char * fileName,const char ** fileNameExtensions,bool separatorIsSlash)365 SidTuneBase* SidTuneBase::getFromFiles(const char* fileName, const char **fileNameExtensions, bool separatorIsSlash)
366 {
367     return getFromFiles(nullptr, fileName, fileNameExtensions, separatorIsSlash);
368 }
369 
getFromFiles(LoaderFunc loader,const char * fileName,const char ** fileNameExtensions,bool separatorIsSlash)370 SidTuneBase* SidTuneBase::getFromFiles(LoaderFunc loader, const char* fileName, const char **fileNameExtensions, bool separatorIsSlash)
371 {
372     buffer_t fileBuf1;
373 
374     if (loader == nullptr)
375         loader = (LoaderFunc) loadFile;
376 
377     loader(fileName, fileBuf1);
378 
379     // File loaded. Now check if it is in a valid single-file-format.
380     std::unique_ptr<SidTuneBase> s(PSID::load(fileBuf1));
381     if (s.get() == nullptr)
382     {
383         // Try some native C64 file formats
384         s.reset(MUS::load(fileBuf1, true));
385         if (s.get() != nullptr)
386         {
387             // Try to find second file.
388             std::string fileName2;
389             int n = 0;
390             while (fileNameExtensions[n] != nullptr)
391             {
392                 createNewFileName(fileName2, fileName, fileNameExtensions[n]);
393                 // 1st data file was loaded into "fileBuf1",
394                 // so we load the 2nd one into "fileBuf2".
395                 // Do not load the first file again if names are equal.
396                 if (!stringutils::equal(fileName, fileName2.data(), fileName2.size()))
397                 {
398                     try
399                     {
400                         buffer_t fileBuf2;
401 
402                         loader(fileName2.c_str(), fileBuf2);
403                         // Check if tunes in wrong order and therefore swap them here
404                         if (stringutils::equal(fileNameExtensions[n], ".mus"))
405                         {
406                             std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf2, fileBuf1, 0, true));
407                             if (s2.get() != nullptr)
408                             {
409                                 s2->acceptSidTune(fileName2.c_str(), fileName, fileBuf2, separatorIsSlash);
410                                 return s2.release();
411                             }
412                         }
413                         else
414                         {
415                             std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf1, fileBuf2, 0, true));
416                             if (s2.get() != nullptr)
417                             {
418                                 s2->acceptSidTune(fileName, fileName2.c_str(), fileBuf1, separatorIsSlash);
419                                 return s2.release();
420                             }
421                         }
422                     // The first tune loaded ok, so ignore errors on the
423                     // second tune, may find an ok one later
424                     }
425                     catch (loadError const &) {}
426                 }
427                 n++;
428             }
429         }
430     }
431     if (s.get() == nullptr) s.reset(p00::load(fileName, fileBuf1));
432     if (s.get() == nullptr) s.reset(prg::load(fileName, fileBuf1));
433     if (s.get() == nullptr) throw loadError(ERR_UNRECOGNIZED_FORMAT);
434 
435     s->acceptSidTune(fileName, nullptr, fileBuf1, separatorIsSlash);
436     return s.release();
437 }
438 
convertOldStyleSpeedToTables(uint_least32_t speed,SidTuneInfo::clock_t clock)439 void SidTuneBase::convertOldStyleSpeedToTables(uint_least32_t speed, SidTuneInfo::clock_t clock)
440 {
441     // Create the speed/clock setting tables.
442     //
443     // This routine implements the PSIDv2NG compliant speed conversion. All tunes
444     // above 32 use the same song speed as tune 32
445     // NOTE: The cast here is used to avoid undefined references
446     // as the std::min function takes its parameters by reference
447     const unsigned int toDo = std::min(info->m_songs, static_cast<unsigned int>(MAX_SONGS));
448     for (unsigned int s = 0; s < toDo; s++)
449     {
450         clockSpeed[s] = clock;
451         songSpeed[s] = (speed & 1) ? SidTuneInfo::SPEED_CIA_1A : SidTuneInfo::SPEED_VBI;
452 
453         if (s < 31)
454         {
455             speed >>= 1;
456         }
457     }
458 }
459 
checkRelocInfo()460 bool SidTuneBase::checkRelocInfo()
461 {
462     // Fix relocation information
463     if (info->m_relocStartPage == 0xFF)
464     {
465         info->m_relocPages = 0;
466         return true;
467     }
468     else if (info->m_relocPages == 0)
469     {
470         info->m_relocStartPage = 0;
471         return true;
472     }
473 
474     // Calculate start/end page
475     const uint_least8_t startp = info->m_relocStartPage;
476     const uint_least8_t endp   = (startp + info->m_relocPages - 1) & 0xff;
477     if (endp < startp)
478     {
479         return false;
480     }
481 
482     {    // Check against load range
483         const uint_least8_t startlp = (uint_least8_t) (info->m_loadAddr >> 8);
484         const uint_least8_t endlp   = startlp + (uint_least8_t) ((info->m_c64dataLen - 1) >> 8);
485 
486         if (((startp <= startlp) && (endp >= startlp))
487             || ((startp <= endlp)   && (endp >= endlp)))
488         {
489             return false;
490         }
491     }
492 
493     // Check that the relocation information does not use the following
494     // memory areas: 0x0000-0x03FF, 0xA000-0xBFFF and 0xD000-0xFFFF
495     if ((startp < 0x04)
496         || ((0xa0 <= startp) && (startp <= 0xbf))
497         || (startp >= 0xd0)
498         || ((0xa0 <= endp) && (endp <= 0xbf))
499         || (endp >= 0xd0))
500     {
501         return false;
502     }
503 
504     return true;
505 }
506 
resolveAddrs(const uint_least8_t * c64data)507 void SidTuneBase::resolveAddrs(const uint_least8_t *c64data)
508 {
509     // Originally used as a first attempt at an RSID
510     // style format. Now reserved for future use
511     if (info->m_playAddr == 0xffff)
512     {
513         info->m_playAddr = 0;
514     }
515 
516     // loadAddr = 0 means, the address is stored in front of the C64 data.
517     if (info->m_loadAddr == 0)
518     {
519         if (info->m_c64dataLen < 2)
520         {
521             throw loadError(ERR_CORRUPT);
522         }
523 
524         info->m_loadAddr = endian_16(*(c64data+1), *c64data);
525         fileOffset += 2;
526         info->m_c64dataLen -= 2;
527     }
528 
529     if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_BASIC)
530     {
531         if (info->m_initAddr != 0)
532         {
533             throw loadError(ERR_BAD_ADDR);
534         }
535     }
536     else if (info->m_initAddr == 0)
537     {
538         info->m_initAddr = info->m_loadAddr;
539     }
540 }
541 
checkCompatibility()542 bool SidTuneBase::checkCompatibility()
543 {
544     if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_R64)
545     {
546         // Check valid init address
547         switch (info->m_initAddr >> 12)
548         {
549         case 0x0A:
550         case 0x0B:
551         case 0x0D:
552         case 0x0E:
553         case 0x0F:
554             return false;
555         default:
556             if ((info->m_initAddr < info->m_loadAddr)
557                 || (info->m_initAddr > (info->m_loadAddr + info->m_c64dataLen - 1)))
558             {
559                 return false;
560             }
561         }
562 
563         // Check tune is loadable on a real C64
564         if (info->m_loadAddr < SIDTUNE_R64_MIN_LOAD_ADDR)
565         {
566             return false;
567         }
568     }
569 
570     return true;
571 }
572 
petsciiToAscii(SmartPtr_sidtt<const uint8_t> & spPet)573 std::string SidTuneBase::petsciiToAscii(SmartPtr_sidtt<const uint8_t>& spPet)
574 {
575     std::string buffer;
576 
577     do
578     {
579         const uint8_t petsciiChar = *spPet;
580         spPet++;
581 
582         if ((petsciiChar == 0x00) || (petsciiChar == 0x0d))
583             break;
584 
585         // If character is 0x9d (left arrow key) then move back.
586         if ((petsciiChar == 0x9d) && (!buffer.empty()))
587         {
588             buffer.resize(buffer.size() - 1);
589         }
590         else
591         {
592             // ASCII CHR$ conversion
593             const char asciiChar = CHR_tab[petsciiChar];
594             if ((asciiChar >= 0x20) && (buffer.length() <= 31))
595                 buffer.push_back(asciiChar);
596         }
597     }
598     while (!spPet.fail());
599 
600     return buffer;
601 }
602 
603 }
604