Skip to content

Instantly share code, notes, and snippets.

@anpage anpage/MW2_FUEL_PATCH.md Secret

Last active May 17, 2020
Embed
What would you like to do?
Patch to Fix Jet Fuel in MechWarrior 2 for DOS

Patch to Fix Jump Jet Fuel in MechWarrior 2 for DOS

This is a patch that attempts to fix an issue in the DOS version of MechWarrior 2 where your jump jet fuel will not recharge if your framerate is too high.

Notes

  • Your mech's fuel is stored as an integer value from 0 (0%) to 1820 (100% full).
  • The game uses a 182Hz hardware interrupt timer provided by Audio Interface Library to keep track of time, which increments a number by 1 each tick.
  • The game intends to give you 1 (0.05%) fuel every 4 ticks of the timer, meaning 45.5 (2.5%) fuel per second, whenever you aren't using the jump jets.
  • The game measures the time between the current frame and the last (delta time) by subtracting the timer's current value from the value during the last frame.
  • Each frame, the game calculates the amount of fuel to give you by dividing the ticks between frames by 4, so that 4 ticks equal 1 fuel.
  • If the timer ticks between frames is 3 or less, that division results in a value less than 1, and computery integer math rounds down to 0, giving you no fuel.
  • This means that if your frames are drawing quicker than 45.5 FPS, the game will not recharge your fuel.

Theory

This patch separates the delta time calculation out into a separate subroutine and ticks counter that will not reset if the result of the division is zero. This allows the timer more than one frame of time to increment until it reaches a value over 4, at which point it will increment your mech's fuel by the result of the division and reset the counter. At a high enough framerate, this value is usually 1 and results in 1 point of fuel every 4 ticks as the game intended.

Issues

  • I'm not 100% certain that the location where I put my subroutine is unused by the game. It's at the very end of a large block of null bytes, so worst case scenario is probably that it will only crash if some array is filled. More testing will be the easiest way to confirm this.
  • Currently, the counter does not get reset while fuel isn't recharging, meaning that as soon as you let off your jump jets, the counter will be at some ridiculously high value. I check for this and try to filter out unreasonably high values, but in theory this means the right rhythm of tapping your jump jets would give you back more fuel than you're spending. This can be fixed by resetting the counter at an appropriate location elsewhere in the code, but I just haven't implemented this yet.
  • I still don't fully understand how the game manages its time-related counters. I believe that the counter is being stored as a 4-byte unsigned int, which has a max value of 4,294,967,295 and at 182Hz, that gives about 23,599 seconds or 6.5 hours before an overflow. I don't know of anyone spending that long in a mission. Maybe that's why there's a time limit?
  • I haven't super thorouhgly tested the patch, so time will tell if it has any notieceable side-effects.

Patch

.IPS Patch Download
This can be applied with Lunar IPS

I used the MS-DOS 1.1 version of the game which came on a disc labeled Windows 95/MS-DOS 1.1. I don't know if there are any differences in 1.0 or other retail releases for DOS, but I've listed the checksum hashes for my MW2.EXE below.

  File: MW2.EXE
CRC-32: 8f5edc84
   MD4: 8f56c3d7d0f65e4c67aed2d324744b0c
   MD5: 055caf939d5efcf7d808058e212a7b23
 SHA-1: 9de84fa766674a3c52b6cf7e9aab7710ba683f07

Here is the assembly code for the patch with comments and addresses for where to write the bytecode. I personally used this online assembler to get the bytecode and made slight modifications to its output by hand.

; Write to 0x8114F in MW2.EXE file
START:
PUSH 0x82
CALL 0xFFFFC2EA ; Get elapsed ticks into EAX
ADD ESP, 0x4
CMP EAX, 182    ; Filter out huge values (hacky)
JA TOO_BIG
SHR EAX, 0x2    ; Divide by 4
JZ RETURN       ; If it's 0, don't reset delta time
PUSH EAX
PUSH 0x82
CALL 0xFFFFC310 ; Record last refuel tick
ADD ESP, 0x4
POP EAX
RETURN:
JMP 0xFFFC1779  ; Return to increment fuel
TOO_BIG:
PUSH 0x82
CALL 0xFFFFC2FD ; Record last refuel tick
ADD ESP, 0x4
XOR EAX, EAX    ; Clear EAX
JMP RETURN
; Write to 0x428E8 in MW2.EXE file
JMP 0xFFFC179E

Here's a diff between the original and patched .EXE files, in case someone wants to apply it manually with a hex editor.

$ diff <(xxd -g 1 MW2.EXE | sed 's/.\{18\}$//') <(xxd -g 1 MW2_FUEL.EXE | sed 's/.\{18\}$//')
17039c17039
< 000428e0: 00 7d 1a a1 e8 58 00 00 89 c2 c1 fa 1f c1 e2 02
---
> 000428e0: 00 7d 1a a1 e8 58 00 00 e9 62 e8 03 00 c1 e2 02
33045,33049c33045,33049
< 00081140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 00081150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 00081160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 00081170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 00081180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
---
> 00081140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68
> 00081150: 82 00 00 00 e8 ea c2 ff ff 83 c4 04 3d b6 00 00
> 00081160: 00 77 19 c1 e8 02 74 0f 50 68 82 00 00 00 e8 10
> 00081170: c3 ff ff 83 c4 04 58 e9 79 17 fc ff 68 82 00 00
> 00081180: 00 e8 fd c2 ff ff 83 c4 04 31 c0 eb ea 00 00 00

Bonus

There's an undocumented command line argument for MW2.EXE (not MECH2.EXE) that will limit your frameate: -f=[fps]

In my experience, setting this to 45 does allow your mech's jump jet fuel to recharge at all times, but it also seems to have some drawbacks. For me, audio was a little choppy and the in-game pause menu caused a freeze, not to mention being limited to a maximum of 45 FPS. Your mileage may vary, but you can try it for yourself with a command like this:

MW2.EXE -f=45 userstar.bwd oranscn1

If anyone finds this useful, this can be patched to be on by default so that you can run the game with the main menu (MECH2.EXE). It might also be possible to fix the in-game menu as well. Ideally, though, I'd personally rather patch the game to work correctly at a smooth, higher framerate.

Thanks

Disclaimer

I'm not an expert at reverse engineering or even writing assembly code. This was my first time writing any at all, so go easy on me.

THIS DOCUMENT AND THE PATCH WITHIN ARE WITHOUT WARRANTY

NetMech Patch

Here is the same fix for jumpjet fuel, ported to the multiplayer component, NetMech.

.IPS Patch Download
This can be applied with Lunar IPS

Remember to apply the patch to NETMW2.EXE, NOT NETMECH.EXE

The code for this patch is nearly identical to the singleplayer base game. The only differences are the address offsets. You can find the same relevant information for this patch below.

  File: NETMW2.EXE
CRC-32: 7fbe42d9
   MD4: b8085c45b134ba0e7f3cdf4ff64ac1b6
   MD5: 502d08280f45974e4c2b574cdc4e7e33
 SHA-1: a223ef54be1d4a46a0ec18f36e649e41fb53b67d
$ diff <(xxd -g 1 NETMW2.EXE | sed 's/.\{18\}$//') <(xxd -g 1 NETMW2_FUEL.EXE | sed 's/.\{18\}$//')
19335,19336c19335,19336
< 0004b860: 1d a1 a0 62 00 00 89 c2 c1 fa 1f c1 e2 02 1b c2
< 0004b870: c1 f8 02 8d 0c 03 89 8e c0 00 00 00 eb 0a c7 86
---
> 0004b860: 1d a1 a0 62 00 00 e9 95 19 04 00 90 90 90 90 90
> 0004b870: 90 90 90 8d 0c 03 89 8e c0 00 00 00 eb 0a c7 86
36129,36132c36129,36132
< 0008d200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 0008d210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 0008d220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
< 0008d230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
---
> 0008d200: 68 82 00 00 00 e8 05 ca ff ff 83 c4 04 3d b6 00
> 0008d210: 00 00 77 19 c1 e8 02 74 0f 50 68 82 00 00 00 e8
> 0008d220: 2b ca ff ff 83 c4 04 58 e9 3e e6 fb ff 68 82 00
> 0008d230: 00 00 e8 18 ca ff ff 83 c4 04 31 c0 eb ea 00 00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.