1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2011-2019 Leandro Nini <drfiemost@users.sourceforge.net>
5  * Copyright 2007-2010 Antti Lankila
6  * Copyright 2000-2001 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 "MUS.h"
24 
25 #include <memory>
26 
27 #include "sidplayfp/SidTuneInfo.h"
28 
29 #include "SmartPtr.h"
30 #include "sidendian.h"
31 #include "sidmemory.h"
32 
33 static const uint8_t sidplayer1[] =
34 {
35 #  include "sidplayer1.bin"
36 };
37 
38 static const uint8_t sidplayer2[] =
39 {
40 #  include "sidplayer2.bin"
41 };
42 
43 namespace libsidplayfp
44 {
45 
46 // Format strings
47 const char TXT_FORMAT_MUS[]        = "C64 Sidplayer format (MUS)";
48 const char TXT_FORMAT_STR[]        = "C64 Stereo Sidplayer format (MUS+STR)";
49 
50 // Error strings
51 const char ERR_2ND_INVALID[]       = "SIDTUNE ERROR: 2nd file contains invalid data";
52 const char ERR_SIZE_EXCEEDED[]     = "SIDTUNE ERROR: Total file size too large";
53 
54 static const uint_least16_t SIDTUNE_MUS_HLT_CMD = 0x14F;
55 
56 static const uint_least16_t SIDTUNE_MUS_DATA_ADDR  = 0x0900;
57 static const uint_least16_t SIDTUNE_SID2_BASE_ADDR = 0xd500;
58 
59 const int o65headersize = 27;
60 const uint8_t* player1 = sidplayer1 + o65headersize;
61 const uint8_t* player2 = sidplayer2 + o65headersize;
62 const size_t player1size = sizeof(sidplayer1) - o65headersize;
63 const size_t player2size = sizeof(sidplayer2) - o65headersize;
64 
detect(const uint8_t * buffer,size_t bufsize,uint_least32_t & voice3Index)65 bool detect(const uint8_t* buffer, size_t bufsize, uint_least32_t& voice3Index)
66 {
67     // sanity check
68     if ((buffer == nullptr) || (bufsize < 8))
69         return false;
70 
71     // Skip load address and 3x length entry.
72     uint_least32_t voice1Index = 2 + 3 * 2;
73     // Add length of voice 1 data.
74     voice1Index += endian_little16(&buffer[2]);
75     // Add length of voice 2 data.
76     uint_least32_t voice2Index = voice1Index + endian_little16(&buffer[4]);
77     // Add length of voice 3 data.
78     voice3Index = voice2Index + endian_little16(&buffer[6]);
79 
80     if (voice3Index > bufsize)
81         return false;
82 
83     return ((endian_big16(&buffer[voice1Index - 2]) == SIDTUNE_MUS_HLT_CMD)
84             && (endian_big16(&buffer[voice2Index - 2]) == SIDTUNE_MUS_HLT_CMD)
85             && (endian_big16(&buffer[voice3Index - 2]) == SIDTUNE_MUS_HLT_CMD));
86 }
87 
setPlayerAddress()88 void MUS::setPlayerAddress()
89 {
90     if (info->getSidChips() == 1)
91     {
92         // Player #1.
93         info->m_initAddr = 0xec60;
94         info->m_playAddr = 0xec80;
95     }
96     else
97     {
98         // Player #1 + #2.
99         info->m_initAddr = 0xfc90;
100         info->m_playAddr = 0xfc96;
101     }
102 }
103 
acceptSidTune(const char * dataFileName,const char * infoFileName,buffer_t & buf,bool isSlashedFileName)104 void MUS::acceptSidTune(const char* dataFileName, const char* infoFileName,
105                             buffer_t& buf, bool isSlashedFileName)
106 {
107     setPlayerAddress();
108     SidTuneBase::acceptSidTune(dataFileName, infoFileName, buf, isSlashedFileName);
109 }
110 
placeSidTuneInC64mem(sidmemory & mem)111 void MUS::placeSidTuneInC64mem(sidmemory& mem)
112 {
113     SidTuneBase::placeSidTuneInC64mem(mem);
114     installPlayer(mem);
115 }
116 
mergeParts(buffer_t & musBuf,buffer_t & strBuf)117 bool MUS::mergeParts(buffer_t& musBuf, buffer_t& strBuf)
118 {
119     const uint_least32_t mergeLen = musBuf.size() + strBuf.size();
120 
121     // Sanity check. I do not trust those MUS/STR files around.
122     const uint_least32_t freeSpace = endian_16(player1[1], player1[0]) - SIDTUNE_MUS_DATA_ADDR;
123     if ((mergeLen - 4) > freeSpace)
124     {
125         throw loadError(ERR_SIZE_EXCEEDED);
126     }
127 
128     if (!strBuf.empty() && info->getSidChips() > 1)
129     {
130         // Install MUS data #2 _NOT_ including load address.
131         musBuf.insert(musBuf.end(), strBuf.begin(), strBuf.end());
132     }
133 
134     strBuf.clear();
135 
136     return true;
137 }
138 
139 /**
140  * Replace useless SID reads with NOPs.
141  */
removeReads(sidmemory & mem,uint_least16_t dest)142 void removeReads(sidmemory& mem, uint_least16_t dest)
143 {
144     const int sid_read_offset = 0x424 - o65headersize - 2;
145     mem.fillRam(dest + sid_read_offset, 0xea, 12);
146 }
147 
installPlayer(sidmemory & mem)148 void MUS::installPlayer(sidmemory& mem)
149 {
150     // Install MUS player #1.
151     uint_least16_t dest = endian_16(player1[1], player1[0]);
152 
153     mem.fillRam(dest, player1 + 2, player1size - 2);
154     removeReads(mem, dest);
155     // Point player #1 to data #1.
156     mem.writeMemByte(dest + 0xc6e, (SIDTUNE_MUS_DATA_ADDR + 2) & 0xFF);
157     mem.writeMemByte(dest + 0xc70, (SIDTUNE_MUS_DATA_ADDR + 2) >> 8);
158 
159     if (info->getSidChips() > 1)
160     {
161         // Install MUS player #2.
162         dest = endian_16(player2[1], player2[0]);
163         mem.fillRam(dest, player2 + 2, player2size - 2);
164         removeReads(mem, dest);
165         // Point player #2 to data #2.
166         mem.writeMemByte(dest + 0xc6e, (SIDTUNE_MUS_DATA_ADDR + musDataLen + 2) & 0xFF);
167         mem.writeMemByte(dest + 0xc70, (SIDTUNE_MUS_DATA_ADDR + musDataLen + 2) >> 8);
168     }
169 }
170 
load(buffer_t & musBuf,bool init)171 SidTuneBase* MUS::load(buffer_t& musBuf, bool init)
172 {
173     buffer_t empty;
174     return load(musBuf, empty, 0, init);
175 }
176 
load(buffer_t & musBuf,buffer_t & strBuf,uint_least32_t fileOffset,bool init)177 SidTuneBase* MUS::load(buffer_t& musBuf,
178                             buffer_t& strBuf,
179                             uint_least32_t fileOffset,
180                             bool init)
181 {
182     uint_least32_t voice3Index;
183     if (!detect(&musBuf[fileOffset], musBuf.size()-fileOffset, voice3Index))
184         return nullptr;
185 
186     std::unique_ptr<MUS> tune(new MUS());
187     tune->tryLoad(musBuf, strBuf, fileOffset, voice3Index, init);
188     tune->mergeParts(musBuf, strBuf);
189 
190     return tune.release();
191 }
192 
tryLoad(buffer_t & musBuf,buffer_t & strBuf,uint_least32_t fileOffset,uint_least32_t voice3Index,bool init)193 void MUS::tryLoad(buffer_t& musBuf,
194                     buffer_t& strBuf,
195                     uint_least32_t fileOffset,
196                     uint_least32_t voice3Index,
197                     bool init)
198 {
199     if (init)
200     {
201         info->m_songs = 1;
202         info->m_startSong = 1;
203 
204         songSpeed[0]  = SidTuneInfo::SPEED_CIA_1A;
205         clockSpeed[0] = SidTuneInfo::CLOCK_ANY;
206     }
207 
208     // Check setting compatibility for MUS playback
209     if ((info->m_compatibility != SidTuneInfo::COMPATIBILITY_C64)
210         || (info->m_relocStartPage != 0)
211         || (info->m_relocPages != 0))
212     {
213         throw loadError(ERR_INVALID);
214     }
215 
216     {
217         // All subtunes should be CIA
218         for (uint_least16_t i = 0; i < info->m_songs; i++)
219         {
220             if (songSpeed[i] != SidTuneInfo::SPEED_CIA_1A)
221             {
222                 throw loadError(ERR_INVALID);
223             }
224         }
225     }
226 
227     musDataLen = musBuf.size();
228     info->m_loadAddr = SIDTUNE_MUS_DATA_ADDR;
229 
230     SmartPtr_sidtt<const uint8_t> spPet(&musBuf[fileOffset], musDataLen - fileOffset);
231 
232     // Voice3Index now is offset to text lines (uppercase Pet-strings).
233     spPet += voice3Index;
234 
235     // Extract credits
236     while (*spPet)
237     {
238         info->m_commentString.push_back(petsciiToAscii(spPet));
239     }
240 
241     spPet++;
242 
243     // If we appear to have additional data at the end, check is it's
244     // another mus file (but only if a second file isn't supplied)
245     bool stereo = false;
246     if (!strBuf.empty())
247     {
248         if (!detect(&strBuf[0], strBuf.size(), voice3Index))
249             throw loadError(ERR_2ND_INVALID);
250         spPet.setBuffer(&strBuf[0], strBuf.size());
251         stereo = true;
252     }
253     else
254     {
255         // For MUS + STR via stdin the files come combined
256         if (spPet.good())
257         {
258             const ulint_smartpt pos = spPet.tellPos();
259             if (detect(&spPet[0], spPet.tellLength()-pos, voice3Index))
260             {
261                 musDataLen = static_cast<uint_least16_t>(pos);
262                 stereo = true;
263             }
264         }
265     }
266 
267     if (stereo)
268     {
269         // Voice3Index now is offset to text lines (uppercase Pet-strings).
270         spPet += voice3Index;
271 
272         // Extract credits
273         while (*spPet)
274         {
275             info->m_commentString.push_back(petsciiToAscii(spPet));
276         }
277 
278         info->m_sidChipAddresses.push_back(SIDTUNE_SID2_BASE_ADDR);
279         info->m_formatString = TXT_FORMAT_STR;
280     }
281     else
282     {
283         info->m_formatString = TXT_FORMAT_MUS;
284     }
285 
286     setPlayerAddress();
287 
288     // Remove trailing empty lines.
289     const int lines = info->m_commentString.size();
290     {
291         for (int line = lines-1; line >= 0; line--)
292         {
293             if (info->m_commentString[line].length() == 0)
294                 info->m_commentString.pop_back();
295             else
296                 break;
297         }
298     }
299 }
300 
301 }
302