Changing Resource Values ​​in Games with Python

In ancient times, when mammoths walked the earth, and I was half my age, a computer program for “hacking” games called ArtMoney was popular among the gaming community. With this software, you could not only make your life easier in the passage of a hardcore adventure by modifying the values ​​of resources in the game, but also just have fun, studying your favorite project from different angles.

And the other day I suddenly wanted to remember my youth and play a boomer diabloid called Titan Quest, released back in 2006. But I don't have time to run around, level up, and all that. And I don't have ArtMoney. But I do have some programming knowledge. So I decided to combine business with pleasure by writing an ArtMoney analogue in Python, and at the same time become super-rich, at least in Titan Quest.

All that was needed for this project was Python and the Pymem library, which allows you to hack Windows processes and manipulate memory (read and write).

The program consists of the MemoryEditor class, which is responsible for interaction with the game process, searching and replacing values ​​in its memory. And a function that acts as an interactive menu for interaction with the user.

MemoryEditor class

class MemoryEditor:
    def __init__(self, process_name: str) -> None:
        self.pm = pymem.Pymem(process_name)
        self.process_base = pymem.process.module_from_name(self.pm.process_handle, process_name).lpBaseOfDll

The class constructor takes the process name (process_name) that needs to be opened (for example, process.exe).

  • pymem.Pymem(process_name) — opens a process and allows interaction with its memory.

  • process_base is the base address of the main module of the process (usually the .exe file itself).

Method search_value

def search_value(self, value: int) -> list:
        search_results = []
        memory_size = 0x7FFFFFFF  # Размер памяти для сканирования (большой диапазон)
        chunk_size = 0x1000  # Размер блока чтения
        search_bytes = ctypes.c_uint32(value).value.to_bytes(4, byteorder="little")
        
        offset = 0
        while offset < memory_size:
            current_address = self.process_base + offset
            
            if self.is_memory_readable(current_address):
                try:
                    buffer = self.pm.read_bytes(current_address, chunk_size)
                except pymem.exception.MemoryReadError:
                    offset += chunk_size
                    continue
                
                chunk_offset = 0
                while True:
                    chunk_offset = buffer.find(search_bytes, chunk_offset)
                    if chunk_offset == -1:
                        break

                    # Сохранение адреса найденного значения
                    address = current_address + chunk_offset
                    search_results.append(address)
                    
                    chunk_offset += len(search_bytes)
            
            offset += chunk_size

        return search_results

This function searches for a given value in the process memory.

  • memory_size specifies the memory area to be searched.

  • chunk_size determines how much data will be read at a time (in this case 4KB).

  • search_bytes converts a value to a byte string for searching in memory.

  • The while offset < memory_size: loop iterates through the entire specified memory area, checking each part to see if the desired value exists.

  • self.is_memory_readable(current_address) checks if the memory is available for reading.

  • If the value is found in the current memory block, its address is stored in search_results.

Method is_memory_readable

def is_memory_readable(self, address) -> bool:
        mbi = pymem.memory.virtual_query(self.pm.process_handle, address)
        if mbi.Protect & 0xF != 0x0 and mbi.State == 0x1000 and mbi.Protect & 0x100 == 0:
            return True
        return False

This function checks if the memory at the specified address is available for reading.

  • Uses the virtual_query function from the pymem library, which returns information about the state and protection of memory.

  • Checks that the memory is accessible, not protected, and does not have the PAGE_GUARD flag set.

Method search_next_value

def search_next_value(self, addresses: list, next_value: int) -> list:
        search_results = []
        search_bytes = ctypes.c_uint32(next_value).value.to_bytes(4, byteorder="little")
        
        for address in addresses:
            if self.is_memory_readable(address):
                try:
                    buffer = self.pm.read_bytes(address, 4)
                except pymem.exception.MemoryReadError:
                    continue
                
                if buffer == search_bytes:
                    search_results.append(address)
        
        return search_results

This function searches for a new value (next_value) only among the addresses found in the previous search step.

  • The function takes a list of addresses (addresses) and a value to search for (next_value).

  • If a new value is found at one of the addresses, that address is added to the search_results list.

And the method replace_value

def replace_value(self, addresses: list, new_value: int) -> None:
        replace_bytes = ctypes.c_uint32(new_value).value.to_bytes(4, byteorder="little")
        for address in addresses:
            self.pm.write_bytes(address, replace_bytes, 4)
            print(f"Замена значения по адресу: {hex(address)} на {new_value}")

The function replaces the values ​​at the specified addresses with a new value (new_value).

  • replace_bytes is the new value as a byte string.

  • self.pm.write_bytes(address, replace_bytes, 4) writes a new value to the memory at the specified address.

That's it. Now in the main part of the program you just need to create an instance of the class written above with the name of the game process, and call the necessary methods one by one to find the necessary cells and replace the values. I didn't bother with the interface, and wrote the simplest menu in the command line, with requests for the necessary information from the user. It looks like this:

In the game, my character had 12345 coins, so I entered this value. However, the program found too many addresses with identical values, so I collected some more gold in the game by changing the number of coins for the character, and filtered the addresses using the new data. The second time, there were significantly fewer addresses, so I decided not to filter any further, but to change the values ​​in all of them. As a result, my character became almost a millionaire (I didn’t get too cheeky).

What can I say, I'm happy, and I can clear dungeons without any cheats, having bought heals for all the money.

Who would like to use the program or supplement it: PyMoney repository.

Thank you for your attention!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *