Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sending game memory changes to outside processes #3403

Merged
merged 1 commit into from
Dec 30, 2015

Conversation

spxtr
Copy link
Member

@spxtr spxtr commented Dec 28, 2015

I previously added support for sending controller inputs from another process to Dolphin, and here I add support for sending game memory pieces to other processes. Currently only supported on Unix, but porting to Windows isn't too big of a deal, although Windows uses named pipes for this. Use it like this:

Create a folder in the user directory called MemoryWatcher, and a file in it called Locations.txt. To that file, write out the memory locations that you want to watch. If this file isn't found or is empty, the polling thread won't be started. For SSBM, for instance, to watch player one and two percent, put in:

804530E0
80453F70

Now listen to MemoryWatcher/MemoryWatcher like so (C code, but this will work with other languages of course):

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc, char *argv[])
{
    char *path = "/path/to/MemoryWatcher/MemoryWatcher";
    int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (fd < 0)
        printf("Can't create socket: %d\n", errno);
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    unlink(path);
    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
    if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
        printf("Can't bind: %d\n", errno);

    int buf[2];
    while (1)
    {
        struct sockaddr remaddr;
        socklen_t addr_len;
        recvfrom(fd, buf, sizeof(buf), 0, &remaddr, &addr_len);
        printf("%d %d\n", buf[0], buf[1]);
    }
    return 0;
}

Every time the locations you specified change in game memory, the address and the new value will be sent to that socket. I've currently set it to poll every 10 ms.

I am not sure that this is the right way to do this, but it works. Please suggest alternatives. Also, where should I document this + the controller inputs?

// (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.
namespace MW

This comment was marked as off-topic.

@spxtr spxtr force-pushed the reporter branch 2 times, most recently from da97d8d to e3c6aae Compare December 28, 2015 05:31
if (!File::Exists(locations_path))
return false;

std::ifstream locations(locations_path.c_str());

This comment was marked as off-topic.

s_running = true;
while (s_running)
{
for (auto iter = s_values.begin(); iter != s_values.end(); iter++)

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

@spxtr spxtr force-pushed the reporter branch 2 times, most recently from 8665204 to d1392c2 Compare December 28, 2015 06:19

static bool LoadAddresses()
{
std::string locations_path = File::GetUserPath(F_MEMORYWATCHERLOCATIONS_IDX);

This comment was marked as off-topic.

@SizzlingCalamari
Copy link
Contributor

The idea is good, but the design seems a bit annoying and limited to work with. For the tool programmer that would use this memory watcher interface, the setup would require a special build of dolphin as well as some folder setup to get things going. It would also be nice if the api allowed for changing the addrs being watched at runtime, as well as a size parameter to specify how many bytes the tool would like to watch for each address.

These extra requirements give me some ideas.

  • A command line parameter (or better, an ipc message, so any builds could have this) to enable/disable the usage of this memory watcher api.
  • A json object for registering/receiving data to/from this api. The schema of these objects would be something like
registering:
{
    "socketInfo": {},
    "watchAddrs": [{
        "addr": 1231312,
        "numBytes": 4
    }, {
        "addr": 3432424,
        "numBytes": 8
    }]
}

watch data:
{
    "watchData": [{
        "addr": 1231312,
        "data": 100
    }, {
        "addr": 3432424,
        "data": 99999
    }]
}

What are you planning on using this mem watcher for anyways?

@SizzlingCalamari
Copy link
Contributor

Also, I'd suggest changing the mem watcher code to be a class instead of a namespace. The compile may be faster when using statics like that, but it's generally better practice to have it as an object, then create an instance of it where it is used (in this case, core). If you're worried about visibility of the member vars in the header, there's always the pImp idiom.

@spxtr
Copy link
Member Author

spxtr commented Dec 28, 2015

It doesn't require a special build, just that you're on a unix system. It does require some folder setup.

@spxtr
Copy link
Member Author

spxtr commented Dec 28, 2015

While it would be nice to have a more powerful API for setting addresses to watch, I shied away from this for a few reasons. I wanted to keep it as simple as possible, and didn't want to add JSON requirements or similar. I also didn't want to have to deal with synchronization, and if I read a hundred bytes at a time it will be possible that the cpu messes with it halfway through the read. If we only read a word at a time this can't happen. Individual packets are going to be consistent, and I make no guarantees about the consistency of anything more than that.

I'm going to start working on an AI for SSBM. Another user has already started on a similar project over here.

@degasus
Copy link
Member

degasus commented Dec 28, 2015

Did you see PR #3031 ? It tries to get the same kind of utility.

@spxtr
Copy link
Member Author

spxtr commented Dec 28, 2015

@SizzlingCalamari I took your changes to the code, thanks :) (Well except for the broad design ones, but we can talk about that more).
@degasus No, I didn't see at that one. I'll take a closer look tomorrow.

@@ -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 MemoryWatcher* s_memory_watcher = nullptr;

This comment was marked as off-topic.

return;
if (!OpenSocket(File::GetUserPath(F_MEMORYWATCHERSOCKET_IDX)))
return;
m_watcher_thread = std::thread(std::bind(&MemoryWatcher::WatcherThread, this));

This comment was marked as off-topic.

@spxtr
Copy link
Member Author

spxtr commented Dec 28, 2015

I think I applied your suggestions properly, thanks.

}

bool MemoryWatcher::LoadAddresses(const std::string& path)
{

This comment was marked as off-topic.

This comment was marked as off-topic.

@SizzlingCalamari
Copy link
Contributor

This PR is probably fine for what you need from it. My only concern is the usefulness of this for others.
The main issues that I can see are:

  • Unix only. Could probably use ENet and have this compile cross platform.
  • Weird setup for users of this api.
  • Synchronization and hardcoded packet for 4 bytes of data.

If these aren't issues for others, then merge away.

@spxtr
Copy link
Member Author

spxtr commented Dec 29, 2015

That's fair. It's pretty similar to the named pipe controller input in that regard (unix-only, awkward setup), but that one's been useful for at least a few other people. I'm under the impression that Windows named pipes can do this, but I don't have a Windows box to develop on.

I'm not too attached to this, so if someone does a better job sometime in the future using a different method then I won't mind deleting this. That's part of why I want to keep it simple :)

@phire
Copy link
Member

phire commented Dec 30, 2015

Ok, I'll merge this.

Someone remember to delete it if there is a better method in the future.

phire added a commit that referenced this pull request Dec 30, 2015
Add support for sending game memory changes to outside processes
@phire phire merged commit afde6ae into dolphin-emu:master Dec 30, 2015
@spxtr spxtr deleted the reporter branch December 30, 2015 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants