News:

Herr Otto Partz says you're all nothing but pipsqueaks!

Main Menu

Stunts modification explanations.

Started by Daniel3D, November 30, 2021, 10:46:46 AM

Previous topic - Next topic

Daniel3D

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:
Edison once said,
"I have not failed 10,000 times,
I've successfully found 10,000 ways that will not work."
---------
Currently running over 20 separate instances of Stunts or 4D Sports Driving.

Daniel3D

#1
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
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.
Edison once said,
"I have not failed 10,000 times,
I've successfully found 10,000 ways that will not work."
---------
Currently running over 20 separate instances of Stunts or 4D Sports Driving.

Daniel3D

#2
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
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

Edison once said,
"I have not failed 10,000 times,
I've successfully found 10,000 ways that will not work."
---------
Currently running over 20 separate instances of Stunts or 4D Sports Driving.

Daniel3D

#3
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-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.
Edison once said,
"I have not failed 10,000 times,
I've successfully found 10,000 ways that will not work."
---------
Currently running over 20 separate instances of Stunts or 4D Sports Driving.

Cas

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
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.
Earth is my country. Science is my religion.

Cas

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.
Earth is my country. Science is my religion.