Starcraft Bladder problems (Of many to come)

If you’re listening to the Undermind podcast (as you should), you might be familiar with my newest project, which is a Human vs. AI ladder, with it’s own client. The aim of this is to have a way for casual gamers to test themselves out versus Brood War bots. At the moment, we don’t have a great name for it, so Hannes gave it the terrible, terrible BLADDER abbreviation. Which is funny as hell, but no way I’m going to launch it with that name.

We’ve been discussing the architecture a while now. The game server, which is the heart of all of it has grown out of the DirectSNP part of BWAPI. I can’t comment much on that, since that is C++, and also not written by me.

First, we had to decide where and how we will host this thing. After some consideration, we decided that Google Cloud is the way to go. Which is another stuff to learn for me, but whatever, I can do that. I am scientist, after all.

And it will be a relatively simple approach. There should be some kind of coordinator server, for filtering requests, and handling everything that is not related to launching SC instances. Which of there are surprisingly many things. Think of user management, data about maps/bots, maybe even learning data, achievements, 100 euro horse armor, that sort of thing. And it needs to be robust as well – I don’t want any bad requests reaching the game server either.

For the client, I began some research, and since I’m most proficient in Java, searched for a Java desktop framework, which is a less than hot area of development nowadays. Last time I did something like that, it was Swing, AWT, SWT, and they were quite outdated even back then. Upon some intensive googling, I decided the weapon of choice will be JavaFX. Not because it’s that impressive, but basically the only one that is actively maintained to this day. Found some nice tutorials about it too.

There is basically one technical challenge at this point, which is launching the Starcraft instance with BWAPI injected. That I never did, and Java is not really the preferred way to do that. So I dove in headfirst.

One, out of the box way to do this in Injectory. I tried it, and it works. The problem is it is under GPL licence, and if I use it, that would require me to use that, which I don’t like to do at this point. It is a little annoying, as I would’ve been perfectly content with it.

Presumably, with JNA, it can be done. I spent a day on it figuring out how. More precisely, trying to figure out. JNA has some interface calls, that are weird, if you come from a Java background, like me. The basic thought was to look up some dll injection, and “do that but in JNA”. Needless to say, it failed miserably. There is one project, the JLoadLibrary, which seemed to be just exactly what I wanted. It takes a process id, and a dll name, and does the rest.

So okay, let’s find out my Starcraft pid. A way to do it – there are many:

Field f = process.getClass().getDeclaredField("handle");
            f.setAccessible(true);
            long handl = f.getLong(process);
            Kernel32.HANDLE hand = new Kernel32.HANDLE();
            hand.setPointer(Pointer.createConstant(handl));
            int pid = kernel32.GetProcessId(hand);

From the Kernel32.HANDLE class name, you can already expect funny stuff to pop up – this is just the humble beginning of the incomprehensibility.

With this pid, I tried from code, and command line both to inject bwapi.dll into the Starcraft process – to no avail. First, I just got Error 5 (Which is access denied, and you can fix it with admin privileges), then a success message, and… then nothing happened. Okay, plan C, let’s re-implement this by myself.

I got the JNA libraries, and tried to work from examples. NiteKat’s Diablo API has a good one in C++ right here. The relevant part looks something like this:

GetWindowThreadProcessId(game_window, &process_id);
	if (!process_id)
		return 2;
	auto process = OpenProcess(PROCESS_ALL_ACCESS, false, process_id);
	if (!process)
		return 3;
	auto vae_return = VirtualAllocEx(process, NULL, sizeof(dll_path), MEM_COMMIT, PAGE_READWRITE);
	if (!vae_return)
		return 4;
	if (!WriteProcessMemory(process, vae_return, (void*)dll_path, sizeof(dll_path), NULL))
		return 5;
	auto hKernel32 = GetModuleHandleA("Kernel32");
	HANDLE thread;
	thread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "LoadLibraryA"), vae_return, 0, NULL);
	WaitForSingleObject(thread, INFINITE);
	GetExitCodeThread(thread, &hLibModule);

I started to mirror these processes in Java, but of course they are not exactly intuitive. You might have heard, but Java doesn’t use pointers, instead, there is a Pointer class for this purpose. I had to have a Kernel32 instance, okay, and the OpenProcess() method worked more or less out of the box.

    Kernel32.HANDLE handle = kernel32.OpenProcess(Kernel32.PROCESS_ALL_ACCESS, false, pid);

Then I tried to call the VirtualAllocEX(), but I found that there isn’t one. Okay, that is strange, maybe I have a different version or something? It turns out nope, I have to add the method signatures (but not the implementation) to my interface. Thus, the ScKernel32 interface was born.

public interface ScKernel32 extends Kernel32 {

    int VirtualAllocEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize,
                       int flAllocationType, int flProtect);

Then the VirtualAllocEx(), and WriteProcessMemory() methods seem to work fine. I have no real way to debugging and verifying their contents, but they are not null, and not throwing errors – should be good enough, right? (If you sense a creeping desperation, you are mean, and also right)

 int vae = kernel32.VirtualAllocEx(handle, Pointer.NULL, new BaseTSD.SIZE_T(bwapiDll.length()-1), Kernel32.MEM_COMMIT, Kernel32.PAGE_READWRITE);
            //4
            Pointer memPointer = new Memory(bwapiDll.length() + 1);
            memPointer.setString(0, bwapiDll);
            boolean b = kernel32.WriteProcessMemory(handle, new Pointer(vae), memPointer, bwapiDll.length(), null);

The variable outputs are mostly for debugging. Next, getting the module handle – this required some googling again, to decide which part to use, but I got it.

//interface
  WinDef.HMODULE GetModuleHandle(String name);
//
            WinDef.HMODULE kernelHmodule = kernel32.GetModuleHandle("Kernel32");

But everything changed, when the CreateRemoteThread process attacked. First, I just got nulls when I tried it. It was actually the GetProcessAddress that gave the nulls.

 Kernel32.HANDLE remoteHandle = kernel32.CreateRemoteThread(handle,
                    null,
                    0,
                    kernel32.GetProcAddress(kernelHmodule, "LoadLibraryA"),
                    new Pointer(vae),
                    new WinDef.DWORD(0),
                    Pointer.NULL);

Upon some googling, I noticed that one problem might have been my initialization of the ScKernel32, which looked like this:

   public static ScKernel32 kernel32 = Native.loadLibrary("kernel32.dll", ScKernel32.class, W32APIOptions.UNICODE_OPTIONS);

The solution is here, and it turns out, in this case, it needs to be switched to ASCII, because GetProcAddress() has no Unicode versions. Okay, let’s try it out. Things just got worse, as I got an incomprehensible error.

java.lang.Error: Structure.getFieldOrder() on class com.sun.jna.platform.win32.WinBase$FOREIGN_THREAD_START_ROUTINE returns names ([foreignLocation]) which do not match declared field names ([])
Képtalálat a következőre: „god fucking dammit”
Some elements of the story might have been dramatized.

This baffled me for a while – what fields, how, what, why? I changed the signature of the GetProcessAddress to everything I could find, maybe I’m not using the correct one? Yeah, you use JNA dumbfuck The best I could do was just a null, and therefore, absolutely nothing.

I briefly considered some reflection-based skullduggery to manipualate some fields. Turns out that the WinBase.FOREIGN_THREAD_START_ROUTINE class does have a foreignLocation field, but that’s not public.

    public static class FOREIGN_THREAD_START_ROUTINE extends Structure {
        LPVOID foreignLocation;

        public FOREIGN_THREAD_START_ROUTINE() {
        }

        protected List<String> getFieldOrder() {
            return Arrays.asList("foreignLocation");
        }
    }

That’s just… weird. Okay, let’s go the other way, and maybe, just maybe if I extend this class, and shadow the variable. (I like to use cool terms like that. I enjoy inventing new ones even more)

I think I’m getting the hang of this “dank memes” thing

And of course I need to change the method signature as well, so in the end, by your combined fuckery, I am spaghetti code:

public class ForeignExtension extends WinBase.FOREIGN_THREAD_START_ROUTINE {

    public WinDef.LPVOID foreignLocation;
}
//In the ScRunner class
 ForeignExtension loadLibraryA = (ForeignExtension) kernel32.GetProcAddress(kernelHmodule, "LoadLibraryA")

One green play button later…

This was the point when I gave up for the day. I might return and revisit this, but it might be just quicker to learn C++, write a command line app, and just launch that. Or bribe Hannes with some pizzas.

And it is a good point to wrap up this terrible, terrible article. If you made it so far, first of all, why, second, thanks for reading, and I hope you enjoy my other content on this blog as well!

1 thought on “Starcraft Bladder problems (Of many to come)

  1. Pingback: StarCraft Bladder problems: No more holding back the streams | Making Computer Do Things

Leave a Reply