Introduction

This is a Daodan-compatible, Windows dynamic library (DLL) to fix a few longstanding bugs in the Bungie Scripting Language (BSL) used by Oni. This page also documents these bugs, their causes, and how they have been fixed by the DLL.

The Else Bug

Background

When executing an if statement, the engine sets a special else_scope variable to the scope we’re entering. Then, when we reach else, it checks that we’re in that same else_scope, and if so, inverts the state of the scope (from enabled to disabled or vice versa). This check is necessary because we might be executing a completely different else now, inside the block of the previous one, and if so, we should NOT change the state of the scope. For example:

if (1) {
	# Do nothing...
} else {
	if (1) {
		# This shouldn't run.
	} else {
		# Neither should this.
	}
}

When execution reaches the inner if, the engine in this case does NOT change the else_scope, because the current scope is disabled (it specifically checks for this scenario). So the else won’t invert the state of the scope, because this isn’t the scope it’s looking for, and everything works as expected.

Problem

But, as I found out last week, this doesn’t always work… Turns out that the else_scope is never decreased, only increased. This example demonstrates the problem:

if (1) {
	if (0) {
		# This shouldn't run.
	} else {
		# Do nothing...
	}
} else {
	# This WILL run, surprisingly!
}

Here, the inner if sets the else_scope to be the scope of its block. This is normal. But, once it’s time to execute the outer else, the else_scope is out of sync! Remember that else does nothing at all if the current scope is not the else_scope it’s expecting, so we keep the scope enabled and end up executing whatever is inside the else block too.

Solution

We must revert the else_scope when leaving a scope, so that it will always align with the current scope. If we could simply decrement else_scope at that point, we could even fit the new bytes just above the original code! Unfortunately, this is not enough. ☹️ Since entering a scope doesn’t always set the else_scope, we too must do it conditionally when leaving.

The easiest way I found was to just duplicate the code from when we enter a new scope. This requires finding some “code cave” elsewhere, because there is not enough space inline, but seems to work. I’ve been running a test script with this change enabled, which tests all sorts of if constructions, and everything is coming out green.

Assembly

For those interested, replace Oni.exe+7A5D6 by a jump to your own routine and have it do this:

TODO: Optimize, explain.

	dec word ptr [eax+184]

  mov ebx, edi
  mov ebx, [eax+184]
  and ebx, FFFF
  mov bl, [ebx+eax+185]
  test bl, bl
  jz exit

  dec word ptr [eax+196]

exit:
  ret

The Return Bug

Background

Whenever BSL reaches a return statement, even if it is inside an if/else block that is not executing, it will exit the function immediately. For example: