diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4636c20..4c6d7c75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: cmake Source -B .\Source\bin -A x64 "-DCMAKE_PREFIX_PATH=..\Externals\Qt\Qt6.5.1\x64" cmake --build .\Source\bin --config ${{ matrix.configuration }} --parallel shell: powershell - + - name: Copy LICENSE, README run: | copy .\LICENSE .\Source\bin\${{ matrix.configuration }} @@ -77,7 +77,7 @@ jobs: cmake Source -B Source/build -DCMAKE_BUILD_TYPE=Release cmake --build Source/build --parallel shell: bash - + - name: Copy LICENSE, README, Prepare Artifacts run: | mkdir Source/build/artifacts @@ -90,7 +90,7 @@ jobs: run: | .github/assets/appimage.sh shell: bash - + - name: Publish Artifacts uses: actions/upload-artifact@v3 with: @@ -110,12 +110,12 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Install Dependencies run: | brew install cmake shell: bash - + - name: Initialize submodules run: git submodule update --init diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index f380a6e9..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "files.associations": { - "__bit_reference": "cpp", - "__node_handle": "cpp", - "atomic": "cpp", - "bitset": "cpp", - "chrono": "cpp", - "cstddef": "cpp", - "deque": "cpp", - "__memory": "cpp", - "filesystem": "cpp", - "limits": "cpp", - "locale": "cpp", - "optional": "cpp", - "ratio": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "vector": "cpp" - } -} \ No newline at end of file diff --git a/README.md b/README.md index 5592b98c..31517955 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,30 @@ If the program unhooks itself from Dolphin, a read/write operation has failed. T Finally, the program includes a memory viewer which shows a hexadecimal view and an ASCII view of the memory. Click on the corresponding button or right click on a watch to browse the memory using the memory viewer. +### Command-line Arguments + +A number of options can be provided as command-line arguments: + +``` +Usage: dolphin-memory-engine [options] +A RAM search made specifically to search, monitor and edit the Dolphin Emulator's emulated memory. + +Options: + -h, --help Displays help on + commandline options. + --help-all Displays help including Qt + specific options. + -v, --version Displays version + information. + -d, --dolphin-process-name Specify custom name for + the Dolphin Emulator + process. By default, + platform-specific names are + used (e.g. "Dolphin.exe" on + Windows, or "dolphin-emu" + on Linux or macOS). +``` + ## Troubleshouting On Linux, the program may require additional kernel permissions to be able to read and write memory to external processes (which is required to read and write the memory of Dolphin). If nothing happens to Dolphin, but the program frequently unhooks itself, the program is missing the required permissions. Grant these permissions by running the following command as root: diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 16fa47c8..0e350a34 100755 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -1,117 +1,117 @@ -cmake_minimum_required(VERSION 3.13) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(GCC_min_version 10) -project(dolphin-memory-engine) - -if(WIN32) - set(DolphinProcessSrc DolphinProcess/Windows/WindowsDolphinProcess.cpp) - set(ExeIconSrc Resources/exeicon.rc) -endif(WIN32) - -if(UNIX AND NOT APPLE) - set(DolphinProcessSrc DolphinProcess/Linux/LinuxDolphinProcess.cpp) -endif(UNIX AND NOT APPLE) - -if(APPLE) - set(DolphinProcessSrc DolphinProcess/Mac/MacDolphinProcess.cpp) -endif(APPLE) - -set(SRCS ${DolphinProcessSrc} - DolphinProcess/DolphinAccessor.cpp - Common/MemoryCommon.cpp - MemoryWatch/MemWatchEntry.cpp - MemoryWatch/MemWatchTreeNode.cpp - CheatEngineParser/CheatEngineParser.cpp - MemoryScanner/MemoryScanner.cpp - GUI/GUICommon.cpp - GUI/Settings/SConfig.cpp - GUI/Settings/DlgSettings.cpp - GUI/MemCopy/DlgCopy.cpp - GUI/MemWatcher/MemWatchDelegate.cpp - GUI/MemWatcher/MemWatchModel.cpp - GUI/MemWatcher/Dialogs/DlgChangeType.cpp - GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp - GUI/MemWatcher/MemWatchWidget.cpp - GUI/MemWatcher/Dialogs/DlgImportCTFile.cpp - GUI/MemScanner/ResultsListModel.cpp - GUI/MemScanner/MemScanWidget.cpp - GUI/MemViewer/MemViewer.cpp - GUI/MemViewer/MemViewerWidget.cpp - GUI/MainWindow.cpp - Resources/resource.qrc - ${ExeIconSrc} - main.cpp) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -find_package(Qt6Widgets REQUIRED) -find_package(Qt6Core REQUIRED) -find_package(Qt6Gui REQUIRED) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -IF(WIN32) -SET(GUI_TYPE WIN32) -ENDIF(WIN32) - -add_executable(dolphin-memory-engine ${GUI_TYPE} ${SRCS}) - -target_link_libraries(dolphin-memory-engine Qt6::Widgets) -target_link_libraries(dolphin-memory-engine Qt6::Gui) -target_link_libraries(dolphin-memory-engine Qt6::Core) - -if(WIN32) - set_target_properties(dolphin-memory-engine PROPERTIES OUTPUT_NAME DolphinMemoryEngine) - if($) - get_target_property(WIDGETDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) - get_target_property(COREDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) - get_target_property(GUIDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) - else($) - get_target_property(WIDGETDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) - get_target_property(COREDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) - get_target_property(GUIDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) - endif($) - add_custom_command( - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/platforms - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $/platforms - COMMAND ${CMAKE_COMMAND} -E make_directory - $/styles - TARGET dolphin-memory-engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $/styles - ) -endif(WIN32) - -if(APPLE) - set_target_properties(dolphin-memory-engine PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_BUNDLE_NAME "Dolphin Memory Engine" - MACOSX_BUNDLE_GUI_IDENTIFIER "com.aldelaro5.dolphin-memory-engine" - ) - - set(MACOSX_BUNDLE_ICON_FILE "logo.icns") - set(app_icon_macos "Resources/logo.icns") - set_source_files_properties(${app_icon_macos} PROPERTIES - MACOSX_PACKAGE_LOCATION "Resources") - configure_file(${app_icon_macos} ${CMAKE_BINARY_DIR}/dolphin-memory-engine.app/Contents/Resources/${MACOSX_BUNDLE_ICON_FILE} COPYONLY) +cmake_minimum_required(VERSION 3.13) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(GCC_min_version 10) +project(dolphin-memory-engine) + +if(WIN32) + set(DolphinProcessSrc DolphinProcess/Windows/WindowsDolphinProcess.cpp) + set(ExeIconSrc Resources/exeicon.rc) +endif(WIN32) + +if(UNIX AND NOT APPLE) + set(DolphinProcessSrc DolphinProcess/Linux/LinuxDolphinProcess.cpp) +endif(UNIX AND NOT APPLE) + +if(APPLE) + set(DolphinProcessSrc DolphinProcess/Mac/MacDolphinProcess.cpp) +endif(APPLE) + +set(SRCS ${DolphinProcessSrc} + DolphinProcess/DolphinAccessor.cpp + Common/MemoryCommon.cpp + MemoryWatch/MemWatchEntry.cpp + MemoryWatch/MemWatchTreeNode.cpp + CheatEngineParser/CheatEngineParser.cpp + MemoryScanner/MemoryScanner.cpp + GUI/GUICommon.cpp + GUI/Settings/SConfig.cpp + GUI/Settings/DlgSettings.cpp + GUI/MemCopy/DlgCopy.cpp + GUI/MemWatcher/MemWatchDelegate.cpp + GUI/MemWatcher/MemWatchModel.cpp + GUI/MemWatcher/Dialogs/DlgChangeType.cpp + GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp + GUI/MemWatcher/MemWatchWidget.cpp + GUI/MemWatcher/Dialogs/DlgImportCTFile.cpp + GUI/MemScanner/ResultsListModel.cpp + GUI/MemScanner/MemScanWidget.cpp + GUI/MemViewer/MemViewer.cpp + GUI/MemViewer/MemViewerWidget.cpp + GUI/MainWindow.cpp + Resources/resource.qrc + ${ExeIconSrc} + main.cpp) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package(Qt6Widgets REQUIRED) +find_package(Qt6Core REQUIRED) +find_package(Qt6Gui REQUIRED) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +IF(WIN32) +SET(GUI_TYPE WIN32) +ENDIF(WIN32) + +add_executable(dolphin-memory-engine ${GUI_TYPE} ${SRCS}) + +target_link_libraries(dolphin-memory-engine Qt6::Widgets) +target_link_libraries(dolphin-memory-engine Qt6::Gui) +target_link_libraries(dolphin-memory-engine Qt6::Core) + +if(WIN32) + set_target_properties(dolphin-memory-engine PROPERTIES OUTPUT_NAME DolphinMemoryEngine) + if($) + get_target_property(WIDGETDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) + get_target_property(COREDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) + get_target_property(GUIDLL Qt6::Widgets IMPORTED_LOCATION_DEBUG) + else($) + get_target_property(WIDGETDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) + get_target_property(COREDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) + get_target_property(GUIDLL Qt6::Widgets IMPORTED_LOCATION_RELEASE) + endif($) + add_custom_command( + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + $/platforms + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $/platforms + COMMAND ${CMAKE_COMMAND} -E make_directory + $/styles + TARGET dolphin-memory-engine POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $/styles + ) +endif(WIN32) + +if(APPLE) + set_target_properties(dolphin-memory-engine PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME "Dolphin Memory Engine" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.aldelaro5.dolphin-memory-engine" + ) + + set(MACOSX_BUNDLE_ICON_FILE "logo.icns") + set(app_icon_macos "Resources/logo.icns") + set_source_files_properties(${app_icon_macos} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources") + configure_file(${app_icon_macos} ${CMAKE_BINARY_DIR}/dolphin-memory-engine.app/Contents/Resources/${MACOSX_BUNDLE_ICON_FILE} COPYONLY) endif(APPLE) \ No newline at end of file diff --git a/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp b/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp index 39380771..bae5a6d4 100644 --- a/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp +++ b/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp @@ -4,6 +4,7 @@ #include "../../Common/CommonUtils.h" #include "../../Common/MemoryCommon.h" +#include #include #include #include @@ -106,6 +107,8 @@ bool LinuxDolphinProcess::findPID() if (directoryPointer == nullptr) return false; + static const char* const s_dolphinProcessName{std::getenv("DME_DOLPHIN_PROCESS_NAME")}; + m_PID = -1; struct dirent* directoryEntry = nullptr; while (m_PID == -1 && (directoryEntry = readdir(directoryPointer))) @@ -118,7 +121,11 @@ bool LinuxDolphinProcess::findPID() std::string line; aCmdLineFile.open("/proc/" + std::string(directoryEntry->d_name) + "/comm"); getline(aCmdLineFile, line); - if (line == "dolphin-emu" || line == "dolphin-emu-qt2" || line == "dolphin-emu-wx") + + const bool match{s_dolphinProcessName ? line == s_dolphinProcessName : + (line == "dolphin-emu" || line == "dolphin-emu-qt2" || + line == "dolphin-emu-wx")}; + if (match) m_PID = aPID; aCmdLineFile.close(); diff --git a/Source/DolphinProcess/Mac/MacDolphinProcess.cpp b/Source/DolphinProcess/Mac/MacDolphinProcess.cpp index b3698b2c..f01fc1ae 100644 --- a/Source/DolphinProcess/Mac/MacDolphinProcess.cpp +++ b/Source/DolphinProcess/Mac/MacDolphinProcess.cpp @@ -4,7 +4,9 @@ #include "../../Common/CommonUtils.h" #include "../../Common/MemoryCommon.h" +#include #include +#include #include #include @@ -22,11 +24,15 @@ bool MacDolphinProcess::findPID() if(sysctl((int*) mib, 4, procs.get(), &procSize, NULL, 0) == -1) return false; + static const char* const s_dolphinProcessName{std::getenv("DME_DOLPHIN_PROCESS_NAME")}; + m_PID = -1; for(int i = 0; i < procSize / sizeof(kinfo_proc); i++) { - if(std::strcmp(procs[i].kp_proc.p_comm, "Dolphin") == 0 || - std::strcmp(procs[i].kp_proc.p_comm, "dolphin-emu") == 0) + const std::string_view name{procs[i].kp_proc.p_comm}; + const bool match{s_dolphinProcessName ? name == s_dolphinProcessName : + (name == "Dolphin" || name == "dolphin-emu")}; + if (match) { m_PID = procs[i].kp_proc.p_pid; } diff --git a/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp b/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp index 8a430a93..1e640d98 100644 --- a/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp +++ b/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp @@ -1,247 +1,273 @@ -#ifdef _WIN32 - -#include "WindowsDolphinProcess.h" -#include "../../Common/CommonUtils.h" -#include "../../Common/MemoryCommon.h" - -#include -#include -#include - -namespace DolphinComm -{ - -bool WindowsDolphinProcess::findPID() -{ - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - - m_PID = -1; - if (Process32First(snapshot, &entry) == TRUE) - { - do - { -#ifdef UNICODE - const std::wstring exeFile{entry.szExeFile}; - if (exeFile == L"Dolphin.exe" || exeFile == L"DolphinQt2.exe" || exeFile == L"DolphinWx.exe") -#else - const std::string exeFile{entry.szExeFile}; - if (exeFile == "Dolphin.exe" || exeFile == "DolphinQt2.exe" || exeFile == "DolphinWx.exe") -#endif - { - m_PID = entry.th32ProcessID; - break; - } - } while (Process32Next(snapshot, &entry) == TRUE); - } - - CloseHandle(snapshot); - if (m_PID == -1) - // Here, Dolphin doesn't appear to be running on the system - return false; - - // Get the handle if Dolphin is running since it's required on Windows to read or write into the - // RAM of the process and to query the RAM mapping information - m_hDolphin = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | - PROCESS_VM_WRITE, - FALSE, m_PID); - return true; -} - -bool WindowsDolphinProcess::obtainEmuRAMInformations() -{ - MEMORY_BASIC_INFORMATION info; - bool MEM1Found = false; - for (unsigned char* p = nullptr; - VirtualQueryEx(m_hDolphin, p, &info, sizeof(info)) == sizeof(info); p += info.RegionSize) - { - // Check region size so that we know it's MEM2 - if (!m_MEM2Present && info.RegionSize == Common::GetMEM2Size()) - { - u64 regionBaseAddress = 0; - std::memcpy(®ionBaseAddress, &(info.BaseAddress), sizeof(info.BaseAddress)); - if (MEM1Found && regionBaseAddress > m_emuRAMAddressStart + 0x10000000) - { - // In some cases MEM2 could actually be before MEM1. Once we find MEM1, ignore regions of - // this size that are too far away. There apparently are other non-MEM2 regions of 64 MiB. - break; - } - // View the comment for MEM1. - PSAPI_WORKING_SET_EX_INFORMATION wsInfo; - wsInfo.VirtualAddress = info.BaseAddress; - if (QueryWorkingSetEx(m_hDolphin, &wsInfo, sizeof(PSAPI_WORKING_SET_EX_INFORMATION))) - { - if (wsInfo.VirtualAttributes.Valid) - { - std::memcpy(&m_MEM2AddressStart, &(regionBaseAddress), sizeof(regionBaseAddress)); - m_MEM2Present = true; - } - } - } - else if (info.RegionSize == Common::GetMEM1Size() && info.Type == MEM_MAPPED) - { - // Here, it's likely the right page, but it can happen that multiple pages with these criteria - // exists and have nothing to do with the emulated memory. Only the right page has valid - // working set information so an additional check is required that it is backed by physical - // memory. - PSAPI_WORKING_SET_EX_INFORMATION wsInfo; - wsInfo.VirtualAddress = info.BaseAddress; - if (QueryWorkingSetEx(m_hDolphin, &wsInfo, sizeof(PSAPI_WORKING_SET_EX_INFORMATION))) - { - if (wsInfo.VirtualAttributes.Valid) - { - if (!MEM1Found) - { - std::memcpy(&m_emuRAMAddressStart, &(info.BaseAddress), sizeof(info.BaseAddress)); - MEM1Found = true; - } - else - { - u64 aramCandidate = 0; - std::memcpy(&aramCandidate, &(info.BaseAddress), sizeof(info.BaseAddress)); - if (aramCandidate == m_emuRAMAddressStart + Common::GetMEM1Size()) - { - m_emuARAMAdressStart = aramCandidate; - m_ARAMAccessible = true; - } - } - } - } - } - } - - if (m_MEM2Present) - { - m_emuARAMAdressStart = 0; - m_ARAMAccessible = false; - } - - if (m_emuRAMAddressStart == 0) - { - // Here, Dolphin is running, but the emulation hasn't started - return false; - } - return true; -} - -bool WindowsDolphinProcess::readFromRAM(const u32 offset, char* buffer, const size_t size, - const bool withBSwap) -{ - u64 RAMAddress = 0; - if (m_ARAMAccessible) - { - if (offset >= Common::ARAM_FAKESIZE) - RAMAddress = m_emuRAMAddressStart + offset - Common::ARAM_FAKESIZE; - else - RAMAddress = m_emuARAMAdressStart + offset; - } - else if (offset >= (Common::MEM2_START - Common::MEM1_START)) - { - RAMAddress = m_MEM2AddressStart + offset - (Common::MEM2_START - Common::MEM1_START); - } - else - { - RAMAddress = m_emuRAMAddressStart + offset; - } - - SIZE_T nread = 0; - bool bResult = ReadProcessMemory(m_hDolphin, (void*)RAMAddress, buffer, size, &nread); - if (bResult && nread == size) - { - if (withBSwap) - { - switch (size) - { - case 2: - { - u16 halfword = 0; - std::memcpy(&halfword, buffer, sizeof(u16)); - halfword = Common::bSwap16(halfword); - std::memcpy(buffer, &halfword, sizeof(u16)); - break; - } - case 4: - { - u32 word = 0; - std::memcpy(&word, buffer, sizeof(u32)); - word = Common::bSwap32(word); - std::memcpy(buffer, &word, sizeof(u32)); - break; - } - case 8: - { - u64 doubleword = 0; - std::memcpy(&doubleword, buffer, sizeof(u64)); - doubleword = Common::bSwap64(doubleword); - std::memcpy(buffer, &doubleword, sizeof(u64)); - break; - } - } - } - return true; - } - return false; -} - -bool WindowsDolphinProcess::writeToRAM(const u32 offset, const char* buffer, const size_t size, - const bool withBSwap) -{ - u64 RAMAddress = 0; - if (m_ARAMAccessible) - { - if (offset >= Common::ARAM_FAKESIZE) - RAMAddress = m_emuRAMAddressStart + offset - Common::ARAM_FAKESIZE; - else - RAMAddress = m_emuARAMAdressStart + offset; - } - else if (offset >= (Common::MEM2_START - Common::MEM1_START)) - { - RAMAddress = m_MEM2AddressStart + offset - (Common::MEM2_START - Common::MEM1_START); - } - else - { - RAMAddress = m_emuRAMAddressStart + offset; - } - - SIZE_T nread = 0; - char* bufferCopy = new char[size]; - std::memcpy(bufferCopy, buffer, size); - if (withBSwap) - { - switch (size) - { - case 2: - { - u16 halfword = 0; - std::memcpy(&halfword, bufferCopy, sizeof(u16)); - halfword = Common::bSwap16(halfword); - std::memcpy(bufferCopy, &halfword, sizeof(u16)); - break; - } - case 4: - { - u32 word = 0; - std::memcpy(&word, bufferCopy, sizeof(u32)); - word = Common::bSwap32(word); - std::memcpy(bufferCopy, &word, sizeof(u32)); - break; - } - case 8: - { - u64 doubleword = 0; - std::memcpy(&doubleword, bufferCopy, sizeof(u64)); - doubleword = Common::bSwap64(doubleword); - std::memcpy(bufferCopy, &doubleword, sizeof(u64)); - break; - } - } - } - - bool bResult = WriteProcessMemory(m_hDolphin, (void*)RAMAddress, bufferCopy, size, &nread); - delete[] bufferCopy; - return (bResult && nread == size); -} -} // namespace DolphinComm -#endif +#ifdef _WIN32 + +#include "WindowsDolphinProcess.h" +#include "../../Common/CommonUtils.h" +#include "../../Common/MemoryCommon.h" + +#include +#ifdef UNICODE +#include +#endif +#include +#include +#include + +namespace +{ +#ifdef UNICODE +std::wstring utf8_to_wstring(const std::string& str) +{ + std::wstring_convert> myconv; + return myconv.from_bytes(str); +} +#endif +} // namespace + +namespace DolphinComm +{ + +bool WindowsDolphinProcess::findPID() +{ + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + static const char* const s_dolphinProcessName{std::getenv("DME_DOLPHIN_PROCESS_NAME")}; + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + m_PID = -1; + if (Process32First(snapshot, &entry) == TRUE) + { + do + { +#ifdef UNICODE + const std::wstring exeFile{entry.szExeFile}; + const bool match{s_dolphinProcessName ? + (exeFile == utf8_to_wstring(s_dolphinProcessName) || + exeFile == utf8_to_wstring(s_dolphinProcessName) + L".exe") : + (exeFile == L"Dolphin.exe" || exeFile == L"DolphinQt2.exe" || + exeFile == L"DolphinWx.exe")}; +#else + const std::string exeFile{entry.szExeFile}; + const bool match{s_dolphinProcessName ? + (exeFile == s_dolphinProcessName || + exeFile == std::string(s_dolphinProcessName) + ".exe") : + (exeFile == "Dolphin.exe" || exeFile == "DolphinQt2.exe" || + exeFile == "DolphinWx.exe")}; +#endif + if (match) + { + m_PID = entry.th32ProcessID; + break; + } + } while (Process32Next(snapshot, &entry) == TRUE); + } + + CloseHandle(snapshot); + if (m_PID == -1) + // Here, Dolphin doesn't appear to be running on the system + return false; + + // Get the handle if Dolphin is running since it's required on Windows to read or write into the + // RAM of the process and to query the RAM mapping information + m_hDolphin = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | + PROCESS_VM_WRITE, + FALSE, m_PID); + return true; +} + +bool WindowsDolphinProcess::obtainEmuRAMInformations() +{ + MEMORY_BASIC_INFORMATION info; + bool MEM1Found = false; + for (unsigned char* p = nullptr; + VirtualQueryEx(m_hDolphin, p, &info, sizeof(info)) == sizeof(info); p += info.RegionSize) + { + // Check region size so that we know it's MEM2 + if (!m_MEM2Present && info.RegionSize == Common::GetMEM2Size()) + { + u64 regionBaseAddress = 0; + std::memcpy(®ionBaseAddress, &(info.BaseAddress), sizeof(info.BaseAddress)); + if (MEM1Found && regionBaseAddress > m_emuRAMAddressStart + 0x10000000) + { + // In some cases MEM2 could actually be before MEM1. Once we find MEM1, ignore regions of + // this size that are too far away. There apparently are other non-MEM2 regions of 64 MiB. + break; + } + // View the comment for MEM1. + PSAPI_WORKING_SET_EX_INFORMATION wsInfo; + wsInfo.VirtualAddress = info.BaseAddress; + if (QueryWorkingSetEx(m_hDolphin, &wsInfo, sizeof(PSAPI_WORKING_SET_EX_INFORMATION))) + { + if (wsInfo.VirtualAttributes.Valid) + { + std::memcpy(&m_MEM2AddressStart, &(regionBaseAddress), sizeof(regionBaseAddress)); + m_MEM2Present = true; + } + } + } + else if (info.RegionSize == Common::GetMEM1Size() && info.Type == MEM_MAPPED) + { + // Here, it's likely the right page, but it can happen that multiple pages with these criteria + // exists and have nothing to do with the emulated memory. Only the right page has valid + // working set information so an additional check is required that it is backed by physical + // memory. + PSAPI_WORKING_SET_EX_INFORMATION wsInfo; + wsInfo.VirtualAddress = info.BaseAddress; + if (QueryWorkingSetEx(m_hDolphin, &wsInfo, sizeof(PSAPI_WORKING_SET_EX_INFORMATION))) + { + if (wsInfo.VirtualAttributes.Valid) + { + if (!MEM1Found) + { + std::memcpy(&m_emuRAMAddressStart, &(info.BaseAddress), sizeof(info.BaseAddress)); + MEM1Found = true; + } + else + { + u64 aramCandidate = 0; + std::memcpy(&aramCandidate, &(info.BaseAddress), sizeof(info.BaseAddress)); + if (aramCandidate == m_emuRAMAddressStart + Common::GetMEM1Size()) + { + m_emuARAMAdressStart = aramCandidate; + m_ARAMAccessible = true; + } + } + } + } + } + } + + if (m_MEM2Present) + { + m_emuARAMAdressStart = 0; + m_ARAMAccessible = false; + } + + if (m_emuRAMAddressStart == 0) + { + // Here, Dolphin is running, but the emulation hasn't started + return false; + } + return true; +} + +bool WindowsDolphinProcess::readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) +{ + u64 RAMAddress = 0; + if (m_ARAMAccessible) + { + if (offset >= Common::ARAM_FAKESIZE) + RAMAddress = m_emuRAMAddressStart + offset - Common::ARAM_FAKESIZE; + else + RAMAddress = m_emuARAMAdressStart + offset; + } + else if (offset >= (Common::MEM2_START - Common::MEM1_START)) + { + RAMAddress = m_MEM2AddressStart + offset - (Common::MEM2_START - Common::MEM1_START); + } + else + { + RAMAddress = m_emuRAMAddressStart + offset; + } + + SIZE_T nread = 0; + bool bResult = ReadProcessMemory(m_hDolphin, (void*)RAMAddress, buffer, size, &nread); + if (bResult && nread == size) + { + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, buffer, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(buffer, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, buffer, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(buffer, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, buffer, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(buffer, &doubleword, sizeof(u64)); + break; + } + } + } + return true; + } + return false; +} + +bool WindowsDolphinProcess::writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) +{ + u64 RAMAddress = 0; + if (m_ARAMAccessible) + { + if (offset >= Common::ARAM_FAKESIZE) + RAMAddress = m_emuRAMAddressStart + offset - Common::ARAM_FAKESIZE; + else + RAMAddress = m_emuARAMAdressStart + offset; + } + else if (offset >= (Common::MEM2_START - Common::MEM1_START)) + { + RAMAddress = m_MEM2AddressStart + offset - (Common::MEM2_START - Common::MEM1_START); + } + else + { + RAMAddress = m_emuRAMAddressStart + offset; + } + + SIZE_T nread = 0; + char* bufferCopy = new char[size]; + std::memcpy(bufferCopy, buffer, size); + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, bufferCopy, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(bufferCopy, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, bufferCopy, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(bufferCopy, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, bufferCopy, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(bufferCopy, &doubleword, sizeof(u64)); + break; + } + } + } + + bool bResult = WriteProcessMemory(m_hDolphin, (void*)RAMAddress, bufferCopy, size, &nread); + delete[] bufferCopy; + return (bResult && nread == size); +} +} // namespace DolphinComm +#endif diff --git a/Source/GUI/MainWindow.cpp b/Source/GUI/MainWindow.cpp index a906d0fe..a8b7ab6a 100644 --- a/Source/GUI/MainWindow.cpp +++ b/Source/GUI/MainWindow.cpp @@ -1,5 +1,6 @@ #include "MainWindow.h" +#include #include #include #include @@ -17,7 +18,7 @@ MainWindow::MainWindow() { - setWindowTitle("Dolphin Memory Engine 0.9.0"); + setWindowTitle(QApplication::applicationName() + " " + QApplication::applicationVersion()); setWindowIcon(QIcon(":/logo.svg")); initialiseWidgets(); makeLayouts(); @@ -476,9 +477,9 @@ void MainWindow::onOpenSettings() void MainWindow::onAbout() { - QString title = tr("About Dolphin Memory Engine"); + QString title = tr("About %1").arg(QApplication::applicationName()); QString text = - "Version 0.9.0

" + + tr("Version %1").arg(QApplication::applicationVersion()) + "

" + tr("A RAM search made to facilitate research and reverse engineering of GameCube and Wii " "games using the Dolphin emulator.") + "
" + diff --git a/Source/dolphin-memory-engine.sln b/Source/dolphin-memory-engine.sln index c95d46be..800c48ba 100644 --- a/Source/dolphin-memory-engine.sln +++ b/Source/dolphin-memory-engine.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2010 diff --git a/Source/main.cpp b/Source/main.cpp index c87991e8..f3fe3822 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -1,10 +1,38 @@ #include +#include #include "GUI/MainWindow.h" int main(int argc, char** argv) { QApplication app(argc, argv); + QApplication::setApplicationName("Dolphin Memory Engine"); + QApplication::setApplicationVersion("0.9.0"); + + QCommandLineParser parser; + parser.setApplicationDescription( + QObject::tr("A RAM search made specifically to search, monitor and edit " + "the Dolphin Emulator's emulated memory.")); + parser.addHelpOption(); + parser.addVersionOption(); + + const QCommandLineOption dolphinProcessNameOption( + QStringList() << "d" + << "dolphin-process-name", + QObject::tr("Specify custom name for the Dolphin Emulator process. By default, " + "platform-specific names are used (e.g. \"Dolphin.exe\" on Windows, or " + "\"dolphin-emu\" on Linux or macOS)."), + "dolphin_process_name"); + parser.addOption(dolphinProcessNameOption); + + parser.process(app); + + const QString dolphinProcessName{parser.value(dolphinProcessNameOption)}; + if (!dolphinProcessName.isEmpty()) + { + qputenv("DME_DOLPHIN_PROCESS_NAME", dolphinProcessName.toStdString().c_str()); + } + MainWindow window; window.show(); return app.exec();