1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /*
21  * FrameResource.c
22  *
23  * Framework Resource file processing functions
24  *
25  */
26 #include "frameresource.h"
27 
28 #include "string_ext.h"
29 #include "physfs_ext.h"
30 
31 #include "file.h"
32 #include "resly.h"
33 
34 // Local prototypes
35 static RES_TYPE *psResTypes = nullptr;
36 
37 /* The initial resource directory and the current resource directory */
38 char aResDir[PATH_MAX];
39 char aCurrResDir[PATH_MAX];
40 
41 // the current resource block ID
42 static SDWORD resBlockID;
43 
44 // prototypes
45 static void ResetResourceFile();
46 
47 // callback to resload screen.
48 static RESLOAD_CALLBACK resLoadCallback = nullptr;
49 
50 
51 /* next four used in HashPJW */
52 #define	BITS_IN_int		32
53 #define	THREE_QUARTERS	((UDWORD) ((BITS_IN_int * 3) / 4))
54 #define	ONE_EIGHTH		((UDWORD) (BITS_IN_int / 8))
55 #define	HIGH_BITS		( ~((UDWORD)(~0) >> ONE_EIGHTH ))
56 
57 /***************************************************************************/
58 /*
59  * HashString
60  *
61  * Adaptation of Peter Weinberger's (PJW) generic hashing algorithm listed
62  * in Binstock+Rex, "Practical Algorithms" p 69.
63  *
64  * Accepts string and returns hashed integer.
65  */
66 /***************************************************************************/
HashString(const char * c)67 static UDWORD HashString(const char *c)
68 {
69 	UDWORD	iHashValue;
70 
71 	assert(c != nullptr);
72 	assert(*c != 0x0);
73 
74 	for (iHashValue = 0; *c; ++c)
75 	{
76 		unsigned int i;
77 		iHashValue = (iHashValue << ONE_EIGHTH) + *c;
78 
79 		i = iHashValue & HIGH_BITS;
80 		if (i != 0)
81 		{
82 			iHashValue = (iHashValue ^ (i >> THREE_QUARTERS)) &
83 			             ~HIGH_BITS;
84 		}
85 	}
86 	return iHashValue;
87 }
88 
89 /* Converts lower case ASCII characters into upper case characters
90  * \param c the character to convert
91  * \return an upper case ASCII character
92  */
upcaseASCII(char c)93 static inline char upcaseASCII(char c)
94 {
95 	// If this is _not_ a lower case character simply return
96 	if (c < 'a' || c > 'z')
97 	{
98 		return c;
99 	}
100 	// Otherwise substract 32 to make the lower case character an upper case one
101 	else
102 	{
103 		return c - 32;
104 	}
105 }
106 
HashStringIgnoreCase(const char * c)107 static UDWORD HashStringIgnoreCase(const char *c)
108 {
109 	UDWORD	iHashValue;
110 
111 	assert(c != nullptr);
112 	assert(*c != 0x0);
113 
114 	for (iHashValue = 0; *c; ++c)
115 	{
116 		unsigned int i;
117 		iHashValue = (iHashValue << ONE_EIGHTH) + upcaseASCII(*c);
118 
119 		i = iHashValue & HIGH_BITS;
120 		if (i != 0)
121 		{
122 			iHashValue = (iHashValue ^ (i >> THREE_QUARTERS)) &
123 			             ~HIGH_BITS;
124 		}
125 	}
126 	return iHashValue;
127 }
128 
129 /* set the callback function for the res loader*/
resSetLoadCallback(RESLOAD_CALLBACK funcToCall)130 void resSetLoadCallback(RESLOAD_CALLBACK funcToCall)
131 {
132 	resLoadCallback = funcToCall;
133 }
134 
135 /* do the callback for the resload display function */
resDoResLoadCallback()136 void resDoResLoadCallback()
137 {
138 	if (resLoadCallback)
139 	{
140 		resLoadCallback();
141 	}
142 }
143 
144 
145 /* Initialise the resource module */
resInitialise()146 bool resInitialise()
147 {
148 	ASSERT(psResTypes == nullptr,
149 	       "resInitialise: resource module hasn't been shut down??");
150 	psResTypes = nullptr;
151 	resBlockID = 0;
152 	resLoadCallback = nullptr;
153 
154 	ResetResourceFile();
155 
156 	return true;
157 }
158 
159 
160 /* Shutdown the resource module */
resShutDown()161 void resShutDown()
162 {
163 	if (psResTypes != nullptr)
164 	{
165 		debug(LOG_WZ, "resShutDown: warning resources still allocated");
166 		resReleaseAll();
167 	}
168 }
169 
170 // force set the base resource directory
resForceBaseDir(const char * pResDir)171 void resForceBaseDir(const char *pResDir)
172 {
173 	sstrcpy(aResDir, pResDir);
174 	sstrcpy(aCurrResDir, pResDir);
175 }
176 
177 // set the base resource directory
resSetBaseDir(const char * pResDir)178 void resSetBaseDir(const char *pResDir)
179 {
180 	sstrcpy(aResDir, pResDir);
181 }
182 
183 /* Parse the res file */
resLoad(const char * pResFile,SDWORD blockID)184 bool resLoad(const char *pResFile, SDWORD blockID)
185 {
186 	bool retval = true;
187 	lexerinput_t input;
188 
189 	sstrcpy(aCurrResDir, aResDir);
190 
191 	// Note the block id number
192 	resBlockID = blockID;
193 
194 	debug(LOG_WZ, "resLoad: loading [directory: %s] %s", WZ_PHYSFS_getRealDir_String(pResFile).c_str(), pResFile);
195 
196 	// Load the RES file; allocate memory for a wrf, and load it
197 	input.type = LEXINPUT_PHYSFS;
198 	input.input.physfsfile = openLoadFile(pResFile, true);
199 	if (!input.input.physfsfile)
200 	{
201 		debug(LOG_FATAL, "Could not open file %s", pResFile);
202 		return false;
203 	}
204 
205 	// and parse it
206 	res_set_extra(&input);
207 	if (res_parse() != 0)
208 	{
209 		debug(LOG_FATAL, "Failed to parse %s", pResFile);
210 		retval = false;
211 	}
212 
213 	res_lex_destroy();
214 	PHYSFS_close(input.input.physfsfile);
215 
216 	return retval;
217 }
218 
219 
220 /* Allocate a RES_TYPE structure */
resAlloc(const char * pType)221 static RES_TYPE *resAlloc(const char *pType)
222 {
223 	RES_TYPE	*psT;
224 
225 #ifdef DEBUG
226 	// Check for a duplicate type
227 	for (psT = psResTypes; psT; psT = psT->psNext)
228 	{
229 		ASSERT(strcmp(psT->aType, pType) != 0, "Duplicate function for type: %s", pType);
230 	}
231 #endif
232 
233 	// setup the structure
234 	psT = (RES_TYPE *)malloc(sizeof(RES_TYPE));
235 	sstrcpy(psT->aType, pType);
236 	psT->HashedType = HashString(psT->aType); // store a hased version for super speed !
237 	psT->psRes = nullptr;
238 
239 	return psT;
240 }
241 
242 
243 /* Add a buffer load function for a file type */
resAddBufferLoad(const char * pType,RES_BUFFERLOAD buffLoad,RES_FREE release)244 bool resAddBufferLoad(const char *pType, RES_BUFFERLOAD buffLoad, RES_FREE release)
245 {
246 	RES_TYPE	*psT = resAlloc(pType);
247 
248 	psT->buffLoad = buffLoad;
249 	psT->fileLoad = nullptr;
250 	psT->release = release;
251 
252 	psT->psNext = psResTypes;
253 	psResTypes = psT;
254 
255 	return true;
256 }
257 
258 
259 /* Add a file name load function for a file type */
resAddFileLoad(const char * pType,RES_FILELOAD fileLoad,RES_FREE release)260 bool resAddFileLoad(const char *pType, RES_FILELOAD fileLoad, RES_FREE release)
261 {
262 	RES_TYPE	*psT = resAlloc(pType);
263 
264 	psT->buffLoad = nullptr;
265 	psT->fileLoad = fileLoad;
266 	psT->release = release;
267 
268 	psT->psNext = psResTypes;
269 	psResTypes = psT;
270 
271 	return true;
272 }
273 
274 // Make a string lower case
resToLower(char * pStr)275 void resToLower(char *pStr)
276 {
277 	while (*pStr != 0)
278 	{
279 		if (isupper(*pStr))
280 		{
281 			*pStr = (char)(*pStr - (char)('A' - 'a'));
282 		}
283 		pStr += 1;
284 	}
285 }
286 
287 
288 static char LastResourceFilename[PATH_MAX];
289 
290 /*!
291  * Returns the filename of the last resource file loaded
292  * The filename is always null terminated
293  */
GetLastResourceFilename()294 const char *GetLastResourceFilename()
295 {
296 	return LastResourceFilename;
297 }
298 
299 /*!
300  * Set the resource name of the last resource file loaded
301  */
SetLastResourceFilename(const char * pName)302 void SetLastResourceFilename(const char *pName)
303 {
304 	sstrcpy(LastResourceFilename, pName);
305 }
306 
307 
308 // Structure for each file currently in use in the resource  ... probably only going to be one ... but we will handle upto MAXLOADEDRESOURCE
309 struct RESOURCEFILE
310 {
311 	char *pBuffer;	// a pointer to the data
312 	UDWORD size;	// number of bytes
313 	UBYTE	type;	// what type of resource is it
314 };
315 
316 #define RESFILETYPE_EMPTY (0)			// empty entry
317 #define RESFILETYPE_LOADED (2)			// Loaded from a file (!)
318 
319 
320 #define MAXLOADEDRESOURCES (6)
321 static RESOURCEFILE LoadedResourceFiles[MAXLOADEDRESOURCES];
322 
323 
324 
325 // Clear out the resource list ... needs to be called during init.
ResetResourceFile()326 static void ResetResourceFile()
327 {
328 	UWORD i;
329 
330 	for (i = 0; i < MAXLOADEDRESOURCES; i++)
331 	{
332 		LoadedResourceFiles[i].type = RESFILETYPE_EMPTY;
333 	}
334 }
335 
336 // Returns an empty resource entry or -1 if none exsist
FindEmptyResourceFile()337 static SDWORD FindEmptyResourceFile()
338 {
339 	UWORD i;
340 	for (i = 0; i < MAXLOADEDRESOURCES ; i++)
341 	{
342 		if (LoadedResourceFiles[i].type == RESFILETYPE_EMPTY)
343 		{
344 			return (i);
345 		}
346 
347 	}
348 	return (-1);			// ERROR
349 }
350 
351 
352 // Get a resource data file ... either loads it or just returns a pointer
RetreiveResourceFile(char * ResourceName,RESOURCEFILE ** NewResource)353 static bool RetreiveResourceFile(char *ResourceName, RESOURCEFILE **NewResource)
354 {
355 	SDWORD ResID;
356 	RESOURCEFILE *ResData;
357 	UDWORD size;
358 	char *pBuffer;
359 
360 	ResID = FindEmptyResourceFile();
361 	if (ResID == -1)
362 	{
363 		return (false);    // all resource files are full
364 	}
365 
366 	ResData = &LoadedResourceFiles[ResID];
367 	*NewResource = ResData;
368 
369 	// This is needed for files that do not fit in the WDG cache ... (VAB file for example)
370 	if (!loadFile(ResourceName, &pBuffer, &size))
371 	{
372 		return false;
373 	}
374 
375 	ResData->type = RESFILETYPE_LOADED;
376 	ResData->size = size;
377 	ResData->pBuffer = pBuffer;
378 	return (true);
379 }
380 
381 
382 // Free up the file depending on what type it is
FreeResourceFile(RESOURCEFILE * OldResource)383 static void FreeResourceFile(RESOURCEFILE *OldResource)
384 {
385 	switch (OldResource->type)
386 	{
387 	case RESFILETYPE_LOADED:
388 		free(OldResource->pBuffer);
389 		OldResource->pBuffer = nullptr;
390 		break;
391 
392 	default:
393 		debug(LOG_WARNING, "resource not freed");
394 	}
395 
396 
397 	// Remove from the list
398 	OldResource->type = RESFILETYPE_EMPTY;
399 }
400 
401 
resDataInit(const char * DebugName,UDWORD DataIDHash,void * pData,UDWORD BlockID)402 static inline RES_DATA *resDataInit(const char *DebugName, UDWORD DataIDHash, void *pData, UDWORD BlockID)
403 {
404 	char *resID;
405 
406 	// Allocate memory to hold the RES_DATA structure plus the identifying string
407 	RES_DATA *psRes = (RES_DATA *)malloc(sizeof(RES_DATA) + strlen(DebugName) + 1);
408 	if (!psRes)
409 	{
410 		debug(LOG_ERROR, "resDataInit: Out of memory");
411 		return nullptr;
412 	}
413 
414 	// Initialize the pointer for our ID string
415 	resID = (char *)(psRes + 1);
416 
417 	// Copy over the identifying string
418 	strcpy(resID, DebugName);
419 	psRes->aID = resID;
420 
421 	psRes->pData = pData;
422 	psRes->blockID = BlockID;
423 	psRes->HashedID = DataIDHash;
424 
425 	psRes->usage = 0;
426 
427 	return psRes;
428 }
429 
430 
431 /*!
432  * check if given file exists in a locale dependent subdir
433  * if so, modify given fileName to hold the locale dep. file,
434  * else do not change given fileName
435  * \param[in,out] fileName must be at least PATH_MAX bytes large
436  * \param maxlen indicates the maximum buffer size of \c fileName
437  */
makeLocaleFile(char * fileName,size_t maxlen)438 static void makeLocaleFile(char *fileName, size_t maxlen)  // given string must have PATH_MAX size
439 {
440 #ifdef ENABLE_NLS
441 	const char *language = getLanguage();
442 	char localeFile[PATH_MAX];
443 
444 	if (language[0] == '\0' ||  // could not get language
445 	    strlen(fileName) + strlen(language) + 1 >= PATH_MAX)
446 	{
447 		return;
448 	}
449 
450 	snprintf(localeFile, sizeof(localeFile), "locale/%s/%s", language, fileName);
451 
452 	if (PHYSFS_exists(localeFile))
453 	{
454 		strlcpy(fileName, localeFile, maxlen);
455 		debug(LOG_WZ, "Found translated file: %s", fileName);
456 	}
457 #endif // ENABLE_NLS
458 
459 	return;
460 }
461 
462 
463 /*!
464  * Call the load function (registered in data.c)
465  * for this filetype
466  */
resLoadFile(const char * pType,const char * pFile)467 bool resLoadFile(const char *pType, const char *pFile)
468 {
469 	RES_TYPE	*psT = nullptr;
470 	void		*pData = nullptr;
471 	RES_DATA	*psRes = nullptr;
472 	char		aFileName[PATH_MAX];
473 	UDWORD HashedName, HashedType = HashString(pType);
474 
475 	// Find the resource-type
476 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
477 	{
478 		if (psT->HashedType == HashedType)
479 		{
480 			ASSERT(strcmp(psT->aType, pType) == 0, "Hash collision \"%s\" vs \"%s\"", psT->aType, pType);
481 			break;
482 		}
483 	}
484 
485 	if (psT == nullptr)
486 	{
487 		debug(LOG_WZ, "resLoadFile: Unknown type: %s", pType);
488 		return false;
489 	}
490 
491 	// Check for duplicates
492 	HashedName = HashStringIgnoreCase(pFile);
493 	for (psRes = psT->psRes; psRes; psRes = psRes->psNext)
494 	{
495 		if (psRes->HashedID == HashedName)
496 		{
497 			ASSERT(strcasecmp(psRes->aID, pFile) == 0, "Hash collision \"%s\" vs \"%s\"", psRes->aID, pFile);
498 			debug(LOG_WZ, "Duplicate file name: %s (hash %x) for type %s",
499 			      pFile, HashedName, psT->aType);
500 			// assume that they are actually both the same and silently fail
501 			// lovely little hack to allow some files to be loaded from disk (believe it or not!).
502 			return true;
503 		}
504 	}
505 
506 	// Create the file name
507 	if (strlen(aCurrResDir) + strlen(pFile) + 1 >= PATH_MAX)
508 	{
509 		debug(LOG_ERROR, "resLoadFile: Filename too long!! %s%s", aCurrResDir, pFile);
510 		return false;
511 	}
512 	sstrcpy(aFileName, aCurrResDir);
513 	sstrcat(aFileName, pFile);
514 
515 	makeLocaleFile(aFileName, sizeof(aFileName));  // check for translated file
516 
517 	SetLastResourceFilename(pFile); // Save the filename in case any routines need it
518 
519 	// load the resource
520 	if (psT->buffLoad)
521 	{
522 		RESOURCEFILE *Resource;
523 
524 		// Load the file in a buffer
525 		if (!RetreiveResourceFile(aFileName, &Resource))
526 		{
527 			debug(LOG_ERROR, "resLoadFile: Unable to retreive resource - %s", aFileName);
528 			return false;
529 		}
530 
531 		// Now process the buffer data
532 		if (!psT->buffLoad(Resource->pBuffer, Resource->size, &pData))
533 		{
534 			ASSERT(false, "The load function for resource type \"%s\" failed for file \"%s\"", pType, pFile);
535 			FreeResourceFile(Resource);
536 			if (psT->release != nullptr)
537 			{
538 				psT->release(pData);
539 			}
540 			return false;
541 		}
542 
543 		FreeResourceFile(Resource);
544 	}
545 	else if (psT->fileLoad)
546 	{
547 		// Process data directly from file
548 		if (!psT->fileLoad(aFileName, &pData))
549 		{
550 			ASSERT(false, "The load function for resource type \"%s\" failed for file \"%s\"", pType, pFile);
551 			if (psT->release != nullptr)
552 			{
553 				psT->release(pData);
554 			}
555 			return false;
556 		}
557 	}
558 
559 	resDoResLoadCallback();		// do callback.
560 
561 	// Set up the resource structure if there is something to store
562 	if (pData != nullptr)
563 	{
564 		// LastResourceFilename may have been changed (e.g. by TEXPAGE loading)
565 		psRes = resDataInit(GetLastResourceFilename(), HashStringIgnoreCase(GetLastResourceFilename()), pData, resBlockID);
566 		if (!psRes)
567 		{
568 			if (psT->release != nullptr)
569 			{
570 				psT->release(pData);
571 			}
572 			return false;
573 		}
574 
575 		// Add the resource to the list
576 		psRes->psNext = psT->psRes;
577 		psT->psRes = psRes;
578 	}
579 	return true;
580 }
581 
582 /* Return the resource for a type and hashedname */
resGetDataFromHash(const char * pType,UDWORD HashedID)583 void *resGetDataFromHash(const char *pType, UDWORD HashedID)
584 {
585 	RES_TYPE	*psT = nullptr;
586 	RES_DATA	*psRes = nullptr;
587 	// Find the correct type
588 	UDWORD HashedType = HashString(pType);
589 
590 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
591 	{
592 		if (psT->HashedType == HashedType)
593 		{
594 			/* We found it */
595 			break;
596 		}
597 	}
598 
599 	ASSERT(psT != nullptr, "resGetDataFromHash: Unknown type: %s", pType);
600 	if (psT == nullptr)
601 	{
602 		return nullptr;
603 	}
604 
605 	for (psRes = psT->psRes; psRes != nullptr; psRes = psRes->psNext)
606 	{
607 		if (psRes->HashedID == HashedID)
608 		{
609 			/* We found it */
610 			break;
611 		}
612 	}
613 
614 	ASSERT(psRes != nullptr, "resGetDataFromHash: Unknown ID: %0x Type: %s", HashedID, pType);
615 	if (psRes == nullptr)
616 	{
617 		return nullptr;
618 	}
619 
620 	psRes->usage += 1;
621 
622 	return psRes->pData;
623 }
624 
625 
626 /* Return the resource for a type and ID */
resGetData(const char * pType,const char * pID)627 void *resGetData(const char *pType, const char *pID)
628 {
629 	void *data = resGetDataFromHash(pType, HashStringIgnoreCase(pID));
630 	ASSERT(data != nullptr, "resGetData: Unable to find data for %s type %s", pID, pType);
631 	return data;
632 }
633 
634 
resGetHashfromData(const char * pType,const void * pData,UDWORD * pHash)635 bool resGetHashfromData(const char *pType, const void *pData, UDWORD *pHash)
636 {
637 	RES_TYPE	*psT;
638 	RES_DATA	*psRes;
639 
640 	// Find the correct type
641 	UDWORD	HashedType = HashString(pType);
642 
643 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
644 	{
645 		if (psT->HashedType == HashedType)
646 		{
647 			break;
648 		}
649 	}
650 
651 	ASSERT_OR_RETURN(false, psT, "Unknown type: %x", HashedType);
652 
653 	// Find the resource
654 	for (psRes = psT->psRes; psRes; psRes = psRes->psNext)
655 	{
656 
657 		if (psRes->pData == pData)
658 		{
659 			break;
660 		}
661 	}
662 
663 	if (psRes == nullptr)
664 	{
665 		ASSERT(false, "resGetHashfromData:: couldn't find data for type %x\n", HashedType);
666 		return false;
667 	}
668 
669 	*pHash = psRes->HashedID;
670 
671 	return true;
672 }
673 
resGetNamefromData(const char * type,const void * data)674 const char *resGetNamefromData(const char *type, const void *data)
675 {
676 	RES_TYPE	*psT;
677 	RES_DATA	*psRes;
678 	UDWORD   HashedType;
679 
680 	if (type == nullptr || data == nullptr)
681 	{
682 		return "";
683 	}
684 
685 	// Find the correct type
686 	HashedType = HashString(type);
687 
688 	// Find the resource table for the given type
689 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
690 	{
691 		if (psT->HashedType == HashedType)
692 		{
693 			break;
694 		}
695 	}
696 
697 	if (psT == nullptr)
698 	{
699 		ASSERT(false, "resGetHashfromData: Unknown type: %x", HashedType);
700 		return "";
701 	}
702 
703 	// Find the resource in the resource table
704 	for (psRes = psT->psRes; psRes; psRes = psRes->psNext)
705 	{
706 		if (psRes->pData == data)
707 		{
708 			break;
709 		}
710 	}
711 
712 	if (psRes == nullptr)
713 	{
714 		ASSERT(false, "resGetHashfromData:: couldn't find data for type %x\n", HashedType);
715 		return "";
716 	}
717 
718 	return psRes->aID;
719 }
720 
721 /* Simply returns true if a resource is present */
resPresent(const char * pType,const char * pID)722 bool resPresent(const char *pType, const char *pID)
723 {
724 	RES_TYPE	*psT;
725 	RES_DATA	*psRes;
726 
727 	// Find the correct type
728 	UDWORD HashedType = HashString(pType);
729 
730 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
731 	{
732 		if (psT->HashedType == HashedType)
733 		{
734 			break;
735 		}
736 	}
737 
738 	/* Bow out if unrecognised type */
739 	ASSERT(psT != nullptr, "resPresent: Unknown type");
740 	if (psT == nullptr)
741 	{
742 		return false;
743 	}
744 
745 	{
746 		UDWORD HashedID = HashStringIgnoreCase(pID);
747 
748 		for (psRes = psT->psRes; psRes; psRes = psRes->psNext)
749 		{
750 			if (psRes->HashedID == HashedID)
751 			{
752 				/* We found it */
753 				break;
754 			}
755 		}
756 	}
757 
758 	/* Did we find it? */
759 	if (psRes != nullptr)
760 	{
761 		return (true);
762 	}
763 
764 	return (false);
765 }
766 
767 
768 /* Release all the resources currently loaded and the resource load functions */
resReleaseAll()769 void resReleaseAll()
770 {
771 	RES_TYPE *psT, *psNT;
772 
773 	resReleaseAllData();
774 
775 	for (psT = psResTypes; psT != nullptr; psT = psNT)
776 	{
777 		psNT = psT->psNext;
778 		free(psT);
779 	}
780 
781 	psResTypes = nullptr;
782 }
783 
784 
785 /* Release all the resources currently loaded but keep the resource load functions */
resReleaseAllData()786 void resReleaseAllData()
787 {
788 	RES_TYPE *psT;
789 	RES_DATA *psRes, *psNRes;
790 
791 	for (psT = psResTypes; psT != nullptr; psT = psT->psNext)
792 	{
793 		for (psRes = psT->psRes; psRes != nullptr; psRes = psNRes)
794 		{
795 			if (psRes->usage == 0)
796 			{
797 				debug(LOG_NEVER, "resReleaseAllData: %s resource: %s(%04x) not used", psT->aType, psRes->aID, psRes->HashedID);
798 			}
799 
800 			if (psT->release != nullptr)
801 			{
802 				psT->release(psRes->pData);
803 			}
804 
805 			psNRes = psRes->psNext;
806 			free(psRes);
807 		}
808 
809 		psT->psRes = nullptr;
810 	}
811 }
812 
813 
814 // release the data for a particular block ID
resReleaseBlockData(SDWORD blockID)815 void resReleaseBlockData(SDWORD blockID)
816 {
817 	RES_TYPE	*psT, *psNT;
818 	RES_DATA	*psPRes, *psRes, *psNRes;
819 
820 	for (psT = psResTypes; psT != nullptr; psT = psNT)
821 	{
822 		psPRes = nullptr;
823 		for (psRes = psT->psRes; psRes; psRes = psNRes)
824 		{
825 			ASSERT(psRes != nullptr, "resReleaseBlockData: null pointer passed into loop");
826 
827 			if (psRes->blockID == blockID)
828 			{
829 				if (psRes->usage == 0)
830 				{
831 					debug(LOG_NEVER, "resReleaseBlockData: %s resource: %s(%04x) not used", psT->aType, psRes->aID,
832 					      psRes->HashedID);
833 				}
834 				if (psT->release != nullptr)
835 				{
836 					psT->release(psRes->pData);
837 				}
838 
839 				psNRes = psRes->psNext;
840 				free(psRes);
841 
842 				if (psPRes == nullptr)
843 				{
844 					psT->psRes = psNRes;
845 				}
846 				else
847 				{
848 					psPRes->psNext = psNRes;
849 				}
850 			}
851 			else
852 			{
853 				psPRes = psRes;
854 				psNRes = psRes->psNext;
855 			}
856 		}
857 
858 		psNT = psT->psNext;
859 	}
860 }
861