This project is read-only.

MumbleLink

Jul 1, 2014 at 12:29 PM
Hey

I'm looking into what sort of character data we can get from the game client through the MumbleLink protocol.

Data includes:
  • Display name
  • Profession
  • Current map and map coordinates
  • Current server IP
  • Team color (when in a competitive map)
  • Camera position
Given that this functionality is limited to machines with an active game client, is there any interest for this sort of data? Should MumbleLink be included it in the library?
Jul 1, 2014 at 6:54 PM
I'm thinking about it. On one hand it's a nice functionality. On the other hand it's is not the main focus of the Library.
Jul 2, 2014 at 2:48 PM
Edited Jul 3, 2014 at 12:26 PM
Should I send you a code sample? It's surprisingly easy to implement. It's not surprisingly useful though. You can use it to plot your position on a world map and stuff, but nothing terribly exciting.
Jul 4, 2014 at 10:41 AM
Here's a code sample with only just enough lines of code to make it work.
using System.IO.MemoryMappedFiles;
public static class GameClient
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct MumbleLink
    {
        public UInt32 uiVersion;
        public UInt32 uiTick;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fAvatarPosition;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fAvatarFront;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fAvatarTop;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string name;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fCameraPosition;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fCameraFront;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public float[] fCameraTop;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string identity;
        public UInt32 context_len;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
        public byte[] context;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)]
        public string description;
    }

    public static MumbleLink GetMumbleLinkData()
    {
        const string Name = "MumbleLink";
        var capacity = Marshal.SizeOf(typeof(MumbleLink));
        using (var file = MemoryMappedFile.CreateOrOpen(Name, capacity, MemoryMappedFileAccess.ReadWrite))
        using (var stream = file.CreateViewStream())
        {
            var buffer = new byte[capacity];

            stream.Read(buffer, 0, capacity);

            var unsafeAddrOfPinnedArrayElement = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);

            return (MumbleLink)Marshal.PtrToStructure(unsafeAddrOfPinnedArrayElement, typeof(MumbleLink));
        }
    }
}
Usage example
private static void Main(string[] args)
{
    var data = GameClient.GetMumbleLinkData();
}
A real application would convert the data to something more object-oriented. There would also have to be some sort of timer that blocks the thread while waiting for data, generating a timeout exception if the game is not running.
Jul 7, 2014 at 11:02 AM
I think we should not offer that in the main library, since we don't get anything from it. A completely separate library would be ok in my eyes though
Sep 18, 2014 at 8:45 AM
Edited Sep 18, 2014 at 8:46 AM
It came to my attention that there is some interest for mumble link data. Currently, the author of GW2PAO uses mumble link data to display an in-game compass that points towards nearby points of interest based on the player's current position and camera angle. To get that info, he uses the GwApiNET mumble link library, but he has stated that this may change in the future. I assume that means whenever a better option becomes available. So let's get to it.
Sep 21, 2014 at 4:08 AM
Yup. In my transition to Guild Wars 2 .NET, I considered just creating my own assembly for accessing the mumble link. However, I haven't gotten around to do doing so yet.

Since you guys have done a great job with the other APIs, I would love to see an interface for the mumble API added to this project. However, I understand if you guys don't want to add this to Guild Wars 2 .NET, since it seems to be mostly focused on making use of the various v1 and v2 APIs. If you decide to create a new project for this, let me know and I'd be happy to contribute.
Sep 22, 2014 at 9:24 AM
If there is interest I'd be happy to include it into GW2.NET. While the focus will always be the "main" API, Mumble support is definitely not out of the question.
Since we are already working on making the library more modular this would be a good addition.

I'll look into a solution later this day. If we can "recycle" parts of the main library we are off for a good start.
Sep 29, 2014 at 9:02 AM
We can easily create a mumble data class that reuses existing map and world name services. It's just the avatar position/camera stuff that I don't know the first thing about. Maths are hard. Also, we'll need a solid polling strategy that leaves no room for failure. A lot of things can go wrong in shared memory.
Oct 5, 2014 at 6:15 PM
How many different coordinate systems are currently used by the game? Correct me if I'm wrong.
  • Individual map coordinates
  • Continent coordinates
  • Positional audio coordinates (Mumble-specific)
Oct 5, 2014 at 9:23 PM
Sneak peek

http://imgur.com/pamz2Rf

As shown on the screenshot, all of the mumble link data is converted to managed types. No messing around with unmanaged structs and whatnot.

There's still a good amount of work that needs to be done. In particular, we don't know all possible enum values for "Profession", "TeamColorId", "MapType". I see no other way to get these than to go in-game and collect them manually.
Oct 6, 2014 at 8:46 AM
Would it be better if we leave polling entirely up to the user? So we just provide the interface that reads data from the shared memory block and converts it to data types. No timer that refreshes the data at a hard-coded interval or anything like that.

Alternatively, we can make the mumble link layer implement the IObservable interface. That layer sits idle until a user subscribes a new IObserver. When that happens, open the shared memory block and push updates to the observer at timed intervals. When the last observer unsubscribes, close the shared memory block.

That last idea is tricky, because we generally don't know what other processes are using the shared memory block. Could be another process running on top of GW2.NET, or could be Mumble itself.
Oct 6, 2014 at 8:52 AM
Another thing we need to account for is other games that also use the MumbleLink protocol. I'm pretty sure that the code will crash and burn if it tries to read mumble data that has even a slightly different format, such as when the mumble data was generated by a different game. How can we detect when that happens though? I don't think we can.
Oct 7, 2014 at 12:52 AM
Sounds pretty good so far! I would say that it might make more sense to leave polling up to the user, much like GW2.NET implementation for the other official v1 and v2 APIs. Rather than implementing threading or other behavior in the library itself, just keep it a clean, managed wrapper to the mumble link, and leave it up to users for how to use that wrapper. Caching some of the information that doesn't often change might be a good idea, such as character name, but I'm not sure if it would really be necessary or provide much of a performance improvement.

For error handling, I haven't looked at what you have implemented so far, but ideally the library would be able to detect if the format was incorrect and at least raise a meaningful exception.
Oct 7, 2014 at 5:59 PM
I suppose I could do a first read of the bytes to verify that their layout is known, and only if it is known, do a second read to convert those bytes to data types. The data structure is only 5460 bytes wide, so the performance hit is acceptable. If possible, I'd like to to avoid writing code like that though.
Oct 7, 2014 at 6:42 PM
Link to the code (under development): https://gw2dotnet.codeplex.com/SourceControl/latest#GW2.NET/Code/GW2.NET.MumbleLink/MumbleLinkFile.cs

This stuff is pretty close to my limit of understanding about interop between managed and native code. Let me know if you spot any weird code.
Oct 8, 2014 at 7:15 PM
I don't think that the code will blow up in our face, if another game that uses the Mumble Protocol is also running. According to the Forum the game implements the protocol almost 1:1, the exception being that GW2 uses inches instead of meters for distances.

Since GW2 writes the map in the context blob, e could theoretically check, if GW2 writes the file. If the context is empty the game is not GW2, if there is data in it we can assume that GW2 just wrote to the file.
Oct 8, 2014 at 7:28 PM
How about this. The value of context_len will always be 0 if no game is running, 48 if GW2 is running, and probably something other than 48 if a different game is running. Is checking for context_len == 48 good enough as an error catching mechanism?
Oct 8, 2014 at 7:41 PM
Even better would be if the values of context and/or identity had a fixed sequence of bytes. All I'd have to do is check that the byte sequence exists before attempting to convert the data.
Oct 8, 2014 at 8:33 PM
Edited Oct 9, 2014 at 8:16 AM
Simply checking for context length 48 works great so far. I think we should stick with that.
public Avatar Read()
{
    var buffer = new byte[this.size];
    using (var stream = this.mumbleLink.CreateViewStream())
    {
        // Copy the shared memory block to a local buffer
        stream.Read(buffer, 0, buffer.Length);

        // Copy the buffer to an unmanaged memory pointer
        var ptr = Marshal.AllocHGlobal(buffer.Length);
        Marshal.Copy(buffer, 0, ptr, buffer.Length);

        // Copy the unmanaged memory to a managed struct
        var avatarDataContract = (AvatarDataContract)Marshal.PtrToStructure(ptr, typeof(AvatarDataContract));

        // Ensure that data is available and that it has a well known format
        // MEMO: the context length for GW2 is always 48 of 256 bytes
        if (avatarDataContract.context_len != 48)
        {
            return null;
        }

        // Convert data contracts to managed data types
        return ConvertAvatarDataContract(avatarDataContract);
    }
}
For the off chance that a different game also has context_len set to 48, the code still goes ahead and assumes that the data was generated by GW2. This can be a problem, because the code also assumes that the value of identity is a JSON-formatted string. If it isn't, the serializer will complain with an exception.
var serializer = new DataContractJsonSerializer(typeof(IdentityDataContract));

IdentityDataContract dataContract;
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(identity)))
{
    dataContract = (IdentityDataContract)serializer.ReadObject(stream);
}

return new Identity
{
    Name = dataContract.Name,
    Profession = dataContract.Profession,
    MapId = dataContract.MapId,
    WorldId = dataContract.WorldId,
    TeamColorId = dataContract.TeamColorId,
    Commander = dataContract.Commander
};
Let's assume that this game also uses JSON for the value of identity. The code will no longer crash, but the return value will contain coordinates that belong to a different game. Also, the identity property will be set to an object with default values and the context property will be set to an object with garbage values.
Oct 10, 2014 at 8:17 PM
OH WOW... I completely missed the name property that is conveniently set to "Guild Wars 2" when the game is running. I'm an idiot.
Oct 10, 2014 at 9:55 PM
Completed implementation: http://imgur.com/ENEJRlW

Does the positional stuff look okay? Note that I'm just passing the values. I'm not doing any conversions.
Oct 10, 2014 at 10:18 PM
Edited Oct 10, 2014 at 10:21 PM
shurne wrote:
Caching some of the information that doesn't often change might be a good idea, such as character name, but I'm not sure if it would really be necessary or provide much of a performance improvement.
I just ran some tests. A single read takes as little as 0.00005 seconds on my laptop. That includes the time it takes to convert the bytes to data types.
using (var mumbler = new MumbleLinkFile())
{
    while (true)
    {
        var sw = Stopwatch.StartNew();
        var avatar = mumbler.Read();
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
    }
}
Oct 10, 2014 at 10:42 PM
Looks really nice! Passing the values should be fine. I'm pretty sure the library I used previously just passed the values without any conversions applied.

Thanks for checking out the performance, looks like caching really isn't necessary.

I'm a bit busy at the moment, but I'm hoping to integrate this into my application ASAP. I'm sure what you have will work well, but I'll definitely let you know if there's anything I find.
Oct 16, 2014 at 8:23 AM
I'm still confused about the coordinate systems in use. And what values are contained in the avatar/camera arrays at each index? Seems that [0] = x, [1] = z, [2] = y.
Oct 17, 2014 at 1:17 AM
Edited Oct 17, 2014 at 1:17 AM
Yea, it's a bit confusing, but maybe this will help.

Basically, the mumble link uses a left-handed coordinate system, which means that instead of having Z as height (as you might expect), Y is height:
Image
Oct 17, 2014 at 6:22 AM
Ohhh... okay. That image explains exactly what I needed to know. Thanks!