Implementing Rain
Okay. Now I've just implemented the rain code in the Low Level Tempskron maps in my Butchered Client based upon the disputed release in this post.
It's posted by Harsaphes (MRX from the M.I.B. forums) but that also points us to MagicPT, and I believe Excelsior has claimed to be the person who implemented that code. I'm fairly certain that he (Excelsior) posted a direct dump from Olly of his code here, but that thread seems to have gone AWOL just as many other recent threads have.
Anyway, I'm going to walk you through my translation of that implementation, and if Excelsior wants to repost his thread, I'll take this post over to his thread and close mine.
In the process of reworking the implementation, I can see that parts of it are brilliantly optimised, and yet other parts look thrown in, or like they have been re-jiggled about by someone who understood what they where doing less. I can't claim that I fully understand how this code interacts with the main game, but I can see how it is working on it's own, and have broken it down and put it together again in a slightly different (cleaner) manner.
First off, I don't like adding sections. MRX has created an MRX section with access for any kind of operation from anywhere, rather like the Global Fantasy team did, and to me that's just an extra hole when PT has too many as it is. Once you have dumped all the XTrap code in a 1977 client you have quite a lot of free space in your exe to add new code and data anyway... and that's what I did.
I'll not give you the offsets I used, because you may be using a different client, or have already put your own groovy code into the space I used. Besides all of which, the ability to be more generally useful is the reason I prefer to see Tutorials rather than releases... let alone re-releases. XD
The code it's self has 3 main sections and a Table of DWords. However, the implementation in the posted game.exe is a little jumbled up, as the primary worker routine (I called it SubRain) is bunged in the middle of another routine, which then has to JMP over it. The alignment of the routines is kinda nasty too, suggesting that someone has been a little liberal with the Binary copy and paste functions in Olly whilst neglecting their fundamental understanding of the i386 PMode architecture. XD (I know Excelsior knows this well, I'm not so sure of the other signatorys')
Okay... on to code. If you're using a 1977 KPT based client as I am, you should consider the following code fragments to be labelled in accordance with the following EQUates:-By which, I mean that in Olly, you should <Ctrl> G to the address post EQU and label it (by tapping the ':' key) in accordance with the name pre EQU. The listing above is how you would define a "constant" if you where using an assembler, not a debugger with live "assemble in place" capability. XDCode:MapLo EQU 0091C3BCh
MapHi EQU 0091C3B0h
RainRet1 EQU 00440D8Ah
RainRet2 EQU 00440E46h
RainRet3 EQU 004435BCh
RainRet4 EQU 00443123h
AmbientSound EQU 004E68C0h
WeatherSelector EQU 00533E60h
If you are using a different basic version for your client, you should probably start by getting a 1977 KPT client, and examining the code at the addresses in the fragment above, in order to locate the equivalent code locations in your client.
Now we can look at the first block of code used to add rain. In KPT 1977, replace the code between 00440D61 and 00440D89 with a JMP to RainCode, and a fist full of NOPs. ;)Again, I'm aiming at a true assembly source listing (though I copied this from Olly and cleaned it up for readability) here. So I've used as few numeric constants as possible, unless the number is the best way to understand what is going on. Wherever a word, name or label would make it easier to read, that's what I do.Code:Align 4
RainCode:
call SubRain
cmp dword ptr [MapLo],00h
jne short .Skip1
Align 4 ; > Optional, but I would advise you try to align loops. <
.Loop:
mov dword ptr [MapLo],00h
mov eax,[esi*04h+eax+C8h]
jmp short .Skip2
.Skip1:
mov ecx,[0091C430h]
mov ecx,[ecx+016Ch]
mov edx,ds:[ecx*04h+0091C360h] ; IDK What this table represents.
mov ecx,[edx+C4h]
cmp ecx,0400h
je short .Loop
cmp ecx,0500h
je short .Loop
cmp ecx,0800h
je short .Loop
cmp ecx,0900h
je short .Loop
mov eax,00h
.Skip2:
cmp eax,ebx
jl short .Ret1
push eax ; Arg1
call WeatherSelector ; WeatherSelector(Arg 1)
mov eax,[MapLo]
add esp,04h
cmp eax,01h
je short .Ret1
cmp eax,ebx
jne short .Sound
.Ret1:
jmp RainRet1
.Sound:
push 0Dh ; Arg1 = 13
call AmbientSound ; AmbientSound(Arg 1)
add esp,04h
cmp dword ptr [MapHi],28h
jg short .MapHiGT40
cmp dword ptr [MapHi],14h
jg short .MapHiGT20
jmp short .Ret2
.MapHiGT40:
cmp dword ptr [MapHi],2Dh
jl short .MapHiRangeOK
.MapHiGT20:
cmp dword ptr [MapHi],24h
jl short .MapHiRangeOK
jmp short .Ret2
.MapHiRangeOK:
push 089Dh ; Arg4 = 2205
push 0190h ; Arg3 = 400
push 00h ; Arg2 = 0
push 02h ; Arg1 = 2
call 004E5110h ; 004E5110(Arg1, Arg2, Arg3, Arg4)
; I've not been able to figure out what this
; routine is, but it's clearly important.
add esp,10h
.Ret2:
jmp RainRet2
Look out for the routine @ 004E5110 if you aren't using KPT 1977 as your base. That is the only core routine I haven't given any name to, and the reason is... I don't understand it's function myself yet. (If I figure it out, or someone let's me know, I'll update this post.)
MapLo and MapHi are probably incorrect initial assumptions for the variables at the address they represent too. I initially thought, from the way they are used, that they related to the map / field in which the player is presently playing, but as I've gone through it's looking more likely that at least one of them relates to the current weather the player is experiencing. Again, I don't fully understand them, so guessing at a better name is pointless. Just be warned that those labels may be misleading.
If you are not familiar with it, the .Label notation in Assembler source suggests that the "scope" for that label is local to the last sub-routine (or, label without a "." before it) and does not apply globally to the entire source file, let alone the whole program. You can (and I do) create them in Olly, but it does not understand them when you are assembling a line of code. So those will need to be manually translated to an address when you are typing code into Olly, or you'll have to use globally unique labels that don't start with a dot. (period in US English or full-stop in British English, but "dot" will do for Web-heads)
Notice also that I advise that where ever you put these code sections you Align their start, and any iterated JMP destinations (loops) to DWord boundaries. Align 4 is a 4 byte, or 32-bit pure boundary, and executes much faster and easier for an x86 32-bit CPU. (some more so than others) It's only an advisory in loops because of the speed issue, but it's almost a must for the actual start of the routine... Olly can get very confused in Analysis if this isn't correct, and although it may only be called infrequently, the delay is compounded by the fact that most of these are not JMP Short instructions, but full Long Jumps.
Of course Olly can't Assemble an Align, because it's more like a compiler directive than and instruction. While in Olly, you really have to play the part of the compiler your self. From real source the Compiler (Assembler) would insert 0, 1, 2 or 3 NOP (or equivalent) instructions depending on how near or far from a 4 byte boundary the next instruction address is. I think you can manage that by hand. ;)
For the next code segment (again, only in a 1977 based client) you should replace the "cmp [MapLo],ecx" @ 0014030E5 with a JMP to RainHack. You'll get an extra NOP out of the deal, because the JMP is smaller bytecode than the rather complex in-direct addressed CMP. (you probably wouldn't see instructions like this on a RISC Architecture CPU)The last code fragment is the subroutine that is CALLed from the first section. This is actually a rather elegant bit of nested looping which would otherwise have been a very large bit of code. It's beautifully optimised, but not easy to read.Code:Align 4
RainHack: ; 3 choices are broken to this code from the main.
mov ecx,02h
cmp dword ptr [MapLo],00h
je short .ZeroMapLo
mov eax,[0091C430h]
mov eax,[eax+16Ch]
cmp eax,edi
jl short .Ret4
mov ecx,ds:[eax*04h+0091C360h] ; There's that table that IDK what it is again. ;)
mov eax,[ecx+C4h]
cmp eax,0400h
je short .ZeroMapLo
cmp eax,0500h
je short .ZeroMapLo
cmp eax,0800h
je short .ZeroMapLo
cmp eax,0900h
je short .ZeroMapLo
cmp dword ptr [MapLo],01h
je short .Ret3
.Ret4:
jmp RainRet4
.ZeroMapLo:
mov dword ptr [MapLo],00h
.Ret3:
jmp RainRet3
Finally, you need your RainTable. Now, I think you only need 5 DWords (20 bytes) to store this in... and the number of places XTrap stored 128-bit signatures in ASCII, this shouldn't be too hard to find space for.Code:Align 4
SubRain:
mov edx,[005FB3B8h]
xor ecx,ecx
Align 4 ; > Optional, but I would advise you try to align loops. <
.Loop1:
cmp edx,[ecx*04h+RainTable]
je short .Loop2
inc ecx
cmp ecx,05h
jl short .Loop1
mov dword ptr [MapLo],00h
ret
Align 4 ; > Optional, but I would advise you try to align loops. <
.Loop2:
jmp short .Branch2
Align 4 ; > Optional, but I would advise you try to align loops. <
.Loop2_1:
jge short .Branch1
.Loop2_2:
mov dword ptr [MapLo],01h
ret
.Branch1:
mov dword ptr [MapLo],02h
ret
Align 4 ; > Optional, but I would advise you try to align loops. <
.Branch2:
cmp edx,10h
je short .Loop2
cmp dword ptr [MapHi],0Ah
jmp short .Loop2_1
In my listing, I stored a full 8 DWords, just so it takes up exactly 2 lines in a hex editor, and because I'm not at all short of space. This should be somewhere in .data or .rdata sections, but you can keep it in .text like a "jump table" if you really feel you need to.Remember please, that Olly doesn't understand dd, dw, db or dq any better than it understands EQU or Align. Again, these are directives to an Assembler (and when in Olly, that means "you") not instructions for the CPU, which is all Olly can "assemble".Code:RainTable:
dd 01h ; 1
dd 06h ; 6
dd 0Bh ; 11
dd 10h ; 16
dd 15h ; 21
dd 00h ; 0
dd 00h ; 0
dd 00h ; 0
In Olly, your best option is to find a hole for this, and display it as Integer -> Unsigned Long in the "dump" panel.
I guess, with all my talk about optimising the alignment of jump destinations, you may be able to squeeze a little more performance out of P4 / Athelon XP based processors by using "branch hinting" to indicate which side of the jumps should be kept in the level 1 and level 2 CPU instruction caches... if anyone would like to try that (just for the fun of it) I'd love to hear how your experiments go.Spoiler:
Anyway... I hope this helps others here get Rain working in their PT clients. :D: Have fun, and happy coding.
(I'll welcome any insight into any of the bit's I'm unsure of, or even thought I was sure of, but am wrong about. And I'll update the main post and credit you for pointing out what a wazzock I am. :wink:)
Again full credits to all who got me to the point where I could write this guide:-
Harsaphes (alias MRX) for making it work in 1977 and sharing with RZ, where I found it.
Exellsior who (I do believe) probably managed to make sense of it in the 166beta (and maybe a peek at a Sandurr client) so that it could be implemented in newer general clients, probably for MagicPT.
At least, that's how I'm assuming the information got to me in a manner I could use to make this guide. That's the problem when you don't cite your sources guys, the paper trail goes cold part way through, and someone gets hurt.
I can't make much sense of this, not just because it's in Portuguese, but because I have no idea what MRX (Harsaphes) is using for the base. So the offsets he begins with are unallocated when I start PT. XD
