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