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