about summary refs log tree commit diff stats
path: root/apworld/__init__.py
Commit message (Collapse)AuthorAgeFilesLines
* Added "Enable Gift Maps" optionStar Rauchenberger2025-10-231-0/+1
| | | | Only supports The Advanced so far. Also added the mastery to The Advanced. Location listeners are now created after any map edits are made since some locations may require custom nodes (like The Advanced's mastery).
* Make icarus optionalStar Rauchenberger2025-10-221-0/+1
|
* Stop dumping region graphStar Rauchenberger2025-10-071-2/+2
|
* Error when no progressionStar Rauchenberger2025-10-061-0/+6
|
* Add setting for not auto-starting the gameStar Rauchenberger2025-10-051-0/+1
|
* Minor import tweaksStar Rauchenberger2025-10-021-1/+1
|
* Change version schemeStar Rauchenberger2025-09-291-2/+1
|
* Added icon for launcherStar Rauchenberger2025-09-281-2/+3
|
* Treat local letters as items for trackerStar Rauchenberger2025-09-271-0/+2
| | | | Local letters are now synced with datastorage, so they transfer to other computers like regular items would, and the tracker also now waits until you collect local letters before showing what they give you in logic.
* Support launching with AP URLStar Rauchenberger2025-09-251-1/+1
|
* Game talks through CommonClient nowStar Rauchenberger2025-09-251-47/+3
|
* Client can be run from zipped apworld nowStar Rauchenberger2025-09-251-23/+45
|
* This should've been part of the prev commitStar Rauchenberger2025-09-251-0/+4
|
* Move the client into the apworldStar Rauchenberger2025-09-251-0/+43
| | | | Only works on source right now, not as an apworld.
* [Apworld] Added worldport shuffleStar Rauchenberger2025-09-221-2/+27
|
* Added strict purple/cyan ending optionsStar Rauchenberger2025-09-191-0/+2
|
* Added anti collectable trapsStar Rauchenberger2025-09-131-1/+16
|
* Added gallery painting shuffleStar Rauchenberger2025-09-121-0/+1
|
* [Apworld] Read major version from datafileStar Rauchenberger2025-09-101-3/+2
|
* [Apworld] Add version numberStar Rauchenberger2025-09-091-0/+3
|
* Added symbol shuffleStar Rauchenberger2025-09-091-0/+1
| | | | | Also fixed unlocked letters + any double letter cyan doors, and tweaked some logic related to important panels with symbols on them.
* Made sure the apworld unit tests passStar Rauchenberger2025-09-081-1/+9
|
* [Apworld] Added item/location groupsStar Rauchenberger2025-09-081-0/+2
|
* Add cyan door behavior optionStar Rauchenberger2025-09-081-0/+1
|
* [Apworld] Add shuffle_control_center_colors to slot dataStar Rauchenberger2025-09-071-0/+1
|
* [Apworld] Added letter shuffleStar Rauchenberger2025-09-061-0/+1
|
* Renamed filler item to "A Job Well Done"Star Rauchenberger2025-09-031-2/+6
|
* Added option for Daedalus roof access logicStar Rauchenberger2025-09-031-0/+1
|
* Added keyholder sanityStar Rauchenberger2025-09-021-1/+3
|
* [Apworld] Added options to slot dataStar Rauchenberger2025-08-311-0/+11
|
* Set apworld victory conditionStar Rauchenberger2025-08-271-0/+5
|
* Fill the item pool with "Nothing"sStar Rauchenberger2025-08-171-1/+8
|
* Items and connections in the apworldStar Rauchenberger2025-08-121-0/+14
|
* Assign AP IDs to doors and panelsStar Rauchenberger2025-08-071-3/+3
|
* Started apworldStar Rauchenberger2025-08-071-0/+38
vcpkg's libprotobuf is older than what PIP has, but neither are completely up to date either. Ugh. Doors have a room now because that's where the location will go.
&id=fd2fa2211dc09c9030601fde1afd2f7823b22ed8'>^
2473736 ^
133975b ^


1d4763b ^
133975b ^
fc8649b ^

133975b ^
fc8649b ^
133975b ^




0baa521 ^


133975b ^
fc8649b ^
133975b ^
fc8649b ^
1d4763b ^
133975b ^


3f16a3e ^
0baa521 ^

fc8649b ^














0baa521 ^















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227



                     
                 
                  



                     
                                                                              





                              
 
                             

                                                                                                








































                                                                                   

                                                                                








                                                                                                                
             
                           











                                                                                              
                     
















                                                                                                           

                                                                 
     
 
                

 

                                                                                                           
                                             


                                                                                                 











                                                                   


                             
 
















                                                             

 
                           
                                    
                                 

                                                                                                                                            


                                                                               

 
                                                       


                                                                                                       
 


                                               
 
                                                                       

                                                                                         
                                                     
      




                                                                                                                                                    


                                                                              
                                                                    
             
         
      
 


                                                                     
 

                                          














                                                                                                    















                                                                                                                                  
#include "Memory.h"
#include <psapi.h>
#include <tlhelp32.h>
#include <iostream>
#include <string>
#include <cassert>

#undef PROCESSENTRY32
#undef Process32Next

Memory::Memory(const std::wstring& processName) : _processName(processName) {}

Memory::~Memory() {
    if (_threadActive) {
        _threadActive = false;
        _thread.join();
    }

    if (_handle != nullptr) {
        for (uintptr_t addr : _allocations) VirtualFreeEx(_handle, (void*)addr, 0, MEM_RELEASE);
        CloseHandle(_handle);
    }
}

void Memory::StartHeartbeat(HWND window, std::chrono::milliseconds beat) {
    if (_threadActive) return;
    _threadActive = true;
    _thread = std::thread([sharedThis = shared_from_this(), window, beat]{
        while (sharedThis->_threadActive) {
            sharedThis->Heartbeat(window);
            std::this_thread::sleep_for(beat);
        }
    });
    _thread.detach();
}

void Memory::Heartbeat(HWND window) {
    if (!_handle && !Initialize()) {
        // Couldn't initialize, definitely not running
        PostMessage(window, WM_COMMAND, HEARTBEAT, (LPARAM)ProcStatus::NotRunning);
        return;
    }

    DWORD exitCode = 0;
    assert(_handle);
    GetExitCodeProcess(_handle, &exitCode);
    if (exitCode != STILL_ACTIVE) {
        // Process has exited, clean up.
        _computedAddresses.clear();
        _handle = NULL;
        PostMessage(window, WM_COMMAND, HEARTBEAT, (LPARAM)ProcStatus::NotRunning);
        return;
    }

#if GLOBALS == 0x5B28C0
    int currentFrame = ReadData<int>({0x5BE3B0}, 1)[0];
#elif GLOBALS == 0x62D0A0
    int currentFrame = ReadData<int>({0x63954C}, 1)[0];
#endif
    int frameDelta = currentFrame - _previousFrame;
    _previousFrame = currentFrame;
    if (frameDelta < 0 && currentFrame < 250) {
        // Some addresses (e.g. Entity Manager) may get re-allocated on newgame.
        _computedAddresses.clear();
        PostMessage(window, WM_COMMAND, HEARTBEAT, (LPARAM)ProcStatus::NewGame);
        return;
    }

    // TODO: Some way to return ProcStatus::Randomized vs ProcStatus::NotRandomized vs ProcStatus::DeRandomized;

    PostMessage(window, WM_COMMAND, HEARTBEAT, (LPARAM)ProcStatus::Running);
}

[[nodiscard]]
bool Memory::Initialize() {
    // First, get the handle of the process
    PROCESSENTRY32W entry;
    entry.dwSize = sizeof(entry);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    while (Process32NextW(snapshot, &entry)) {
        if (_processName == entry.szExeFile) {
            _handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
            break;
        }
    }
    if (!_handle) {
        std::cerr << "Couldn't find " << _processName.c_str() << ", is it open?" << std::endl;
        return false;
    }

    // Next, get the process base address
    DWORD numModules;
    std::vector<HMODULE> moduleList(1024);
    EnumProcessModulesEx(_handle, &moduleList[0], static_cast<DWORD>(moduleList.size()), &numModules, 3);

    std::wstring name(64, '\0');
    for (DWORD i = 0; i < numModules / sizeof(HMODULE); i++) {
        int length = GetModuleBaseNameW(_handle, moduleList[i], &name[0], static_cast<DWORD>(name.size()));
        name.resize(length);
        if (_processName == name) {
            _baseAddress = (uintptr_t)moduleList[i];
            break;
        }
    }
    if (_baseAddress == 0) {
        std::cerr << "Couldn't locate base address" << std::endl;
        return false;
    }

    return true;
}

void Memory::AddSigScan(const std::vector<byte>& scanBytes, const std::function<void(int index)>& scanFunc)
{
    _sigScans[scanBytes] = {scanFunc, false};
}

int find(const std::vector<byte> &data, const std::vector<byte>& search, size_t startIndex = 0) {
    for (size_t i=startIndex; i<data.size() - search.size(); i++) {
        bool match = true;
        for (size_t j=0; j<search.size(); j++) {
            if (data[i+j] == search[j]) {
                continue;
            }
            match = false;
            break;
        }
        if (match) return static_cast<int>(i);
    }
    return -1;
}

int Memory::ExecuteSigScans()
{
    for (int i=0; i<0x200000; i+=0x1000) {
        std::vector<byte> data = ReadData<byte>({i}, 0x1100);

        for (auto& [scanBytes, sigScan] : _sigScans) {
            if (sigScan.found) continue;
            int index = find(data, scanBytes);
            if (index == -1) continue;
            sigScan.scanFunc(i + index);
            sigScan.found = true;
        }
    }

    int notFound = 0;
    for (auto it : _sigScans) {
        if (it.second.found == false) notFound++;
    }
    return notFound;
}

void Memory::ThrowError() {
    std::wstring message(256, '\0');
    DWORD error = GetLastError();
    int length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error, 1024, &message[0], static_cast<DWORD>(message.size()), nullptr);
    message.resize(length);
#ifndef NDEBUG
    MessageBox(NULL, message.c_str(), L"Please tell darkid about this", MB_OK);
#endif
}

void* Memory::ComputeOffset(std::vector<int> offsets) {
    // Leave off the last offset, since it will be either read/write, and may not be of type uintptr_t.
    int final_offset = offsets.back();
    offsets.pop_back();

    uintptr_t cumulativeAddress = _baseAddress;
    for (const int offset : offsets) {
        cumulativeAddress += offset;

        const auto search = _computedAddresses.find(cumulativeAddress);
        // This is an issue with re-randomization. Always. Just disable it in debug mode!
#ifdef NDEBUG
        if (search == std::end(_computedAddresses)) {
#endif
            // If the address is not yet computed, then compute it.
            uintptr_t computedAddress = 0;
            if (bool result = !ReadProcessMemory(_handle, reinterpret_cast<LPVOID>(cumulativeAddress), &computedAddress, sizeof(uintptr_t), NULL)) {
                ThrowError();
            }
            if (computedAddress == 0) { // Attempting to dereference a nullptr
                ThrowError();
            }
            _computedAddresses[cumulativeAddress] = computedAddress;
#ifdef NDEBUG
        }
#endif

        cumulativeAddress = _computedAddresses[cumulativeAddress];
    }
    return reinterpret_cast<void*>(cumulativeAddress + final_offset);
}

uintptr_t Memory::Allocate(size_t bytes) {
/*
uintptr_t ForeignProcessMemory::AllocateMemory(size_t Size, DWORD Flags) const {
    if (!ProcessHandle) {
        return 0;
    }
    return (uintptr_t)VirtualAllocEx(ProcessHandle, nullptr, Size, MEM_RESERVE | MEM_COMMIT, Flags);
}

void ForeignProcessMemory::DeallocateMemory(uintptr_t Addr) const {
    if (!ProcessHandle || Addr == 0) {
        return;
    }
    VirtualFreeEx(ProcessHandle, (void*)Addr, 0, MEM_RELEASE);
}
*/
    uintptr_t current = _freeMem;
    _freeMem += bytes;

    if (_freeMem > _freeMemEnd) {
        // If we don't have enough space at our current location, go allocate some more space.
        // Note that the remaining space in our current page is unused. Oh well.
        _freeMem = reinterpret_cast<uintptr_t>(::VirtualAllocEx(_handle, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
        _freeMemEnd = _freeMem + 0x1000;

        current = _freeMem;
        _freeMem += bytes;
        assert(_freeMem <= _freeMemEnd); // Don't allocate data > 0x1000 at a time. Duh.
    }

    return current;
}