• Unfortunately, we have experienced significant hard drive damage that requires urgent maintenance and rebuilding. The forum will be a state of read only until we install our new drives and rebuild all the configurations needed. Please follow our Facebook page for updates, we will be back up shortly! (The forum could go offline at any given time due to the nature of the failed drives whilst awaiting the upgrades.) When you see an Incapsula error, you know we are in the process of migration.

What is /reload= command arguement (good example of code bumming)

Custom Title Activated
Loyal Member
Joined
May 26, 2007
Messages
5,545
Reaction score
1,315
[SIZE=+2]What is /reload= for?[/SIZE]
(good example of code bumming)
[size=+1]Contents[/size]
Introduction
Code Analysis
Code Bumming​
[size=+1]Introduction[/size]
I've checked the parameter passing game.exe /reload= and can pretty much say that the only valid parameter is "0".

(or "00", or any 16 characters or less at all that don't evaluate to a number higher or lower than 0, and has no "&" or "" characters)

If game.exe /reload=0 is run, .smds will not be loaded, thereby forcing the game to re-load every mesh from the original .ase files, and re-create new SMDs for everything.

Of course, for this to work, you need to have the original .ase files for every single asset the game might request. :wink:

Just thought this information might be useful for those who have been looking into why the client bothers to read parameters passed to it.

[size=+1]Code Analysis[/size]
Here is the code which is passed the command line arguments (called near sub-main, after the HanDes routine has had a go at them)
Code:
subCmdLineProcess:
    mov eax,[arg1]                           ; ASCII "/reload=WhatEver"
    push offset KPT1977NoXTrap2b.005C60C0    ; /Arg2 = ASCII "/reload="
    push eax                                 ; |Arg1 => [Arg1]
    call subCmpStrStart                      ; \KPT1977NoXTrap2b.00413A40    add esp,8
    test eax,eax
     je .NotFound
    xor ecx,ecx
    lea esp,[arg.retaddr]
.Loop:
        mov dl,[eax+ecx]
        cmp dl,20
        je .Str2Int
        cmp dl,26
        je .Str2Int
        test dl,dl
        je .Str2Int
.GetChar2:
        mov dl,[eax+ecx+1]
        cmp dl,' '
        je .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar3:
        mov dl,[eax+ecx+2]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar4:
        mov dl,[eax+ecx+3]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar5:
        mov dl,[eax+ecx+4]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar6
        mov dl,[eax+ecx+5]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar7
mov dl,[eax+ecx+6]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
.GetChar8
        mov dl,[eax+ecx+7]
        cmp dl,' '
        je short .Str2Int
        cmp dl,'&'
        je short .Str2Int
        test dl,dl
        je short .Str2Int
        add ecx,8
        cmp ecx,10
.While
        jl .Loop
.Str2Int:
    push eax                                 ; /Arg1
    call jmp_subAsc2Val                      ; \KPT1977NoXTrap2b.subAsc2Val
    add esp,4
    test eax,eax
    jne short .NotFound
.ReloadEq0:
    push 1
    push eax
    call subReloadEq0
    add esp,8
.NotFound:
    mov eax,1
    ret
So basically you are looping around looking for a space or an ampersand in the remaining command line arguments. (why it breaks them down into 8 and looks for less than 16 of them IDK.

Two routines I've not shown off yet. subCmpStrStart (you'll find the offset in your client) this simply compares the all characters in one string with the first characters in the second.

So subCmpStrStart("Mine", "Mineute") == 1 but subCmpStrStar("Bow", "LongBow") == 0... get it?

Second one, I'll go into now. subReloadEq0 looks like this.
Code:
subReloadEq0
    mov eax,[arg.1]
    mov ecx,[arg.2]
    mov [dwReloadNE0],eax
    mov [dwReloadEq0],ecx
    ret
Super simple. But looks more complex than it needs to be. (get to that later)

Quite why it stores one word in memory which starts out 0, and becomes 1 if "/reload=0" is passed, and another that starts out 1 and becomes 0 if "/reload=0" is passed I couldn't say either.

If you compare them, if one is 0 the other should be 1... what ever else happens. If you search for references to either of these values, they lead to all places where .SMD files are loaded. Then, either via JE, or via JNE, or CMP against a register known to be 0 or known to be 1 they bypass the entire loading of the .SMD (which is usually preferred over .ASE) so that .ASE is forced to be loaded instead.

When this happens, loading of a .ASE (ASCII Shape Export) file always creates a new Binary cache .SMD. (Staged Model Data)

So assuming you had freshly updated a load of your .ASE files, and wanted to ensure that all the .SMDs where up-to-date with your changes, you could pass this parameter, and run around checking all the maps, monsters or armour which you just created, to ensure that the .SMD caches where updated... but if you have just one .ASE missing, you are heading for a crash. XD

[size=+1]Code Bumming[/size]
The first code routine is ridiculously complex for a simple job. This smaller code is functionally identical.
Code:
subCmdLineProcess:
    mov eax,[arg1]                           ; ASCII "/reload=WhatEver"
    push offset KPT1977NoXTrap2b.005C60C0    ; /Arg2 = ASCII "/reload="
    push eax                                 ; |Arg1 => [Arg1]
    call subCmpStrStart                      ; \KPT1977NoXTrap2b.00413A40
    add esp,8
    test eax,eax
    je .NotFound
    xor ecx,ecx
    lea esp,[arg.retaddr]
.Loop:
        mov dl,[eax+ecx]
        cmp dl,' '
        je .Str2Int
        cmp dl,'&'
        je .Str2Int
        test dl,dl
        je .Str2Int
; Snip
        add ecx,1
        cmp ecx,10
.WhileECX<10
        jl .Loop
.Str2Int:
    push eax                                 ; /Arg1
    call jmp_subAsc2Val                      ; \KPT1977NoXTrap2b.subAsc2Val
    add esp,4
    test eax,eax
    jne short .NotFound
.ReloadEq0:
    push 1
    push eax
    call subReloadEq0
    add esp,8
.NotFound:
    mov eax,1
    ret
The second routine looks more complex than necessary... so let's examine what it's for.
Key points:-
  1. It is only called from .ReloadEq0: shown in the listings above. Nothing else in the game will ever use this code.
  2. Because we have already made absolutely certain that /reload=0 (or zero equivalent) we also know that EAX will be 0 when it is PUSHed in the calling routine above the last one shown.
  3. Because of point 2, we know that this routine doesn't need to take 2 arguments, and could simply substitute EAX for 0 and ECX for 1. (alone the 2 DWords instead of registers make that code bigger, but slightly faster.)
  4. It is cdecl, not stdcall, so the main calling routine needs to clear the two arguments PUSHed when it returns. If you don't pass those arguments, you don't need to SUB ESP,8 either. :wink:
  5. You could also not push EAX and 1, but simply MOV ECX,1 and CALL... then it wouldn't have to MOV Arg1, and Arg2 back into EAX and ECX making it a fastcall. (Again, no need to SUB ESP,8 on return) This would be very fast, not only because of the reduction in instructions, but because you have reduced 4 DWord calls to the address bus (slow, compared to register use) to 1.
  6. You could POP EAX and ECX to make it stdcall, and then dump the SUB ESP,8 too.
None of these changes will have any effect on it's operation, but all will reduce the size and increase its speed of it's operation. (not that it's slow, and it's only called once anyway but...) This whole thing is very wasteful.
 
Last edited:
Custom Title Activated
Loyal Member
Joined
May 26, 2007
Messages
5,545
Reaction score
1,315
Re: /reload=

That's what I told you in the first post.

I'm just about to expand and correct it by listing the code.

Maybe you'll get it then.

--- EDIT ---
P.S. Now this guide now has more information, it could be moved to the Tutorials section if people find it interesting / useful. Particularly as implementing the reduced code would save a lot of space without removing or affecting any functionality. (create a nice "code cave")

Additional to the above abbreviation, you can integrate the sub-procedure into the main code, freeing that section, and saving 2 PUSHes, two POPs and a CALL and RET pair, and the SUB ESP,8. (unless you went for the modification to stdcall or fastcall. You could also go further by reducing the boolean storage to 1 DWord location, replacing all references to the other one with references to the remaining one and negating their "direction of effect".

This would then save one more instruction; the MOV you no longer need, and give you a single DWord (the one you chose not to use) which you know is now free to place a setting of your own choosing.

This is a "nice" place to have a "cave" too... because it already has access to a clean command line (free of HanDes parameters) that you can pick up a special switch for a special mode. GM Mode, Window Mode, or 1st Person (Observer) Mode would be simple examples, but you may have other interests... like "VOIP" mode, or OpenGL mode, or Debug mode. (to log more about what the game is doing) Just as examples, I'm sure each of you can think of things that "might" be useful.

If it is moved... I'd like to add some descriptions of how you might go about implementing some of those changes... if not, I'll leave it as documenting the way the game is without going into more detail about how you could change it.
 
Last edited:
Back
Top