Jump to content

[SA] Disarming drug dealers


vladvo

Recommended Posts

I want to remove or replace drug dealer's weapon. Tried a few options but they don't work. For example:

if you remove his weapon with 048F: actor 1@ remove_weapons - at some point he will pull the pistol out of nowhere.
Memory editing, slot manipulation - nothing gives a 100% result. He gets his pistol back at some point.
I think the problem is in the main.scm file - the way R* made the dealer's script.

if or
051A:   actor 0@ damaged_by_actor $PLAYER_ACTOR 
0457:   player $PLAYER_CHAR aiming_at_actor 0@ 
jf @DEALER_429 
//various jumps here - skipped
:DEALER_846
if 
  not Actor.HasWeapon(0@, 22)
jf @DEALER_929 
Actor.GiveWeaponAndAmmo(0@, WeaponType.Pistol, 1000)

Looks like the script checks if the dealer was damaged or aimed at by CJ and gives him the pistol. The worst thing is that this check-give weapon part is repeating. If the script was done the right way - dealer would get his pistol upon creation and is could be easily changed using opcodes.

So far, the only options I see
- Edit and recompile main. That would be only for personal use, likely leading to problems with other mods (Jack, now I understand why you are using pointers instead of addresses)
- Stop external script and do all the dealer stuff with cleo script. Might work, but that's not the best way. 
The best option would be to change Actor.GiveWeaponAndAmmo(0@, WeaponType.Pistol, 1000) in the game memory. I think the weapon ID must be somewhere out there. I came up with this script:
 

{$CLEO .cs}
script_name 'testing'
0000:
:START
wait  0
if
Player.Defined($PLAYER_CHAR)
jf @START

:main
wait 0
0AAA: 1@ = get_script_struct_named 'DEALER'
0AD1: show_formatted_text_highpriority "%d" 20 1@
if 
0AB0:   key_pressed 114  //F3 pressed
then
jump @save_do
end

if 
0AB0:   key_pressed 115  //F4 pressed
then
jump @change_weapon
end


jump @main
     
:save_do

0AAA: 1@ = get_script_struct_named 'DEALER'

0A9A: 28@ = open_file "cleo\testing4.dat" mode "at+" //the file where mem contents will be stored
for 29@ = 0 to 50000 step 1 //loop
0A8D: 2@ = read_memory 1@ size 4 virtual_protect 0  //reading memory
    if 
    2@ == 22
    then
    0AD9: write_formatted_text "Offset:%d Val:%d%c" in_file 28@ 29@ 2@ 0xA //writing it to file
    end
1@ += 0x4 //going for the next dword
end
0AD1: show_formatted_text_highpriority "done" 1000
wait 1000
jump @main

:change_weapon
0AAA: 1@ = get_script_struct_named 'DEALER'
for 29@ = 0 to 50000 step 1 //loop
0A8D: 2@ = read_memory 1@ size 4 virtual_protect 0  //reading memory
    if 
    2@ == 22
    then
    0A8C: write_memory 1@ size 4 value 23 virtual_protect 0
    0AD1: show_formatted_text_highpriority "weapon removed" 1000
    wait 1000
    break
    end
1@ += 0x4 //going for the next dword
end

jump @main

As soon as we see something except 0 on the screen, press F3 and save offset of mem location that has 22 (pistol id). Ok, it works. In file I get only one instance of

22, like Offset:9156 Val:22. When I encounter another dealer (with different script struct) the offset changes slightly but there is still only one mem address with 22.
Ok, pressing F4. It should search for 22 again and change it to 23. Nothing changed - the dealer is still using a regular pistol. Tried using 0x1 instead of 0x4 - still no success. Any ideas ?

Link to comment
Share on other sites

3 hours ago, vladvo said:

Any ideas ?

I haven't tried this, but I'll trust that you can round off the edges if things don't quite go as expect.

 

0AAA: 1@ = get_script_struct_named 'DEALER'

Provides a pointer to 'DEALER' in CRunningScripts. This is a struct that holds the instruction pointer, local vars and whatnot for the script, not the compiled code. It's the machine that runs the compiled script. See CRunningScripts.h.

 

I'm expecting the binary script to be loaded at m_pBaseIP at offset... 0x10(?).

Read that and add 846 to get to :DEALER_846.

if +4 (?, opcode + type + int8)
  not Actor.HasWeapon(0@,   +6 (?, opcode + type + lvar + type)
 

So... BaseIP + 856 should get you really close to 22. Hopefully.

 

If you want to be more precise, compile the relevant code and open the script with a hex editor for measurements.

 

Edited by OrionSR
Link to comment
Share on other sites

Update: I tried writing a test script to read the first reference to the weapon but ran into a couple of problems.

 

The basic process seems to work when stepping through memory with a hex editor.

 

But in cleo, 0AAA isn't reporting the offsets of any running threads - not even MAIN. I'm not sure why; it used to work just fine.

 

The coding after :DEALER_846 doesn't match your posted example. This sort of thing is sensitive to script version. I'm pretty sure I'm working with v1 scripts. How about you?

 

This is what I've got for that segment. Showing the opcodes helps a lot when trying to read the binary data.

:DEALER_846
00D6: if 
0248:   model #COLT45 available 
004D: jump_if_false @DEALER_929 
00D6: if 
8491:   not actor 0@ has_weapon 22 
004D: jump_if_false @DEALER_929 
01B2: give_actor 0@ weapon 22 ammo 1000 // Load the weapon model before using this 
00D6: if 
8039:   not  15@ == 1 
004D: jump_if_false @DEALER_922 
01B9: set_actor 0@ armed_weapon_to 0 
0002: jump @DEALER_929 

:DEALER_922
01B9: set_actor 0@ armed_weapon_to 22 

:DEALER_929
0051: return 

 

Another thought: If there's more than one dealer in the area then there could be more than one DEALER script running. 

 

@vladvo I tried to measured things out a little closer with a hex editor:

                                                   BaseIP
:DEALER_846                                        label +846
D6 00 04 00 48 02 05 5A 01 4D 00 01 5F FC FF FF    modelID +7
D6 00 04 00 91 84 03 00 00 04 16 4D 00 01 5F FC    weaponID +19
FF FF B2 01 03 00 00 04 16 05 E8 03 D6 00 04 00    weaponID +14
39 80 03 0F 00 04 01 4D 00 01 66 FC FF FF B9 01
03 00 00 04 00 02 00 01 5F FC FF FF B9 01 03 00
00 04 16 51 00                                     weaponID +42

 

Edited by OrionSR
Link to comment
Share on other sites

2 hours ago, OrionSR said:

The coding after :DEALER_846 doesn't match your posted example. This sort of thing is sensitive to script version. I'm pretty sure I'm working with v1 scripts. How about you?

Same v1. I've cut it in the post, just to show the most important lines.

Handling more than one script is not a problem. Change var, store script addr, search for script, compare addr with stored.

Edited by vladvo
Upd
Link to comment
Share on other sites

5 hours ago, vladvo said:

Same v1. I've cut it in the post, just to show the most important lines.

Yeah, I kinda figured that when the jf label had the expected offset, so I continued on with the measurement of hex data.

 

5 hours ago, vladvo said:

Handling more than one script is not a problem.

Here's a few constants I use when my scripts need version detection - like CallFix and OpenFix, which need version specific offsets to relaunch closed threads. It's not 100% reliable for missions or external scripts as players often make modified mains designed to be save compatible.

const
  Offset_to_Size_Of_Main = 0xA5690C
  Size_Of_Main_v1 = 194146
  Size_Of_Main_v2 = 194125
  NormalVarspace = 43808
end

 

Another safety check when working extensively with global variables: v1, v2 and save compatible main.scm scripts generally have identical global variable tables. The following check can weed out everything else.

0@ = 0
if
  not &3(0@,1i) == NormalVarspace
then
  gosub @Error_Report

 

BTW, the external scripts are encoded in script.img. Main.scm holds everything else. Both decompile to main.txt.

 

Any luck getting 0AAA working? I suspect Win10 is messing with my opcodes. I had to put SA into compatibility mode to stop mem writes from crashing my game.

 

An alternative to 0AAA would be stepping through CRunningScripts to find the name of the active scripts. This might be a good idea anyway if multiple DEALER scripts are activating at the same time. CRunningScripts is kind of an odd pool - it fills from back to front, so MAIN is usually found at index 95.

Link to comment
Share on other sites

24 minutes ago, OrionSR said:

Any luck getting 0AAA working?

Nope. I am on win7.

External Scripts Info

0xA47B60 – Start of the external scripts info pool. There are 82 elements with 32 bytes of size each

+0x0 = [dword] Script IP

+0x4 = [word] Status (can be obtained by 0926)

+0x6 = [word] Index in SCM (a number as defined in the scm header)

+0x8 = [char] Name, char 20

+0x1C = [dword] Size

Altered my code a bit. Still no luck. Tried byte, word, dword. Nothing changes. I noticed one thing that could possibly ruin the whole idea. In the first script with thread detection it worked kinda like 'show if dealer script is running' with 0 if no and struct pointer/numbers when it is running. At some points when dealer was firing at me it showed 0. Maybe it is reloading at some point ? Anyways, with script like that it replaces all 22's to 23's but it changes absolutely nothing.
 

1@ = 0xA47B60
1@ += 0x6

:read_mem
wait 0
0A8D: 2@ = read_memory 1@ size 2 virtual_protect 0  //reading 
if 
2@ == 19  //check external script id (19 is 'dealer')
then
0AD1: show_formatted_text_highpriority "Found" time 1000
wait 1000
jump @process_info
end
                    
1@ += 32
jump @read_mem

:process_info
wait 0
1@ += -0x6
0A8D: 10@ = read_memory 1@ size 4 virtual_protect 0
0A9A: 28@ = open_file "cleo\testing4.dat" mode "at+" //the file where mem contents will be stored
for 29@ = 0 to 10000 step 1 //loop
0A8D: 2@ = read_memory 10@ size 1 virtual_protect 0  //reading memory
    if 
    2@ == 22
    then
    0AD9: write_formatted_text "Offset:%d Val:%d%c" in_file 28@ 29@ 2@ 0xA //writing it to file
    0A8C: write_memory 1@ size 1 value 23 virtual_protect 0
    end
10@ += 0x1 //going for the next dword
end
0AD1: show_formatted_text_highpriority "done" 1000

 

Link to comment
Share on other sites

2 hours ago, vladvo said:

External Scripts Info

0xA47B60 – Start of the external scripts info pool. There are 82 elements with 32 bytes of size each

+0x0 = [dword] Script IP

 

Interesting. I haven't messed with this pool at all. Poking around with my trusty hex editor:

 

_ZN11CTheScripts15StreamedScriptsE = 0xA47DC0

No luck finding a pointer.

 

external_IP = external_index * external_info_size + _ZN11CTheScripts15StreamedScriptsE
dealer_IP = 19 * 32 + 0xA47B60 = 0xA47DC0  

dealer_IP = 27 * 32 + 0xA47B60 = 0xA47EC0  

 

The pointer reported here (0x15194628) is the same as reported as the BaseIP in a running dealer script, but the script doesn't need to be loaded, and 0AAA doesn't need to work, to find the offset. I suspect that 0x15194628 is not a static address.

 

From there:

dealer_IP

+ 0x355 = modelID

+ 0x13 = weaponID 1

+ 0x0E = weaponID 2

+ 0x2A = weaponID 3

 

@vladvo  Correction: The external scripts are sorted alphabeticly, so the DEALER index is 27. There's bound to be a lookup table somewhere that links the defined external script numbers with their IP or proper index. 

 

And yes, the struct seems to be dynamic, the pointer wasn't the same after reloading (15975718h).

Edited by OrionSR
Link to comment
Share on other sites

@OrionSR
Ok, just extracted dealer.scm from scripts.img. With hex editor replaced all seven 0x16's with 0x17 and packed scm back into img. Guess what - it's working. ))
Well, another thing I tried is replacing 0x16 with 0xFF and 0xFE. This lead to a crash, as expected. Crashlogs below. Can they be useful in any way ? First one with FF, second one with FE
 

Spoiler

GTA SA 1.0.0.0 US
Unhandled exception at 0x0073B525 in gta_sa.exe (+0x33b525): 0xC0000005: Access violation reading location 0xFAA9B0C8.
    Register dump:
        EAX: 0x00C8A9D8  EBX: 0x00000000  ECX: 0xFFFFFF01  EDX: 0xFFFFFFFE  
        EDI: 0x3E800000  ESI: 0x0E576380  EBP: 0x3DCCCCCD  EIP: 0x0073B525  
        ESP: 0x0028F56C  EFL: 0x00210213  CS: 0x00000023   SS: 0x0000002B   
        GS: 0x0000002B   FS: 0x00000053   ES: 0x0000002B   DS: 0x0000002B   
        
    Stack dump:
        0x0028F56C:  0E575DE0 0E575DE0 0E576380 FFFFFFFE 005E6197 FFFFFFFE
        0x0028F584:  000003E8 0E575DE0 00A8F6B0 0E575DE0 0028F668 00000001
        0x0028F59C:  0E575DE0 0047D33A 00000000 000003E8 00000001 00A8F6B0
        0x0028F5B4:  00A8F330 00000001 00A8F6B0 00A8F330 0028F668 00000001
        0x0028F5CC:  004693D8 00000001 00A8F6B0 00A8F330 0028F668 00A8F6B0
        0x0028F5E4:  00A8F330 0028F668 00000001 004667AC 00000001 00A8F6B0
        0x0028F5FC:  00A8F330 00000010 708B21CC 0046CF29 00000000 708B21CC
        0x0028F614:  0028F6D8 00838329 FFFFFFFF 00469FF7 000001B2 00A8F330
        0x0028F62C:  00000000 0046A220 708B21CC 01E02C34 0028F668 01E02C34
        0x0028F644:  00000011 105F1B92 006819EE 00000392 7089B2AF 00000000
        base: 0x00090000   top: 0x0028F56C   bottom: 0x00290000
        
    Backtrace (may be wrong):
        =>0x0073B525 _ZN7CWeapon10InitialiseE11eWeaponTypeiP4CPed+0x85 in gta_sa.exe (+0x33b525) (0x0028F578) 
          0x005E6197 _ZN4CPed10GiveWeaponE11eWeaponTypejb+0x117 in gta_sa.exe (+0x1e6197) (0x0028F59C) 
          0x0047D33A _ZN14CRunningScript23ProcessCommands400To499Ei+0x12a in gta_sa.exe (+0x7d33a) (0x0028F5C8) 
          0x004693D8 _ZN14CRunningScript23ProcessCommands200To299Ei+0x48 in gta_sa.exe (+0x693d8) (0x0028F5EC) 
          0x004667AC _ZN14CRunningScript20ProcessCommands0To99Ei+0x94c in gta_sa.exe (+0x667ac) (0x0028F604) 
          0x0046CF29 CTheScripts::processExternalScriptTriggers+0x29 in gta_sa.exe (+0x6cf29) (0x0028F61C) 
          0x00469FF7 _ZN14CRunningScript7ProcessEv+0xf7 in gta_sa.exe (+0x69ff7) (0x0028F62C) 
          0x0046A220 _ZN11CTheScripts7ProcessEv+0x220 in gta_sa.exe (+0x6a220) 

 

Spoiler

GTA SA 1.0.0.0 US
Unhandled exception at 0x0073B525 in gta_sa.exe (+0x33b525): 0xC0000005: Access violation reading location 0xFAA9B0C8.
    Register dump:
        EAX: 0x00C8A9D8  EBX: 0x00000000  ECX: 0xFFFFFF01  EDX: 0xFFFFFFFE  
        EDI: 0x3E800000  ESI: 0x0E576380  EBP: 0x3DCCCCCD  EIP: 0x0073B525  
        ESP: 0x0028F56C  EFL: 0x00210213  CS: 0x00000023   SS: 0x0000002B   
        GS: 0x0000002B   FS: 0x00000053   ES: 0x0000002B   DS: 0x0000002B   
        
    Stack dump:
        0x0028F56C:  0E575DE0 0E575DE0 0E576380 FFFFFFFE 005E6197 FFFFFFFE
        0x0028F584:  000003E8 0E575DE0 00A8F6B0 0E575DE0 0028F668 00000001
        0x0028F59C:  0E575DE0 0047D33A 00000000 000003E8 00000001 00A8F6B0
        0x0028F5B4:  00A8F330 00000001 00A8F6B0 00A8F330 0028F668 00000001
        0x0028F5CC:  004693D8 00000001 00A8F6B0 00A8F330 0028F668 00A8F6B0
        0x0028F5E4:  00A8F330 0028F668 00000001 004667AC 00000001 00A8F6B0
        0x0028F5FC:  00A8F330 00000010 708B21CC 0046CF29 00000000 708B21CC
        0x0028F614:  0028F6D8 00838329 FFFFFFFF 00469FF7 000001B2 00A8F330
        0x0028F62C:  00000000 0046A220 708B21CC 01E02C34 0028F668 01E02C34
        0x0028F644:  00000011 105F1B92 006819EE 00000392 7089B2AF 00000000
        base: 0x00090000   top: 0x0028F56C   bottom: 0x00290000
        
    Backtrace (may be wrong):
        =>0x0073B525 _ZN7CWeapon10InitialiseE11eWeaponTypeiP4CPed+0x85 in gta_sa.exe (+0x33b525) (0x0028F578) 
          0x005E6197 _ZN4CPed10GiveWeaponE11eWeaponTypejb+0x117 in gta_sa.exe (+0x1e6197) (0x0028F59C) 
          0x0047D33A _ZN14CRunningScript23ProcessCommands400To499Ei+0x12a in gta_sa.exe (+0x7d33a) (0x0028F5C8) 
          0x004693D8 _ZN14CRunningScript23ProcessCommands200To299Ei+0x48 in gta_sa.exe (+0x693d8) (0x0028F5EC) 
          0x004667AC _ZN14CRunningScript20ProcessCommands0To99Ei+0x94c in gta_sa.exe (+0x667ac) (0x0028F604) 
          0x0046CF29 CTheScripts::processExternalScriptTriggers+0x29 in gta_sa.exe (+0x6cf29) (0x0028F61C) 
          0x00469FF7 _ZN14CRunningScript7ProcessEv+0xf7 in gta_sa.exe (+0x69ff7) (0x0028F62C) 
          0x0046A220 _ZN11CTheScripts7ProcessEv+0x220 in gta_sa.exe (+0x6a220) 

 

Edited by vladvo
Link to comment
Share on other sites

4 hours ago, vladvo said:

Well, another thing I tried is replacing 0x16 with 0xFF and 0xFE.

Oh, yeah. Better stick with normal IDs. I'm pretty sure this opcode can't support random or disabled settings. Those -1 and -2 settings are pretty rare. The opcode needs to have special handling for those cases. 0 should be unarmed.

 

Last reminder; you really should update the weapon's model ID and not just the weapon ID. 

 

You could let him keep his weapon and take away the ammo. Behavior tends to change if there's no ammo, which can be a factor in unexpected results. Stuff like parachutes and IR Goggle need ammo too, for example. An easy way to experiment is by changing gang weapons.

 

9 hours ago, OrionSR said:

Any luck getting 0AAA working?

I thought it odd that opcode 0AAA wasn't finding script pointers for me, but very strange that it didn't work for you either. I asked Seemann (SB/CLEO author) about it and he got the same results. A short investigation discovered an issue with the 0AAA opcode when used on CRunningScripts, but it works just fine for custom scripts, so it's gone unidentified for a quite a while.

 

CLEO v4.4.2 - This is a pre-release for testing; just the new .asi file. 0AAA appears to be working properly now.

 

@ArmanCan - Remember a while back when we were struggling to help a user with a Glitch Fix (CallFix, maybe) and the script kept crashing? This was probably caused by the buggy 0AAA opcode. And since you tend to help a lot of people with these sorts of issues, I figured I'd better clue you into the new cleo.

Edited by OrionSR
Link to comment
Share on other sites

7 hours ago, OrionSR said:

CLEO v4.4.2 - This is a pre-release for testing; just the new .asi file. 0AAA appears to be working properly now.

An exclusive fresh bread from the bakery.. mmm delicious 😋

7 hours ago, OrionSR said:

Remember a while back when we were struggling to help a user with a Glitch Fix (CallFix, maybe) and the script kept crashing?

I couldn't remember but you've helped so many peoples property errors.. (and maybe one of the players Blackboard glitches 😉)

7 hours ago, OrionSR said:

And since you tend to help a lot of people with these sorts of issues, I figured I'd better clue you into the new cleo.

yes.. i love you guys both so which script am i going to test? 😎

Link to comment
Share on other sites

8 hours ago, OrionSR said:

CLEO v4.4.2 - This is a pre-release for testing; just the new .asi file. 0AAA appears to be working properly now.

What about the broad audience ? Will the scripts that use fixed 0AAA work with 4.4.1 ?

Link to comment
Share on other sites

CLEO v4.4.2 - Official Release

  • fix eventual crash when using the game's user track player radio station (see #38 for details)
  • fix 0AAA opcode not working with scripts from main.scm #56

 

5 hours ago, vladvo said:

What about the broad audience ?

It would be best to reference official links like the one above. An auto-installer version isn't currently available. Sanny Builder won't include the new CLEO version until next release.

 

5 hours ago, vladvo said:

Will the scripts that use fixed 0AAA work with 4.4.1 ?

Yes and No:

  • Yes - Scripts that use 0AAA to search for custom cleo scripts will work as expected. This is the most common use scenario.
  • No - Scripts that use 0AAA to search for scripts from main.scm (internal, missions, streamed external) won't work on v4.4.0 or v4.4.1.

 

7 hours ago, ArmanCan said:

which script am i going to test? 😎

I don't think there are any scripts in this topic in good enough shape for testing. The problems with 0AAA prevented any progress finding the code base with this method. vladvo found a better strategy to find the code base using the Streamed Script Info struct, but we finished one step shy of linking the defined external script index with the info struct. But vladvo has proven to be quick to adapt to alternate strategies as new information was learned, and appears to have settled on hex editing script.img, which is similar to recompiling main.txt. 

 

Three scripts that need a working 0AAA are BlackboardFix, CallFix, and OpenFix. To test these properly you'd need broken saves, and most reference links to broken saves have probably expired. Not to create busy work for you but, you might find it an interesting challenge to recreate the glitches. Most of the testing goes towards testing the fix, but to complete the scientific process, the cause of the glitches should also be tested.

 

Link to comment
Share on other sites

46 minutes ago, OrionSR said:

and appears to have settled on hex editing script.img

Wait a second. I am not settled at all. ) The hex editing was just for fun and testing. What I want to achieve is to give dealers random weapons. Or make them unarmed. That's surely not possible with hex editing the file. 
Even that the dealer script can be stopped and restarted by the game for some reason  (thus giving the dealer gun back - and ammo too - 01B2: give_actor 0@ weapon 22 ammo 1000)- it can be fixed by checking the dealer's handle (his handle is stored in script's local vars (they start at CRunningScript struct +0x3C).
So, I am still into this. Another reason why I am kinda obsessed with finding a way to alter external scripts 'on-the-fly' is that it could bring some more modding possibilities and ability to tweak some aspects of the game without messing with scm file. But I am kinda stuck. No progress so far.

Edited by vladvo
Link to comment
Share on other sites

2 hours ago, vladvo said:

Wait a second. I am not settled at all. ) The hex editing was just for fun and testing.

Good to hear, but I didn't want to assume. Some interesting mysteries and challenges remain in this project. Either way, I really appreciate your efforts to pursue alternate strategies.

 

2 hours ago, vladvo said:

Even that the dealer script can be stopped and restarted by the game for some reason

These scripts attach themselves to ped models. I'm expecting multiple instances of the DEALER script, but that each script will maintain it's connection with a specific ped. What might appear as a script shutting down and restarting could be a new instance of the script popping in and getting found first.

 

//ALLOCATE_STREAMED_SCRIPT_TO_RANDOM_PED
0928: init_external_script_trigger 19 (DEALER) with_actor_model #BMYDRUG priority 100 
0928: init_external_script_trigger 19 (DEALER) with_actor_model #WMYDRUG priority 100 
0928: init_external_script_trigger 19 (DEALER) with_actor_model #HMYDRUG priority 100 
0928: init_external_script_trigger 19 (DEALER) with_actor_model #BIKDRUG priority 100 
// See also: Home Coming - :MANSIO3

 

2 hours ago, vladvo said:

checking the dealer's handle (his handle is stored in script's local vars (they start at CRunningScript struct +0x3C).

Which reminds me, I'm trying to practice with the new Cleo+ opcodes.

0D2E: set_script 0@ var 10 to 10.0
0D2F: 1@ = get_script 0@ var 10

@0 is a script pointer, so we get to use the fixed 0AAA. It might help to tag an unused lvar so you can tell which instances of the DEALER script have been managed.

 

2 hours ago, vladvo said:

alter external scripts 'on-the-fly'

The hex editing method is a really good way to measure and test modifications but, this gets to my major concern of the current strategy. Each instance of a DEALER "script" in CRunningScripts will execute the same binary DEALER "code" - the compiled script. So any changes on-the-fly will effect all of the dealers, depending on where their script is in it's execution loop.

 

A possible solution: As I was browsing through the DEALER code in the StreamScripts struct, I noticed what appeared to be a lot of slack, blank bytes, after the code. Normally it's pretty hard to make modifications to existing code that doesn't match the exact size of the original code. For example, changing the int8 model ID to a local var; the 2 byte lvar wouldn't fit. However, if there's enough slack in the code pool, new codes could be appended and execution can be redirected by changing a jump_to pointer. Basically, skip all that and do this instead.

 

Chew on that for a bit. If you like the idea I can follow up with some more research. But first, I've got some pickup tests I need to finish up. Seemann wasn't able to replicate my results; and since he was kind enough to push out a cleo update for our 0AAA problem, I think it best to follow through with additional information sooner rather than later.

Edited by OrionSR
Link to comment
Share on other sites

Rolling back in time a bit. 

On 5/2/2023 at 8:15 PM, OrionSR said:

Correction: The external scripts are sorted alphabeticly, so the DEALER index is 27.

I thought that this requires some confirmation and I checked 0x8 name  db 20 dup(?)
 

wait 0
1@ = 0xA47B60 //Ext scripts info pool
1@ += 0x6  //wSCMindex
:read_mem
wait 0
0A8D: 2@ = read_memory 1@ size 2 virtual_protect 0  //reading 
if 
2@ == 19  //check external script id (19 is 'dealer')
then
1@ += 0x2 // add 0x2 to get 0x8 - offset to name
0A9A: 28@ = open_file "cleo\testing5.dat" mode "at+" //the file where mem contents will be stored
for 29@ = 0 to 20 step 1 //loop
0A8D: 2@ = read_memory 1@ size 1 virtual_protect 0  //reading memory
0AD9: write_formatted_text "B: %d%c" in_file 28@ 2@ 0xA //writing it to file
1@ += 0x1 //going for next byte
end
jump @process_info //this is not importans here
end
1@ += 32 //increase by 32 bytes to get next ext script wSCMindex
jump @read_mem

 

In the output file I got these numbers: 100 101 97 108 101 114. Using decimal to text converter these numbers turned into 'dealer'.

Link to comment
Share on other sites

Ok. No we got somewhere.
Using the code from comment above I've read (CExternalScriptInfo struc) 0x0 data dd.
Read it, save to a file.
Result: 592684944 / 0x2353A790

Downloaded HeapMemView. Found this address. And there it is. Check the screen.
 

Spoiler

I60pPgX.jpg

Next. Start artmoney and in address range 0x2353A790 to 0x2353B750 I searched for 22. Found 8 of them. Changed them all to 23 and switched back to SA. Aimed at dealer and he pulled out a silenced pistol. Killed him, found another one and he had a silenced pistol as well. Ok. Good. Why wasn't it working in game ? I posted this script already so I'll hide it under a spoiler. 

Spoiler

 

:functions
wait 0
0000: NOP
:get_script_info_pointer
wait 0
1@ = 0xA47B60
1@ += 0x6

:read_mem
wait 0
0A8D: 2@ = read_memory 1@ size 2 virtual_protect 0  //reading 
if 
2@ == 19  //check external script id (19 is 'dealer')
then
jump @process_info
end
1@ += 32
jump @read_mem
//   0x2353A790
:process_info
wait 0
0A9A: 28@ = open_file "cleo\testing4.dat" mode "at+" //the file where mem contents will be stored
1@ += -0x6
0A8D: 10@ = read_memory 1@ size 4 virtual_protect 0
0AD9: write_formatted_text "Script: %d%c" in_file 28@ 10@ 0xA //writing it to file

for 29@ = 0 to 10000 step 1 //loop
0A8D: 2@ = read_memory 10@ size 1 virtual_protect 0  //reading memory
    if 
    2@ == 22
    then
    0AD9: write_formatted_text "Offset:%d Val:%d%c" in_file 28@ 29@ 2@ 0xA //writing it to file
    0A8C: write_memory 10@ size 1 value 23 virtual_protect 0
    end
10@ += 0x1 //going for the next dword
end
0AD1: show_formatted_text_highpriority "done" 1000

 

It does work. It replaces all vars because if you run it twice - nothing appears in output file. It means that the vars are changed ! Wtf then ?
The solution is so simple....Press ESC to go to menu, and then press it again to go back into the game. Aim at dealer. He pulls out a silenced pistol.

I think we missed one thing - refreshing the script. Reassigning it to the dealer again. 
Upd. Now it works even without pressing ESC. Find dealer, press key, run script and he has a silenced pistol. I just don't know what's the reason. Why wasn't it working before ? 0AAA bug ? 

Link to comment
Share on other sites

Good news and bad news. Bad new first:

 

I tried to implement the following functions without success. All attempts resulted in crashes, which is pretty common when I'm trying to figure out game functions on my own. These functions would certainly be useful but we could manage their function without them easily enough.

 

    SUPPORTED_10US signed int FindStreamedScript(char const *scriptname);
    SUPPORTED_10US signed int FindStreamedScriptQuiet(char const *scriptName);
    SUPPORTED_10US signed short GetProperIndexFromIndexUsedByScript(short scmIndex);
    SUPPORTED_10US char const *GetStreamedScriptFilename(unsigned short index);
    SUPPORTED_10US unsigned short GetStreamedScriptWithThisStartAddress(unsigned char *dataPtr);

 

Most of my research so far has been based on constants for standard v1. I'm starting to think ahead to non-standard environments.

 

I have been unable to find a pointer for _ZN11CTheScripts15StreamedScriptsE. _ZN11CTheScripts15StreamedScriptsE_ptr turned up nothing.

It would be nice to read the size of the streamed script records without relying on a constant 32 bytes.

 

DEALER, "scmIndex" 19. proper "Index" 27.

scmIndex is based on script order and defined in the header of main.scm.

The scmIndex is required to work with external scripts using SCM commands.

Index is the alphabetically sorted index for external scripts as used in StreamedScripts.

If main.scm is modified, the script should be able to discover if 'DEALER' is a current script and return the scmIndex, etc.

 

Stuct info with comments for reference:

class PLUGIN_API CStreamedScripts {
public:
    struct
    {
        void *data;           +0x0  pointer to code base if loaded
        char m_nStatus;       +0x4  bool? True when loaded
        char field_5;
        short m_nScmIndex;    +0x6  index defined in scm
        char m_Name[20];      +0x8  name
        int m_nsize;          +0x1C size of data to load
    } m_aScripts[82];

 

*data is empty on a new game start. Contains the pointer of code base when m_nStatus is 1. I'm not sure if old pointers remain when no longer loaded. 

 

Status is probably used for :

0926: $SCRIPT_STATUS = external_script_status 66 (CARMOD1)
08AB:   external_script 66 (CARMOD1) loaded

 

I saved the standard v1 data of StreamedScripts to text for reference:

(Includes ScmIndex, Name, Size and proper Index)

Spoiler
_ZN11CTheScripts15StreamedScriptsE = 0xA47B60
CStreamedScripts = A47B60h
(PCv1)

ScmIndex, Name, Size, proper Index 
78, aaa, 8, 0
60, ammu, 12663, 1
7, arcade, 787, 2
62, barber, 9366, 3
48, barguy, 1227, 4
28, bar_ambience, 2367, 5
38, bar_staff, 2379, 6
15, basketb, 21589, 7
2, bcesar2, 6383, 8
3, bcesar3, 7720, 9
17, blackj, 34802, 10
39, bouncer, 954, 11
55, browse, 1327, 12
23, burg_brains, 5675, 13
75, camera, 3792, 14
66, carmod1, 24477, 15
70, carpark1, 2778, 16
27, casino_ambience, 3185, 17
64, clothes, 10619, 18
57, coplook, 1480, 19
56, copsit, 2715, 20
67, crane1, 3078, 21
68, crane2, 5734, 22
69, crane3, 1773, 23
37, customer_panic, 337, 24
35, dance, 17372, 25
52, dancer, 1584, 26
19, dealer, 4043, 27
76, debt, 4054, 28
47, fboothl, 3444, 29
46, fboothr, 3444, 30
29, foodbrains, 946, 31
9, food_vendor, 1187, 32
10, gates_script, 1008, 33
25, gf_date, 33719, 34
24, gf_meeting, 6171, 35
26, gf_sex, 14242, 36
12, gymbench, 8102, 37
11, gymbike, 6341, 38
14, gymdumb, 7926, 39
13, gymtread, 5909, 40
20, home_brains, 3703, 41
77, hotdog, 725, 42
71, impound, 22357, 43
65, junkfud, 13002, 44
22, lowr_cont, 1833, 45
44, otbslp, 1062, 46
45, otbtill, 815, 47
43, otbwtch, 1282, 48
30, otb_ambience, 739, 49
6, otb_script, 663, 50
40, otb_staff, 1008, 51
1, parachute, 7354, 52
41, pchair, 3456, 53
42, pcustom, 980, 54
50, pedcard, 1495, 55
49, pedroul, 1158, 56
51, pedslot, 1202, 57
73, photo, 2601, 58
32, planes, 8106, 59
0, player_parachute, 5631, 60
21, pool_script, 2196, 61
74, prisonr, 1446, 62
5, roulette, 18246, 63
36, shopkeeper, 1852, 64
59, shopper, 1233, 65
4, slot_machine, 3583, 66
54, stripm, 1248, 67
53, stripw, 3675, 68
31, strip_ambience, 5881, 69
61, tattoo, 15539, 70
58, ticket, 965, 71
33, trains, 790, 72
72, valet, 17471, 73
8, vending_machine, 1676, 74
16, vidpok, 35122, 75
63, wardrobe, 12534, 76
18, wheelo, 16507, 77
34, zero_ambience, 556, 78
255, ...................., 0, 79
255, ...................., 0, 80
255, ...................., 0, 81

 

 

It might be a good idea to load the dealer script with your script and keep it in memory so you don't need to track it's status and reapply fixes.

 

It looks like you can successfully edit the code on-the-fly and assign alternate weapons to your dealer. However, if you keep swapping weapon models using a simple search and replace strategy then you'll eventually run into trouble with an opcode like 0216 getting changed to 0217. This problem can easily be solved using specific offsets or each model ID.

 

The main problem with this strategy is all dealers will get the same weapon and change to the new weapon when applied. It won't present as good of an illusion as random weapons for each dealer ped, but maybe it's good enough.

 

There's a reasonably good chance that a jump destination can be changed to avoid the arming and rearming routines. This might allow the dealers weapons to be changed using conventional ped commands. I'm leaning towards a more robust strategy.

 

I checked loaded code block again and this time there was a lot of unknown data following the codes. There also appears to be some additional script info following the code block. The structure where the codes are loaded doesn't seem to have a fixed size. So before the script was loaded I changed the size value to 8000, and when it loaded I had an extra 3957 blank bytes before that mysterious extra data. So it should be possible to add additional codes to DEALER and take complete control of his behavior.

 

Adding additional codes is (probably) not as hard as it sounds. Most of the work can be done by Sanny. Decompile main.scm and edit the Dealer script. Let the script do as much as is appropriate before changing a single jump label to new codes at the end of the script. Compile and test, and if everything is working properly then copy the extra codes to a hex construct in your script. The hex data can copied to the extra data created by the size adjustment. This strategy keeps the jump offsets aligned properly. And, if a dealer script is currently active everything should keep working normally until the jump offset is changed to redirect control to the appended codes.

 

This idea may seem far-fetched, and there's bound to be additional problems, but the process is similar to and less complex than embedding custom scripts in PS2 save files. 

Edited by OrionSR
Link to comment
Share on other sites

8 hours ago, OrionSR said:

It looks like you can successfully edit the code on-the-fly and assign alternate weapons to your dealer. However, if you keep swapping weapon models using a simple search and replace strategy then you'll eventually run into trouble with an opcode like 0216 getting changed to 0217. This problem can easily be solved using specific offsets or each model ID.

Yep. I already got crash because of that. Here is a small how-to for the curious ones that follow our topic.

Found a solution, using your advice.

In dealer.scm (script file, extracted from scripts.img), when we open it in Sanny we have the following lines:
Note: Set a checkmark near SKIP_SCM_HEADER in Sanny's debug options (gear icon on the right side of menu) to decompile external script scm.

:DEALER_922
01B9: set_actor 0@ armed_weapon_to 22

So, looking at _922 we can figure that 0x16 (22 - pistol id) is somewhere there. In hex editor we see the following (ignore the image file name - imgur set it, ask them). 

Spoiler

sexyAs4.jpg

What do we have here. Left red circle - the '16' we're looking for. Lower circle - 928 - offset from start of file. In our case - from ScriptIP. And top-right circle has B9 01 and that's 01 B9 in reverse order. That's the opcode 01B9. So, that's the right 16 to change.
A bit on top there are 16 near 01B2 and another 16 near 8491 (not Actor.HasWeapon(0@, 22) in dealer.scm.
In case of this file there were only eight 16's (including the one in Actor.GiveWeaponAndAmmo(0@, WeaponType.Pistol, 1000) ) - in hex the 'Pistol' is written as 22. So, a only a few minutes to check them all. And the first one turned out to be something else, not related to weapon id.

Edited by vladvo
Link to comment
Share on other sites

4 hours ago, vladvo said:

Here is a small how-to for the curious ones that follow our topic.

Nice tutorial. Here's a little binary scm info that can help refine your search:

 

004D: jump_if_false @DEALER_922 
01B9: set_actor 0@ armed_weapon_to 0 
0002: jump @DEALER_929 

:DEALER_922
01B9: set_actor 0@ armed_weapon_to 22

Working backwards through this segment;

The data after opcode 01B9 (B9 01) is;

  • 03 - following data type is lvar
  • 00 00 - lvar 0@
  • 04 - data type int8 (-128 to 127)
  • 16 - weapon type 22, pistol

We only need to check if the dealer's data is int8 22, so 0x16040000 would refine your search considerably.

 

:DEALER_929 - labels don't really exist in the code, the offset (929) is the important information, but...

 

004D: jump_if_false @DEALER_922 // (look ahead a bit in your image)

  • 4D 00 - jump_if_false
  • 01 - int32
  • 66 FC FF FF = -922

The jump offsets for external script, cleo scripts, and missions (iirc) are encoded with negative offset relative to the start of the script. Regular scripts, those listed before missions in main.scm, have a BaseIP of 0 and positive offsets are relative to CTheScripts, or $0. 

 

Additional reference: I'm not sure of GTAF's policy for reference links, so search github for Sergeanur's UndefinitiedGrove project and follow the Tag to pc_1.00. This archive has the file \Scripts\main\misc\Dealer.sc, a text file with useful information like descriptive local variable names, subroutine names, and high level script structure that you might find useful.

 

Whether you're interested or not, I'm planning to follow through with adding additional codes to the Dealer script, just to see if I can pull it off. 

 

:DEALER_478
0002: jump @DEALER_345 

This marks the end of the main dealer loop. All codes funnel here, so I could redirect to a completely different set of added dealer codes by changing the destination. However, there are only three subroutines (4 gosubs) that alter the weapon.  It might be easier to add only the new subroutines to the dealer script, and simply replace the 4 original gosub destinations.

 

Of particular interest are:

  • :DEALER_846  // Dealer_InventoryCheck:
  • :DEALER_3595 // Dealer_HatePlayer:
  • :DEALER_3368 // Dealer_DislikePlayer:

 

Edited by OrionSR
Link to comment
Share on other sites

@OrionSR

Yep, I am in for further developement. So far, I made this https://www.mediafire.com/file/i3zxv1zpgk3ir00/armdealer.txt/file
Sort of working version. The main problem is the one you mentioned - when there are 2 instances of the script. Here's where the mess starts. In this case dealer sometimes drop 3 different weapons. 

Changed 01B9: set_actor 0@ armed_weapon_to 0 to 01B9: set_actor 0@ armed_weapon_to xx/weap-id, so that the dealer will always have his weapon visible.
Another issue is that the dealer isn't aggresive enough and tends to flee when in melee combat.

Edited by vladvo
Upd
Link to comment
Share on other sites

5 hours ago, vladvo said:

Another issue is that the dealer isn't aggresive enough and tends to flee when in melee combat.

This might be working as intended. The comments from this mission suggest the dealer should attempt to run away depending on the player's respect or total_respect level.  I'm actually not all that sure what the dealer should be doing. This script always seemed rather incomplete.

Link to comment
Share on other sites

@OrionSR
Some more question about writing to memory.
In decompiled original dealer.scm there is a line "Model.Available(#COLT45)". I want to replace #COLT45 with something else.

That's how it looks in hex editor:

00000350h: 04 00 48 02 05 5A 01 4D 00 01 5F FC FF FF D6 00 ; ..H..Z.M.._ьяяЦ.

#COLT45 is modelID (346) = 0x15A = 5A 01 reversed. (Offset 853 and 854)
What if I want to write something like ABCD (43981 dec) there ? (Yea, I know it won't work, but just as an exampler).

What size should I put in 0A8C: write_memory 10@ size ?? value 5@ virtual_protect 0

Size 1 or size 2 ?
Or maybe use 2 writes with 5@ = 0xCD for offset 853 and 0xAB for offset 854?

Link to comment
Share on other sites

6 hours ago, vladvo said:

#COLT45 is modelID (346) = 0x15A = 5A 01 reversed. (Offset 853 and 854)

const
  modelID_offset = 0x355 // dec2hex(853)
//  weapon_offset_1 =
//  weapon_offset_2 =
//  .. all relative to start of element
end

10@ = pointer to DEALER element + modelID_offset
0A8C: write_memory 10@ size 2 value #SILENCED virtual_protect 0

I intend to finish a proof of concept script today.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • 1 User Currently Viewing
    0 members, 0 Anonymous, 1 Guest

×
×
  • Create New...

Important Information

By using GTAForums.com, you agree to our Terms of Use and Privacy Policy.