• Welcome to Valhalla Legends Archive.
 

In Game Messaging

Started by Don Cullen, June 15, 2005, 03:27 PM

Previous topic - Next topic

Kp

Quote from: Kyro on June 16, 2005, 03:41 PMcan you give a screen shot of sc in a window? i'm curious as to what it looks like. im not home right now, still at work, but I plan on trying that- it'd be very benefitical if i could get sc to run in a window- it'd allow me to multitask much easier...

It looks pretty much like you'd expect: (mostly) correct, but smaller.  battle.snp's GUI code is an absolute mess, and doesn't interact with ScWnd well at all.  In game looks ok, but the window-izing hacks make it run slower, and of course everything is scaled down.  IMO, it's not generally useful if you want to play, but it can be handy if you want to debug, or hang out in a game and lag it up while watching IM clients (I've had people do this to me; it's quite irritating).
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

Don Cullen

I've been scanning Storm.dll via IDA for the function you mentioned... Could it be this one:

.text:15001960 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:15001960
.text:15001960
.text:15001960 sub_15001960    proc near               ; CODE XREF: sub_150013F0+225p
.text:15001960                                         ; sub_150013F0+287p
.text:15001960
.text:15001960 arg_0           = dword ptr  4
.text:15001960 arg_4           = dword ptr  8
.text:15001960 arg_8           = dword ptr  0Ch
.text:15001960
.text:15001960                 test    ecx, 80000000h
.text:15001966                 jz      short locret_1500197C
.text:15001968                 mov     eax, [esp+arg_8]
.text:1500196C                 mov     ecx, [esp+arg_4]
.text:15001970                 push    eax
.text:15001971                 mov     eax, [esp+4+arg_0]
.text:15001975                 push    ecx
.text:15001976                 push    eax
.text:15001977                 call    edx
.text:15001979                 add     esp, 0Ch
.text:1500197C
.text:1500197C locret_1500197C:                        ; CODE XREF: sub_15001960+6j
.text:1500197C                 retn    0Ch
.text:1500197C sub_15001960    endp
.text:1500197C
.text:1500197C ; ---------------------------------------------------------------------------


I'm hoping that's the one- because it has three arguements, and the third arguement seems to be a char*... I could be wrong tho. If I'm wrong, can I have a hint? LOL...
Regards,
Don
-------

Don't wonder why people suddenly are hostile when you treat them the way they shouldn't be- it's called 'Mutual Respect'.

Blaze

I'll look at it tomorrow and try it out.  You like IDA Kyro?
Quote
Mitosis: Haha, Im great arent I!
hismajesty[yL]: No

Don Cullen

Yea, IDA's pretty cool- I just wish I could understand assembly, and the code itself...
Regards,
Don
-------

Don't wonder why people suddenly are hostile when you treat them the way they shouldn't be- it's called 'Mutual Respect'.


Ringo

Quote from: NicoQwertyu on June 17, 2005, 12:17 AM
Userloser, or anyone, how can I "run multiple desktops at once?"  I'd like to be able to do this without relying on Skywing's program.

hmm, i had somthing like this ages ago, witch would let you install multiple os's on the desctop, but it used to lag like hell when the host and virtual PC where on the net, and i only ever remember installing window 98's on it.
It sounds abit like what your after, but i dunno where you would find a cracked/trail copy :(

Warrior

They released some sort of tweak for allowing multiple desktops. That's multiple OS's. Isn't that overcomplicating it :P
Quote from: effect on March 09, 2006, 11:52 PM
Islam is a steaming pile of fucking dog shit. Everything about it is flawed, anybody who believes in it is a terrorist, if you disagree with me, then im sorry your wrong.

Quote from: Rule on May 07, 2006, 01:30 PM
Why don't you stop being American and start acting like a decent human?

Blaze

My videocard has multiple desktop software, but its crappy.  Go ATI!
Quote
Mitosis: Haha, Im great arent I!
hismajesty[yL]: No

Don Cullen

#38
Okay, I just attempted to use function 607... And guess what happened? It CRASHED Visual Basic... I didn't know using Storm.dll would do that... If testing involves rebooting VB constantly, so be it. ;-p

I also tried function 613... Also resulted in VB crashing.

Anyway, this is my Main.frm code so far:

Private Sub cmdDisplay_Click()
    STextOut 5000, 16777215, Text1.Text
End Sub


And this is my Storm.bas code:

Option Explicit

'Storm declarations

Public Declare Function STextOut Lib "Storm.dll" Alias "#607" (ByVal Arg1 As Long, ByVal Arg2 As Long, ByRef Arg3 As String) As Long


Where in the STextOut function (STextOut 5000, 16777215, Text1.Text), 5000 is 5 seconds (5000 milliseconds) of display, 16777215 is the color white (as converted from hex to deca), and Text1.text is the text to display (as obtained from a text box.

Now, this was coded by purely guesswork- I don't even know if I have the right function, or what the timeout argument is (I presumed it was how long to display text?), or what color format should be used, or how the values should be passed to the function. Any input would be appreciated...
Regards,
Don
-------

Don't wonder why people suddenly are hostile when you treat them the way they shouldn't be- it's called 'Mutual Respect'.

NicoQwertyu

#39
I believe what you want to do is inject a .dll, into Starcraft, which calls the function from within the process.  In which case VB wouldn't be the language of choice.

And as far as multiple desktops go:

1.) I have VMware, but havn't been able to test it yet.

2.) I've tried Virtual Desk which allows "virtual desktops," but is completely useless because I still get stuck in SC when a bp triggers.

Warrior

your best bet would be to run in a window. Maybe if you program and compile using the same compiler SC used you might be able to call functions directly after you inject *shrug*
Quote from: effect on March 09, 2006, 11:52 PM
Islam is a steaming pile of fucking dog shit. Everything about it is flawed, anybody who believes in it is a terrorist, if you disagree with me, then im sorry your wrong.

Quote from: Rule on May 07, 2006, 01:30 PM
Why don't you stop being American and start acting like a decent human?

warz

Tutorial: Starcraft Screen Messages
Author: Drakken
Date: 4/24/03
Site: http://www.gamethreat.com/
Game: Starcraft/Broodwar version 1.10
_____________________________________

Intro:
In this tutorial I'm going to show you a way to send client side messages to the screen in starcraft. This is not the same method that the current Ally Alert uses. I did use this method in Ally Alert before version 3.0. You'll only need to use three windows API (ReadProcessMemory, WriteProcessMemory, GetTickCount) to do this so even if you're programming in visual basic this shouldn't be too hard for you. This will not work with TMK. I'll go through the whole process of developing this method so that hopefully you will follow along and learn some new techniques that you can use in the future. Try to resist skimming through this tutorial.
_____________________________________

What you will need:
Softice - I highly reccomend you get softice. It is the BEST and most valuable tool EVER. Trust me. You could try to use the autohack in TSearch instead, but that's about as silly as trying to hack starcraft on a mac.

Memory Searcher/Editor - I use Winhack. It's decent and easy to use but it is shareware and requires registration to use all the features. You can use another program if you like. As long as it can do ascii memory searches and view hex/ascii memory it will be fine. TSearch and Artmoney are good programs.

W32Dasm - This is a disassembler. You can also use IDA which is more advanced but I prefer w32 because I'm used to it.

You should be able to find these tools pretty easily on the net. Search on google.com or check Gamehacking.com or protools.cjb.net.

Some knowledge of assembly will really be helpful but not totally necessary. Hopefully you'll learn a little bit while reading this.

_____________________________________

Let's begin:
Start up starcraft and load a game. Do not start a single player campaign game because you can't type and send text to the screen. Either get on battle.net or load up a lan game.

First we want to find where starcraft stores the text that is displayed on screen. If you type a bunch of messages quickly you can see that there are eleven lines of text that can fit on the screen at one time. So we can assume that starcraft has eleven offsets for these. Now let's find them.

Open the text box and type "Drakken is my hero" and press enter. hehe . Now open Winhack or your favorite memory editor and select the starcraft process. Now we want to do an ascii search. In Winhack select the edit memory tab and make sure "Ascii String" is selected next to the search box. Do an ascii search for "Drakken". For me it lands at the offset 651535. If you got a different offset don't worry about that, remember there's eleven offsets for the screen text. Now scroll up through the memory and you can see the whole string that was displayed on screen. "yourname: Drakken is my hero". It begins at offset 65152C. Notice that the first byte of yourname is 00. This is how starcraft removes the messages from the screen. It nulls (null=00) the first byte of the message to remove it from the screen.

Now go through and find all eleven offsets for the screen text. Write a different message for every search so you don't find the same offset again. And make sure to use a word that is unique that you won't find somewhere else in starcraft's memory space. For instance, use a message like "GameThreat.com ownz" then search for "GameThreat". Write down the offsets for the beginning of each of the text spots.

When finished you should have found these eleven offsets:
650CA8
650D82
650E5C
650F36
651010
6510EA
6511C4
65129E
651378
651452
65152C

Now we've got all eleven offsets. (there's actually a couple more but don't worry about those.) You're probably thinking we're done now. Try writing a string to one of those offsets. Then maximize starcraft to see your message on screen. What?!? It's not there? Look at the string you just wrote. Starcraft has nulled the first byte of it immediately after you wrote it to memory. Why? Starcraft creates a timer value for each message so that it knows when to remove it from the screen. So the next thing we want to do is find where starcraft writes the null byte to the message to remove it.

Why don't we just search for the timer value?
Because we have no idea what that value might be and since the messages only stay on screen for a few seconds we would have little time to find it.

Why do we want to find where it writes the null byte?
Because shortly before it writes the null byte starcraft will check the timer to see if it's time to write the null byte. We can find a reference to the timer there.

We will have to set a breakpoint in softice on one of the offsets to find where starcraft writes the null byte. Let's use the first offset on that list. 650CA8. It is already null but starcraft is constantly checking those offsets to see if anything is there. Maximize starcraft and press ctrl-D to bring up softice. Look to the bottom right corner of softice to make sure you are in the starcraft process. If not press ctrl-D a couple times until you are. Then type:

BPM 650CA8

Press enter to set the breakpoint. Then press ctrl-D to exit softice. Softice will immediately pop again at this offset: 46B825. Press ctrl-D a few more times to see if starcraft accesses that offset anywhere else. It doesn't. Now type "bc *" in softice to clear your breakpoints then press ctrl-D one last time to exit softice.

Now let's look at the disassembly so we can see what's going on at 46B825. Open W32Dasm, select Disassembler, Open file to Disassemble. Then find starcraft.exe. It will take a few minutes to disassemble. Now is a good time for a break. Go grab a snack, get a drink, take a leak, or have a smoke.

When it's done you can click disassembler and save the disassembly to a project file so that you don't have to disassemble starcraft.exe every time you open w32dasm. This is optional but it saves time.

Now click Goto, then Goto Code Location. In the code offset box enter the offset we want to look at. 46B825. I've written down some of the important code and made some comments to show you what it's doing.


ASM Code:
:0046B812 Call dword ptr [004E61A0]
:0046B818 mov edi, eax
:0046B81A xor esi, esi

:0046B81C lea eax, dword ptr [esi+2*esi]
:0046B81F lea eax, dword ptr [eax+8*eax]
:0046B822 lea eax, dword ptr [esi+4*eax]
:0046B825 mov cl, byte ptr [2*eax+00650CA8]
:0046B82C test cl, cl
:0046B82E je 0046B8D9
:0046B834 mov edx, dword ptr [4*esi+006517BC]
:0046B83B mov ecx, edi
:0046B83D sub ecx, edx
:0046B83F js 0046B8D9
:0046B845 cmp esi, 0000000C
:0046B848 mov byte ptr [2*eax+00650CA8], 00
:0046B850 jne 0046B86C


Comments:
:0046B812 calls GetTickCount (result ends up in eax)
:0046B818 moves the tick count from eax to edi for use below
:0046B81A clears esi

:0046B81C sets up eax for the pointer to the message offset
:0046B81F " "
:0046B822 " "
:0046B825 moves the first byte of the message offset into cl
:0046B82C tests cl against itself
:0046B82E if cl is null (00) it jumps to 46B8D9
:0046B834 moves the message timer value into edx
:0046B83B moves the current tick count from edi into ecx
:0046B83D subtracts the message timer from the current tick count
:0046B83F depending on the result it will jump to 46B8D9
:0046B845 compares esi to 0C
:0046B848 writes the null byte to the message offset
:0046B850 jumps to 46B86C if esi isn't 0C


You may still be confused by all that. I'll try to sum it up. First it calls GetTickCount and stores that value. Then it sets up the pointer to the message offset. Next it reads the first byte of the message offset and checks if it's null. If it's already null it jumps over the following code since there is no message there. If it isn't zero it then continues and reads the timer value for that message. Then it subtracts the timer from the tick count that was called above. This is where it determines if it is time to remove the message from the screen. If it isn't time it jumps over the following code. If it is time to remove the message it continues and writes the null byte to the message offset.

Now we know that it's using GetTickCount to check the message timer. So we also know that it must be using GetTickCount to set the timer. So if you look at code at 46B834 we can determine what the offsets are for the timers. [4*esi+006517BC]. Four times the value in esi plus 6517BC is the offset for the current message offset. With this info we can determine this:

Message - Timer
650CA8 - 6517BC
650D82 - 6517C0
650E5C - 6517C4
650F36 - 6517C8
651010 - 6517CC
6510EA - 6517D0
6511C4 - 6517D4
65129E - 6517D8
651378 - 6517DC
651452 - 6517E0
65152C - 6517E4

Now that you know the timers for each message offset you can write a timer value there and a message to the appropriate message offset. For fun let's test this out. Write the following to memory then switch to starcraft.

6517BC 0F 0F 0F 0F
650CA8 44 72 61 6B 6B 65 6E 20 72 75 6C 65 73 21 00

Neat eh? But there are two problems. That message will stay on screen for a really really long time or until it is scrolled off screen. Also it probably isn't at the bottom message position on screen.

To solve the timer problem, in your program make a call to GetTickCount. To determine how long you want it to display add to the result. I found that adding 2000 to the tick count seemed to make the messages stay on screen about as long as they normally do. Then write that to the timer offset then write your message to the appropriate message offset.

Now let's figure out how to tell which message offset is the bottom one. Look back at the disassembly in w32dasm. Hopefully the asm is starting to look a little more friendly to you now. You can see an offset reference to the message offset at 46B825. You can also see a reference to the timer offset at 46B834. And again you can see another reference to the message offset at 46B848.

:0046B825 mov cl, byte ptr [2*eax+00650CA8]
:0046B834 mov edx, dword ptr [4*esi+006517BC]
:0046B848 mov byte ptr [2*eax+00650CA8], 00

Now look down just below these offsets and you'll see another offset sticking out in the asm:

:0046B852 mov edx, dword ptr [006517F4]

Hrm what could this be? Let's take a look. First notice that it's pointing to a dword value. Now take a look at 6517F4 in your memory editor. Now go back to starcraft and type in a message and hit enter. Then take a look back at 6517F4 to see if it's changed at all. 6517F4 hasn't changed but 6517F0 has. Remember that it was pointing to a dword value so 6517F0 is still part of it. Send some more messages in starcraft and take notice of how 6517F0 changes. Now document how this offset refers to which message offset is being used. So with the following info you can determine which text spot to write to to make the message appear at the bottom of the screen.

Text spot pointer
6517F0

Spot Message Timer
01 - 650CA8 - 6517BC
02 - 650D82 - 6517C0
03 - 650E5C - 6517C4
04 - 650F36 - 6517C8
05 - 651010 - 6517CC
06 - 6510EA - 6517D0
07 - 6511C4 - 6517D4
08 - 65129E - 6517D8
09 - 651378 - 6517DC
0A - 651452 - 6517E0
00 - 65152C - 6517E4

Now we have all the info we need.

_____________________________________

Summary:
Now to put all this info to good use in your program. First you want to use ReadProcessMemory to read one byte at 6517F0 to determine which message offset you want to write to. Once you've determined that then you want to use GetTickCount to get the current tick count on your system. Then add the amount of time you want your message to stay on screen. I suggest trying 2000 or 2100. Next write that value to the timer offset. And finally write your message to the message offset. Starcraft will automatically remove your message from the screen when your timer expires.

Update:
Use these hex bytes in your messages to add colors.
0x02 default White/dull blue
0x03 Yellow
0x04 Bright White
0x05 Grey
0x06 Red
0x07 Green

_____________________________________

Conclusion:
Remember that these are client side messages only. No one else will see them but you. You will notice that these messages don't always appear on screen right away. This is because they don't actually get drawn until starcraft updates the screen. It's an unfortunate side effect of writing messages this way but at least it works.

I'm sure some of you are going to want to go and make an ally detector program right away. While that will be a good learning experience, I suggest that you try to make something new. How about a unit detector? A program that alerts you when someone has Dark Templars or Lurkers would be very useful and hasn't been done yet... as far as I know. There are offsets in starcraft that store units counts of each unit type for each player. All you have to do is find them.

I hope you learned something new from this tutorial. If not, at least you know how to write messages on screen now. I hope you appreciate the time I've taken to develope this method and write up this tutorial. All I ask is that if you use this method in your program just give me a little thanks somewhere in your credits. 

Feel free to post this tutorial on your own site as long as it remains unmodified.
_____________________________________

Drakken
©GameThreat.com 2003

Don Cullen

#42
Thanks for the tutorial! I was able to find the strings I typed in Starcraft, and now I'm trying to write to the offsets I found the strings at. But I keep getting a type mismatch error... Here's my code so far:

Main.frm code:

Private Sub cmdDisplay_Click()
   
    Dim lPid As Long
    lPid = FindProcessId("Starcraft.exe")
   
    If lPid > -1 Then
        wHandle = lPid
        Address = &H650CA8 'Memory Segment to write to...
        display$ = "This is a test..." 'String to display
        WriteProcessMemory wHandle, Address, StrPtr(display$), LenB(display$), 0&
        Text1.Text = "String displayed."
    Else
        MsgBox "Please activate Starcraft.", vbInformation, "Error"
        DoEvents
    End If

End Sub


And this is the functions.bas module code:

Option Explicit

'Process Function Management Declarations
'---------------------------------------------------
'Used to read and write to memory. Memory Writing must be enabled
'For this to work.

Public Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, ByVal lpNumberOfBytesWritten As Long) As Long
Private Declare Function CloseHandle Lib "Kernel32.dll" (ByVal Handle As Long) As Long
Private Declare Function OpenProcess Lib "Kernel32.dll" (ByVal dwDesiredAccessas As Long, ByVal bInheritHandle As Long, ByVal dwProcId As Long) As Long
Private Declare Function EnumProcesses Lib "PSAPI.DLL" (ByRef lpidProcess As Long, ByVal cb As Long, ByRef cbNeeded As Long) As Long
Private Declare Function GetModuleFileNameExA Lib "PSAPI.DLL" (ByVal hProcess As Long, ByVal hModule As Long, ByVal ModuleName As String, ByVal nSize As Long) As Long
Private Declare Function EnumProcessModules Lib "PSAPI.DLL" (ByVal hProcess As Long, ByRef lphModule As Long, ByVal cb As Long, ByRef cbNeeded As Long) As Long

Public Function FindProcessId(ByVal sExeName As String, Optional lStartFromPID = -1) As Long
   
    'FindProcessId function code obtained from VBUSERS.COM
   
    Const clInitNumProcesses As Long = 500
    Const MAX_PATH = 260, PROCESS_QUERY_INFORMATION = 1024, PROCESS_VM_READ = 16
    Dim sModuleName As String * MAX_PATH, sProcessNamePath As String, sProcessName As String
    Dim alMatchingProcessIDs() As Long
    Dim alModules(1 To 400) As Long
    Dim lBytesReturned As Long, lNumMatching As Long, lArraySize As Long
    Dim lNumProcesses As Long, lBytesNeeded As Long, alProcIDs() As Long
    Dim lHwndProcess As Long, lThisProcess As Long, lRet As Long
    Dim bPastLastMatch As Boolean
   
    On Error GoTo ErrFailed
    FindProcessId = -1
    sExeName = UCase$(Trim$(sExeName))
   
    'Get the list of processes
    Do
        If lArraySize = 0 Then
            lArraySize = clInitNumProcesses
        Else
            lArraySize = lArraySize + clInitNumProcesses
        End If
        'Size array to hold process IDs
        ReDim alProcIDs(lArraySize * 4) As Long
        'Populate an array containing all process ID's
        lRet = EnumProcesses(alProcIDs(1), lArraySize * 4, lBytesReturned)
        'Count number of processes returned
        lNumProcesses = lBytesReturned / 4
        'Resize the array containing all the processes
        ReDim Preserve alProcIDs(lNumProcesses)
        'Resize the array to contain all the matching processes
        ReDim alMatchingProcessIDs(1 To lNumProcesses)
    Loop While lArraySize <= lBytesReturned

    For lThisProcess = 1 To lNumProcesses
        'Open the process
        lHwndProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, alProcIDs(lThisProcess))
       
        If lHwndProcess <> 0 Then
            'Get an array of the module handles for the specified process
            lRet = EnumProcessModules(lHwndProcess, alModules(1), 200&, lBytesNeeded)
           
            If lRet <> 0 Then
                'Get Process Path and Name
                lRet = GetModuleFileNameExA(lHwndProcess, alModules(1), sModuleName, MAX_PATH)
                sProcessNamePath = Trim$(UCase$(Left$(sModuleName, lRet)))
                'Get the Process Name
                sProcessName = Mid$(sProcessNamePath, InStrRev(sProcessNamePath, "\") + 1)
               
                If sProcessName = sExeName Then
                    'Found a matching process ID
                    If lStartFromPID = -1 Then
                        'Return the first matching Id
                        FindProcessId = alProcIDs(lThisProcess)
                    Else
                        If bPastLastMatch Then
                            'Return the next matching process Id
                            FindProcessId = alProcIDs(lThisProcess)
                        End If
                        If alProcIDs(lThisProcess) = lStartFromPID Then
                            'Start the search for the previous matching PID
                            bPastLastMatch = True
                        End If
                    End If
                End If
            End If
        End If
        'Close the handle to this process
        lRet = CloseHandle(lHwndProcess)
        If FindProcessId > -1 Then
            Exit For
        End If
    Next

    Exit Function

ErrFailed:
    Debug.Print "Error in FindProcessId: " & Err.Description
    FindProcessId = -1
End Function


When I execute the program, I get a type mismatch error with the Address variable... Any idea what I'm doing wrong?
Regards,
Don
-------

Don't wonder why people suddenly are hostile when you treat them the way they shouldn't be- it's called 'Mutual Respect'.

Warrior

Quote from: effect on March 09, 2006, 11:52 PM
Islam is a steaming pile of fucking dog shit. Everything about it is flawed, anybody who believes in it is a terrorist, if you disagree with me, then im sorry your wrong.

Quote from: Rule on May 07, 2006, 01:30 PM
Why don't you stop being American and start acting like a decent human?

R.a.B.B.i.T

Kyro, you never declare it.  Variant isn't an acceptable "Any" type (stupid VB).  I put the address value right in and it didn't give an error...but it didn't work either.

|