# when switching levels. This isn't required, but it will make the generated # code smaller as it avoids inlining many additional copies of the main loops. # Since there isn't a jump we can easily replace here, we'll replace a # "mov si, 8" instruction with a smaller variant and a tail-call. b.patch('0E3B:2C04', 'mov si, 8', length=1) b.patch('0E3B:2C05', 'call 0x2C07', length=1) b.patch('0E3B:2C06', 'ret') # Break control flow at remaining locations that wait for keyboard for call_site in [ '0E3B:3B8D', # Menu for "?" key '0E3B:3B9A', # Menu for "ESC" key '0E3B:6921', # "To go back to the room you just left, press Enter" '0E3B:B5A7', # Computer disk terminal in world 4 ]: call_site = sbt86.Addr16(str=call_site) continue_at = call_site.add(1) subroutine = b.jumpTarget(call_site) b.patchAndHook( call_site, 'ret', 'g.hw->output.pushDelay(20);' 'g.proc->continueFrom(r, &sub_%X);' % continue_at.linear) b.patch(continue_at, 'call 0x%04x' % subroutine.offset, length=2) b.markSubroutine(continue_at) # Break control flow in the transporter animation loop, # it's too long to store in the output queue. video_blit_frame = sbt86.Addr16(str='0E3B:1700') for call_site in [ '0E3B:6E77', # Sewer -> Subway '0E3B:898B', # Subway -> Town '0E3B:958A', # Town -> Comp
basedir = sys.argv[1] b = sbt86.DOSBinary(os.path.join(basedir, 'menu.exe')) b.decl('#include <string.h>') bt_common.patchJoystick(b) bt_common.patchFramebufferTrace(b) b.hook(b.entryPoint, 'enable_framebuffer_trace = true;') # Time everything in this EXE, not just sound subroutines sbt86.Subroutine.clockEnable = True # Dynamic branch for cutscene sound effects b.patchDynamicBranch('019E:0778', [ sbt86.Addr16(str='019E:0788'), sbt86.Addr16(str='019E:07D0'), sbt86.Addr16(str='019E:07AB') ]) # This isn't actually self-modifying code, but some data stored in the code segment # will trigger some warnings that we can silence by marking the area explicitly as data. b.patchDynamicLiteral('019E:0517', length=2) b.patchDynamicLiteral('019E:0519', length=2) # The main menu is a subroutine with a single caller; # inline it to make cutting the control flow easier. Also disable framebuffer traces # while drawing the main menu, so we can draw it faster. b.patchAndHook('019E:012A', 'jmp 0x310', 'enable_framebuffer_trace = false;') b.patchAndHook('019E:034A', 'jmp 0x12d', 'enable_framebuffer_trace = true;')
b.patch('0D63:5CFB', 'jmp 0x5D14') # Remove model "A disk error has occurred" b.patch('0D63:5E3E', 'ret') # Remove modal "Insert disk 1" message before exiting back to menu b.patch('0D63:5FFD', 'jmp 0x6016') # Break control flow at remaining locations that wait for keyboard for call_site in [ '0D63:26D7', # Menu for "?" key '0D63:26E4', # Menu for "ESC" key '0D63:7F78', # Chip data editor (press "?" while holding a chip) '0D63:821D', # Help for chip data editor ("?" in editor) ]: call_site = sbt86.Addr16(str=call_site) continue_at = call_site.add(1) subroutine = b.jumpTarget(call_site) b.patchAndHook( call_site, 'ret', 'g.hw->output.pushDelay(20);' 'g.proc->continueFrom(r, &sub_%X);' % continue_at.linear) b.patch(continue_at, 'call 0x%04x' % subroutine.offset, length=2) b.markSubroutine(continue_at) # Inline the single-caller chip data editor so we can insert continuations # within the editor's help screen without losing control flow in the outer editor. b.patch('0D63:7F5F', 'jmp 0x7FB1') b.patch('0D63:8061', 'jmp 0x7F62') b.patch('0D63:8232', 'jmp 0x7F62') # Inline check_main_game_keys too, since the chip editor's outer event loop ends
bt_common.patchJoystick(b) bt_common.patchFramebufferTrace(b) b.hook(b.entryPoint, 'enable_framebuffer_trace = true;') # Go directly to the new-game cutscene after we get the SHW file reader setup b.patch('019E:00F8', 'jmp 0x1A6') # Launch the game once this cutscene ends b.patchAndHook('019E:04DB', 'ret', 'g.hw->exec("game.exe");') # Time everything in this EXE, not just sound subroutines sbt86.Subroutine.clockEnable = True # Dynamic branch for cutscene sound effects b.patchDynamicBranch('019E:0778', [ sbt86.Addr16(str='019E:0788'), sbt86.Addr16(str='019E:07D0'), sbt86.Addr16(str='019E:07AB') ]) # This isn't actually self-modifying code, but some data stored in the code segment # will trigger some warnings that we can silence by marking the area explicitly as data. b.patchDynamicLiteral('019E:0517', length=2) b.patchDynamicLiteral('019E:0519', length=2) # This binary uses wallclock time for some delays. # A save function is called before drawing the screen, to store a seconds timestamp. # Then, one of two fixed-duration delay functions are called to wait 4 seconds or 1 second # after the timestamp that was saved earlier. # Stub out the function which saves the wallclock time ref
import sbt86 import bt_common basedir = sys.argv[1] b = sbt86.DOSBinary(os.path.join(basedir, 'menu2.exe')) bt_common.patchJoystick(b) bt_common.patchFramebufferTrace(b) b.hook(b.entryPoint, 'enable_framebuffer_trace = true;') # Time everything in this EXE, not just sound subroutines sbt86.Subroutine.clockEnable = True # Dynamic branch for cutscene sound effects b.patchDynamicBranch('019E:068B', [ sbt86.Addr16(str='019E:069B'), sbt86.Addr16(str='019E:06E3'), sbt86.Addr16(str='019E:06BE') ]) # This isn't actually self-modifying code, but some data stored in the code segment # will trigger some warnings that we can silence by marking the area explicitly as data. b.patchDynamicLiteral('019E:0434', length=2) b.patchDynamicLiteral('019E:0436', length=2) # Remove the unneeded splashscreen and main menu b.patch('019E:0102', 'ret') # At the very end of the final cutscene, we're greeted with an # infinite loop. Break that loop with a delay. final_loop = sbt86.Addr16(str='019E:0221')