diff --git a/CMakeLists.txt b/CMakeLists.txt index f51696e3c2..e565353ec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -552,6 +552,8 @@ endif() if(UNIX) message("Using named pipes as controller inputs") add_definitions(-DUSE_PIPES=1) + message("Watching game memory for changes") + add_definitions(-DUSE_MEMORYWATCHER=1) endif() ######################################## diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 2233f0efd8..c6d559e2e5 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -73,6 +73,7 @@ #define THEMES_DIR "Themes" #define ANAGLYPH_DIR "Anaglyph" #define PIPES_DIR "Pipes" +#define MEMORYWATCHER_DIR "MemoryWatcher" // This one is only used to remove it if it was present #define SHADERCACHE_LEGACY_DIR "ShaderCache" @@ -94,6 +95,10 @@ #define ARAM_DUMP "aram.raw" #define FAKEVMEM_DUMP "fakevmem.raw" +// Files in the directory returned by GetUserPath(D_MEMORYWATCHER_IDX) +#define MEMORYWATCHER_LOCATIONS "Locations.txt" +#define MEMORYWATCHER_SOCKET "MemoryWatcher" + // Sys files #define TOTALDB "totaldb.dsy" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index f3af55548c..6857262579 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -810,6 +810,10 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[F_FAKEVMEMDUMP_IDX] = s_user_paths[D_DUMP_IDX] + FAKEVMEM_DUMP; s_user_paths[F_GCSRAM_IDX] = s_user_paths[D_GCUSER_IDX] + GC_SRAM; + s_user_paths[D_MEMORYWATCHER_IDX] = s_user_paths[D_USER_IDX] + MEMORYWATCHER_DIR DIR_SEP; + s_user_paths[F_MEMORYWATCHERLOCATIONS_IDX] = s_user_paths[D_MEMORYWATCHER_IDX] + MEMORYWATCHER_LOCATIONS; + s_user_paths[F_MEMORYWATCHERSOCKET_IDX] = s_user_paths[D_MEMORYWATCHER_IDX] + MEMORYWATCHER_SOCKET; + // The shader cache has moved to the cache directory, so remove the old one. // TODO: remove that someday. File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP); diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 0736d7c88f..9ed318d1c9 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -42,6 +42,7 @@ enum { D_MAILLOGS_IDX, D_THEMES_IDX, D_PIPES_IDX, + D_MEMORYWATCHER_IDX, F_DOLPHINCONFIG_IDX, F_DEBUGGERCONFIG_IDX, F_LOGGERCONFIG_IDX, @@ -50,6 +51,8 @@ enum { F_ARAMDUMP_IDX, F_FAKEVMEMDUMP_IDX, F_GCSRAM_IDX, + F_MEMORYWATCHERLOCATIONS_IDX, + F_MEMORYWATCHERSOCKET_IDX, NUM_PATH_INDICES }; diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 99bc5ced18..10a51b4a65 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -273,4 +273,8 @@ if(GDBSTUB) set(SRCS ${SRCS} PowerPC/GDBStub.cpp) endif(GDBSTUB) +if(UNIX) + set(SRCS ${SRCS} MemoryWatcher.cpp) +endif(UNIX) + add_dolphin_library(core "${SRCS}" "${LIBS}") diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 4712b1e4ee..c49d89c847 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -28,6 +28,9 @@ #include "Core/DSPEmulator.h" #include "Core/Host.h" #include "Core/MemTools.h" +#ifdef USE_MEMORYWATCHER +#include "Core/MemoryWatcher.h" +#endif #include "Core/Movie.h" #include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" @@ -109,6 +112,10 @@ static bool s_request_refresh_info = false; static int s_pause_and_lock_depth = 0; static bool s_is_framelimiter_temp_disabled = false; +#ifdef USE_MEMORYWATCHER +static std::unique_ptr s_memory_watcher; +#endif + #ifdef ThreadLocalStorage static ThreadLocalStorage bool tls_is_cpu_thread = false; #else @@ -280,6 +287,10 @@ void Stop() // - Hammertime! #if defined(__LIBUSB__) || defined(_WIN32) SI_GCAdapter::ResetRumble(); #endif + +#ifdef USE_MEMORYWATCHER + s_memory_watcher.reset(); +#endif } void DeclareAsCPUThread() @@ -349,6 +360,10 @@ static void CpuThread() } #endif +#ifdef USE_MEMORYWATCHER + s_memory_watcher = std::make_unique(); +#endif + // Enter CPU run loop. When we leave it - we are done. CPU::Run(); diff --git a/Source/Core/Core/MemoryWatcher.cpp b/Source/Core/Core/MemoryWatcher.cpp new file mode 100644 index 0000000000..ecce7aa714 --- /dev/null +++ b/Source/Core/Core/MemoryWatcher.cpp @@ -0,0 +1,85 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "Common/FileUtil.h" +#include "Common/Thread.h" +#include "Core/MemoryWatcher.h" +#include "Core/HW/Memmap.h" + +// We don't want to kill the cpu, so sleep for this long after polling. +static const int SLEEP_DURATION = 10; // ms + +MemoryWatcher::MemoryWatcher() +{ + if (!LoadAddresses(File::GetUserPath(F_MEMORYWATCHERLOCATIONS_IDX))) + return; + if (!OpenSocket(File::GetUserPath(F_MEMORYWATCHERSOCKET_IDX))) + return; + m_running = true; + m_watcher_thread = std::thread(&MemoryWatcher::WatcherThread, this); +} + +MemoryWatcher::~MemoryWatcher() +{ + if (!m_running) + return; + + m_running = false; + m_watcher_thread.join(); + close(m_fd); +} + +bool MemoryWatcher::LoadAddresses(const std::string& path) +{ + std::ifstream locations(path); + if (!locations) + return false; + + u32 data; + locations >> std::hex; + while (locations >> data) + m_values[data] = 0; + + return m_values.size() > 0; +} + +bool MemoryWatcher::OpenSocket(const std::string& path) +{ + memset(&m_addr, 0, sizeof(m_addr)); + m_addr.sun_family = AF_UNIX; + strncpy(m_addr.sun_path, path.c_str(), sizeof(m_addr.sun_path) - 1); + + m_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + return m_fd >= 0; +} + +void MemoryWatcher::WatcherThread() +{ + while (m_running) + { + for (auto& entry : m_values) + { + u32 address = entry.first; + u32& current_value = entry.second; + + u32 new_value = Memory::Read_U32(address); + if (new_value != current_value) + { + current_value = new_value; + u32 buf[2] = {address, current_value}; + sendto( + m_fd, + static_cast(buf), + sizeof(buf), + 0, + reinterpret_cast(&m_addr), + sizeof(m_addr)); + } + } + Common::SleepCurrentThread(SLEEP_DURATION); + } +} diff --git a/Source/Core/Core/MemoryWatcher.h b/Source/Core/Core/MemoryWatcher.h new file mode 100644 index 0000000000..8f2e36c1bc --- /dev/null +++ b/Source/Core/Core/MemoryWatcher.h @@ -0,0 +1,39 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +// MemoryWatcher reads a file containing in-game memory addresses and outputs +// changes to those memory addresses to a unix domain socket as the game runs. +// +// The input file is a newline-separated list of hex memory addresses +// (without the "0x"). The output to the socket is two words long, the first +// containing the address, and the second containing the data as stored in +// game memory. +class MemoryWatcher final +{ +public: + MemoryWatcher(); + ~MemoryWatcher(); + +private: + bool LoadAddresses(const std::string& path); + bool OpenSocket(const std::string& path); + void WatcherThread(); + + std::thread m_watcher_thread; + std::atomic_bool m_running; + + int m_fd; + sockaddr_un m_addr; + + // Address -> last value + std::map m_values; +};