How to quickly find the element I want using the new 64bit approach?

I have developed an app using the old method, however, CCP will use the 64bit client soon. So I have to change the code of my app.

Now, the 64-bit sanderling only updates Overview and Context Menu dynamically, how can I get the element I want quickly? For example. after I set the destination, I want to right-click on the first square in the route(up-left corner), and then left-click on “Jump though stargate” in the context menu. There are so many trees that I can not easily find what I want. It is much easier using the old method.

In this case, the quickest way I see is to look into the example project with the warp-to-0 autopilot bot. This bot uses the squares representing the solar systems in the autopilot route and also the context menu entries.

TL DR

Before diving into the details, a quick summary to copy and paste into your app code:

Function to get the first square icon from the info panel:

infoPanelRouteFirstMarkerFromParsedUserInterface : EveOnline.MemoryReading.ParsedUserInterface -> Maybe InfoPanelRouteRouteElementMarker
infoPanelRouteFirstMarkerFromParsedUserInterface =
     .infoPanelRoute
     >> maybeNothingFromCanNotSeeIt
     >> Maybe.map .routeElementMarker
     >> Maybe.map (List.sortBy (\routeMarker -> routeMarker.uiNode.totalDisplayRegion.x + routeMarker.uiNode.totalDisplayRegion.y))
     >> Maybe.andThen List.head

Function to get the context menu entry:

menuEntryToJumpFromParsedUserInterface : EveOnline.MemoryReading.ParsedUserInterface -> Maybe EveOnline.MemoryReading.ContextMenuEntry
menuEntryToJumpFromParsedUserInterface =
    .contextMenus
        >> List.head
        >> Maybe.andThen (.entries >> List.filter (.text >> String.toLower >> String.contains "jump") >> List.head)

The Long Version

Wondering how I found these functions? Then read on:

To find the paths to the UI elements in the program code, I start at the location where the mouse click effects are computed and then trace back to the EveOnline.MemoryReading.ParsedUserInterface that we receive from the EVE Online bot framework with the MemoryReadingCompleted event.

The command to open the context menu on the square is computed here:

Following the propagation of infoPanelRouteFirstMarker back its source, we arrive at this function:

infoPanelRouteFirstMarkerFromParsedUserInterface : ParsedUserInterface -> Maybe InfoPanelRouteRouteElementMarker
    infoPanelRouteFirstMarkerFromParsedUserInterface =
     .infoPanelRoute
     >> maybeNothingFromCanNotSeeIt
     >> Maybe.map .routeElementMarker
     >> Maybe.map (List.sortBy (\routeMarker -> routeMarker.uiNode.totalDisplayRegion.x + routeMarker.uiNode.totalDisplayRegion.y))
     >> Maybe.andThen List.head

What is most important about this function is that you can reuse it by just copy and paste it into your bot: In the functions type annotation at the top, we see it takes only an EveOnline.MemoryReading.ParsedUserInterface as input.
The returned InfoPanelRouteRouteElementMarker is wrapped in a Maybe, because you can have no squares at all, and in this case, it returns the Nothing value.

Since there can be multiple squares in the autopilot info panel, the function sorts them by their location on the screen. In this functions, we also find also the field names we need to navigate from the root type down to the square icon: infoPanelRoute and routeElementMarker. Since even the route info panel itself can be hidden, there is some unpacking included for these cases.

When the right context menu is open, the bot clicks on the menu entry with this implementation:

Also here following the dataflow backward, we land here:

maybeMenuEntryToClick =
    firstMenu.entries
        |> List.filter
            (\menuEntry ->
                let
                    textLowercase =
                        menuEntry.text |> String.toLower
                in
                (textLowercase |> String.contains "dock")
                    || (textLowercase |> String.contains "jump")
            )
        |> List.head

The function the bot uses is a bit more complicated than what you are looking for because it not only clicks on the entry to jump but also to dock. You can use this simplified version:

maybeMenuEntryToClick =
    firstMenu.entries
        |> List.filter (.text >> String.toLower >> String.contains "jump")
        |> List.head

For the context menu entry, we use an instance of the Maybe type again because not always is there a matching menu entry, for example, when no context menu is open at all.
You can integrate this into one function that you can use directly on the memory reading value that you get from the bot framework:

menuEntryToJumpFromParsedUserInterface : EveOnline.MemoryReading.ParsedUserInterface -> Maybe EveOnline.MemoryReading.ContextMenuEntry
menuEntryToJumpFromParsedUserInterface =
    .contextMenus
        >> List.head
        >> Maybe.andThen (.entries >> List.filter (.text >> String.toLower >> String.contains "jump") >> List.head)

Hi. It greatly helps. I will look into it later and post any problems here. Thank you.

Edited:

I got used to the old ways, not familiar with the ELM language, is it possible to debug my program in C#?

There is a " Memory reading parsing library" there, How can I use it?

The below code is what I used to activate ship modules. In this code, I have used a third party virtual mouse/keyboard lib called “dm”, you can ignore it.

So I want to ask, is it possible to recode as the old way I use as below?

public static void ActiveModule()
        {
            var response = sensor.MeasurementTakeNewRequest(eveOnlineClientProcessId.Value);       
       
            if (response.MemoryMeasurement.Value.ShipUi == null)
            {
                Console.WriteLine("not in space.");
                return;
            }
            else
            {
                var module = response.MemoryMeasurement.Value.ShipUi.Module;
                if (response.MemoryMeasurement.Value.ShipUi.Module.FirstOrDefault() == null)
                {
                    Console.WriteLine("no active module.");
                    return;
                }
                else
                {
                    var activemodulenum = response.MemoryMeasurement.Value.ShipUi.Module.LongLength;            
                    int i;
                    for (i = 0; i < activemodulenum; i++)
                    {
                        if (response.MemoryMeasurement.Value.ShipUi.Module[i].GlowVisible == null)
                        {
                            var x1 = Convert.ToInt32(module[i].Region.Min0);
                            var y1 = Convert.ToInt32(module[i].Region.Min1);//get module positon
                            dm.MoveTo(x1 + 30, y1 + 30);
                            dm.LeftClick();
                            dm.delay(150);
                        }
                        var limitnum = 2;//set active module
                        if ((i + 1) > limitnum)
                            break;
                    }
                }
            }
        }

You can use it with the EveOnline.BotFramework.MemoryReadingCompleted event type. In the autopilot example bot, this part of the code is at bots/implement/applications/eve-online/eve-online-warp-to-0-autopilot/src/Bot.elm at cd4da26c3f9c34e81ea18869578c6170d09e7d57 · Viir/bots · GitHub
This event gives you the EveOnline.MemoryReading.ParsedUserInterface, so this is already after parsing. In case you want to work with the data as it was before parsing, you can get it using the uiTree field of the ParsedUserInterface

I see now that the example code can be confusing in this regard since it uses the name memoryReading for this value. I will change that.


No idea, I don’t know what that means.

Using the old 32bit method, I can observe the dynamic results from the sensor function “response.MemoryMeasurement.Value”, when I debug the code in C#.

When i set the Response to watch, I can see every parameters in the game. so I can quickly get the value. That is what i actually want.

http://prntscr.com/qwp6zb

You can watch these values from a live game client process using the “Alternate UI for EVE Online” app. The default refresh interval in this app is one second so that you see the change in the parsed user interface shortly after a change in the game client.

Based on the screenshot you linked: Here is a video that shows the corresponding user interface for the live inspection of the EVE Online user interface:

The guide on starting the alternate UI is at Sanderling/implement/alternate-ui at main · Arcitectus/Sanderling · GitHub

For your use-case, you want to adapt the command to build the config, to include the inspection tool. The command then is this:

"C:\replace-this-the-path-on-your-system\PersistentProcess.WebHost.exe" build-config --frontend-web-elm-make-appendix="--debug" --output="./build-output/app-config.zip"

(The guide does not use the --debug flag for default because it costs more processing power and is not needed for other use-cases)

Hey, I have learnt from your src c# code and understood that:

1, Your code first uses memoryReader to get raw data from EVE;
2, Parse it to uiTrees->uiTreesWithStats->largestUiTree;
3, Use Newtonsoft.json to convert the largestUiTee to JSON;
4, Finally output a JSON file into the CurrentDirectory;

The process is clear. However, it is so different from the old implementation.
I really hope the new implementation is the same as the old one :blush:
My questions are:

1, I do not care whether the JSON file is created or not. I just want to get the information from the EVE. And use my C# code to modify and operate the game. How can I use your src code to dynamically update the data from the game?
Debug Screeshot

2, The read process needs 10000ms to save a JSON file (read-memory-eve-online --remove-other-dict-entries --pid=xxxxx). Can the update interval time be shorter and quicker? I have used your alternate UI, I think it is quick but I do not know how to realize it. (I am not familiar with frontend especially the elm language really is a big problem for me ) So I want to develop the app with C#.

3, Any suggestions on parsing from the UiTree only to get minimal useful information, such as position, name, description, etc? I want to display them on my desktop app.

4, How to get the actual X Y positions of the element (up-left XY display position)? So I can decide where to click by my program.

{
	"_displayX": "0",
	"_displayWidth": "41",
	"_width": "41",
	"_setText": "Quickbar",
	"_height": "13",
	"_displayY": "3",
	"_displayHeight": "13",
	"_name": "tabLabel",
	"_color": {
		"aPercent": "150",
		"rPercent": "100",
		"gPercent": "93",
		"bPercent": "80"
	}
}

Thank you.

I have just seen demo-for-ackurdeeve. I will look into it later.

When you need to use C#, you could use the same way as the CLI tool that is the usual entry point. That tool is already written in C#, so it this is as easy as it gets given the constraint of working with C#. If you want to update the data, call the memory reading method again. You don’t need to create a JSON file.

To get locations to click at, use totalDisplayRegion. The mining bot example does this too, so that seems to work well: bots/implement/applications/eve-online/eve-online-mining-bot/src/Bot.elm at bf51d851ea1b452ed031155a52303d8cfc4acc36 · Viir/bots · GitHub

I have tested the code with a While (ProcessID>0)

while (processId > 0)
 {
  var (uiRootCandidatesAddresses, memoryReader) = GetRootAddressesAndMemoryReader();
//... to
  var largestUiTree =uiTreesWithStat.OrderByDescending(uiTreeWithStats => uiTreeWithStats.nodeCount) .FirstOrDefault().uiTree;
}

The whole reading from memory cost 8-10 seconds and system memory used is high 4~5gb. How can I reduce this time and memory use?
The elm method seems to update the data less than 1 second.

Reading from process sample A809ECB1B0A665734F25D110358C9463F191BE7ECFE631EFB1A6AAE1C4D5596F.
Found 13 candidates for UIRoot in 38 seconds: 0x2262027FC78,0x22620649478,0x22620664BE0,0x22623CAFD68,0x22623EDCFD0,0x22624B7BF98,0x22627C44DF8,0x2262A116170,0x2262B6C4338,0x2262CAF41E0,0x22634568438,0x22634568E10,0x2266CC4E888
Read 3 UI trees in 10678 milliseconds:
0x22634568438: 769 nodes.
0x22634568E10: 4 nodes.
0x22623EDCFD0: 1 nodes.
Reading from process sample E328370B8B05657B35DB8920629667CEC898F1EC37683B13A4CFA7E81284FCAB.
Found 14 candidates for UIRoot in 37 seconds: 0xE15E8F2C70,0x2262027FC78,0x22620649478,0x22620664BE0,0x22623CAFD68,0x22623EDCFD0,0x22624B7BF98,0x22627C44DF8,0x2262A116170,0x2262B6C4338,0x2262CAF41E0,0x22634568438,0x22634568E10,0x2266CC4E888
Read 3 UI trees in 7632 milliseconds:
0x22634568438: 757 nodes.
0x22634568E10: 4 nodes.
0x22623EDCFD0: 1 nodes.
Reading from process sample 3BBE8406203DACB371DA4C56A7F1036B538B113EF97574CF9D2D9C194796FC9A.
Found 13 candidates for UIRoot in 35 seconds: 0x2262027FC78,0x22620649478,0x22620664BE0,0x22623CAFD68,0x22623EDCFD0,0x22624B7BF98,0x22627C44DF8,0x2262A116170,0x2262B6C4338,0x2262CAF41E0,0x22634568438,0x22634568E10,0x2266CC4E888
Read 3 UI trees in 8433 milliseconds:
0x22634568438: 773 nodes.
0x22634568E10: 4 nodes.
0x22623EDCFD0: 1 nodes.

Depends on what you are doing. In the code you posted, I see you use a method GetRootAddressesAndMemoryReader.
How much time is spent in this method?

In which methods do you spend so much time?

1, The first one : GetRootAddressesAndMemoryReader()
I have just tested it for this method which initiates many threads. The time is 69s.
GetRootAddressesAndMemoryReader() Test

2, The second one: ReadUITrees ()
The time is about 8s.

ReadUITrees () Test

==========
Question : Do I need to call GetRootAddressesAndMemoryReader() every time when EVE ui is changed during the game?

Well, even if you dont, 8s for memory reading is hell too much. Old implementation was doing it in about 400-600ms with a decent PC and even VM.
I’m quite busy atm but i’m also interested in a 64b implementation of memory reading so i hope i’ll be able to take a look at the weekend.

Good question. Why do you call GetRootAddressesAndMemoryReader multiple times?

I think that the memory will change when eve ui changes. So I thought it was like the old implementation. I have to call sanderling.mesurement method multiple times.

Hi. How to use read-memory-64bit.dll to quickly update the game information and output JSON format file every 200ms? I have tested the alternate-UI, it does very well. However, I do not know the mechanism.

Could you please give me a demo using C#? Thank you

For now, I focus on helping people to train bots and build MMO games. I find that more interesting than adding another programming language.

@Viir
Thanks. Using the alternate ui, the api updates every 200ms, however it is a http post to location 127.0.0.1. If I send a getrequest to 127.0.0.1/api, it returns an error message. Is it possible to add a API to get the parsed JSON information when I send a request to it? I think it is easier than adding a new coding language.

Yes that seems relatively easy.

What HTTP response did you get from the server?

I use the Postman to test it.

  1. When I send a Get to http://127.0.0.1/api, it returns the original html page.
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <title>FrontendWeb.Main</title>
  <style>body { padding: 0; margin: 0; }</style>
</head>

<body>

<pre id="elm"></pre>

<script>
...
</script>

</body>
</html>

  1. When I send a POST to http://127.0.0.1/api, it returns the error.
    “This resource only supports the GET method.”. Noted that from the app page, I must choose " From live game client process" and then press the button “Click here to download this memory reading to a JSON file” to get JSON file.

  2. Suggestion:

  • If possible the API can give the latest original (/API/Original) and parsed (/API/Parsed) memory read as the responses.
  • Using the Parsed API, system resources can be highly integrated. However, it needs adequate skills in Elm language.
  • Using the Original API, developers can code with their mastered language to make beautiful web apps/GUI.
  • The web service hosted now is not needed if the API offers. Only the memory reading service to update the API is required. So that the memory consume can be lower, and is better for low performance PCs.

Thanks.