ugBASIC 1.18
An isomorphic BASIC language compiler for retrocomputers
Loading...
Searching...
No Matches
sid_file.c
Go to the documentation of this file.
1/*****************************************************************************
2 * ugBASIC - an isomorphic BASIC language compiler for retrocomputers *
3 *****************************************************************************
4 * Copyright 2021-2024 Marco Spedaletti (asimov@mclink.it)
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *----------------------------------------------------------------------------
18 * Concesso in licenza secondo i termini della Licenza Apache, versione 2.0
19 * (la "Licenza"); è proibito usare questo file se non in conformità alla
20 * Licenza. Una copia della Licenza è disponibile all'indirizzo:
21 *
22 * http://www.apache.org/licenses/LICENSE-2.0
23 *
24 * Se non richiesto dalla legislazione vigente o concordato per iscritto,
25 * il software distribuito nei termini della Licenza è distribuito
26 * "COSÌ COM'È", SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO, esplicite o
27 * implicite. Consultare la Licenza per il testo specifico che regola le
28 * autorizzazioni e le limitazioni previste dalla medesima.
29 ****************************************************************************/
30
31/****************************************************************************
32 * INCLUDE SECTION
33 ****************************************************************************/
34
35#include <stddef.h>
36#include <stdio.h>
37#include <string.h>
38#include <stdlib.h>
39
40#include "sid_file.h"
41#include "sidreloc.h"
42
43char * strcopy( char * _dest, const char * _source );
44
45/****************************************************************************
46 * DATA TYPES
47 ****************************************************************************/
48
49// The SID file header v1
50// ======================
51
52typedef struct _SIDFILEHeader {
53
54 unsigned char magicID[4]; // 'PSID' or 'RSID'
55
56 unsigned char version[2];
57
58 unsigned char dataOffset[2];
59
60 unsigned char loadAddress[2];
61
62 unsigned char initAddress[2];
63
64 unsigned char playAddress[2];
65
66 unsigned char songs;
67
68 unsigned char startSong;
69
70 unsigned char speed[4];
71
72 unsigned char name[32];
73 unsigned char author[32];
74 unsigned char released[32];
75
76 unsigned char flags[2];
77
78 unsigned char startPage;
79
80 unsigned char pageLength;
81
82 unsigned char secondSIDAddress;
83
84 unsigned char thirdSIDAddres;
85
87
88static char lastErrorString[1024];
89
90/****************************************************************************
91 * CODE SECTION
92 ****************************************************************************/
93
94SIDFILE * sid_file_read( char * _filename, int _reloc_address ) {
95
96 SIDFILE * result = NULL;
97
98 char * entireFile = NULL;
99
100 FILE * fhandle = fopen( _filename, "rb" );
101
102 if ( !fhandle ) {
103 goto failed;
104 }
105
106 result = malloc( sizeof( SIDFILE ) );
107 memset( result, 0, sizeof( SIDFILE ) );
108
109 fseek( fhandle, 0, SEEK_END );
110 int fileSize = ftell( fhandle );
111 fseek( fhandle, 0, SEEK_SET );
112
113 entireFile = malloc( fileSize );
114 memset( entireFile, 0, fileSize );
115
116 (void)!fread( entireFile, fileSize, 1, fhandle );
117
118 if ( _reloc_address ) {
119
120 char * relocatedFile = malloc( fileSize );
121 memset( relocatedFile, 0, fileSize );
122
123 if ( !sidreloc_set_page( ( _reloc_address >> 8 ) ) ) {
124 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
125 }
126 if ( !sidreloc_set_force( ) ) {
127 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
128 }
129 if ( !sidreloc_set_verbosity( 0 ) ) {
130 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
131 }
132 if ( !sidreloc_set_input_data( entireFile, fileSize ) ) {
133 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
134 }
135 if ( !sidreloc_set_output_data( relocatedFile ) ) {
136 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
137 }
138 if ( !sidreloc_main() ) {
139 strcopy( lastErrorString, sidreloc_get_lasterror_string( ) );
140 }
141
142 free(entireFile);
143 entireFile = relocatedFile;
144
145 }
146
147 SIDFILEHeader * header = NULL;
148
149 char signature[4];
150 memcpy( signature, entireFile, 4 );
151
152 // This is a four byte long ASCII character string containing the value
153 // 0x50534944 or 0x52534944. 'RSID' (Real SID) denotes that the file strictly
154 // requires a true Commodore-64 environment to run properly. 'PSID' files will
155 // generally run trouble-free on older PlaySID and libsidplay1 based emulators,
156 // too.
157
158 if ( memcmp( signature, "RSID", 4 ) && memcmp( signature, "PSID", 4 ) ) {
159 sprintf( lastErrorString, "Unable to load sid file, wrong signature: %2.2x%2.2x%2.2x%2.2x", signature[0], signature[1], signature[2], signature[3] );
160 goto failed;
161 }
162
163 header = malloc( sizeof( SIDFILEHeader ) );
164 memset( header, 0, sizeof( SIDFILEHeader ) );
165
166 memcpy( header, entireFile, sizeof( SIDFILEHeader ) );
167
168 // RSID is based on PSIDv2NG with the following modifications:
169
170 // magicID = RSID
171 // version = 2, 3 and 4 only
172 // loadAddress = 0 (reserved)
173 // playAddress = 0 (reserved)
174 // speed = 0 (reserved)
175 // psidSpecific flag is called C64BASIC flag
176
177 // The above fields MUST be checked and if any differ from the above then the
178 // tune MUST be rejected. The definitions above will force tunes to contain
179 // proper hardware configuration code and install valid interrupt handlers.
180
181 // See section "The SID file environment" mentioned later in this document for the
182 // default C64 power-on environment for each SID file format.
183
184 // Available version number can be 0001, 0002, 0003 or 0004. Headers of version 2,
185 // 3 and 4 provide additional fields. RSID and PSID v2NG files must have 0002,
186 // 0003 or 0004 here.
187
188 // This is the offset from the start of the file to the C64 binary data area.
189 // Because of the fixed size of the header, this is either 0x0076 for version 1
190 // and 0x007C for version 2, 3 and 4.
191
192 int dataOffset = (header->dataOffset[0] << 8) | header->dataOffset[1];
193 switch ( header->version[1] ) {
194 case 1:
195 if ( dataOffset != 0x76 ) {
196 sprintf( lastErrorString, "Unable to load sid, wrong offset for v1: %4.4x", dataOffset );
197 goto failed;
198 }
199 break;
200 case 2:
201 case 3:
202 case 4:
203 if ( dataOffset != 0x7c ) {
204 sprintf( lastErrorString, "Unable to load sid, wrong offset for v%d: %4.4x", header->version[1], dataOffset );
205 goto failed;
206 }
207 break;
208 default:
209 sprintf( lastErrorString, "Unknown version (%d)", header->version[1] );
210 goto failed;
211 }
212
213 // The C64 memory location where to put the C64 data. 0 means the data are in
214 // original C64 binary file format, i.e. the first two bytes of the data contain
215 // the little-endian load address (low byte, high byte). This must always be true
216 // for RSID files. Furthermore, the actual load address must NOT be less than
217 // $07E8 in RSID files.
218
219 result->loadAddress = (header->loadAddress[0]<<8) | header->loadAddress[1];
220
221 // You must be absolutely sure what to enter here. There is no way to detect
222 // automatically whether the first two bytes in a C64 data file are meant to be a
223 // load address or some arbitrary bytes of code or data. Unless your C64 file is
224 // not a normal binary file and thus has no load address in front, you need not
225 // enter anything else than 0 here. The SID tune will not play if you specify a
226 // load address which is present in the C64 file already.
227
228 if ( result->loadAddress == 0 ) {
229 memcpy( &header->loadAddress[0], &entireFile[dataOffset], 2 );
230 dataOffset += 2;
231 result->loadAddress = (header->loadAddress[1]<<8) | header->loadAddress[0];
232 }
233
234 // Normal C64 binary data files have a load address in their first two bytes, so
235 // they can be loaded to a pre-defined destination address by executing
236 // LOAD"FILE",8,1, for instance. If a load address is explicitly specified in the
237 // sidtune info file, some sidtune converters and utilities conjecture that the
238 // C64 data don't have a load address in their first two bytes. Hence, the
239 // explicit load address from the info file is moved in front of the C64 data to
240 // create a valid C64 binary file which can be easily loaded on a C64, too. If
241 // that C64 file were to be saved, it would contain two superfluous data bytes at
242 // offset 2 if an original load address had been in the first two bytes of the
243 // old file. This process of adding a duplicate load address can be repeated. The
244 // file loader strips off the first two bytes (the used load address) and puts
245 // the rest of the file contents (including the now obsolete load address at file
246 // offset 2) into memory. If the new load address is the same than the old one
247 // the two added bytes cause the whole data to be displaced by two bytes, which
248 // most likely results in malfunctioning code. Also, superfluous bytes in memory
249 // then can confuse disassemblers which start at the beginning of the file or
250 // memory buffer.
251
252 result->initAddress = (header->initAddress[0]<<8) | header->initAddress[1];
253
254 // The start address of the machine code subroutine that initializes a song,
255 // accepting the contents of the 8-bit 6510 Accumulator as the song number
256 // parameter. 0 means the address is equal to the effective load address.
257
258 if ( result->initAddress == 0 ) {
259 result->initAddress = result->loadAddress;
260 }
261
262 // In RSID files initAddress must never point to a ROM area ($A000-$BFFF or
263 // $D000-$FFFF) or be lower than $07E8. Also, if the C64 BASIC flag is set,
264 // initAddress must be 0.
265
266 if ( memcmp( signature, "RSID", 4 ) == 0 ) {
267 if ( (result->initAddress >= 0xa000) && (result->initAddress <= 0xbfff) ) {
268 sprintf( lastErrorString, "Unable to load sid, RSID on ROM: %4.4x", result->initAddress );
269 goto failed;
270 }
271 if ( (result->initAddress < 0x07e8) ) {
272 sprintf( lastErrorString, "Unable to load sid, RSID on ZERO PAGE: %4.4x\n", result->initAddress );
273 goto failed;
274 }
275 }
276
277 // The start address of the machine code subroutine that can be called frequently
278 // to produce a continuous sound. 0 means the initialization subroutine is
279 // expected to install an interrupt handler, which then calls the music player at
280 // some place. This must always be true for RSID files.
281
282 result->playAddress = (header->playAddress[0]<<8) | header->playAddress[1];
283
284 // The number of songs (or sound effects) that can be initialized by calling the
285 // init address. The minimum is 1. The maximum is 256.
286
287 result->songs = header->songs;
288
289 // The song number to be played by default. This value is optional. It often
290 // specifies the first song you would hear upon starting the program is has been
291 // taken from. It has a default of 1.
292
293 result->startSong = header->startSong;
294
295 // unsigned char speed[4];
296
297 // This is a 32 bit big endian number.
298 // For version 1 and 2 and for version 2NG, 3 and 4 with PlaySID specific flag
299 // (+76) set, the 'speed' should be handled as follows:
300 // Each bit in 'speed' specifies the speed for the corresponding tune number,
301 // i.e. bit 0 specifies the speed for tune 1. If there are more than 32 tunes,
302 // the speed specified for tune 32 is the same as tune 1, for tune 33 it is the
303 // same as tune 2, etc.
304 // For version 2NG, 3 and 4 with PlaySID specific flag (+76) cleared, the 'speed'
305 // should be handled as follows:
306 // Each bit in 'speed' specifies the speed for the corresponding tune number,
307 // i.e. bit 0 specifies the speed for tune 1. If there are more than 32 tunes,
308 // the speed specified for tune 32 is also used for all higher numbered tunes.
309 // For all version counts:
310 // A 0 bit specifies vertical blank interrupt (50Hz PAL, 60Hz NTSC), and a 1 bit
311 // specifies CIA 1 timer interrupt (default 60Hz).
312 // Surplus bits in 'speed' should be set to 0.
313 // For RSID files 'speed' must always be set to 0.
314
315 // unsigned char name[32];
316 // unsigned char author[32];
317 // unsigned char released[32];
318
319 // These are 32 byte long ASCII character strings. Upon evaluating the header,
320 // these fields may hold a character string of 32 bytes which is not zero
321 // terminated. For less than 32 characters the string should be zero terminated.
322 // The maximum number of available free characters is 32.
323
324 // unsigned char flags[2];
325 // unsigned char startPage;
326 // unsigned char pageLength;
327 // unsigned char secondSIDAddress;
328 // unsigned char thirdSIDAddres;
329
330 fileSize -= dataOffset;
331 result->data = malloc( fileSize );
332 memset( result->data, 0, fileSize );
333 memcpy( result->data, &entireFile[dataOffset], fileSize );
334 result->size = fileSize;
335
336 free( header );
337
338 return result;
339
340failed:
341
342 if ( fhandle ) {
343 fclose( fhandle );
344 }
345
346 if ( header ) {
347 free(header);
348 }
349
350 if ( result ) {
351 free(result);
352 }
353
354 if ( entireFile ) {
355 free(entireFile);
356 }
357
358 return NULL;
359}
360
361int sid_file_size( SIDFILE * _sid_file ) {
362 return _sid_file->size;
363}
364
365unsigned char * sid_file_data( SIDFILE * _sid_file ) {
366 return _sid_file->data;
367}
368
369void sid_file_free( SIDFILE * _sid_file ) {
370
371 free( _sid_file->data );
372 free( _sid_file );
373
374}
375
377 return lastErrorString;
378}
struct _SIDFILEHeader SIDFILEHeader
SIDFILE * sid_file_read(char *_filename, int _reloc_address)
Definition sid_file.c:94
char * sid_file_get_lasterror_string()
Definition sid_file.c:376
unsigned char * sid_file_data(SIDFILE *_sid_file)
Definition sid_file.c:365
void sid_file_free(SIDFILE *_sid_file)
Definition sid_file.c:369
char * strcopy(char *_dest, const char *_source)
int sid_file_size(SIDFILE *_sid_file)
Definition sid_file.c:361
struct _SIDFILE SIDFILE
char * sidreloc_get_lasterror_string()
Definition sidreloc.c:2022
int sidreloc_set_input_data(unsigned char *_data, int _size)
Definition sidreloc.c:2151
int sidreloc_set_verbosity(int _verbose)
Definition sidreloc.c:2087
int sidreloc_set_page(int _page)
Definition sidreloc.c:2026
int sidreloc_set_force()
Definition sidreloc.c:2082
int sidreloc_set_output_data(unsigned char *_data)
Definition sidreloc.c:2157
int sidreloc_main()
Definition sidreloc.c:2162
unsigned char loadAddress[2]
Definition sid_file.c:60
unsigned char thirdSIDAddres
Definition sid_file.c:84
unsigned char songs
Definition sid_file.c:66
unsigned char dataOffset[2]
Definition sid_file.c:58
unsigned char startSong
Definition sid_file.c:68
unsigned char author[32]
Definition sid_file.c:73
unsigned char name[32]
Definition sid_file.c:72
unsigned char speed[4]
Definition sid_file.c:70
unsigned char magicID[4]
Definition sid_file.c:54
unsigned char flags[2]
Definition sid_file.c:76
unsigned char version[2]
Definition sid_file.c:56
unsigned char playAddress[2]
Definition sid_file.c:64
unsigned char secondSIDAddress
Definition sid_file.c:82
unsigned char startPage
Definition sid_file.c:78
unsigned char initAddress[2]
Definition sid_file.c:62
unsigned char pageLength
Definition sid_file.c:80
unsigned char released[32]
Definition sid_file.c:74
int initAddress
Definition sid_file.h:37
int loadAddress
Definition sid_file.h:36
int size
Definition sid_file.h:42
int startSong
Definition sid_file.h:40
int playAddress
Definition sid_file.h:38
unsigned char * data
Definition sid_file.h:41
int songs
Definition sid_file.h:39
void * malloc(YYSIZE_T)
void free(void *)