Categories
Uncategorized

Week 3: Reading Between the Bytes

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 — digging into array indices, stale pointers, and byte alignment bugs that were quietly corrupting things under the hood.
This 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.

Stride and Seek

Flying items rendering with garbled pixels when shrunk on screen. The cause was in blitToBitmapShrinkWithPalChange, which computed the destination row stride from destPixelWidth, the raw pixel width. But allocated bitmap rows are actually 8-byte aligned via getNormalizedByteWidth, 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.

Fix: compute destStride = getNormalizedByteWidth(destPixelWidth / 2) * 2 and use it instead of destPixelWidth when indexing destLine = &destBitmap[destY * destStride]

Before:

After:

 

The Defense Drift

Next was a crash in processEventEnableChampionAction, but the more dangerous part of this bug wasn’t the crash. _actionDefense is a lookup table indexed by action type, via _actionIndex, but the code was indexing it with curChampion->_actionDefense instead, the champion’s current defense value. That’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.

The real problem 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.

Fix: a one-character typo fix, replacing _actionDefense[curChampion->_actionDefense] with _actionDefense[curChampion->_actionIndex].

The Gimme Slot Bug

Next one came from the gimme debug command. Cmd_gimme loops over every slot for a given thing type, but unused slots have their first word set to thingNone and the loop wasn’t checking for that before passing them to getIconIndex, which calls into getWeaponInfo or getObjectType. Those ended up reading garbage from empty slots, sometimes crashing.

Fix: read the raw first word of each slot, and skip it with continue if it equals thingNone.

The Duplicate Item Bug

Another gimme bug. When it allocated a new item slot by copying thing data and bumping _thingCounts, it forgot to update dummyThing’s index to point at the new slot, still handing over the original thingIndex instead of thingCount. Two references ended up pointing at the same thing slot, causing duplicate item corruption and crashes whenever one reference was modified or deleted.

Fix: one line, dummyThing.setIndex(thingCount), after the allocation.

The Object Aspect Crash

Next one was inside drawObjectsCreaturesProjectilesExplosions, the function responsible for rendering objects, creatures, and projectiles in the dungeon view. It chained getObjectInfoIndex straight into _objectInfos[...]._objectAspectIndex and then into _objectAspects209[...], all in one expression, with no validation along the way. But getObjectInfoIndex can return an out-of-range value for invalid or corrupt things, and _objectAspectIndex itself can exceed k85_ObjAspectCount, causing crashes.

Fix: guard both indices before use, skipping the object with continue if either is out of range.

An Enum That Wasn’t One

Next one was about ActiveGroup::_directions, typed as Direction, an enum. But the field doesn’t actually store a single direction, it stores packed 2-bit direction values for up to 4 creatures combined together, built via getGroupValueUpdatedWithCreatureValue. That’s a bit-packed integer, not a valid enum value, and casting a packed integer to an enum type is undefined behavior in C++.

Fix: 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’ve been.

The Freeze That Wouldn’t Crash

This one ate up most of my week, and it didn’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’s backtrace wasn’t any more helpful either, it didn’t even surface the method actually responsible.
So 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 GroupMan, so I started going through its methods one by one, comparing them against the original reversed code to spot where our implementation had drifted.
That led to dropCreatureFixedPossessions, which iterates a fixedPossessions 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.

Fix: advance currFixedPossession = *fixedPossessions++ in both continue branches before skipping, so the loop always makes forward progress.

The Projectile Crash

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 initializeGraphicData, where the loop over 6 projectile scale levels was writing all 6 byte-counts to the same fixed offsets, derivedBitmapIndex, derivedBitmapIndex+6, derivedBitmapIndex+12, every iteration, missing + projectileScaleIndex. Only the last iteration’s values stuck, leaving every earlier entry sized incorrectly.

Fix: add + projectileScaleIndex to all three cache offsets, so each scale level writes to its own correct slot.

New Addition

Added a nuke debugger command. Killing monsters manually just to clear a path for testing wasted time, so nuke finds the group directly in front of the party via mapCoordsAfterRelMovement and groupGetThing, then removes it instantly with groupDelete. No gameplay impact.

Looking Ahead, Faster

I’m going to push to finish the DM engine faster going forward, since there are more engines waiting in line after this one. I’ll do my best to wrap it up as quickly as I can, though I’ll admit, it’s turned out to be far more complex and broken than I initially expected.

Thank you for reading. See You in the Next One šŸ™‚


 

Categories
Uncategorized

Week 2: More Bug Fixes in the DM Engine

Hi everyone! Following up on Week 1, most of this week was spent resolving PVS-Studio warnings in the DM engine. Many of these warnings were responsible for crashes or pointed to code paths that could lead to unstable behavior.

By the end of the week, I was able to reduce the warning count from 103 to 13, with the remaining ones being false positives. With the static analysis issues largely addressed, I can now focus fully on gameplay-related bugs, fixing the remaining crashes, and implementing the remaining methods.

One such crash was in DisplayMan::drawDoorButton():
doorButtonOrdinal = kDMDerivedBitmapFirstDoorButton + (doorButtonOrdinal * 2) + ((doorButton != kDMDoorButtonD3R) ? 0 : doorButton - 1)
The existing logic worked for most door button values but failed when doorButton = 0, leading to an incorrect offset and eventually a crash. I corrected the expression to ((!doorButton) ? 0 : doorButton - 1), fixing the crash and restoring the intended behavior.

Another crash occurred while picking up a coin placed on a pressure plate. The game would immediately crash when processing the resulting door animation. After tracing the issue, I found that processEventDoorAnimation() unconditionally queried creature attributes before checking whether a creature group actually existed on that square. Reordering the checks fixed the heap buffer overflow and matched the behavior of the reversed source code.

I also fixed several other crashes and memory safety issues in the DM engine, including an incorrect bitmask in ProjExpl::projectileDelete() and an out-of-bounds champion access in drawPanelFoodWaterPoisoned().

Towards the end of the week, I shifted my focus back to the GUI, where I fixed a bug in the launcher that caused the collapsed state of groups to be lost when switching between views.
The fix prevents LauncherSimple::updateListing() from saving the collapsed group state during the initial build, avoiding an unnecessary overwrite of the already saved configuration and correctly preserving the collapsed groups when switching views.

I also added support for stopping fluid scrolling animations with a click, allowing the first click to stop the ongoing animation and the next click to perform the intended selection in lists, grids, and scroll containers.

Additionally, I added configuration support for fluid scrolling, allowing it to be toggled from the options menu, as the feature did not turn out to be a pleasant experience for everyone.

That wraps up this week’s progress. Next week, I’ll continue working on the remaining gameplay issues and implementing the remaining methods. Thanks for reading, and see you in the next update!

Categories
Uncategorized

Week 1: Chasing Crashes in the DM Engine

Hi everyone! Now that the GSoC coding period has begun, I moved from working primarily on the GUI to investigating crashes and gameplay bugs in the DM engine while becoming more familiar with its internals.

The first issue I looked at was a progression blocker near the start of the game. After assembling my party in the Hall of Champions, I found, as Strangerke had pointed out, that the gate to the dungeon remained shut despite stepping on the floor sensors. The issue turned out to be an incorrect early return in the sensor effect logic, preventing the pressure plate from triggering the door-opening event.

Another crash occurred when highlighting a selection in the game’s save dialog. The cause was a simple parameter ordering mistake in a box constructor used to draw the selection highlight.

Another task involved implementing EventManager::highlightScreenBox(), enabling visual highlighting for UI elements such as the movement controls.

 

Next was a save-loading bug, where the first in-game load attempt would start a new game instead of loading the selected save. Setting the correct game mode when a valid save slot was chosen resolved the issue.

Next was a stairs transition crash when moving from level 0 to level 1. The crash is currently fixed, but Sev explained that I will need to revisit this area and make the implementation aware of endianness and struct padding to achieve a fully portable solution.

 

Next was a crash that occurred whenever certain creatures, such as mummies, entered the player’s field of view.

 

Another bug involved a teleporter that was intended to appear as a normal wall. Incorrect bitfield masks caused it to render instead as a room filled with teleporter, hiding the key that was supposed to be visible on the ground.

Before:

After:

 

Next was a crash in the game’s debugger, where entering an invalid gimme command such as gimme gold key instead of the expected gimme GOLD KEY would result in a crash.

Another set of fixes involved correcting several incorrectly ordered CLIP calls across the codebase, which were responsible for a number of crashes.

I also resolved several warnings reported by PVS-Studio, addressing a number of potential issues in the codebase.

Now that I’m gaining momentum, I hope to tackle many more bugs in the coming weeks while continuing to resolve PVS-Studio warnings and improve the overall stability of the DM engine. That’s all for this week. See you in the next update!

Categories
Uncategorized

About Me and My GSoC 2026 Project

Hello everyone! My name is Mohit, and I’m a 2nd-year Engineering student. I’m excited to participate in Google Summer of Code 2026 with ScummVM and very thankful for this opportunity.

I was drawn to ScummVM because of my interest in C++ and low-level programming. Working with a large legacy codebase has already been a great learning experience during my contributions so far.

My project this summer focuses on completing the implementation for four incomplete engines in ScummVM. The main challenges involve architectural issues, missing core features, and gameplay bugs.

Excited to learn more throughout the summer while doing my best to successfully deliver the project and share my progress along the way.

Stay tuned for upcoming blogs and progress updates throughout the summer!