1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1994-1998 Revolution Software Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 
26 #include "common/file.h"
27 #include "common/system.h"
28 #include "common/textconsole.h"
29 
30 #include "sword2/sword2.h"
31 #include "sword2/defs.h"
32 #include "sword2/header.h"
33 #include "sword2/console.h"
34 #include "sword2/logic.h"
35 #include "sword2/memory.h"
36 #include "sword2/resman.h"
37 #include "sword2/router.h"
38 #include "sword2/screen.h"
39 #include "sword2/sound.h"
40 
41 #define Debug_Printf _vm->_debugger->debugPrintf
42 
43 namespace Sword2 {
44 
45 // Welcome to the easy resource manager - written in simple code for easy
46 // maintenance
47 //
48 // The resource compiler will create two files
49 //
50 //	resource.inf which is a list of ascii cluster file names
51 //	resource.tab which is a table which tells us which cluster a resource
52 //	is located in and the number within the cluster
53 
54 enum {
55 	BOTH		= 0x0,		// Cluster is on both CDs
56 	CD1		= 0x1,		// Cluster is on CD1 only
57 	CD2		= 0x2,		// Cluster is on CD2 only
58 	LOCAL_CACHE	= 0x4,		// Cluster is cached on HDD
59 	LOCAL_PERM	= 0x8		// Cluster is on HDD.
60 };
61 
62 struct CdInf {
63 	uint8 clusterName[20];	// Null terminated cluster name.
64 	uint8 cd;		// Cd cluster is on and whether it is on the local drive or not.
65 };
66 
ResourceManager(Sword2Engine * vm)67 ResourceManager::ResourceManager(Sword2Engine *vm) {
68 	_vm = vm;
69 
70 	_totalClusters = 0;
71 	_resList = NULL;
72 	_resConvTable = NULL;
73 	_cacheStart = NULL;
74 	_cacheEnd = NULL;
75 	_usedMem = 0;
76 }
77 
~ResourceManager()78 ResourceManager::~ResourceManager() {
79 	Resource *res = _cacheStart;
80 	while (res) {
81 		_vm->_memory->memFree(res->ptr);
82 		res = res->next;
83 	}
84 	for (uint i = 0; i < _totalClusters; i++)
85 		free(_resFiles[i].entryTab);
86 	free(_resList);
87 	free(_resConvTable);
88 }
89 
90 
init()91 bool ResourceManager::init() {
92 	uint32 i, j;
93 
94 	// Until proven differently, assume we're on CD 1. This is so the start
95 	// dialog will be able to play any music at all.
96 
97 	setCD(1);
98 
99 	// We read in the resource info which tells us the names of the
100 	// resource cluster files ultimately, although there might be groups
101 	// within the clusters at this point it makes no difference. We only
102 	// wish to know what resource files there are and what is in each
103 
104 	Common::File file;
105 
106 	if (!file.open("resource.inf")) {
107 		GUIErrorMessage("Broken Sword II: Cannot open resource.inf");
108 		return false;
109 	}
110 
111 	// The resource.inf file is a simple text file containing the names of
112 	// all the resource files.
113 
114 	while (1) {
115 		char *buf = _resFiles[_totalClusters].fileName;
116 		uint len = sizeof(_resFiles[_totalClusters].fileName);
117 
118 		if (!file.readLine(buf, len))
119 			break;
120 
121 		int pos = strlen(buf);
122 		if (buf[pos - 1] == 0x0A)
123 			buf[pos - 1] = 0;
124 
125 		_resFiles[_totalClusters].numEntries = -1;
126 		_resFiles[_totalClusters].entryTab = NULL;
127 		if (++_totalClusters >= MAX_res_files) {
128 			GUIErrorMessage("Broken Sword II: Too many entries in resource.inf");
129 			return false;
130 		}
131 	}
132 
133 	file.close();
134 
135 	// Now load in the binary id to res conversion table
136 	if (!file.open("resource.tab")) {
137 		GUIErrorMessage("Broken Sword II: Cannot open resource.tab");
138 		return false;
139 	}
140 
141 	// Find how many resources
142 	uint32 size = file.size();
143 
144 	_totalResFiles = size / 4;
145 
146 	// Table seems ok so malloc some space
147 	_resConvTable = (uint16 *)malloc(size);
148 
149 	for (i = 0; i < size / 2; i++)
150 		_resConvTable[i] = file.readUint16LE();
151 
152 	if (file.eos() || file.err()) {
153 		file.close();
154 		GUIErrorMessage("Broken Sword II: Cannot read resource.tab");
155 		return false;
156 	}
157 
158 	file.close();
159 
160 	// Check that we have cd.inf file, unless we are running PSX
161 	// version, which has all files on one disc.
162 
163 	if (!file.open("cd.inf") && !Sword2Engine::isPsx()) {
164 		GUIErrorMessage("Broken Sword II: Cannot open cd.inf");
165 		return false;
166 	}
167 
168 	CdInf *cdInf = new CdInf[_totalClusters];
169 
170 	for (i = 0; i < _totalClusters; i++) {
171 
172 		if (Sword2Engine::isPsx()) { // We are running PSX version, artificially fill CdInf structure
173 			cdInf[i].cd = CD1;
174 		} else { // We are running PC version, read cd.inf file
175 			file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
176 
177 			cdInf[i].cd = file.readByte();
178 
179 			if (file.eos() || file.err()) {
180 				delete[] cdInf;
181 				file.close();
182 				GUIErrorMessage("Broken Sword II: Cannot read cd.inf");
183 				return false;
184 			}
185 
186 		}
187 
188 		// It has been reported that there are two different versions
189 		// of the cd.inf file: One where all clusters on CD also have
190 		// the LOCAL_CACHE bit set. This bit is no longer used. To
191 		// avoid future problems, let's normalize the flag once and for
192 		// all here.
193 
194 		if (cdInf[i].cd & LOCAL_PERM)
195 			cdInf[i].cd = 0;
196 		else if (cdInf[i].cd & CD1)
197 			cdInf[i].cd = 1;
198 		else if (cdInf[i].cd & CD2)
199 			cdInf[i].cd = 2;
200 		else
201 			cdInf[i].cd = 0;
202 
203 		// Any file on "CD 0" may be needed at all times. Verify that
204 		// it exists. Any other missing cluster will be requested with
205 		// an "insert CD" message. Of course, the file may still vanish
206 		// during game-play (oh, that wascally wabbit!) in which case
207 		// the resource manager will print a fatal error.
208 
209 		if (cdInf[i].cd == 0 && !Common::File::exists((char *)cdInf[i].clusterName)) {
210 			GUIErrorMessage("Broken Sword II: Cannot find " + Common::String((char *)cdInf[i].clusterName));
211 			delete[] cdInf;
212 			return false;
213 		}
214 	}
215 
216 	file.close();
217 
218 	// We check the presence of resource files in cd.inf
219 	// This is ok in PC version, but in PSX version we don't
220 	// have cd.inf so we'll have to skip this.
221 	if (!Sword2Engine::isPsx()) {
222 		for (i = 0; i < _totalClusters; i++) {
223 			for (j = 0; j < _totalClusters; j++) {
224 				if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
225 					break;
226 			}
227 
228 			if (j == _totalClusters) {
229 				delete[] cdInf;
230 				GUIErrorMessage(Common::String(_resFiles[i].fileName) + " is not in cd.inf");
231 				return false;
232 			}
233 
234 			_resFiles[i].cd = cdInf[j].cd;
235 		}
236 	}
237 
238 	delete[] cdInf;
239 
240 	debug(1, "%d resources in %d cluster files", _totalResFiles, _totalClusters);
241 	for (i = 0; i < _totalClusters; i++)
242 		debug(2, "filename of cluster %d: -%s (%d)", i, _resFiles[i].fileName, _resFiles[i].cd);
243 
244 	_resList = (Resource *)malloc(_totalResFiles * sizeof(Resource));
245 
246 	for (i = 0; i < _totalResFiles; i++) {
247 		_resList[i].ptr = NULL;
248 		_resList[i].size = 0;
249 		_resList[i].refCount = 0;
250 		_resList[i].prev = _resList[i].next = NULL;
251 	}
252 
253 	return true;
254 }
255 
256 /**
257  * Returns the address of a resource. Loads if not in memory. Retains a count.
258  */
openResource(uint32 res,bool dump)259 byte *ResourceManager::openResource(uint32 res, bool dump) {
260 	assert(res < _totalResFiles);
261 
262 
263 	// FIXME: In PSX edition, not all top menu icons are present (TOP menu is not used).
264 	// Though, at present state, the engine still ask for the resources.
265 	if (Sword2Engine::isPsx()) { // We need to "rewire" missing icons
266 		if (res == 342) res = 364; // Rewire RESTORE ICON to SAVE ICON
267 	}
268 
269 	// Is the resource in memory already? If not, load it.
270 
271 	if (!_resList[res].ptr) {
272 		// Fetch the correct file and read in the correct portion.
273 		uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename
274 
275 		assert(cluFileNum != 0xffff);
276 
277 		// Relative resource within the file
278 		// First we have to find the file via the _resConvTable
279 		uint16 actual_res = _resConvTable[(res * 2) + 1];
280 
281 		debug(5, "openResource %s res %d", _resFiles[cluFileNum].fileName, res);
282 
283 		// If we're loading a cluster that's only available from one
284 		// of the CDs, remember which one so that we can play the
285 		// correct speech and music.
286 
287 		if (Sword2Engine::isPsx()) // We have only one disk in PSX version
288 			setCD(CD1);
289 		else
290 			setCD(_resFiles[cluFileNum].cd);
291 
292 		// Actually, as long as the file can be found we don't really
293 		// care which CD it's on. But if we can't find it, keep asking
294 		// for the CD until we do.
295 
296 		Common::File *file = openCluFile(cluFileNum);
297 
298 		if (_resFiles[cluFileNum].entryTab == NULL) {
299 			// we didn't read from this file before, get its index table
300 			readCluIndex(cluFileNum, file);
301 		}
302 
303 		assert(_resFiles[cluFileNum].entryTab);
304 
305 		uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0];
306 		uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1];
307 
308 		file->seek(pos, SEEK_SET);
309 
310 		debug(6, "res len %d", len);
311 
312 		// Ok, we know the length so try and allocate the memory.
313 		_resList[res].ptr = _vm->_memory->memAlloc(len, res);
314 		_resList[res].size = len;
315 		_resList[res].refCount = 0;
316 
317 		file->read(_resList[res].ptr, len);
318 
319 		debug(3, "Loaded resource '%s' (%d) from '%s' on CD %d (%d)", fetchName(_resList[res].ptr), res, _resFiles[cluFileNum].fileName, getCD(), _resFiles[cluFileNum].cd);
320 
321 		if (dump) {
322 			char buf[256];
323 			const char *tag;
324 
325 			switch (fetchType(_resList[res].ptr)) {
326 			case ANIMATION_FILE:
327 				tag = "anim";
328 				break;
329 			case SCREEN_FILE:
330 				tag = "layer";
331 				break;
332 			case GAME_OBJECT:
333 				tag = "object";
334 				break;
335 			case WALK_GRID_FILE:
336 				tag = "walkgrid";
337 				break;
338 			case GLOBAL_VAR_FILE:
339 				tag = "globals";
340 				break;
341 			case PARALLAX_FILE_null:
342 				tag = "parallax";	// Not used!
343 				break;
344 			case RUN_LIST:
345 				tag = "runlist";
346 				break;
347 			case TEXT_FILE:
348 				tag = "text";
349 				break;
350 			case SCREEN_MANAGER:
351 				tag = "screen";
352 				break;
353 			case MOUSE_FILE:
354 				tag = "mouse";
355 				break;
356 			case WAV_FILE:
357 				tag = "wav";
358 				break;
359 			case ICON_FILE:
360 				tag = "icon";
361 				break;
362 			case PALETTE_FILE:
363 				tag = "palette";
364 				break;
365 			default:
366 				tag = "unknown";
367 				break;
368 			}
369 
370 			sprintf(buf, "dumps/%s-%d.dmp", tag, res);
371 
372 			if (!Common::File::exists(buf)) {
373 				Common::DumpFile out;
374 				if (out.open(buf))
375 					out.write(_resList[res].ptr, len);
376 			}
377 		}
378 
379 		// close the cluster
380 		file->close();
381 		delete file;
382 
383 		_usedMem += len;
384 		checkMemUsage();
385 	} else if (_resList[res].refCount == 0)
386 		removeFromCacheList(_resList + res);
387 
388 	_resList[res].refCount++;
389 
390 	return _resList[res].ptr;
391 }
392 
closeResource(uint32 res)393 void ResourceManager::closeResource(uint32 res) {
394 	assert(res < _totalResFiles);
395 
396 	// Don't try to close the resource if it has already been forcibly
397 	// closed, e.g. by fnResetGlobals().
398 
399 	if (_resList[res].ptr == NULL)
400 		return;
401 
402 	assert(_resList[res].refCount > 0);
403 
404 	_resList[res].refCount--;
405 	if (_resList[res].refCount == 0)
406 		addToCacheList(_resList + res);
407 
408 	// It's tempting to free the resource immediately when refCount
409 	// reaches zero, but that'd be a mistake. Closing a resource does not
410 	// mean "I'm not going to use this resource any more". It means that
411 	// "the next time I use this resource I'm going to ask for a new
412 	// pointer to it".
413 	//
414 	// Since the original memory manager had to deal with memory
415 	// fragmentation, keeping a resource open - and thus locked down to a
416 	// specific memory address - was considered a bad thing.
417 }
418 
removeFromCacheList(Resource * res)419 void ResourceManager::removeFromCacheList(Resource *res) {
420 	if (_cacheStart == res)
421 		_cacheStart = res->next;
422 
423 	if (_cacheEnd == res)
424 		_cacheEnd = res->prev;
425 
426 	if (res->prev)
427 		res->prev->next = res->next;
428 	if (res->next)
429 		res->next->prev = res->prev;
430 	res->prev = res->next = NULL;
431 }
432 
addToCacheList(Resource * res)433 void ResourceManager::addToCacheList(Resource *res) {
434 	res->prev = NULL;
435 	res->next = _cacheStart;
436 	if (_cacheStart)
437 		_cacheStart->prev = res;
438 	_cacheStart = res;
439 	if (!_cacheEnd)
440 		_cacheEnd = res;
441 }
442 
openCluFile(uint16 fileNum)443 Common::File *ResourceManager::openCluFile(uint16 fileNum) {
444 	Common::File *file = new Common::File;
445 	while (!file->open(_resFiles[fileNum].fileName)) {
446 		// HACK: We have to check for this, or it'll be impossible to
447 		// quit while the game is asking for the user to insert a CD.
448 		// But recovering from this situation gracefully is just too
449 		// much trouble, so quit now.
450 		if (_vm->shouldQuit())
451 			g_system->quit();
452 
453 		// If the file is supposed to be on hard disk, or we're
454 		// playing a demo, then we're in trouble if the file
455 		// can't be found!
456 
457 		if ((_vm->_features & GF_DEMO) || _resFiles[fileNum].cd == 0)
458 			error("Could not find '%s'", _resFiles[fileNum].fileName);
459 
460 		askForCD(_resFiles[fileNum].cd);
461 	}
462 	return file;
463 }
464 
readCluIndex(uint16 fileNum,Common::File * file)465 void ResourceManager::readCluIndex(uint16 fileNum, Common::File *file) {
466 	// we didn't read from this file before, get its index table
467 	assert(_resFiles[fileNum].entryTab == NULL);
468 	assert(file);
469 
470 	// 1st DWORD of a cluster is an offset to the look-up table
471 	uint32 table_offset = file->readUint32LE();
472 	debug(6, "table offset = %d", table_offset);
473 	uint32 tableSize = file->size() - table_offset; // the table is stored at the end of the file
474 	file->seek(table_offset);
475 
476 	assert((tableSize % 8) == 0);
477 	_resFiles[fileNum].entryTab = (uint32 *)malloc(tableSize);
478 	_resFiles[fileNum].numEntries = tableSize / 8;
479 
480 	assert(_resFiles[fileNum].entryTab);
481 
482 	file->read(_resFiles[fileNum].entryTab, tableSize);
483 	if (file->eos() || file->err())
484 		error("unable to read index table from file %s", _resFiles[fileNum].fileName);
485 
486 #ifdef SCUMM_BIG_ENDIAN
487 	for (int tabCnt = 0; tabCnt < _resFiles[fileNum].numEntries * 2; tabCnt++)
488 		_resFiles[fileNum].entryTab[tabCnt] = FROM_LE_32(_resFiles[fileNum].entryTab[tabCnt]);
489 #endif
490 }
491 
492 /**
493  * Returns true if resource is valid, otherwise false.
494  */
495 
checkValid(uint32 res)496 bool ResourceManager::checkValid(uint32 res) {
497 	// Resource number out of range
498 	if (res >= _totalResFiles)
499 		return false;
500 
501 	// Points to the number of the ascii filename
502 	uint16 parent_res_file = _resConvTable[res * 2];
503 
504 	// Null & void resource
505 	if (parent_res_file == 0xffff)
506 		return false;
507 
508 	return true;
509 }
510 
511 /**
512  * Fetch resource type
513  */
514 
fetchType(byte * ptr)515 uint8 ResourceManager::fetchType(byte *ptr) {
516 	if (!Sword2Engine::isPsx()) {
517 		return ptr[0];
518 	} else { // in PSX version, some files got a "garbled" resource header, with type stored in ninth byte
519 		if (ptr[0]) {
520 			return ptr[0];
521 		} else if (ptr[8]) {
522 			return ptr[8];
523 		} else  {            // In PSX version there is no resource header for audio files,
524 			return WAV_FILE; // but hopefully all audio files got first 16 bytes zeroed,
525 		}                    // Allowing us to check for this condition.
526 							 // Alas, this doesn't work with PSX DEMO audio files.
527 
528 	}
529 }
530 
531 /**
532  * Returns the total file length of a resource - i.e. all headers are included
533  * too.
534  */
535 
fetchLen(uint32 res)536 uint32 ResourceManager::fetchLen(uint32 res) {
537 	if (_resList[res].ptr)
538 		return _resList[res].size;
539 
540 	// Does this ever happen?
541 	warning("fetchLen: Resource %u is not loaded; reading length from file", res);
542 
543 	// Points to the number of the ascii filename
544 	uint16 parent_res_file = _resConvTable[res * 2];
545 
546 	// relative resource within the file
547 	uint16 actual_res = _resConvTable[(res * 2) + 1];
548 
549 	// first we have to find the file via the _resConvTable
550 	// open the cluster file
551 
552 	if (_resFiles[parent_res_file].entryTab == NULL) {
553 		Common::File *file = openCluFile(parent_res_file);
554 		readCluIndex(parent_res_file, file);
555 		delete file;
556 	}
557 	return _resFiles[parent_res_file].entryTab[actual_res * 2 + 1];
558 }
559 
checkMemUsage()560 void ResourceManager::checkMemUsage() {
561 	while (_usedMem > MAX_MEM_CACHE) {
562 		// we're using up more memory than we wanted to. free some old stuff.
563 		// Newly loaded objects are added to the start of the list,
564 		// we start freeing from the end, to free the oldest items first
565 		if (_cacheEnd) {
566 			Resource *tmp = _cacheEnd;
567 			assert((tmp->refCount == 0) && (tmp->ptr) && (tmp->next == NULL));
568 			removeFromCacheList(tmp);
569 
570 			_vm->_memory->memFree(tmp->ptr);
571 			tmp->ptr = NULL;
572 			_usedMem -= tmp->size;
573 		} else {
574 			warning("%d bytes of memory used, but cache list is empty", _usedMem);
575 			return;
576 		}
577 	}
578 }
579 
remove(int res)580 void ResourceManager::remove(int res) {
581 	if (_resList[res].ptr) {
582 		removeFromCacheList(_resList + res);
583 
584 		_vm->_memory->memFree(_resList[res].ptr);
585 		_resList[res].ptr = NULL;
586 		_resList[res].refCount = 0;
587 		_usedMem -= _resList[res].size;
588 	}
589 }
590 
591 /**
592  * Remove all res files from memory - ready for a total restart. This includes
593  * the player object and global variables resource.
594  */
595 
removeAll()596 void ResourceManager::removeAll() {
597 	// We need to clear the FX queue, because otherwise the sound system
598 	// will still believe that the sound resources are in memory. We also
599 	// need to kill the movie lead-in/out.
600 
601 	_vm->_sound->clearFxQueue(true);
602 
603 	for (uint i = 0; i < _totalResFiles; i++)
604 		remove(i);
605 }
606 
607 /**
608  * Remove all resources from memory.
609  */
610 
killAll(bool wantInfo)611 void ResourceManager::killAll(bool wantInfo) {
612 	int nuked = 0;
613 
614 	// We need to clear the FX queue, because otherwise the sound system
615 	// will still believe that the sound resources are in memory. We also
616 	// need to kill the movie lead-in/out.
617 
618 	_vm->_sound->clearFxQueue(true);
619 
620 	for (uint i = 0; i < _totalResFiles; i++) {
621 		// Don't nuke the global variables or the player object!
622 		if (i == 1 || i == CUR_PLAYER_ID)
623 			continue;
624 
625 		if (_resList[i].ptr) {
626 			if (wantInfo)
627 				Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
628 
629 			remove(i);
630 			nuked++;
631 		}
632 	}
633 
634 	if (wantInfo)
635 		Debug_Printf("Expelled %d resources\n", nuked);
636 }
637 
638 /**
639  * Like killAll but only kills objects (except George & the variable table of
640  * course) - ie. forcing them to reload & restart their scripts, which
641  * simulates the effect of a save & restore, thus checking that each object's
642  * re-entrant logic works correctly, and doesn't cause a statuette to
643  * disappear forever, or some plaster-filled holes in sand to crash the game &
644  * get James in trouble again.
645  */
646 
killAllObjects(bool wantInfo)647 void ResourceManager::killAllObjects(bool wantInfo) {
648 	int nuked = 0;
649 
650 	for (uint i = 0; i < _totalResFiles; i++) {
651 		// Don't nuke the global variables or the player object!
652 		if (i == 1 || i == CUR_PLAYER_ID)
653 			continue;
654 
655 		if (_resList[i].ptr) {
656 			if (fetchType(_resList[i].ptr) == GAME_OBJECT) {
657 				if (wantInfo)
658 					Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
659 
660 				remove(i);
661 				nuked++;
662 			}
663 		}
664 	}
665 
666 	if (wantInfo)
667 		Debug_Printf("Expelled %d resources\n", nuked);
668 }
669 
askForCD(int cd)670 void ResourceManager::askForCD(int cd) {
671 	byte *textRes;
672 
673 	// Stop any music from playing - so the system no longer needs the
674 	// current CD - otherwise when we take out the CD, Windows will
675 	// complain!
676 
677 	_vm->_sound->stopMusic(true);
678 
679 	textRes = openResource(2283);
680 	_vm->_screen->displayMsg(_vm->fetchTextLine(textRes, 5 + cd) + 2, 0);
681 	closeResource(2283);
682 
683 	// The original code probably determined automagically when the correct
684 	// CD had been inserted, but our backend doesn't support that, and
685 	// anyway I don't know if all systems allow that sort of thing. So we
686 	// wait for the user to press any key instead, or click the mouse.
687 	//
688 	// But just in case we ever try to identify the CDs by their labels,
689 	// they should be:
690 	//
691 	// CD1: "RBSII1" (or "PCF76" for the PCF76 version, whatever that is)
692 	// CD2: "RBSII2"
693 }
694 
695 } // End of namespace Sword2
696