Star Wars Episode I: Racer (aka Podracer)

General information and assistance with dgVoodoo.

Re: Star Wars Episode I: Racer (aka Podracer)

Postby masterotaku » 2018-6-10 @ 08:19

How about this?:

- The user runs a ".bat" script (that we could make).
- It asks the user for their desired width resolution, the user types it and presses enter.
- It asks the user for their desired height resolution, the user types it and presses enter.
- That resolution is written into the registry (https://docs.microsoft.com/en-us/window ... /reg-query) (https://www.computing.net/answers/progr ... 28873.html). "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\LucasArts Entertainment Company LLC\Star Wars: Episode I Racer\v1.0", "Display Width" and "Display Height" but converted into hexadecimal.
- The aspect ratio of that resolution is saved into a variable, and then multiplied by 0.75. The resulting value is then used to patch "SWEP1RCR.EXE" (what would you use to do this?).
- If you want to go even further, the script could open my "d3dx.ini" if it exists and change the line starting by "z1=" into "z1=" plus the result of 1.333333/resolution aspect ratio. For example, for 2560x1440 it's 0.75, for 2560x720 it's 0.375, and for 2560x480 it's 0.25.

Testing those super wide resolutions made me notice two problems with the sun and lens flares:

1- My correction in the shader doesn't fix the occlusion caused by the sun being originally at a lower position at that aspect ratio. Meaning that if the sun is (wrongly) supposed to disappear because of some geometry, you may see it disappear in the sky for no reason. In triple monitor it's actually hard to see the sun at all in the first track.
2- With the fix I made to correct its Y axis position, I actually make the sun bigger than it should. Not very noticeable at 16:9, but it's very big at 48:9. If I correct the X axis instead, it has the correct size but it has a lower position as you know. Maybe I can correct its aspect ratio in the X axis (edit: I don't think so. It behaved in a weird way horizontally) and then move the effect up, but we still have problem number 1.

Other than these problems, the game looks nice at a very wide aspect ratio.
User avatar
masterotaku
Newbie
 
Posts: 13
Joined: 2017-8-31 @ 18:55

Re: Star Wars Episode I: Racer (aka Podracer)

Postby CoolGamer » 2018-6-10 @ 23:29

masterotaku wrote:- The aspect ratio of that resolution is saved into a variable, and then multiplied by 0.75. The resulting value is then used to patch "SWEP1RCR.EXE" (what would you use to do this?).
- If you want to go even further, the script could open my "d3dx.ini" if it exists and change the line starting by "z1=" into "z1=" plus the result of 1.333333/resolution aspect ratio. For example, for 2560x1440 it's 0.75, for 2560x720 it's 0.375, and for 2560x480 it's 0.25.



Unfortunately I don't have the programming skills necessary to make a patcher. I can't do the steps that you outlined above. I can only make simple modifications using Ollydbg. I couldn't figure out how to do floating number division in assembly language. That's why I am not able to add assembly code to the EXE that calculates Aspect Ratio and FOV automatically.

In case somebody with good assembly skills want to pick up this issue, I summarized everything I figured out below. The modifications shown below set memory address 00DFB1F0 to 1.33333, which is 3FAAAAAB in hexadecimal representation. This is a static patch that only works for 16:9 resolutions.


Code: Select all
Address   Original Value                     Changed Value
00483265   NOP                               MOV DWORD PTR DS:[ESI+44],3FAAAAAB
0048326C   NOP                               MOV DWORD PTR DS:[ESI+48],EAX
0048326F   NOP                               RETN
004832EE   MOV DWORD PTR DS:[ESI+44],ECX     CALL 00483265
004832F1   MOV DWORD PTR DS:[ESI+48],EAX     (occupied as part of above instruction)
004832F3   (occupied as part of above)       NOP


For a patch that calculates Aspect Ratio and FOV automatically, the following method can be used:

width = get from memory address 00EC8E10 (e.g. 1280 decimal represented in Hexadecimal)
height = get from memory address 00EC8E0C (e.g. 720 decimal represented in Hexadecimal)
AspectRatio = width/height = result will be a float represented in Hexadecimal
NewFOV = AspectRatio / 1.33333 = result will be a float represented in Hexadecimal
MOV DWORD PTR DS:[00DFB1F0],NewFOV

Of course this is easier said than done. I looked at youtube videos but I couldn't figure out how to do floating point division using assembly code. Finding space in the executable to insert that code is another issue. I would appreciate if someone with advanced assembly skills can help.

Until we get some help in assembly coding, CheatEngine seems to be the only option that works for everybody.
CoolGamer
Member
 
Posts: 121
Joined: 2017-1-14 @ 17:22

Re: Star Wars Episode I: Racer (aka Podracer)

Postby Falcosoft » 2018-6-11 @ 17:57

width = get from memory address 00EC8E10 (e.g. 1280 decimal represented in Hexadecimal)
height = get from memory address 00EC8E0C (e.g. 720 decimal represented in Hexadecimal)
AspectRatio = width/height = result will be a float represented in Hexadecimal
NewFOV = AspectRatio / 1.33333 = result will be a float represented in Hexadecimal
MOV DWORD PTR DS:[00DFB1F0],NewFOV

Of course this is easier said than done. I looked at youtube videos but I couldn't figure out how to do floating point division using assembly code

It's not too difficult but I do not know if you can have enough space to inject the code. I have tried to write a solution with as few instructions as possible. Also you will have to find a writable place in memory where you can store your '1.3333' constant (named 'Cnst_43' in code) since you cannot load arbitrary constants into the x87 FPU stack. You have to give a memory location instead:

Code: Select all
 
  MOV  Cnst_43, 0x3faaa993        //Load 1.3333 float to a 32 bit memory location;
  FILD width                      //Load the int width value to stack register ST(0)
  FIDIV height                    //Divide ST(0) by int height value
  FDIV Cnst_43                    //now ST(0) stores the AspectRatio value, divide this by 1.3333
  FSTP NewFOV                     //now ST(0) stores the final result, save it to NewFOV and pop ST(0) register.
User avatar
Falcosoft
Oldbie
 
Posts: 665
Joined: 2016-5-21 @ 13:46
Location: Pécs, Hungary

Re: Star Wars Episode I: Racer (aka Podracer)

Postby CoolGamer » 2018-6-12 @ 01:42

Falcosoft, thank you so much for providing the floating point division code. I was easily able to patch the EXE using your instructions. I would not be able to do it without your help.

The patched EXEs for US CD version and GOG version (v1.0_hotfix3_20791) of the game are attached below. Remember to run the game EXE with -v switch to change game resolution before you play.

The following addresses were changed using OllyDbg to add automatic FOV Calculation. Game will look better on Widescreen monitors. The top and bottom of the screen will not be cut off. There will be more view on the sides.
Code: Select all
ADDRESS    ORIGINAL VALUE                    CHANGED VALUE
004832EE   MOV DWORD PTR DS:[ESI+44],ECX     CALL 004AB750
004832F1   MOV DWORD PTR DS:[ESI+48],EAX     (occupied as part of above instruction)
004832F3   (occupied as part of above)       NOP
004AB750   DB 00                             MOV DWORD PTR DS:[ESI+44],3FAAAAAB
004AB757   DB 00                             FILD DWORD PTR DS:[00EC8E10]
004AB75D   DB 00                             FIDIV DWORD PTR DS:[00EC8E0C]
004AB763   DB 00                             FDIV DWORD PTR DS:[ESI+44]
004AB766   DB 00                             FSTP DWORD PTR DS:[ESI+44]
004AB769   DB 00                             MOV DWORD PTR DS:[ESI+48],EAX
004AB76C   DB 00                             RETN
004AB76D   DB 00                             NOP


We still have the issue where the double Suns in the sky shift down a little (and become slightly oval) since they are drawn with the HUD, but we can use masterotaku's fix for those.

Please note that the shift in double suns is actually worse on vanilla GOG version (with widescreen resolutions) without this FOV calculation fix. This FOV fix is not causing any extra problems.
Attachments
StarWarsRacer_Unoffical_ScreenResolutionCheckRemoved_AND_AutomaticFOVcalculationAdded.rar
StarWarsRacer_Unoffical_ScreenResolutionCheckRemoved_AND_AutomaticFOVcalculationAdded
(710.42 KiB) Downloaded 50 times
CoolGamer
Member
 
Posts: 121
Joined: 2017-1-14 @ 17:22

Re: Star Wars Episode I: Racer (aka Podracer)

Postby Falcosoft » 2018-6-16 @ 07:51

Just a non-essential remark: a little more optimized code would be if instead of dividing by 1.33333 you would multiply by 0.75. I do not think performance is relevant here (multiplication is much faster than division) but it could give a little more precise result since contrary to 1.33333... 0.75 is not a repeating decimal. so:

Code: Select all
 
  MOV  Cnst_34, 0x3f400000        //Load 0.75 float to a 32 bit memory location;
  FILD width                      //Load the int width value to stack register ST(0)
  FIDIV height                    //Divide ST(0) by int height value
  FMUL Cnst_34                    //now ST(0) stores the AspectRatio value, multiply this by 0.75
  FSTP NewFOV                     //now ST(0) stores the final result, save it to NewFOV and pop ST(0) register.
User avatar
Falcosoft
Oldbie
 
Posts: 665
Joined: 2016-5-21 @ 13:46
Location: Pécs, Hungary

Re: Star Wars Episode I: Racer (aka Podracer)

Postby CoolGamer » 2018-6-16 @ 22:35

I tested and compared both methods using cheat engine. It seems like we got lucky :). I agree that the multiplication method is more precise in theory but it does not make a difference in this case. The hexadecimal constant 3FAAAAAB that I used is 1.3333333 (7 decimal places), so in theory a division error might be 0.0000001 in some cases but that does not produce any visual difference in the game. In fact, we don't even see that error in most cases due to truncation. All 4:3 resolutions give a perfect 1.0 decimal FOV since truncation errors cancel out (1.3333333/1.3333333=1.0). Furthermore, many other widescreen resolutions that I tested produced identical FOVs up to 7 decimal places under both methods (due to truncation of the numbers after 7 decimal places). I actually could not find a resolution that produced 0.0000001 error in calculated FOV.

I will keep the same EXEs for now in order not to confuse people with a new revision. It does not make any visual difference. In the future if I make other modifications to the EXE, I might switch to multiplication method.

I think the best way to absolutely minimize any error (due to repeating numbers and truncations) should be doing two integer multiplications and only one integer division:
FOV = (width/height) / (640/480)= (width/height) / (4/3) = (width X 3) / (height x4)

Code: Select all
numerator = width x 3          ;we are multiplying two integers here and the result is an integer.  No truncation or rounding errors.
denominator = height x 4       ;we are multiplying two integers here and the result is an integer.  No truncation or rounding errors.
FOV = numerator/denominator    ;we are dividing two integers here and the result is a float.


How can we implement this method in assembly?
CoolGamer
Member
 
Posts: 121
Joined: 2017-1-14 @ 17:22

Re: Star Wars Episode I: Racer (aka Podracer)

Postby Falcosoft » 2018-6-17 @ 06:11

How can we implement this method in assembly?

It can be something like this:
Code: Select all
 
  PUSH EAX                    // Save EAX to stack
  MOV EAX, Width              // Move Width to EAX
  IMUL EAX, 3                 // Multiply Width by 3 (use the sign extended byte opcode '6BC003' here, it's only 3 bytes)
  MOV NewFOV, EAX             // Move Width * 3 to NewFOV
  FILD NewFOV                 // Load Width * 3 to ST(0)
  MOV EAX, Height             // Move Height to EAX
  IMUL EAX, 4                // Multiply Height by 4 (use the sign extended byte opcode '6BC004' here, it's only 3 bytes)
  MOV NewFOV, EAX             // Move Height * 4 to NewFOV
  FIDIV NewFOV                // Divide Width * 3 in ST(0) by Height * 4 in NewFOV
  FSTP NewFOV                 // Now ST(0) contains the final result, save it to NewFOV and pop ST(0)
  POP EAX                     // Load EAX from stack

Since MUL (unsigned integer multiplication) has no 2/3 operand form where you can use a constant, IMUL (signed integer multiplication) is used instead. The PUSH EAX/POP EAX instructions at the begin/end are only necessary if EAX register contains useful data here, so the content should be preserved. You can also use another general purpose register (ECX, EDX, etc.) instead of EAX if it does not contain relevant data here. The memory location 'NewFov' is also used as a temporary place storing immediate results.

@Edit:
You can also use the 3 operand version of IMUL. This way you can save a few bytes:
Code: Select all
 
  PUSH EAX                    // Save EAX to stack
  IMUL EAX, Width, 3          // Multiply Width by 3 and store result in EAX
  MOV NewFOV, EAX             // Move Width * 3 to NewFOV
  FILD NewFOV                 // Load Width * 3 to ST(0)
  IMUL EAX, Height, 4         // Multiply Height by 4 and store result in EAX
  MOV NewFOV, EAX             // Move Height * 4 to NewFOV
  FIDIV NewFOV                // Divide Width * 3 in ST(0) by Height * 4 in NewFOV
  FSTP NewFOV                 // Now ST(0) contains the final result, save it to NewFOV and pop ST(0)
  POP EAX                     // Load EAX from stack
User avatar
Falcosoft
Oldbie
 
Posts: 665
Joined: 2016-5-21 @ 13:46
Location: Pécs, Hungary

Re: Star Wars Episode I: Racer (aka Podracer)

Postby CoolGamer » 2018-6-17 @ 23:26

Falcosoft, thanks for the code. In the future if I make other modifications to the EXE or get complaints about FOV precision, I can use this code.

I also added assembly integer multiplication to my skill set by looking at this example. Thanks again.

I have a general question. Do we have any risk of overwriting/loosing valuable data that is already in the stack when we use "PUSH EAX" and "POP EAX" commands? For example, if an earlier code pushed EAX or another general purpose register (ECX, EDX, etc.) to the stack.

ST(0) does not contain any data in the place where I injected the code. I verified that with OllyDbg debugger before I made the FOV patch. So the floating division did not have any adverse effects on ST(0). EAX does contain a saved value but I can check what it is and I can manually reset it to its original value after the FOV calculation is done, if necessary. If there is no risk of messing up the stack, POP/PUSH EAX is of course the most convenient option for me.
CoolGamer
Member
 
Posts: 121
Joined: 2017-1-14 @ 17:22

Re: Star Wars Episode I: Racer (aka Podracer)

Postby Falcosoft » 2018-6-18 @ 05:32

CoolGamer wrote:I have a general question. Do we have any risk of overwriting/loosing valuable data that is already in the stack when we use "PUSH EAX" and "POP EAX" commands? For example, if an earlier code pushed EAX or another general purpose register (ECX, EDX, etc.) to the stack.


Generally no, since the x86 stack always 'grows' while there is enough memory. Then you will get a stack overflow that is basically a crash. In case of Win32 applications the typical stack size is at least 256K-1M so unless you use very deep call chains (recursion) or you use very big local variables stack size is not a problem. While you have enough memory you cannot 'overwrite data that is already in the stack' by using PUSH. x86 stack does not work this way, since it's a 'stack'. :) You can only write to the 'top of the stack' where the stack pointer points to by using PUSH (and it never overwrites the top element but adds another one to the top). And POP also always loads the data from the top of the stack.
https://en.wikipedia.org/wiki/Stack_(abstract_data_type)
https://eli.thegreenplace.net/2011/02/0 ... -is-on-x86
https://en.wikipedia.org/wiki/Stack_overflow

I think your confusion partly can be attributed to the fact that the x87 also has a 'stack like' finite register set (that is there are only 8 x87 registers). So in the case of x87 stack registers pushing too many data can cause errors. But contrary to your intuition in case of an FLD x instruction (that loads data always to ST(0)) not ST(0) value would be lost but ST(7) value. It's because an FLD instruction moves the whole stack by 1, so the earlier ST(0) value will be in ST(1), ST(1) value will be in ST(2) and so on. Since there is no ST(8), in case ST(7) contains valid data you will get an error.
It's not a coincidence that you do not find a valid value in ST(0). Usually subroutines always clear the x87 register stack after floating point calculations (that's why we also use FSTP as the last instruction).

Contrary the x86 stack is not a finite register set but a continuous memory region. The rule you should follow is to use as many POP x in your routine as many PUSH x you have used so the stack pointer points to the same place as before your calculations.
User avatar
Falcosoft
Oldbie
 
Posts: 665
Joined: 2016-5-21 @ 13:46
Location: Pécs, Hungary

Re: Star Wars Episode I: Racer (aka Podracer)

Postby CoolGamer » 2018-6-18 @ 12:40

Thanks for the clear explanations. I previously thought that the stack was like a register. Now I understand how the stack works.
CoolGamer
Member
 
Posts: 121
Joined: 2017-1-14 @ 17:22

Previous

Return to dgVoodoo General

Who is online

Users browsing this forum: No registered users and 2 guests