File Coverage

blib/lib/Audio/SID.pm
Criterion Covered Total %
statement 18 639 2.8
branch 0 394 0.0
condition 0 222 0.0
subroutine 6 42 14.2
pod 36 36 100.0
total 60 1333 4.5


line stmt bran cond sub pod time code
1             package Audio::SID;
2              
3             require 5;
4              
5 1     1   4918 use Carp;
  1         2  
  1         46  
6 1     1   5 use strict;
  1         2  
  1         20  
7 1     1   4 use vars qw($VERSION);
  1         4  
  1         34  
8 1     1   351 use FileHandle;
  1         9385  
  1         7  
9 1     1   362 use Digest::MD5;
  1         2  
  1         47  
10 1     1   673 use Encode;
  1         10740  
  1         7335  
11              
12             $VERSION = "4.00";
13              
14             # These are the recognized field names for a SID file. They must appear in
15             # the order they appear in a SID file.
16             # 'flags', 'startPage' and 'pageLength' is valid for v2 only.
17             # 'secondSIDAddress' is valid for v3+ only, 'thirdSIDAddress' is valid for
18             # v4+ only. In addition, these last two fields are replaced with 'reserved' for
19             # v2 only.
20             my (@SIDfieldNames) = qw(magicID version dataOffset loadAddress initAddress
21             playAddress songs startSong speed title author
22             released flags startPage pageLength secondSIDAddress
23             thirdSIDAddress reserved data);
24              
25             # Additional data stored in the class that are not part of the SID file
26             # format are: FILESIZE, FILENAME, and the implicit REAL_LOAD_ADDRESS.
27             #
28             # PADDING is used to hold any extra bytes that may be between the standard
29             # SID header and the data (usually happens when dataOffset is more than
30             # 0x007C).
31              
32             # Constants for individual fields inside 'flags'.
33             my $MUSPLAYER_OFFSET = 0; # Bit 0.
34             my $PLAYSID_OFFSET = 1; # Bit 1. (PSID v2NG only)
35             my $C64BASIC_OFFSET = 1; # Bit 1. (RSID only)
36             my $CLOCK_OFFSET = 2; # Bits 2-3.
37             my $SIDMODEL_OFFSET = 4; # Bits 4-5.
38             my $SECOND_SIDMODEL_OFFSET = 6; # Bits 6-7.
39             my $THIRD_SIDMODEL_OFFSET = 8; # Bits 8-9.
40              
41             sub new {
42 0     0 1   my $type = shift;
43 0           my %params = @_;
44 0   0       my $class = ref($type) || $type;
45 0           my $self = {};
46              
47 0           bless ($self, $class);
48              
49 0           $self->initialize();
50              
51 0           $self->{validateWrite} = 0;
52              
53 0 0         if (defined($_[0])) {
54             # Read errors are taken care of by read().
55 0 0         return ($self->read(%params) ? $self : undef);
56             }
57              
58 0           return $self;
59             }
60              
61             sub initialize() {
62 0     0 1   my ($self) = $_[0];
63              
64             # Initial SID data.
65             $self->{SIDdata} = {
66 0           magicID => 'RSID',
67             version => 4,
68             dataOffset => 0x7C,
69             loadAddress => 0,
70             initAddress => 0,
71             playAddress => 0,
72             songs => 1,
73             startSong => 1,
74             speed => 0,
75             title => '',
76             author => '',
77             released => '20?? ',
78             flags => 0,
79             startPage => 0,
80             pageLength => 0,
81             secondSIDAddress => 0,
82             thirdSIDAddress => 0,
83             reserved => undef,
84             data => '',
85             };
86              
87 0           $self->{PADDING} = '';
88              
89 0           $self->{FILESIZE} = 0x7C;
90 0           $self->{FILENAME} = '';
91             }
92              
93             sub read {
94 0     0 1   my $self = shift;
95 0           my $filename;
96             my $filedata;
97 0           my $hdr;
98 0           my $i;
99 0           my ($size, $totsize);
100 0           my $data;
101 0           my $FH;
102 0           my ($SID, $version, $dataOffset);
103 0           my @hdr;
104 0           my $hdrlength;
105              
106             # Check parameters.
107              
108 0 0 0       if (($_[0] =~ /^\-filedata$/i) and defined($_[1])) {
    0 0        
    0 0        
    0          
109 0           $filedata = $_[1];
110             }
111             elsif (($_[0] =~ /^\-file(name)|(handle)$/i) and defined($_[1])) {
112 0           $filename = $_[1];
113             }
114             elsif (defined($_[0]) and !defined($_[1])) {
115 0           $filename = $_[0];
116             }
117             elsif (defined($_[0])) {
118 0           confess("Unknown parameter '$_[0]'!");
119 0           $self->initialize();
120 0           return undef;
121             }
122              
123 0 0         unless (defined($filedata)) {
124             # Either a scalar filename (or nothing) was passed in, in which case
125             # we'll open it, or a filehandle was passed in, in which case we just
126             # skip the following step.
127              
128 0 0         if (ref(\$filename) ne "GLOB") {
129              
130 0 0         $filename = $self->{FILENAME} unless (defined($filename));
131              
132 0 0         unless ($filename) {
133 0           confess("No filename was specified");
134 0           $self->initialize();
135 0           return undef;
136             }
137              
138 0 0         unless ($FH = new FileHandle ("< $filename")) {
139 0           confess("Error opening $filename");
140 0           $self->initialize();
141 0           return undef;
142             }
143             }
144             else {
145 0           $FH = $filename;
146             }
147              
148             # Just to make sure...
149 0           binmode $FH;
150 0           seek($FH,0,0);
151              
152 0           $size = read ($FH, $hdr, 8);
153             }
154             else {
155 0           $hdr = substr($filedata, 0, 8);
156 0           $size = length($hdr);
157             }
158              
159 0 0         unless ($size) {
160             # confess("Error reading $filename");
161 0           $self->initialize();
162 0           return undef;
163             }
164              
165 0           $totsize += $size;
166              
167 0           ($SID, $version, $dataOffset) = unpack ("A4nn", $hdr);
168              
169 0 0 0       unless ( (($SID eq 'PSID') and (($version >= 1) and ($version <= 4))) or
      0        
      0        
      0        
      0        
170             (($SID eq 'RSID') and ($version >= 2) and ($version <=4)) ) {
171             # Not a valid SID file recognized by this class.
172             # confess("File $filename is not a valid SID file");
173 0           $self->initialize();
174 0           return undef;
175             }
176              
177             # Valid SID file.
178              
179 0           $self->{SIDdata}{magicID} = $SID;
180 0           $self->{SIDdata}{version} = $version;
181 0           $self->{SIDdata}{dataOffset} = $dataOffset;
182              
183             # Slurp up the rest of the header.
184 0 0         unless (defined($filedata)) {
185 0           $size = read ($FH, $hdr, $dataOffset-8);
186             }
187             else {
188 0           $hdr = substr($filedata, 8, $dataOffset-8);
189 0           $size = length($hdr);
190             }
191              
192             # If the header is not as big as indicated by the dataOffset,
193             # we have a problem.
194 0 0         if ($size != ($dataOffset-8)) {
195             # confess("Error reading $filename - incorrect header");
196 0           $self->initialize();
197 0           return undef;
198             }
199              
200 0           $totsize += $size;
201              
202 0           $hdrlength = 2*5+4+32*3;
203 0           (@hdr) = unpack ("nnnnnNA32A32A32", substr($hdr,0,$hdrlength));
204              
205 0 0         if ($version == 1) {
206             # SID v1 doesn't have these fields.
207 0           $self->{SIDdata}{flags} = undef;
208 0           $self->{SIDdata}{startPage} = undef;
209 0           $self->{SIDdata}{pageLength} = undef;
210 0           $self->{SIDdata}{secondSIDAddress} = undef;
211 0           $self->{SIDdata}{thirdSIDAddress} = undef;
212 0           $self->{SIDdata}{reserved} = undef;
213             }
214            
215 0 0         if ($version >= 2) {
216             # SID v2+ has 3 more fields.
217              
218 0           my @temphdr;
219 0           (@temphdr) = unpack ("nCC", substr($hdr,$hdrlength,2+1+1));
220 0           push (@hdr, @temphdr);
221 0           $hdrlength += 2+1+1;
222            
223 0 0         if ($version == 2) {
224             # SID v2 doesn't have these fields.
225 0           $self->{SIDdata}{secondSIDAddress} = undef;
226 0           $self->{SIDdata}{thirdSIDAddress} = undef;
227            
228             # But it has this field (for backwards compatibility).
229 0           $self->{SIDdata}{reserved} = unpack("n",substr($hdr,$hdrlength,1));
230 0           $hdrlength += 2;
231             }
232             }
233            
234 0 0         if ($version >= 3) {
235             # SID v3+ has 1 more field.
236 0           my @temphdr;
237 0           (@temphdr) = unpack ("C", substr($hdr,$hdrlength,1));
238 0           push (@hdr, @temphdr);
239 0           $hdrlength += 1;
240              
241 0 0         if ($version == 3) {
242             # SID v3 doesn't have these fields.
243 0           $self->{SIDdata}{thirdSIDAddress} = undef;
244              
245             # But it has this field.
246 0           $self->{SIDdata}{reserved} = unpack("C",substr($hdr,$hdrlength,1));
247 0           $hdrlength += 1;
248             }
249             }
250              
251 0 0         if ($version >= 4) {
252             # SID v4+ has 1 more field.
253 0           my @temphdr;
254 0           (@temphdr) = unpack ("C", substr($hdr,$hdrlength,1));
255 0           push (@hdr, @temphdr);
256 0           $hdrlength += 1;
257              
258 0 0         if ($version == 4) {
259             # SID v4 doesn't have these fields.
260 0           $self->{SIDdata}{reserved} = undef;
261             }
262             }
263            
264             # Store header info.
265 0           for ($i=0; $i <= $#hdr; $i++) {
266 0           $self->{SIDdata}{$SIDfieldNames[$i+3]} = $hdr[$i];
267             }
268              
269             # Put the rest into PADDING. This might put nothing in it!
270 0           $self->{PADDING} = substr($hdr,$hdrlength);
271              
272             # Read the C64 data - can't be more than 64KB + 2 bytes load address.
273 0 0         unless (defined($filedata)) {
274 0           $size = read ($FH, $data, 65536+2);
275             }
276             else {
277 0           $data = substr($filedata, $dataOffset);
278 0           $size = length($data);
279             }
280              
281             # We allow a 0 length data.
282 0 0         unless (defined($size)) {
283             # confess("Error reading $filename");
284 0           $self->initialize();
285 0           return undef;
286             }
287              
288 0           $totsize += $size;
289              
290 0 0 0       if ((ref(\$filename) ne "GLOB") and !defined($filedata)) {
291 0           $FH->close();
292 0           $self->{FILENAME} = $filename;
293             }
294              
295 0           $self->{SIDdata}{data} = $data;
296              
297 0           $self->{FILESIZE} = $totsize;
298              
299 0           return 1;
300             }
301              
302             sub write {
303 0     0 1   my $self = shift;
304 0           my $filename;
305             my $output;
306 0           my @hdr;
307 0           my $i;
308 0           my $FH;
309              
310             # Check parameters.
311              
312 0 0 0       if (($_[0] =~ /^\-file(name)|(handle)$/i) and defined($_[1])) {
    0 0        
    0          
313 0           $filename = $_[1];
314             }
315             elsif (defined($_[0]) and !defined($_[1])) {
316 0           $filename = $_[0];
317             }
318             elsif (defined($_[0])) {
319 0           confess("Unknown parameter '$_[0]'!");
320 0           $self->initialize();
321 0           return undef;
322             }
323              
324             # Either a scalar filename (or nothing) was passed in, in which case
325             # we'll open it, or a filehandle was passed in, in which case we just
326             # skip the following step.
327              
328 0 0         if (ref(\$filename) ne "GLOB") {
329 0 0         $filename = $self->{FILENAME} unless (defined($filename));
330              
331 0 0         unless ($filename) {
332 0           confess("No filename was specified");
333 0           return undef;
334             }
335              
336 0 0         unless ($FH = new FileHandle ("> $filename")) {
337 0           confess("Couldn't write $filename");
338 0           return undef;
339             }
340             }
341             else {
342 0           $FH = $filename;
343             }
344              
345             # Just to make sure...
346 0           binmode $FH;
347 0           seek($FH,0,0);
348              
349 0 0         if ($self->{validateWrite}) {
350 0           $self->validate();
351             }
352              
353             # SID files use ISO 8859-1 encoding for textual fields, not Unicode.
354 0           foreach (qw/title author released/) {
355 0           $self->{SIDdata}{$_} = encode("latin1", $self->{SIDdata}{$_});
356             }
357              
358 0           for ($i=0; $i <= 11; $i++) {
359 0           $hdr[$i] = $self->{SIDdata}{$SIDfieldNames[$i]};
360             }
361              
362 0           $output = pack ("A4nnnnnnnNA32A32A32", @hdr);
363              
364 0           print $FH $output;
365              
366             # SID version 2+ has 3 more fields.
367 0 0         if ($self->{SIDdata}{version} > 1) {
368 0           $output = pack ("nCC", ($self->{SIDdata}{flags}, $self->{SIDdata}{startPage}, $self->{SIDdata}{pageLength}));
369 0           print $FH $output;
370             }
371            
372 0 0         if ($self->{SIDdata}{version} == 2) {
    0          
    0          
373             # SID version 2 has the 'reserved' field.
374 0           $output = pack ("n", ($self->{SIDdata}{reserved}));
375 0           print $FH $output;
376             }
377             elsif ($self->{SIDdata}{version} == 3) {
378             # SID version 3 has one more field.
379 0           $output = pack ("CC", ($self->{SIDdata}{secondSIDAddress}, $self->{SIDdata}{reserved}));
380 0           print $FH $output;
381             }
382             elsif ($self->{SIDdata}{version} == 4) {
383             # SID version 4 has two more fields.
384 0           $output = pack ("CC", ($self->{SIDdata}{secondSIDAddress}, $self->{SIDdata}{thirdSIDAddress}));
385 0           print $FH $output;
386             }
387              
388 0           print $FH $self->{PADDING};
389              
390 0           print $FH $self->{SIDdata}{data};
391              
392 0 0         if (ref(\$filename) ne "GLOB") {
393 0           $FH->close();
394             }
395             }
396              
397             # Notice that if no specific fieldname is given and we are in array/hash
398             # context, all fields are returned!
399             sub get {
400 0     0 1   my ($self, $fieldname) = @_;
401 0           my %SIDhash;
402             my $field;
403              
404 0           foreach $field (keys %{$self->{SIDdata}}) {
  0            
405 0           $SIDhash{$field} = $self->{SIDdata}{$field};
406             }
407              
408             # Strip off trailing NULLs.
409 0           $SIDhash{title} =~ s/\x00*$//;
410 0           $SIDhash{author} =~ s/\x00*$//;
411 0           $SIDhash{released} =~ s/\x00*$//;
412              
413 0 0         return unless (defined(wantarray()));
414              
415 0 0         unless (defined($fieldname)) {
416             # No specific fieldname is given. Assume user wants a hash of
417             # field values.
418 0 0         if (wantarray()) {
419 0           return %SIDhash;
420             }
421             else {
422 0           confess ("Nothing to get, not in array context");
423 0           return undef;
424             }
425             }
426              
427             # Backwards compatibility.
428 0 0         $fieldname = "released" if ($fieldname =~ /^copyright$/);
429 0 0         $fieldname = "title" if ($fieldname =~ /^name$/);
430              
431 0 0         unless (grep(/^$fieldname$/, @SIDfieldNames)) {
432 0           confess ("No such fieldname: $fieldname");
433 0           return undef;
434             }
435              
436 0           return $SIDhash{$fieldname};
437             }
438              
439             sub getFileName {
440 0     0 1   my $self = shift;
441              
442 0           return $self->{FILENAME};
443             }
444              
445             sub getFileSize {
446 0     0 1   my $self = shift;
447              
448 0           return $self->{FILESIZE};
449             }
450              
451             sub getRealLoadAddress {
452 0     0 1   my $self = shift;
453 0           my $REAL_LOAD_ADDRESS;
454              
455             # It's a read-only "implicit" field, so we just calculate it
456             # on the fly.
457 0 0 0       if ($self->{SIDdata}{data} and $self->{SIDdata}{loadAddress} == 0) {
458 0           $REAL_LOAD_ADDRESS = unpack("v", substr($self->{SIDdata}{data}, 0, 2));
459             }
460             else {
461 0           $REAL_LOAD_ADDRESS = $self->{SIDdata}{loadAddress};
462             }
463              
464 0           return $REAL_LOAD_ADDRESS;
465             }
466              
467             sub getSpeed($) {
468 0     0 1   my ($self, $songnumber) = @_;
469              
470 0 0 0       $songnumber = 1 if ((!defined($songnumber)) or ($songnumber < 1));
471              
472 0 0         if ($songnumber > $self->{SIDdata}{songs}) {
473 0           confess ("Song number '$songnumber' is invalid!");
474 0           return undef;
475             }
476              
477 0 0         if ($self->isPlaySIDSpecific()) {
478 0           $songnumber = $songnumber % 32;
479             }
480             else {
481 0 0         $songnumber = 32 if ($songnumber > 32);
482             }
483              
484 0           return (($self->{SIDdata}{speed} >> ($songnumber-1)) & 0x1);
485             }
486              
487             sub getMUSPlayer {
488 0     0 1   my $self = shift;
489              
490 0 0         return undef unless (defined($self->{SIDdata}{flags}));
491              
492 0           return (($self->{SIDdata}{flags} >> $MUSPLAYER_OFFSET) & 0x1);
493             }
494              
495             sub isMUSPlayerRequired {
496 0     0 1   my $self = shift;
497              
498 0           return $self->getMUSPlayer();
499             }
500              
501             sub getPlaySID {
502 0     0 1   my $self = shift;
503            
504             # All version 1 files are PlaySID specific.
505 0 0         return 1 if ($self->{SIDdata}{version} == 1);
506              
507             # Check the PlaySID specific flag.
508            
509 0 0         return undef unless (defined($self->{SIDdata}{flags}));
510 0 0         return undef if ($self->isRSID() );
511              
512 0           return (($self->{SIDdata}{flags} >> $PLAYSID_OFFSET) & 0x1);
513             }
514              
515             sub isPlaySIDSpecific {
516 0     0 1   my $self = shift;
517              
518 0           return $self->getPlaySID();
519             }
520              
521             sub isRSID {
522 0     0 1   my $self = shift;
523              
524 0           return ($self->{SIDdata}{magicID} eq 'RSID');
525             }
526              
527             sub getC64BASIC {
528 0     0 1   my $self = shift;
529              
530             # This is an RSID specific flag.
531 0 0         return undef unless (defined($self->{SIDdata}{flags}));
532 0 0         return undef unless ($self->isRSID() );
533              
534 0           return (($self->{SIDdata}{flags} >> $C64BASIC_OFFSET) & 0x1);
535             }
536              
537             sub isC64BASIC {
538 0     0 1   my $self = shift;
539              
540 0           return $self->getC64BASIC();
541             }
542              
543             sub getClock {
544 0     0 1   my $self = shift;
545              
546 0 0         return undef unless (defined($self->{SIDdata}{flags}));
547              
548 0           return (($self->{SIDdata}{flags} >> $CLOCK_OFFSET) & 0x3);
549             }
550              
551             sub getClockByName {
552 0     0 1   my $self = shift;
553 0           my $clock;
554              
555 0 0         return undef unless (defined($self->{SIDdata}{flags}));
556              
557 0           $clock = $self->getClock();
558              
559 0 0         if ($clock == 0) {
    0          
    0          
    0          
560 0           $clock = 'UNKNOWN';
561             }
562             elsif ($clock == 1) {
563 0           $clock = 'PAL';
564             }
565             elsif ($clock == 2) {
566 0           $clock = 'NTSC';
567             }
568             elsif ($clock == 3) {
569 0           $clock = 'EITHER';
570             }
571              
572 0           return $clock;
573             }
574              
575             sub getSIDModel {
576 0     0 1   my ($self, $sidNumber) = @_;
577              
578 0 0         return undef unless (defined($self->{SIDdata}{flags}));
579            
580 0 0 0       if (!defined($sidNumber) or ($sidNumber == 1)) {
    0          
    0          
581 0           return (($self->{SIDdata}{flags} >> $SIDMODEL_OFFSET) & 0x3);
582             }
583             elsif ($sidNumber == 2) {
584 0 0         return undef unless ($self->{SIDdata}{version} >= 3);
585              
586 0           return (($self->{SIDdata}{flags} >> $SECOND_SIDMODEL_OFFSET) & 0x3);
587             }
588             elsif ($sidNumber == 3) {
589 0 0         return undef unless ($self->{SIDdata}{version} >= 4);
590              
591 0           return (($self->{SIDdata}{flags} >> $THIRD_SIDMODEL_OFFSET) & 0x3);
592             }
593             }
594              
595             sub getSIDModelByName {
596 0     0 1   my ($self, $sidNumber) = @_;
597 0           my $SIDModel;
598              
599 0           $SIDModel = $self->getSIDModel($sidNumber);
600            
601 0 0         return undef unless (defined($SIDModel));
602              
603 0 0         if ($SIDModel == 0) {
    0          
    0          
    0          
604 0           $SIDModel = 'UNKNOWN';
605             }
606             elsif ($SIDModel == 1) {
607 0           $SIDModel = '6581';
608             }
609             elsif ($SIDModel == 2) {
610 0           $SIDModel = '8580';
611             }
612             elsif ($SIDModel == 3) {
613 0           $SIDModel = 'EITHER';
614             }
615              
616 0           return $SIDModel;
617             }
618              
619             sub getSIDAddress($) {
620 0     0 1   my ($self, $sidNumber) = @_;
621 0           my $SIDAddressMiddle;
622             my $fullSIDAddress;
623              
624 0 0 0       if (!defined($sidNumber) or ($sidNumber == 1)) {
    0          
    0          
625             # Original SID is always at $D400. This is implied, it's not contained
626             # in SID file data at all - it's returned just for completeness.
627 0           $SIDAddressMiddle = 0x40;
628             }
629             elsif ($sidNumber == 2) {
630             # Second SID address is valid for v3+ only.
631 0 0         return undef unless $self->{SIDdata}{version} >= 3;
632            
633 0           $SIDAddressMiddle = $self->{SIDdata}{secondSIDAddress};
634             }
635             elsif ($sidNumber == 3) {
636             # Third SID address is valid for v4+ only.
637 0 0         return undef unless $self->{SIDdata}{version} >= 4;
638            
639 0           $SIDAddressMiddle = $self->{SIDdata}{thirdSIDAddress};
640             }
641            
642 0           $fullSIDAddress = 0xD000 + $SIDAddressMiddle * 0x10;
643            
644 0           return $fullSIDAddress;
645             }
646              
647             # Notice that you have to pass in a hash (field-value pairs)!
648             sub set(@) {
649 0     0 1   my ($self, %SIDhash) = @_;
650 0           my $fieldname;
651             my $paddinglength;
652 0           my $i;
653 0           my $version;
654 0           my $offset;
655 0           my $changePSIDSpecific = 0;
656              
657 0           foreach $fieldname (keys %SIDhash) {
658              
659             # Backwards compatibility.
660 0 0         $fieldname = "released" if ($fieldname =~ /^copyright$/);
661 0 0         $fieldname = "title" if ($fieldname =~ /^name$/);
662              
663 0 0         unless (grep(/^$fieldname$/, @SIDfieldNames)) {
664 0           confess ("Not a supported fieldname: $fieldname");
665 0           next;
666             }
667              
668             # Do some basic sanity checking.
669              
670 0 0         if ($fieldname eq 'magicID') {
671 0 0 0       if (($SIDhash{$fieldname} ne 'PSID') and ($SIDhash{$fieldname} ne 'RSID')) {
672 0           confess ("Unrecognized magicID: $SIDhash{$fieldname}");
673 0           next;
674             }
675              
676 0 0         if ($SIDhash{$fieldname} ne $self->{SIDdata}{magicID}) {
677 0           $changePSIDSpecific = 1;
678             }
679             }
680              
681 0 0         if ($fieldname eq 'version') {
682 0 0 0       if (($SIDhash{$fieldname} < 1) or ($SIDhash{$fieldname} > 4)) {
683 0           confess ("Invalid SID version number '$SIDhash{$fieldname}' - ignored");
684 0           next;
685             }
686             }
687              
688 0 0 0       if (($self->{SIDdata}{version} < 2) and
      0        
689             (($fieldname eq 'magicID') or ($fieldname eq 'flags') or ($fieldname eq 'reserved') or
690             ($fieldname eq 'startPage') or ($fieldname eq 'pageLength'))) {
691              
692 0           confess ("Can't change '$fieldname' when SID version is set to 1");
693 0           next;
694             }
695              
696 0 0 0       if (($self->{SIDdata}{version} < 3) and
      0        
697             (($fieldname eq 'secondSIDAddress') or ($fieldname eq 'thirdSIDAddress'))) {
698              
699 0           confess ("Can't change '$fieldname' when SID version is less than 3");
700 0           next;
701             }
702              
703 0 0 0       if (($self->{SIDdata}{version} < 4) and
704             ($fieldname eq 'thirdSIDAddress')) {
705              
706 0           confess ("Can't change '$fieldname' when SID version is less than 4");
707 0           next;
708             }
709              
710             # SID files use ISO 8859-1 encoding for textual fields, not Unicode.
711 0 0 0       if (($fieldname eq 'title') or ($fieldname eq 'author') or ($fieldname eq 'released')) {
      0        
712 0           $SIDhash{$fieldname} = encode("latin1", $SIDhash{$fieldname});
713             }
714              
715 0           $self->{SIDdata}{$fieldname} = $SIDhash{$fieldname};
716             }
717              
718 0 0 0       if ($self->{SIDdata}{version} == 1) {
    0          
719             # PSID v1 values are set in stone.
720 0           $self->{SIDdata}{magicID} = 'PSID';
721 0           $self->{SIDdata}{version} = 1;
722 0           $self->{SIDdata}{dataOffset} = 0x76;
723 0           $self->{SIDdata}{flags} = undef;
724 0           $self->{SIDdata}{startPage} = undef;
725 0           $self->{SIDdata}{pageLength} = undef;
726 0           $self->{SIDdata}{reserved} = undef;
727 0           $self->{PADDING} = '';
728             }
729             elsif (($self->{SIDdata}{version} >= 2) and ($self->{SIDdata}{version} <= 4)) {
730             # In PSID v2NG/RSID we allow dataOffset to be larger than 0x7C.
731              
732 0           $self->{PADDING} = '';
733              
734 0 0         if ($self->{SIDdata}{dataOffset} <= 0x7C) {
735 0           $self->{SIDdata}{dataOffset} = 0x7C;
736             }
737             else {
738 0           $paddinglength = $self->{SIDdata}{dataOffset} - 0x7C;
739              
740             # Add as many zeroes as necessary.
741 0           for ($i=1; $i <= $paddinglength; $i++) {
742 0           $self->{PADDING} .= pack("C", 0x00);
743             }
744             }
745              
746             # Make sure these are not undef'd.
747 0 0         unless (defined($self->{SIDdata}{flags})) {
748 0           $self->{SIDdata}{flags} = 0;
749             }
750              
751 0 0         unless (defined($self->{SIDdata}{startPage})) {
752 0           $self->{SIDdata}{startPage} = 0;
753             }
754              
755 0 0         unless (defined($self->{SIDdata}{pageLength})) {
756 0           $self->{SIDdata}{pageLength} = 0;
757             }
758              
759 0 0 0       if (($self->{SIDdata}{version} == 2) or ($self->{SIDdata}{version} == 3)) {
760 0 0         unless (defined($self->{SIDdata}{reserved})) {
761 0           $self->{SIDdata}{reserved} = 0;
762             }
763             }
764              
765 0 0         if ($self->{SIDdata}{version} >= 3) {
766 0 0         unless (defined($self->{SIDdata}{secondSIDAddress})) {
767 0           $self->{SIDdata}{secondSIDAddress} = 0;
768             }
769 0           $self->{SIDdata}{thirdSIDAddress} = undef;
770             }
771              
772 0 0         if ($self->{SIDdata}{version} >= 4) {
773 0 0         unless (defined($self->{SIDdata}{thirdSIDAddress})) {
774 0           $self->{SIDdata}{thirdSIDAddress} = 0;
775             }
776 0           $self->{SIDdata}{reserved} = undef;
777             }
778              
779 0 0         if ($changePSIDSpecific) {
780             # Zero this flag only if 'flags' is not explicitly set at the same time.
781 0 0         if (!$SIDhash{'flags'}) {
782 0 0         if ($self->isRSID() ) {
783 0           $self->setC64BASIC(0);
784             }
785             else {
786 0           $self->setPlaySID(0);
787             }
788             }
789             }
790              
791             # RSID values are set in stone.
792 0 0         if ($self->isRSID() ) {
793 0           $self->{SIDdata}{playAddress} = 0;
794 0           $self->{SIDdata}{speed} = 0;
795              
796             # The preferred way is for loadAddress to be 0. The data is
797             # prepended by those 2 bytes if it needs to be changed.
798              
799 0 0         if ($self->{SIDdata}{loadAddress} != 0) {
800 0           $self->{SIDdata}{data} = pack("v", $self->{SIDdata}{loadAddress}) . $self->{SIDdata}{data};
801 0           $self->{SIDdata}{loadAddress} = 0;
802             }
803              
804             # initAddress must be 0 if the C64 BASIC flag is set.
805 0 0         if ($self->getC64BASIC() ) {
806 0           $self->{SIDdata}{initAddress} = 0;
807             }
808             }
809             }
810              
811             $self->{FILESIZE} = $self->{SIDdata}{dataOffset} + length($self->{PADDING}) +
812 0           length($self->{SIDdata}{data});
813              
814 0           return 1;
815             }
816              
817             sub setFileName($) {
818 0     0 1   my ($self, $filename) = @_;
819              
820 0           $self->{FILENAME} = $filename;
821             }
822              
823             sub setSpeed($$) {
824 0     0 1   my ($self, $songnumber, $value) = @_;
825              
826 0 0         unless (defined($songnumber)) {
827 0           confess ("No song number was specified!");
828 0           return undef;
829             }
830              
831 0 0         unless (defined($value)) {
832 0           confess ("No speed value was specified!");
833 0           return undef;
834             }
835              
836 0 0 0       if (($songnumber > $self->{SIDdata}{songs}) or ($songnumber < 1)) {
837 0           confess ("Song number '$songnumber' is invalid!");
838 0           return undef;
839             }
840              
841 0 0 0       if (($value ne 0) and ($value ne 1)) {
842 0           confess ("Specified value '$value' is invalid!");
843 0           return undef;
844             }
845              
846 0 0         $songnumber = 1 if ($songnumber < 1);
847              
848 0 0 0       if (($self->{SIDdata}{version} == 1) or $self->isPlaySIDSpecific()) {
849 0           $songnumber = $songnumber % 32;
850             }
851             else {
852 0 0         $songnumber = 32 if ($songnumber > 32);
853             }
854              
855             # First, clear the bit in question.
856 0           $self->{SIDdata}{speed} &= ~(0x1 << ($songnumber-1));
857              
858             # Then set it.
859 0           $self->{SIDdata}{speed} |= ($value << ($songnumber-1));
860             }
861              
862             sub setMUSPlayer($) {
863 0     0 1   my ($self, $MUSplayer) = @_;
864              
865 0 0         unless (defined($self->{SIDdata}{flags})) {
866 0           confess ("Cannot set this field when SID version is 1!");
867 0           return undef;
868             }
869              
870 0 0 0       if (($MUSplayer ne 0) and ($MUSplayer ne 1)) {
871 0           confess ("Specified value '$MUSplayer' is invalid!");
872 0           return undef;
873             }
874              
875             # First, clear the bit in question.
876 0           $self->{SIDdata}{flags} &= ~(0x1 << $MUSPLAYER_OFFSET);
877              
878             # Then set it.
879 0           $self->{SIDdata}{flags} |= ($MUSplayer << $MUSPLAYER_OFFSET);
880             }
881              
882             sub setPlaySID($) {
883 0     0 1   my ($self, $PlaySID) = @_;
884              
885 0 0         if ($self->isRSID() ) {
886 0           confess ("Cannot set this field for RSID!");
887 0           return undef;
888             }
889              
890 0 0         unless (defined($self->{SIDdata}{flags})) {
891 0           confess ("Cannot set this field when SID version is 1!");
892 0           return undef;
893             }
894              
895 0 0 0       if (($PlaySID ne 0) and ($PlaySID ne 1)) {
896 0           confess ("Specified value '$PlaySID' is invalid!");
897 0           return undef;
898             }
899              
900             # First, clear the bit in question.
901 0           $self->{SIDdata}{flags} &= ~(0x1 << $PLAYSID_OFFSET);
902              
903             # Then set it.
904 0           $self->{SIDdata}{flags} |= ($PlaySID << $PLAYSID_OFFSET);
905             }
906              
907             sub setC64BASIC($) {
908 0     0 1   my ($self, $C64BASIC) = @_;
909              
910 0 0         unless ($self->isRSID() ) {
911 0           confess ("Cannot set this field for PSID!");
912 0           return undef;
913             }
914              
915 0 0         unless (defined($self->{SIDdata}{flags})) {
916 0           confess ("Cannot set this field when SID version is 1!");
917 0           return undef;
918             }
919              
920 0 0 0       if (($C64BASIC ne 0) and ($C64BASIC ne 1)) {
921 0           confess ("Specified value '$C64BASIC' is invalid!");
922 0           return undef;
923             }
924              
925             # First, clear the bit in question.
926 0           $self->{SIDdata}{flags} &= ~(0x1 << $C64BASIC_OFFSET);
927              
928             # Then set it.
929 0           $self->{SIDdata}{flags} |= ($C64BASIC << $C64BASIC_OFFSET);
930              
931 0 0         if ($C64BASIC) {
932 0           $self->{SIDdata}{initAddress} = 0;
933             }
934             }
935              
936             sub setClock($) {
937 0     0 1   my ($self, $clock) = @_;
938              
939 0 0         unless (defined($self->{SIDdata}{flags})) {
940 0           confess ("Cannot set this field when SID version is 1!");
941 0           return undef;
942             }
943              
944 0 0 0       if (($clock < 0) or ($clock > 3)) {
945 0           confess ("Specified value '$clock' is invalid!");
946 0           return undef;
947             }
948              
949             # First, clear the bits in question.
950 0           $self->{SIDdata}{flags} &= ~(0x3 << $CLOCK_OFFSET);
951              
952             # Then set them.
953 0           $self->{SIDdata}{flags} |= ($clock << $CLOCK_OFFSET);
954             }
955              
956             sub setClockByName($) {
957 0     0 1   my ($self, $clock) = @_;
958              
959 0 0         unless (defined($self->{SIDdata}{flags})) {
960 0           confess ("Cannot set this field when SID version is 1!");
961 0           return undef;
962             }
963              
964 0 0         if ($clock =~ /^(unknown|none|neither)$/i) {
    0          
    0          
    0          
965 0           $clock = 0;
966             }
967             elsif ($clock =~ /^PAL$/i) {
968 0           $clock = 1;
969             }
970             elsif ($clock =~ /^NTSC$/i) {
971 0           $clock = 2;
972             }
973             elsif ($clock =~ /^(any|both|either)$/i) {
974 0           $clock = 3;
975             }
976             else {
977 0           confess ("Specified value '$clock' is invalid!");
978 0           return undef;
979             }
980              
981 0           $self->setClock($clock);
982             }
983              
984             sub setSIDModel($) {
985 0     0 1   my ($self, $SIDModel, $sidNumber) = @_;
986              
987 0 0         unless (defined($self->{SIDdata}{flags})) {
988 0           confess ("Cannot set this field when SID version is 1!");
989 0           return undef;
990             }
991              
992 0 0 0       if (($SIDModel < 0) or ($SIDModel > 3)) {
993 0           confess ("Specified value '$SIDModel' is invalid!");
994 0           return undef;
995             }
996              
997 0 0 0       if (!defined($sidNumber) or ($sidNumber == 1)) {
    0          
    0          
998             # First, clear the bits in question.
999 0           $self->{SIDdata}{flags} &= ~(0x3 << $SIDMODEL_OFFSET);
1000              
1001             # Then set them.
1002 0           $self->{SIDdata}{flags} |= ($SIDModel << $SIDMODEL_OFFSET);
1003             }
1004             elsif ($sidNumber == 2) {
1005            
1006 0 0         return undef unless ($self->{SIDdata}{version} >= 3);
1007            
1008             # First, clear the bits in question.
1009 0           $self->{SIDdata}{flags} &= ~(0x3 << $SECOND_SIDMODEL_OFFSET);
1010              
1011             # Then set them.
1012 0           $self->{SIDdata}{flags} |= ($SIDModel << $SECOND_SIDMODEL_OFFSET);
1013             }
1014             elsif ($sidNumber == 3) {
1015 0 0         return undef unless ($self->{SIDdata}{version} >= 4);
1016              
1017             # First, clear the bits in question.
1018 0           $self->{SIDdata}{flags} &= ~(0x3 << $THIRD_SIDMODEL_OFFSET);
1019              
1020             # Then set them.
1021 0           $self->{SIDdata}{flags} |= ($SIDModel << $THIRD_SIDMODEL_OFFSET);
1022             }
1023             }
1024              
1025             sub setSIDModelByName($) {
1026 0     0 1   my ($self, $SIDModel, $sidNumber) = @_;
1027              
1028 0 0         unless (defined($self->{SIDdata}{flags})) {
1029 0           confess ("Cannot set this field when SID version is 1!");
1030 0           return undef;
1031             }
1032              
1033 0 0 0       if ($SIDModel =~ /^(unknown|none|neither)$/i) {
    0 0        
    0          
    0          
1034 0           $SIDModel = 0;
1035             }
1036             elsif (($SIDModel =~ /^6581$/) or ($SIDModel == 6581)) {
1037 0           $SIDModel = 1;
1038             }
1039             elsif (($SIDModel =~ /^8580$/i) or ($SIDModel == 8580)) {
1040 0           $SIDModel = 2;
1041             }
1042             elsif ($SIDModel =~ /^(any|both|either)$/i) {
1043 0           $SIDModel = 3;
1044             }
1045             else {
1046 0           confess ("Specified value '$SIDModel' is invalid!");
1047 0           return undef;
1048             }
1049              
1050 0           return $self->setSIDModel($SIDModel, $sidNumber);
1051             }
1052              
1053             sub setSIDAddress($) {
1054 0     0 1   my ($self, $sidNumber, $fullSIDAddress) = @_;
1055            
1056 0 0 0       if (!defined($sidNumber) or ($sidNumber < 2) or ($sidNumber > 3)) {
      0        
1057 0           confess("Invalid SID number: '$sidNumber'!");
1058 0           return undef;
1059             }
1060            
1061 0 0         if (!defined($fullSIDAddress)) {
1062 0           confess("SID address was not specified!");
1063 0           return undef;
1064             }
1065            
1066 0 0 0       if ( (($sidNumber == 2) and ($self->{SIDdata}{version} < 3)) or
      0        
      0        
1067             (($sidNumber == 3) and ($self->{SIDdata}{version} < 4))
1068             ) {
1069 0           confess("SID address for SID number '$sidNumber' is not allowed to be set when SID version is $self->{SIDdata}{version} !");
1070 0           return undef;
1071             }
1072              
1073 0 0         if ($fullSIDAddress =~ /^\s*\$/) {
    0          
1074             # Convert C64-style hex string to hex number.
1075 0           $fullSIDAddress =~ s/^\s*\$//;
1076 0           $fullSIDAddress = hex($fullSIDAddress);
1077             }
1078             elsif ($fullSIDAddress =~ /^\s*0x/) {
1079             # Convert hex string to hex number.
1080 0           $fullSIDAddress = hex($fullSIDAddress);
1081             }
1082              
1083 0 0 0       if (($fullSIDAddress < 0xD420) or ($fullSIDAddress > 0xE000) or
      0        
      0        
1084             (($fullSIDAddress >= 0xD800) and ($fullSIDAddress < 0xDE00))) {
1085 0           confess(sprintf("SID address of '\$%04X' for SID number '%d' is not in the allowed range!", $fullSIDAddress, $sidNumber));
1086 0           return undef;
1087             }
1088            
1089 0           my $middleSIDAddress = ($fullSIDAddress & 0x0FF0) >> 4;
1090            
1091 0 0         if (($middleSIDAddress % 2) != 0) {
1092 0           confess(sprintf("The middle 2 digits of the SID address of '\$%04X' for SID number '%d' must be even!", $fullSIDAddress, $sidNumber));
1093 0           return undef;
1094             }
1095            
1096 0 0         if ($self->{SIDdata}{version} >= 4) {
1097 0 0 0       if (($sidNumber == 2) and ($self->{SIDdata}{thirdSIDAddress} == $middleSIDAddress)) {
1098 0           confess(sprintf("The SID address of '\$%04X' for SID number '%d' cannot be the same as for SID number 3!", $fullSIDAddress, $sidNumber));
1099 0           return undef;
1100             }
1101              
1102 0 0 0       if (($sidNumber == 3) and ($self->{SIDdata}{secondSIDAddress} == $middleSIDAddress)) {
1103 0           confess(sprintf("The SID address of '\$%04X' for SID number '%d' cannot be the same as for SID number 2!", $fullSIDAddress, $sidNumber));
1104 0           return undef;
1105             }
1106             }
1107            
1108             # If everything checks out, we can finally set the value.
1109            
1110 0 0         if ($sidNumber == 2) {
    0          
1111 0           $self->{SIDdata}{secondSIDAddress} = $middleSIDAddress;
1112             }
1113             elsif ($sidNumber == 3) {
1114 0           $self->{SIDdata}{thirdSIDAddress} = $middleSIDAddress;
1115             }
1116             }
1117              
1118             sub getFieldNames {
1119 0     0 1   my $self = shift;
1120 0           my (@SIDfields) = @SIDfieldNames;
1121              
1122 0           return (@SIDfields);
1123             }
1124              
1125             sub getMD5 {
1126 0     0 1   my ($self, $oldMD5) = @_;
1127              
1128 0           my $md5 = Digest::MD5->new;
1129              
1130 0 0 0       if (($self->{SIDdata}{loadAddress} == 0) and $self->{SIDdata}{data}) {
1131 0           $md5->add(substr($self->{SIDdata}{data},2));
1132             }
1133             else {
1134 0           $md5->add($self->{SIDdata}{data});
1135             }
1136              
1137 0           $md5->add(pack("v", $self->{SIDdata}{initAddress}));
1138 0           $md5->add(pack("v", $self->{SIDdata}{playAddress}));
1139              
1140 0           my $songs = $self->{SIDdata}{songs};
1141 0           $md5->add(pack("v", $songs));
1142              
1143 0           my $speed = $self->{SIDdata}{speed};
1144              
1145 0           for (my $songNo = 1; $songNo <= $songs; $songNo++) {
1146 0           my $speedFlag;
1147 0 0         if ($self->isRSID()) {
1148 0           $speedFlag = 60;
1149             }
1150             else {
1151 0 0         if ($self->getSpeed($songNo) == 1) {
1152 0           $speedFlag = 60;
1153             }
1154             else {
1155 0           $speedFlag = 0;
1156             }
1157             }
1158              
1159 0           $md5->add(pack("C",$speedFlag));
1160             }
1161              
1162 0           my $clock = $self->getClock();
1163              
1164 0 0 0       if (($self->{SIDdata}{version} > 1) and ($clock == 2) and !$oldMD5) {
      0        
1165 0           $md5->add(pack("C",$clock));
1166             }
1167              
1168 0           return ($md5->hexdigest);
1169             }
1170              
1171             sub alwaysValidateWrite($) {
1172 0     0 1   my ($self, $setting) = @_;
1173              
1174 0           $self->{validateWrite} = $setting;
1175             }
1176              
1177             sub validate {
1178 0     0 1   my $self = shift;
1179 0           my $field;
1180             my $MUSPlayer;
1181 0           my $PlaySID;
1182 0           my $C64BASIC;
1183 0           my $clock;
1184 0           my $SIDModel;
1185 0           my $secondSIDModel;
1186 0           my $thirdSIDModel;
1187              
1188             # Change to version v2.
1189 0 0         if ($self->{SIDdata}{version} < 2) {
1190             # carp ("Changing SID to v2");
1191 0           $self->{SIDdata}{version} = 2;
1192             }
1193              
1194 0 0         if ($self->isRSID() ) {
1195 0           $self->{SIDdata}{playAddress} = 0;
1196 0           $self->{SIDdata}{speed} = 0;
1197             }
1198              
1199 0 0         if ($self->{SIDdata}{dataOffset} != 0x7C) {
1200 0           $self->{SIDdata}{dataOffset} = 0x7C;
1201             # carp ("'dataOffset' was not 0x007C - set to 0x007C");
1202             }
1203              
1204             # Sanity check the fields.
1205              
1206             # Textual fields can't be longer than 32 chars.
1207 0           foreach $field (qw(title author released)) {
1208              
1209             # Strip trailing whitespace.
1210 0           $self->{SIDdata}{$field} =~ s/\s+$//;
1211              
1212             # Convert to ISO 8859-1 ASCII.
1213 0           $self->{SIDdata}{$field} = encode("latin1", $self->{SIDdata}{$field});
1214              
1215             # Take off any superfluous null-padding.
1216 0           $self->{SIDdata}{$field} =~ s/\x00+$//;
1217              
1218 0 0         if (length($self->{SIDdata}{$field}) > 32) {
1219 0           $self->{SIDdata}{$field} = substr($self->{SIDdata}{$field}, 0, 32);
1220             # carp ("'$field' field was longer than 32 chars - chopped to 32");
1221             }
1222             }
1223              
1224             # If this is an RSID, initAddress shouldn't be pointing to a ROM memory
1225             # area, or be outside the load range. Also, if the C64 BASIC flag is set,
1226             # initAddress must be 0.
1227              
1228 0 0 0       if ( ($self->isRSID() ) and
      0        
1229             ( ((($self->{SIDdata}{initAddress} > 0) and ($self->{SIDdata}{initAddress} < 0x07E8)) or
1230             (($self->{SIDdata}{initAddress} >= 0xA000) and ($self->{SIDdata}{initAddress} < 0xC000)) or
1231             (($self->{SIDdata}{initAddress} >= 0xD000) and ($self->{SIDdata}{initAddress} <= 0xFFFF)) or
1232             ($self->{SIDdata}{initAddress} < $self->getRealLoadAddress()) or
1233             ($self->{SIDdata}{initAddress} > ($self->getRealLoadAddress() + length($self->{SIDdata}{data}) - 3))
1234             ) or
1235             ($self->getC64BASIC() )
1236             )
1237             ) {
1238              
1239 0           $self->{SIDdata}{initAddress} = 0;
1240              
1241             # carp ("'initAddress' was invalid - set to 0");
1242             }
1243              
1244             # The preferred way is for loadAddress to be 0. It also shouldn't be less
1245             # than 0x07E8 in RSID files. The data is prepended by those 2 bytes if it
1246             # needs to be changed.
1247              
1248 0 0         if ($self->{SIDdata}{loadAddress} != 0) {
1249              
1250             # Load address must not be less than 0x07E8 in RSID files.
1251 0 0 0       if (($self->isRSID() ) and
1252             ($self->{SIDdata}{loadAddress} < 0x07E8) ) {
1253              
1254 0           $self->{SIDdata}{loadAddress} = 0x07E8;
1255             }
1256              
1257 0           $self->{SIDdata}{data} = pack("v", $self->{SIDdata}{loadAddress}) . $self->{SIDdata}{data};
1258 0           $self->{SIDdata}{loadAddress} = 0;
1259             # carp ("'loadAddress' was non-zero - set to 0");
1260             }
1261              
1262             # If this is a PSID, initAddress shouldn't be outside the load range.
1263              
1264 0 0 0       if ( (!$self->isRSID() ) and
      0        
1265             (($self->{SIDdata}{initAddress} < $self->getRealLoadAddress()) or
1266             ($self->{SIDdata}{initAddress} > ($self->getRealLoadAddress() + length($self->{SIDdata}{data}) - 3))
1267             )
1268             ) {
1269              
1270 0           $self->{SIDdata}{initAddress} = 0;
1271              
1272             # carp ("'initAddress' was invalid - set to 0");
1273             }
1274              
1275             # These fields should better be in the 0x0000-0xFFFF range!
1276 0           foreach $field (qw(loadAddress initAddress playAddress)) {
1277 0 0 0       if (($self->{SIDdata}{$field} < 0) or ($self->{SIDdata}{$field} > 0xFFFF)) {
1278             # confess ("'$field' value of $self->{SIDdata}{$field} is out of range");
1279 0           $self->{SIDdata}{$field} = 0;
1280             }
1281             }
1282              
1283             # These fields should better be in the 0x00-0xFF range!
1284 0           foreach $field (qw(startPage pageLength)) {
1285 0 0 0       if (!defined($self->{SIDdata}{$field}) or ($self->{SIDdata}{$field} < 0) or ($self->{SIDdata}{$field} > 0xFF)) {
      0        
1286             # confess ("'$field' value of $self->{SIDdata}{$field} is out of range");
1287 0           $self->{SIDdata}{$field} = 0;
1288             }
1289             }
1290              
1291             # This field's max is 256.
1292 0 0         if ($self->{SIDdata}{songs} > 256) {
1293 0           $self->{SIDdata}{songs} = 256;
1294             # carp ("'songs' was more than 256 - set to 256");
1295             }
1296              
1297             # This field's min is 1.
1298 0 0         if ($self->{SIDdata}{songs} < 1) {
1299 0           $self->{SIDdata}{songs} = 1;
1300             # carp ("'songs' was less than 1 - set to 1");
1301             }
1302              
1303             # If an invalid startSong is specified, set it to 1.
1304 0 0         if ($self->{SIDdata}{startSong} > $self->{SIDdata}{songs}) {
1305 0           $self->{SIDdata}{startSong} = 1;
1306             # carp ("Invalid 'startSong' field - set to 1");
1307             }
1308              
1309 0 0         unless ($self->isRSID() ) {
1310             # Only the relevant fields in 'speed' will be set.
1311 0           my $tempSpeed = 0;
1312 0           my $maxSongs = $self->{SIDdata}{songs};
1313              
1314             # There are only 32 bits in speed.
1315 0 0         if ($maxSongs > 32) {
1316 0           $maxSongs = 32;
1317             }
1318              
1319 0           for (my $i=0; $i < $maxSongs; $i++) {
1320 0           $tempSpeed += ($self->{SIDdata}{speed} & (1 << $i));
1321             }
1322 0           $self->{SIDdata}{speed} = $tempSpeed;
1323             }
1324              
1325 0 0         unless (defined($self->{SIDdata}{flags})) {
1326 0           $self->{SIDdata}{flags} = 0;
1327             }
1328             else {
1329             # Only the relevant fields in 'flags' will be set.
1330 0           $MUSPlayer = $self->isMUSPlayerRequired();
1331 0           $clock = $self->getClock();
1332 0           $SIDModel = $self->getSIDModel();
1333 0           $secondSIDModel = $self->getSIDModel(2);
1334 0           $thirdSIDModel = $self->getSIDModel(3);
1335              
1336 0 0         unless ($self->isRSID() ) {
1337 0           $PlaySID = $self->isPlaySIDSpecific();
1338             }
1339             else {
1340 0           $C64BASIC = $self->isC64BASIC();
1341             }
1342              
1343 0           $self->{SIDdata}{flags} = 0;
1344              
1345 0           $self->setMUSPlayer($MUSPlayer);
1346 0           $self->setClock($clock);
1347 0           $self->setSIDModel($SIDModel);
1348            
1349 0 0         if ($self->{SIDdata}{version} >= 3) {
1350 0 0         if (defined($secondSIDModel)) {
1351 0           $self->setSIDModel($secondSIDModel, 2);
1352             }
1353             }
1354              
1355 0 0         if ($self->{SIDdata}{version} >= 4) {
1356 0 0         if (defined($thirdSIDModel)) {
1357 0           $self->setSIDModel($thirdSIDModel, 3);
1358             }
1359             }
1360              
1361 0 0         unless ($self->isRSID() ) {
1362 0           $self->setPlaySID($PlaySID);
1363             }
1364             else {
1365 0           $self->setC64BASIC($C64BASIC);
1366             }
1367             }
1368              
1369 0 0 0       if (($self->{SIDdata}{startPage} == 0) or ($self->{SIDdata}{startPage} == 0xFF)) {
    0          
    0          
1370 0           $self->{SIDdata}{pageLength} = 0;
1371             }
1372             elsif ((($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1) > 0xFFFF) {
1373 0           $self->{SIDdata}{pageLength} = 0xFF - $self->{SIDdata}{startPage};
1374             }
1375             elsif ($self->{SIDdata}{pageLength} == 0) {
1376 0           $self->{SIDdata}{pageLength} = 1;
1377             }
1378              
1379 0 0         if ($self->isRSID() ) {
1380              
1381             # Reloc info must not overlap or encompass the ROM/IO and
1382             # reserved memory areas.
1383              
1384             # Is startPage within the ROM or reserved memory areas?
1385 0 0 0       if ( (($self->{SIDdata}{startPage} >= 0xA0) and ($self->{SIDdata}{startPage} < 0xC0)) or
      0        
      0        
      0        
      0        
1386             (($self->{SIDdata}{startPage} >= 0xD0) and ($self->{SIDdata}{startPage} < 0xFF)) or
1387             (($self->{SIDdata}{startPage} > 0x00) and ($self->{SIDdata}{startPage} < 0x04)) ) {
1388              
1389 0           $self->{SIDdata}{startPage} = 0xFF;
1390 0           $self->{SIDdata}{pageLength} = 0x00;
1391             }
1392              
1393             # Is the end of the relocation range within the ROM or reserved memory areas?
1394 0 0 0       if ( (( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 >= 0xA000) and ( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 < 0xC000)) or
      0        
      0        
      0        
      0        
1395             (( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 >= 0xD000) and ( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 <= 0xFFFF)) or
1396             (( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 > 0x0000) and ( ($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 < 0x0400)) ) {
1397              
1398 0           $self->{SIDdata}{startPage} = 0xFF;
1399 0           $self->{SIDdata}{pageLength} = 0x00;
1400             }
1401              
1402             # Does the relocation range encompass a ROM area?
1403 0 0 0       if ( ($self->{SIDdata}{startPage} < 0xA0) and (($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 >= 0xC000) ) {
1404              
1405 0           $self->{SIDdata}{startPage} = 0xFF;
1406 0           $self->{SIDdata}{pageLength} = 0x00;
1407             }
1408             }
1409            
1410             # Relocation range must not overlap or encompass the load range.
1411              
1412 0 0 0       if ( (($self->{SIDdata}{startPage} << 8) >= $self->getRealLoadAddress()) and
1413             (($self->{SIDdata}{startPage} << 8) <= ($self->getRealLoadAddress() + length($self->{SIDdata}{data}) - 3)
1414             ) ) {
1415              
1416 0           $self->{SIDdata}{startPage} = 0xFF;
1417 0           $self->{SIDdata}{pageLength} = 0x00;
1418             }
1419              
1420 0 0 0       if ( (($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 >= $self->getRealLoadAddress()) and
1421             (($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 <= ($self->getRealLoadAddress() + length($self->{SIDdata}{data}) - 3))
1422             ) {
1423              
1424 0           $self->{SIDdata}{startPage} = 0xFF;
1425 0           $self->{SIDdata}{pageLength} = 0x00;
1426             }
1427              
1428 0 0 0       if ( (($self->{SIDdata}{startPage} << 8) < $self->getRealLoadAddress()) and
1429             (($self->{SIDdata}{startPage} << 8) + ($self->{SIDdata}{pageLength} << 8) - 1 > ($self->getRealLoadAddress() + length($self->{SIDdata}{data}) - 3))
1430             ) {
1431              
1432 0           $self->{SIDdata}{startPage} = 0xFF;
1433 0           $self->{SIDdata}{pageLength} = 0x00;
1434             }
1435              
1436 0           $self->{SIDdata}{reserved} = 0;
1437              
1438 0 0         if ($self->{SIDdata}{version} >= 3) {
1439             # The secondSIDAddress field is valid only for v3+.
1440 0           my $secondSIDAddress = $self->getSIDAddress(2);
1441              
1442 0 0         if ($secondSIDAddress) {
1443             # This function will also validate the value.
1444 0           my $result = $self->setSIDAddress(2, $secondSIDAddress);
1445            
1446 0 0         if (!$result) {
1447             # Value validation failed, set SID address to 0.
1448 0           $self->setSIDAddress(2, 0);
1449             }
1450             }
1451             }
1452              
1453 0 0         if ($self->{SIDdata}{version} >= 4) {
1454             # The thirdSIDAddress field is valid only for v4+.
1455 0           my $thirdSIDAddress = $self->getSIDAddress(3);
1456              
1457 0 0         if ($thirdSIDAddress) {
1458             # This function will also validate the value.
1459 0           my $result = $self->setSIDAddress(3, $thirdSIDAddress);
1460            
1461 0 0         if (!$result) {
1462             # Value validation failed, set SID address to 0.
1463 0           $self->setSIDAddress(3, 0);
1464             }
1465             }
1466            
1467 0           $self->{SIDdata}{reserved} = undef;
1468             }
1469              
1470             # The preferred way is to have no padding between the v2 header and the
1471             # C64 data.
1472 0 0         if ($self->{PADDING}) {
1473 0           $self->{PADDING} = '';
1474             # carp ("Invalid bytes were between the header and the data - removed them");
1475             }
1476              
1477             # Recalculate size.
1478             $self->{FILESIZE} = $self->{SIDdata}{dataOffset} + length($self->{PADDING}) +
1479 0           length($self->{SIDdata}{data});
1480             }
1481              
1482             1;
1483              
1484             __END__