{"id":108,"date":"2026-06-16T12:40:38","date_gmt":"2026-06-16T12:40:38","guid":{"rendered":"https:\/\/blogs.scummvm.org\/mohit\/?p=108"},"modified":"2026-06-16T14:37:48","modified_gmt":"2026-06-16T14:37:48","slug":"week-3-reading-between-the-bytes","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/mohit\/2026\/06\/16\/week-3-reading-between-the-bytes\/","title":{"rendered":"Week 3: Reading Between the Bytes"},"content":{"rendered":"<p>Hi, this is week 3 of my GSoC journey. After spending the last two weeks chasing crashes across the DM engine, this week turned into something more like detective work \u2014 digging into array indices, stale pointers, and byte alignment bugs that were quietly corrupting things under the hood.<br \/>\nThis week, I faced freezes along with crashes. Some of these bugs only revealed themselves when stress-testing specific actions, like throwing explosives at a creature group, which would lock up the game entirely instead of just throwing an error.<\/p>\n<h4><span style=\"color: #993300\">Stride and Seek<\/span><\/h4>\n<p>Flying items rendering with garbled pixels when shrunk on screen. The cause was in <code>blitToBitmapShrinkWithPalChange<\/code>, which computed the destination row stride from <code>destPixelWidth<\/code>, the raw pixel width. But allocated bitmap rows are actually 8-byte aligned via <code>getNormalizedByteWidth<\/code>, so the real stride is wider than that. Using the raw width meant every row started at the wrong offset, bleeding into the next and corrupting pixel data.<\/p>\n<p><strong>Fix:<\/strong> compute <code>destStride = getNormalizedByteWidth(destPixelWidth \/ 2) * 2<\/code> and use it instead of <code>destPixelWidth<\/code> when indexing <code>destLine = &amp;destBitmap[destY * destStride]<\/code><\/p>\n<p><strong>Before:<\/strong><\/p>\n<div style=\"width: 500px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-108-1\" width=\"500\" height=\"300\" loop autoplay preload=\"auto\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/before_blitToBitmapShrinkWithPalChange.mp4?_=1\" \/><a href=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/before_blitToBitmapShrinkWithPalChange.mp4\">https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/before_blitToBitmapShrinkWithPalChange.mp4<\/a><\/video><\/div>\n<p><strong>After:<\/strong><\/p>\n<div style=\"width: 500px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-108-2\" width=\"500\" height=\"300\" loop autoplay preload=\"auto\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/after_blitToBitmapShrinkWithPalChange.mp4?_=2\" \/><a href=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/after_blitToBitmapShrinkWithPalChange.mp4\">https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/after_blitToBitmapShrinkWithPalChange.mp4<\/a><\/video><\/div>\n<p>&nbsp;<\/p>\n<h4><span style=\"color: #993300\">The Defense Drift<\/span><\/h4>\n<p>Next was a crash in <code>processEventEnableChampionAction<\/code>, but the more dangerous part of this bug wasn&#8217;t the crash. <code>_actionDefense<\/code> is a lookup table indexed by action type, via <code>_actionIndex<\/code>, but the code was indexing it with <code>curChampion-&gt;_actionDefense<\/code> instead, the champion&#8217;s current defense value. That&#8217;s an arbitrary runtime number, not a valid index, so when it grew large enough to exceed the table size, the read went out of bounds and crashed outright. That part was at least loud and easy to notice.<\/p>\n<p>The <strong>real problem<\/strong> showed up when the stat stayed within bounds. The lookup would quietly succeed, but against the wrong table entry, subtracting an incorrect defense bonus every time a combat action ended. There was no crash, no error, nothing to flag it. Just a small wrong number applied again and again, every fight, every action. Over enough time, that drift compounded into a champion whose defense stat had wandered far from what it should be, sometimes ending up nearly immortal, sometimes alarmingly fragile, with nothing in the logs to explain why.<\/p>\n<p><strong>Fix:<\/strong> a one-character typo fix, replacing <code>_actionDefense[curChampion-&gt;_actionDefense]<\/code> with <code>_actionDefense[curChampion-&gt;_actionIndex]<\/code>.<\/p>\n<h4><span style=\"color: #993300\">The Gimme Slot Bug<\/span><\/h4>\n<p>Next one came from the gimme debug command. <code>Cmd_gimme<\/code> loops over every slot for a given thing type, but unused slots have their first word set to <code>thingNone<\/code> and the loop wasn&#8217;t checking for that before passing them to <code>getIconIndex,<\/code> which calls into <code>getWeaponInfo<\/code> or <code>getObjectType<\/code>. Those ended up reading garbage from empty slots, sometimes crashing.<\/p>\n<p><strong>Fix:<\/strong> read the raw first word of each slot, and skip it with continue if it equals <code>thingNone<\/code>.<\/p>\n<h4><span style=\"color: #993300\">The Duplicate Item Bug<\/span><\/h4>\n<p>Another gimme bug. When it allocated a new item slot by copying thing data and bumping <code>_thingCounts<\/code>, it forgot to update dummyThing&#8217;s index to point at the new slot, still handing over the original <code>thingIndex<\/code> instead of <code>thingCount<\/code>. Two references ended up pointing at the same thing slot, causing duplicate item corruption and crashes whenever one reference was modified or deleted.<\/p>\n<p><strong>Fix:<\/strong> one line, <code>dummyThing.setIndex(thingCount)<\/code>, after the allocation.<\/p>\n<h4><span style=\"color: #993300\">The Object Aspect Crash<\/span><\/h4>\n<p>Next one was inside <code>drawObjectsCreaturesProjectilesExplosions<\/code>, the function responsible for rendering objects, creatures, and projectiles in the dungeon view. It chained <code>getObjectInfoIndex<\/code> straight into <code>_objectInfos[...]._objectAspectIndex<\/code> and then into <code>_objectAspects209[...]<\/code>, all in one expression, with no validation along the way. But <code>getObjectInfoIndex<\/code> can return an out-of-range value for invalid or corrupt things, and <code>_objectAspectIndex<\/code> itself can exceed <code>k85_ObjAspectCount<\/code>, causing crashes.<\/p>\n<p><strong>Fix:<\/strong> guard both indices before use, skipping the object with continue if either is out of range.<\/p>\n<h4><span style=\"color: #993300\">An Enum That Wasn&#8217;t One<\/span><\/h4>\n<p>Next one was about <code>ActiveGroup::_directions<\/code>, typed as Direction, an enum. But the field doesn&#8217;t actually store a single direction, it stores packed 2-bit direction values for up to 4 creatures combined together, built via <code>getGroupValueUpdatedWithCreatureValue<\/code>. That&#8217;s a bit-packed integer, not a valid enum value, and casting a packed integer to an enum type is undefined behavior in C++.<\/p>\n<p><strong>Fix:<\/strong> change the field type from Direction to uint16, and remove the (Direction) casts at every assignment site, letting the packed value be stored and manipulated as a plain integer like it should&#8217;ve been.<\/p>\n<h4><span style=\"color: #993300\">The Freeze That Wouldn&#8217;t Crash<\/span><\/h4>\n<p><strong>This one<\/strong> ate up most of my week, and it didn&#8217;t go down easy. The game would freeze, not crash, whenever explosives were thrown at certain creature groups. No ASan output to point at anything, since nothing was actually invalid memory. GDB&#8217;s backtrace wasn&#8217;t any more helpful either, it didn&#8217;t even surface the method actually responsible.<br \/>\nSo it came down to reasoning it out manually. The freeze only ever happened when throwing explosives at a group with loot to drop, which meant something to do with damaging groups and handing out their possessions. That pointed toward <code>GroupMan<\/code>, so I started going through its methods one by one, comparing them against the original reversed code to spot where our implementation had drifted.<br \/>\nThat led to <code>dropCreatureFixedPossessions<\/code>, which iterates a <code>fixedPossessions<\/code> pointer array to hand out loot. Two early-exit continue paths, one for skipping a random drop, one for when no unused thing slot was available, both forgot to advance the pointer before looping back. Without that advance, the loop kept re-reading the exact same array entry forever, spinning in place instead of crashing.<\/p>\n<p><strong>Fix:<\/strong> advance <code>currFixedPossession = *fixedPossessions++<\/code> in both continue branches before skipping, so the loop always makes forward progress.<\/p>\n<h4><span style=\"color: #993300\">The Projectile Crash<\/span><\/h4>\n<p>Next one started simple, throw a sword or any projectile, and the game crashed. No obvious cause at first. Going through the methods from the GDB backtrace one by one eventually led to <code>initializeGraphicData<\/code>, where the loop over 6 projectile scale levels was writing all 6 byte-counts to the same fixed offsets, <code>derivedBitmapIndex<\/code>, <code>derivedBitmapIndex+6<\/code>, <code>derivedBitmapIndex+12<\/code>, every iteration, missing <code>+ projectileScaleIndex<\/code>. Only the last iteration&#8217;s values stuck, leaving every earlier entry sized incorrectly.<\/p>\n<p><strong>Fix:<\/strong> add <code>+ projectileScaleIndex<\/code> to all three cache offsets, so each scale level writes to its own correct slot.<\/p>\n<h4><span style=\"color: #993300\">New Addition<\/span><\/h4>\n<p>Added a <code>nuke<\/code> debugger command. Killing monsters manually just to clear a path for testing wasted time, so <code>nuke<\/code> finds the group directly in front of the party via <code>mapCoordsAfterRelMovement<\/code> and <code>groupGetThing<\/code>, then removes it instantly with <code>groupDelete<\/code>. No gameplay impact.<\/p>\n<div>\n<div data-test-render-count=\"1\">\n<div class=\"group\">\n<div class=\"contents\">\n<div class=\"group relative relative pb-3\" data-is-streaming=\"false\">\n<div class=\"font-claude-response relative leading-[1.65rem] [&amp;_pre&gt;div]:bg-bg-000\/50 [&amp;_pre&gt;div]:border-0.5 [&amp;_pre&gt;div]:border-border-400 [&amp;_.ignore-pre-bg&gt;div]:bg-transparent [&amp;_.standard-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.standard-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8 [&amp;_.progressive-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.progressive-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8\">\n<div>\n<div class=\"standard-markdown grid-cols-1 grid [&amp;_&gt;_*]:min-w-0 gap-3 standard-markdown\">\n<h3 class=\"font-claude-response-body break-words whitespace-normal\"><span style=\"color: #993300\"><strong>Looking Ahead, Faster<\/strong><\/span><\/h3>\n<\/div>\n<p>I&#8217;m going to push to finish the DM engine faster going forward, since there are more engines waiting in line after this one. I&#8217;ll do my best to wrap it up as quickly as I can, though I&#8217;ll admit, it&#8217;s turned out to be far more complex and broken than I initially expected.<\/p>\n<\/div>\n<p>Thank you for reading. See You in the Next One \ud83d\ude42<\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Hi, this is week 3 of my GSoC journey. After spending the last two weeks chasing crashes across the DM engine, this week turned into something more like detective work \u2014 digging into array indices, stale pointers, and byte alignment bugs that were quietly corrupting things under the hood. This week, I faced freezes along [&hellip;]<\/p>\n","protected":false},"author":30,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-108","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/108","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/users\/30"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/comments?post=108"}],"version-history":[{"count":47,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/108\/revisions"}],"predecessor-version":[{"id":153,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/108\/revisions\/153"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/media?parent=108"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/categories?post=108"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/tags?post=108"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}