Hi All.
There has been progress in changing the assembly code of the game to make small changes.
I want these to be well documented en therefore is this topic.
Please consider this topic as a READ ONLY. Each modification has its own topic where you can post your reaction.
Most changes are no more than small modifications. The first real mod is the Needle colour mod. That adds new code and has possibly not yet discovered bugs.
NB: The new original with separate executables for each graphic option is not a modification of the code.
Changes were made for:
- Ferrari edition main menu buttons (http://forum.stunts.hu/index.php?topic=3872.msg82710#msg82710)
- changing the default car part one (the easy one) (http://forum.stunts.hu/index.php?topic=3872.msg82711#msg82711)
- changing the default car part two (the tricky one) (http://forum.stunts.hu/index.php?topic=3872.msg82712#msg82712)
- Changing the colour of the needle part one (redirecting the code) (http://forum.stunts.hu/index.php?topic=3872.msg82717#msg82717)
- Changing the colour of the needle part two (adding a feature) (http://forum.stunts.hu/index.php?topic=3872.msg82718#msg82718)
After the first Ferrari edition (which was made by changing the binary directly) we searched for the parts in the code to make it easier to reproduce.
The menu button locations were the easiest to do. They were already found in the code and just needed new coordinates.
The original code can be reviewed here:
https://bitbucket.org/dreadnaut/restunts/src/aa1e714a66f8f9bd0d78bb1c0c3ab6b69252721d/src/restunts/asmorig/dseg.asm#lines-2141 (https://bitbucket.org/dreadnaut/restunts/src/aa1e714a66f8f9bd0d78bb1c0c3ab6b69252721d/src/restunts/asmorig/dseg.asm#lines-2141)
menu_buttons_x1 dw 105
dw 66
dw 5
dw 190
dw 255
menu_buttons_x2 dw 208
dw 107
dw 67
dw 253
dw 312
menu_buttons_y1 dw 119
dw 77
dw 114
dw 76
dw 116
menu_buttons_y2 dw 197
dw 120
dw 170
dw 122
dw 166
These give the four sides of each of the 5 the buttons. They could be modified to new locations and no further adaptation was needed to make it work.
The new code looks like this.
menu_buttons_x1 dw 128
dw 64
dw 0
dw 192
dw 256
menu_buttons_x2 dw 191
dw 127
dw 63
dw 255
dw 319
menu_buttons_y1 dw 185
dw 185
dw 185
dw 185
dw 185
menu_buttons_y2 dw 199
dw 199
dw 199
dw 199
dw 199
You can see that the y values for all buttons are the same now because in the Ferrari edition all buttons are at the bottom of the screen.
Changing the Default car was more difficult. It looked straight forward at first because of the easy one that we found in the code.
The easy one was well documented by clvn and dstien when working on the restunts project and marked with "set_default_car" and the letters identified
https://bitbucket.org/dreadnaut/restunts/src/aa1e714a66f8f9bd0d78bb1c0c3ab6b69252721d/src/restunts/asmorig/seg000.asm#lines-7329 (https://bitbucket.org/dreadnaut/restunts/src/aa1e714a66f8f9bd0d78bb1c0c3ab6b69252721d/src/restunts/asmorig/seg000.asm#lines-7329)
set_default_car proc far
mov gameconfig.game_playercarid, 43h ; 'C'
loc_146E9:
mov gameconfig.game_playercarid+1, 4Fh ; 'O'
loc_146EE:
mov gameconfig.game_playercarid+2, 55h ; 'U'
loc_146F3:
mov gameconfig.game_playercarid+3, 4Eh ; 'N'
loc_146F8:
mov gameconfig.game_playermaterial, 0
loc_146FD:
mov gameconfig.game_opponenttype, 0
loc_14702:
mov gameconfig.game_opponentmaterial, 0
loc_14707:
mov gameconfig.game_playertransmission, 1
loc_1470C:
mov gameconfig.game_opponentcarid, 0FFh
locret_14711:
retf
set_default_car endp
The default car is the Lamborghini Countach and the game will not run when the car is not in the game directory.
Changing this value changes the default car to, in this case, the Ferrari GTO. But the came still crashed when the car was not available.
The new code looks like:
set_default_car proc far
mov gameconfig.game_playercarid, 46h ; 'F'
loc_146E9:
mov gameconfig.game_playercarid+1, 47h ; 'G'
loc_146EE:
mov gameconfig.game_playercarid+2, 54h ; 'T'
loc_146F3:
mov gameconfig.game_playercarid+3, 4Fh ; 'O'
loc_146F8:
mov gameconfig.game_playermaterial, 0
loc_146FD:
mov gameconfig.game_opponenttype, 0
loc_14702:
mov gameconfig.game_opponentmaterial, 0
loc_14707:
mov gameconfig.game_playertransmission, 1
loc_1470C:
mov gameconfig.game_opponentcarid, 0FFh
locret_14711:
retf
set_default_car endp
Because the game kept crashing when the Countach was not present, there had to be another location where the game looked for the file.
We could find it in the binary but not in the code, and it took a while to figure out where it was.
We had to look for COUN or CARCOUN, that much we knew. But all hits when searching on coun were either aCarcoun that broke the game when changed or referred to COUNter or COUNtry. There were no other Hex coded versions found like the first.
Still the answer was found in aCarcoun and aCarcoun_0.
https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/dseg.asm#lines-1899 (https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/dseg.asm#lines-1899)
https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/dseg.asm#lines-3980 (https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/dseg.asm#lines-3980)
aCarcoun db 99 ; 'C'
db 97 ; 'A'
db 114 ; 'R'
db 99 ; 'C'
db 111 ; 'O'
db 117 ; 'U'
db 110 ; 'N'
db 0
Now we knew why we could find it in the binary but not in the code. The letters were separated in individual ASCII coded numbers.
After that, changing was possible, and the new code for aCarcoun and aCarcoun_0 looks like this.
aCarcoun db 99 ; 'C'
db 97 ; 'A'
db 114 ; 'R'
db 102 ; 'F'
db 103 ; 'G'
db 116 ; 'T'
db 111 ; 'O'
db 0
That was the last unknown for the Ferrari edition. With these modifications, we can make all future Mods available for the Ferrari edition if needed/wanted, unless they affect this code in another way.
For the configurable needle colour, the initial implementation was very simple. Near the middle of seg005.asm, there's the label loc_23456. At this point, the game engine calculates how the needle is to be drawn by passing the needle axis coordinates as one point and the corresponding spoke for the current speed as the second point and then calling a function that draws a line between them. This functions also requests a line colour as a parameter, so Stunts was originally passing a variable that, reaching this point, was always valued at 15 (white). The same thing is later done for the tachometer:
https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/seg005.asm#lines-2710 (https://bitbucket.org/dreadnaut/restunts/src/master/src/restunts/asmorig/seg005.asm#lines-2710)
loc_23456:
cmp [bp+var_6], 0
jnz short loc_23485
mov ax, si
shl ax, 1
mov [bp+var_20], ax
push meter_needle_color ; This variable in Stunts original code always ended up with a value of 15
mov bx, ax
mov al, (simd_player.spdpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.spdpoints[bx]
push ax
push simd_player.spdcenter.y2
push simd_player.spdcenter.x2
call preRender_line
add sp, 0Ah
loc_23485:
mov ax, di
shl ax, 1
mov [bp+var_20], ax
push meter_needle_color ; Again, same colour is passed for the tachometer
mov bx, ax
mov al, (simd_player.revpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.revpoints[bx]
push ax
push simd_player.revcenter.y2
push simd_player.revcenter.x2
call preRender_line
add sp, 0Ah
mov al, [bp+var_2]
cbw
or ax, ax
jz short loc_234BE
cmp ax, 2 ; st. whl. position flag
jz short loc_234EC
jmp short loc_234DE
; align 2
db 144
I noticed that there exists a structure called simd_player, which contains the car information from the "simd" chunk in the corresponding CAR*.RES file for the current player car. Individual fields from this structure could be used as variables, so I found the reference name for an unused car configuration that's known in CarWorks as "Red #5". I picked this one because all original cars have this value set to 16, which is the closest to a perfect white (15) in Stunts palette. It's actually just slightly darker. This way, after the patch, old cars would continue to display white needles. So all we had to do was replace meter_needle_color with simd_player.field_A6+8, which is a pointer to Red #5. The resulting code goes as follows:
loc_23456:
cmp [bp+var_6], 0
jnz short loc_23485
mov ax, si
shl ax, 1
mov [bp+var_20], ax
; Patch #1 ----*
; Replaced needle colour for speed-o-meter
; Original code:
;push meter_needle_color
push simd_player.field_A6+8
mov bx, ax
mov al, (simd_player.spdpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.spdpoints[bx]
push ax
push simd_player.spdcenter.y2
push simd_player.spdcenter.x2
call preRender_line
add sp, 0Ah
loc_23485:
mov ax, di
shl ax, 1
mov [bp+var_20], ax
; Replaced needle colour for RPM meter
; Original code:
;push meter_needle_color
push simd_player.field_A6+8
mov bx, ax
mov al, (simd_player.revpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.revpoints[bx]
push ax
push simd_player.revcenter.y2
push simd_player.revcenter.x2
call preRender_line
add sp, 0Ah
mov al, [bp+var_2]
cbw
or ax, ax
jz short loc_234BE
cmp ax, 2 ; st. whl. position flag
jz short loc_234EC
jmp short loc_234DE
; align 2
db 144
Notice how this change only replaces a pointer with another, thus resulting in the same code length, guaranteed to be perfectly stable. Only drawback is that both needles have to be the same colour.
To achieve a separately configurable second needle, more complex changes had to be made. The first needle (speedometer) works exactly the same way as before, but now, for the tachometer needle, we needed a procedure that guaranteed the default would also result in white-white, but that could also allow for two different colours being represented. Because the colour parameter accepted by the line function in Stunts is a word, yet only the lower byte is read (since Stunts uses 8 bit colour), the most efficient way of achieving this was by using the higher byte of Red #5 to define the tachometer colour. But the default for this high byte is zero. So I had to make it so that when this byte is zero, both needles will be the colour defined by the lower byte, while, when non-zero, this value would give the tachometer needle colour and the lower byte would give the one for the speedometer. Implementation resulted in the following code:
loc_23456:
cmp [bp+var_6], 0
jnz short loc_23485
mov ax, si
shl ax, 1
mov [bp+var_20], ax
; Patch #1 ----*
; Replaced needle colour for speed-o-meter
; Original code:
;push meter_needle_color
push simd_player.field_A6+8
mov bx, ax
mov al, (simd_player.spdpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.spdpoints[bx]
push ax
push simd_player.spdcenter.y2
push simd_player.spdcenter.x2
call preRender_line
add sp, 0Ah
loc_23485:
mov ax, di
shl ax, 1
mov [bp+var_20], ax
; Replaced needle colour for RPM meter
; Original code:
;push meter_needle_color
; This line is for the first version of the patch
;push simd_player.field_A6+8
; This block is the new version of the patch
cmp byte ptr [simd_player.field_A6+9], 0 ; Check to see if the high byte is zero
jz use_the_same_colour
push simd_player.field_A6+9 ; It's not, so push this high byte as the colour
jmp needle_value_successfully_set ; Continue with the old code
use_the_same_colour:
push simd_player.field_A6+8 ; It is, so push the low byte as the colour
needle_value_successfully_set:
nop ; Alignment bytes
nop
nop
mov bx, ax
mov al, (simd_player.revpoints+1)[bx]
sub ah, ah
push ax
mov al, simd_player.revpoints[bx]
push ax
push simd_player.revcenter.y2
push simd_player.revcenter.x2
call preRender_line
add sp, 0Ah
mov al, [bp+var_2]
cbw
or ax, ax
jz short loc_234BE
cmp ax, 2 ; st. whl. position flag
jz short loc_234EC
jmp short loc_234DE
; align 2
db 144
Because this change required additional bytes in the code, Stunts quickly complained about the alignment. We solved this by testing how many bytes we needed to add. We correctly guessed that an alignment to 16 bytes would be enough because it's the real mode paragraph length. It turned out that four bytes did the trick to align to 16 bytes. Note that these four nops actually take processor time, which is negligible, but it'd be better to move these bytes to non-executable space, like after the db 144, for example. That'd also make the code cleaner.
Even with this alignment, it's impossible to be sure that Stunts doesn't make any assumption that could make it unstable. After quite some testing, it appears that it is indeed stable, but the more we test, the better. While having different colour needles isn't a very useful feature, implementing this is important as it shows how Stunts is accepting inserted code and complex mods can be made.
Quote from: Daniel3D on November 30, 2021, 11:12:36 AMThe default car is the Lamborghini Countach and the game will not run when the car is not in the game directory.
The game will run, only will hang at the intro screen, saying that you'll have to check the diskette, but if you skip the intro, the game will run (Also, you have to select next, than previous if you wanna race with the first car)