Search code examples
javawinapijna

ERROR_PARTIAL_COPY reading value from memory pointer in JNA


I'm trying to read a (float) value from a pointer within a process which window is called Age of Empires II: Definitive Edition.

This is my code:

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;

public class Main {

    final static long baseAddress = 0x02BC6620L;
    final static Pointer baseAddressPointer = new Pointer(baseAddress);
    final static int[] offsets = new int[]{0x00, 0x1A8, 0x00, 0x158, 0x28, 0x270};

    static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32",Kernel32.class);
    static User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

    public static int PROCESS_VM_READ = 0x0010;
    public static int PROCESS_VM_WRITE = 0x0020;
    public static int PROCESS_VM_OPERATION = 0x0008;

    public static interface User32 extends StdCallLibrary
    {
        final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
        WinDef.HWND FindWindowExA(WinDef.HWND hwndParent, WinDef.HWND childAfter, String className, String windowName);
        WinDef.HWND FindWindowA(String className, String windowName);
        void GetWindowThreadProcessId(WinDef.HWND hwnd, IntByReference intByReference);
    }

    public static void main(String[] args) {
        System.out.println("System.getProperty(\"sun.arch.data.model\") = " + System.getProperty("sun.arch.data.model"));
        int pid = getProcessId("Age of Empires II: Definitive Edition");
        System.out.println("pid = " + pid);
        com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);

        long theDynAddress = findDynAddress(process, offsets);
        System.out.println("theDynAddress = " + theDynAddress);
        Pointer dynAddress = new Pointer(theDynAddress);

        Memory scoreMem = readMemory(process, dynAddress, 4);
        float score = scoreMem.getFloat(0);
        System.out.println(score);
    }

    public static Memory readMemory(com.sun.jna.platform.win32.WinNT.HANDLE process, Pointer address, int bytesToRead) {
        IntByReference read = new IntByReference(0);
        Memory output = new Memory(bytesToRead);

        boolean readProcessMemorySucceeded = kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
        System.out.println("readProcessMemorySucceeded = " + readProcessMemorySucceeded);
        if (!readProcessMemorySucceeded) {
            int lasterrorReadMemory = kernel32.GetLastError();
            System.out.println("lasterror = " + lasterrorReadMemory);
        }

        return output;
    }

    public static int getProcessId(String window) {
        IntByReference pid = new IntByReference(0);
        user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);

        return pid.getValue();
    }

    public static com.sun.jna.platform.win32.WinNT.HANDLE openProcess(int permissions, int pid) {
        return kernel32.OpenProcess(permissions, true, pid);
    }

    public static long findDynAddress(com.sun.jna.platform.win32.WinNT.HANDLE process, int[] offsets)
    {

        int size = 4;
        Memory pTemp = new Memory(size);
        long pointerAddress = 0;

        for(int i = 0; i < offsets.length; i++)
        {
            if(i == 0)
            {
                boolean test = kernel32.ReadProcessMemory(process, baseAddressPointer, pTemp, size, null);
                int lasterror = kernel32.GetLastError();
                System.out.println("test = " + test + ", lasterror = " + lasterror);
            }

            pointerAddress = ((pTemp.getInt(0)+offsets[i]));

            if(i != offsets.length-1)
                kernel32.ReadProcessMemory(process, new Pointer(pointerAddress), pTemp, size, null);


        }

        return pointerAddress;
    }

}

This is the terminal output after running the jar:

System.getProperty("sun.arch.data.model") = 64
pid = 2192
test = false, lasterror = 299
theDynAddress = 1177510878
readProcessMemorySucceeded = false
lasterror = 299
1.64011448E10

Please note:

  • As you can see, the jar is compiled for 64-bit.
  • I run the jar with administrative privileges.
  • The process I'm trying to get access to is also 64-bit.

I'm getting error 299 which is labeled as ERROR_PARTIAL_COPY. The value I'm getting seems to be random. What can I do to prevent this?

I have no problem accessing the same value using Cheat Engine:

Cheat Engine screenshot


Solution

  • Rather than looking at the final incorrect value, your debugging should focus on the first case of the error. In this case, you've shown it's the initial ReadProcessMemory() call inside your findDynAddress() method, which attempts to read from the Pointer variable baseAddressPointer.

    In the initial version of your code, you defined:

    final static long baseAddress = 0x02B7C950L;
    final static Pointer baseAddressPointer = new Memory(baseAddress);
    

    You've defined a base address (which I assume you got from somewhere else, it's generally dangerous to hardcode this) and then you attempt to create a pointer with it, but use the Memory class, a subclass of Pointer. The argument for the Memory constructor is a size, and the constructor allocates new memory at a new address, given the size of its argument, in this case 0x02B7C950 bytes of memory, about 45.6 megabytes!

    While you've allocated that memory (and thus your later code doesn't fail to read, as it's reading memory you "own") it contains random data.

    You probably meant to do:

    final static Pointer baseAddressPointer = new Pointer(baseAddress);
    

    After editing your code, you still have an error. In this case, you may be reading a memory address from the native code, but you have only allocated 4 bytes in which to store it with this:

    int size = 4;
    Memory pTemp = new Memory(size);
    

    And you're fetching what is supposed to be a 64-bit Pointer with getInt():

    pointerAddress = ((pTemp.getInt(0)+offsets[i]));
    

    You should be using 8 bytes to store the address, and use getLong() to fetch it after the call. The prologue to the answer in the post you are referencing does mention you need to be careful about pointer sizes when using this approach.


    One key thing is to understand symbols that CheatEngine uses.

    Regarding the screenshot:

    • "AoE2DE_s.exe" is the load address of the module with the same name. lpmodinfo.lpBaseOfDll is where it's retrieved in the code. This address can be seen in CheatEngine under Memory View -> View -> Enumerate DLL's and Symbols.
    • +02BC6620 part is simple arithmetic operation. The 02BC6620 is stored in the code as baseAddress.
    • -> means that the address on the left hand was accessed and under that address is stored another address which is on the right hand.

    Finally, the code should look like this:

    import com.sun.jna.Memory;
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.Kernel32;
    import com.sun.jna.platform.win32.Psapi;
    import com.sun.jna.platform.win32.WinDef;
    import com.sun.jna.ptr.IntByReference;
    import com.sun.jna.win32.StdCallLibrary;
    
    import java.util.Arrays;
    
    public class Main {
    
        final static long baseAddress = 0x02BC6620L;
        private static final String MODULE_FILENAME = "AoE2DE_s.exe";
        private static final int SIZE_OF_LONG = 8;
        private static final String WINDOW_NAME = "Age of Empires II: Definitive Edition";
        final static int[] offsets = new int[]{0x00, 0x1A8, 0x00, 0x158, 0x28, 0x270};
    
        static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32",Kernel32.class);
        static User32 user32 = (User32) Native.loadLibrary("user32", User32.class);
    
        public static int PROCESS_VM_READ = 0x0010;
        public static int PROCESS_VM_WRITE = 0x0020;
        public static int PROCESS_VM_OPERATION = 0x0008;
    
        public static interface User32 extends StdCallLibrary
        {
            final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
            WinDef.HWND FindWindowExA(WinDef.HWND hwndParent, WinDef.HWND childAfter, String className, String windowName);
            WinDef.HWND FindWindowA(String className, String windowName);
            void GetWindowThreadProcessId(WinDef.HWND hwnd, IntByReference intByReference);
        }
    
        public static void main(String[] args) {
            System.out.println("System.getProperty(\"sun.arch.data.model\") = " + System.getProperty("sun.arch.data.model"));
            int pid = getProcessId(WINDOW_NAME);
            System.out.println("pid = " + pid);
    
            com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);;
    
            long theDynAddress = findDynAddress(process, offsets);
            System.out.printf("theDynAddress = %x\n", theDynAddress);
            Pointer dynAddress = new Pointer(theDynAddress);
    
            Memory scoreMem = readMemory(process, dynAddress, 8);
            float score = scoreMem.getFloat(0);
            System.out.println(score);
        }
    
        public static long getBaseAddress(String windowName, String moduleFilename) {
            int pid = getProcessId(windowName);
    
            com.sun.jna.platform.win32.WinNT.HANDLE process = openProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, pid);
            WinDef.HMODULE[] hModules = new WinDef.HMODULE[100 * 4]; //I'm not sure how big the array should be, this is some big random number
            IntByReference lpcbNeeded = new IntByReference();
            Psapi.INSTANCE.EnumProcessModules(process, hModules, hModules.length, lpcbNeeded);
    
            hModules = Arrays.copyOf(hModules, lpcbNeeded.getValue() / SIZE_OF_LONG);
    
            for(WinDef.HMODULE m: hModules){
                Psapi.MODULEINFO lpmodinfo = new Psapi.MODULEINFO();
    
                if (!Psapi.INSTANCE.GetModuleInformation(process, m, lpmodinfo, lpmodinfo.size())) {
                    //TODO: Error handling
                }
                char[] filename = new char[100 * 4];
                int filenameLength = Psapi.INSTANCE.GetModuleFileNameExW(process, m, filename, 100 * 4);
                String filenameString = new String(filename);
                if (filenameString.contains(moduleFilename)) {
                    return readMemory(process, new Pointer((Pointer.nativeValue(lpmodinfo.lpBaseOfDll) + baseAddress)), SIZE_OF_LONG).getLong(0);
                }
            }
            return -1;
        }
    
        public static Memory readMemory(com.sun.jna.platform.win32.WinNT.HANDLE process, Pointer address, int bytesToRead) {
            IntByReference read = new IntByReference(0);
            Memory output = new Memory(bytesToRead);
            boolean readProcessMemorySucceeded = kernel32.ReadProcessMemory(process, address, output, bytesToRead, read);
            if (!readProcessMemorySucceeded) {
                int lasterrorReadMemory = kernel32.GetLastError();
                System.out.println("lasterror = " + lasterrorReadMemory);
            }
    
            return output;
        }
    
        public static int getProcessId(String window) {
            IntByReference pid = new IntByReference(0);
            user32.GetWindowThreadProcessId(user32.FindWindowA(null, window), pid);
    
            return pid.getValue();
        }
    
        public static com.sun.jna.platform.win32.WinNT.HANDLE openProcess(int permissions, int pid) {
            return kernel32.OpenProcess(permissions, true, pid);
        }
    
        public static long findDynAddress(com.sun.jna.platform.win32.WinNT.HANDLE process, int[] offsets)
        {
            long pointerAddress = getBaseAddress(WINDOW_NAME, MODULE_FILENAME);
            for(int i = 0; i < offsets.length; i++)
            {
                if (i != offsets.length-1) {
                    pointerAddress = readMemory(process, new Pointer(pointerAddress + offsets[i]), SIZE_OF_LONG).getLong(0);
                } else {
                    pointerAddress = pointerAddress + offsets[i];
                }
            }
            return pointerAddress;
        }
    
    }