--- a/src/Libraries/2D/Source/RSD/rsdcvt.c +++ b/src/Libraries/2D/Source/RSD/rsdcvt.c @@ -37,6 +37,7 @@ * */ #include +#include #include #include "grs.h" #include "bitmap.h" @@ -47,6 +48,14 @@ uchar *grd_unpack_buf=NULL; +// Set by debug_dump_critter_animation() (engine-debug-tools/, root +// README.md's "Debugging" section) around its own gr_rsd8_convert() calls, +// so a trace of this decode's internals - which path ran, whether it wrote +// fewer bytes than the bitmap's own area, and (gr_rsd8_unpack(), +// RSDUnpack.c) each opcode consumed - prints only for those calls, not +// every RSD8 decode in the game. Debug-build only. +int qs_debug_rsd8_trace = 0; + /*************************************************/ /* Puts 0's in place of skips and pads with 0's. */ /* i.e., grd_unpack_buf is entirely overwritten. */ @@ -138,6 +147,15 @@ } } rsd_done: + if (qs_debug_rsd8_trace) { + long written = (long)(p_dst - dbm->bits); + long expected = (long)dbm->row * (long)dbm->h; + fprintf(stderr, + "[debug rsd8] path=%s sbm(w=%d h=%d row=%d) dbm(row=%d h=%d) " + "written=%ld expected=%ld short_by=%ld\n", + (dbm->w == dbm->row) ? "fast" : "slow", sbm->w, sbm->h, sbm->row, + dbm->row, dbm->h, written, expected, expected - written); + } if ((over_run = (dbm->bits+dbm->row*dbm->h)-p_dst)) { assert(over_run > 0); LG_memset (p_dst, kSkipColor, over_run); --- a/src/Libraries/2D/Source/RSD/RSDUnpack.c +++ b/src/Libraries/2D/Source/RSD/RSDUnpack.c @@ -22,6 +22,7 @@ // #include "grs.h" +#include #include //---------------------------------------------------------------------------- @@ -34,6 +35,10 @@ short count,count2; ushort longcode; uint32_t longval, *longdest, *longsrc; + + extern int qs_debug_rsd8_trace; // rsdcvt.c - engine-debug-tools/, root README.md's "Debugging" section + uchar *src_start = src, *dest_start = dest; + long op_index = 0; do { @@ -42,6 +47,7 @@ { count = *(src++); // get count val = *(src++); // get val + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=run count=%d val=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count, (int)val); if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { @@ -63,6 +69,7 @@ else if (code<0x80) // dump (copy) bytes { count = code; + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=dump count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count); if (code>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { count2 = count>>2; @@ -84,6 +91,7 @@ { count = code & 0x007f; // clear high byte val = longval = 0L; + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=skip count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count); if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { @@ -105,11 +113,15 @@ longcode = * (ushort *) src; src += 2L; - if (!longcode) break; // done? + if (!longcode) { + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=terminator\n", op_index++, (long)(src-src_start), (long)(dest-dest_start)); + break; // done? + } else if (longcode<0x8000) // skip (zero) { count = longcode; val = longval = 0L; + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longskip count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count); if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { @@ -129,6 +141,7 @@ else if (longcode<0xC000) // dump (copy) { count = longcode & 0x7fff; // clear high bit + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longdump count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count); if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { @@ -151,6 +164,7 @@ { count = longcode & 0x3fff; val = *(src++); // get val + if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longrun count=%d val=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count, (int)val); if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff { --- a/src/GameSrc/objsim.c +++ b/src/GameSrc/objsim.c @@ -1979,11 +1979,43 @@ } if (fd->bm.type == BMT_RSD8) { - if (gr_rsd8_convert(&fd->bm, &decoded) != 0 /* GR_UNPACK_RSD8_OK */) { + extern int qs_debug_rsd8_trace; // rsdcvt.c - engine-debug-tools/, root README.md's "Debugging" section + uchar *first_copy; + size_t nbytes; + int rsd8_ok; + + qs_debug_rsd8_trace = 1; + rsd8_ok = gr_rsd8_convert(&fd->bm, &decoded) == 0 /* GR_UNPACK_RSD8_OK */; + qs_debug_rsd8_trace = 0; + if (!rsd8_ok) { fprintf(stderr, "[debug dump] gr_rsd8_convert frame %d failed\n", frame); RefUnlock(MKREF(s_lastCritterId, frame)); continue; } + + // Re-decode the same compressed source (fd->bm.bits, unchanged + // by the first call - only grd_unpack_buf, the shared + // decode-output buffer, gets overwritten) a second time and + // compare: decode runs back to back on this single thread, with + // nothing else able to touch fd->bm.bits or grd_unpack_buf in + // between, so a byte-for-byte match here means any garbled + // pixels are a deterministic property of this exact compressed + // stream (bad source data, or a decoder bug it triggers), not a + // one-off from a resource-cache slot being reused mid-decode. + nbytes = (size_t)decoded.w * (size_t)decoded.h; + first_copy = malloc(nbytes); + if (first_copy != NULL) { + memcpy(first_copy, decoded.bits, nbytes); + + qs_debug_rsd8_trace = 1; + gr_rsd8_convert(&fd->bm, &decoded); + qs_debug_rsd8_trace = 0; + + fprintf(stderr, "[debug dump] frame %d repeat-decode of bm.bits=%p %s\n", frame, + (void *)fd->bm.bits, memcmp(first_copy, decoded.bits, nbytes) == 0 ? "identical" : "DIFFERS"); + free(first_copy); + } + bits = decoded.bits; w = decoded.w; h = decoded.h;