├── .gitignore
├── BUILDING.md
├── Example.png
├── LICENSE
├── README.md
├── arcdps_healing_stats
├── arcdps_healing_stats.vcxproj
├── arcdps_healing_stats.vcxproj.filters
├── arcdps_healing_stats.vcxproj.user
└── dllmain.cpp
├── arcdps_personal_stats.rc
├── arcdps_personal_stats.sln
├── arcdps_personal_stats.vcxproj
├── arcdps_personal_stats.vcxproj.filters
├── arcdps_personal_stats.vcxproj.user
├── arcdps_personal_stats_build_all.proj
├── evtc_rpc_server
├── evtc_rpc_server.vcxproj
├── evtc_rpc_server.vcxproj.filters
├── evtc_rpc_server.vcxproj.user
├── linux_versions_auto.h
└── main.cpp
├── get_dependency_versions.py
├── icons
└── specs
│ ├── 000.png
│ ├── 001.png
│ ├── 002.png
│ ├── 003.png
│ ├── 004.png
│ ├── 005.png
│ ├── 006.png
│ ├── 007.png
│ ├── 008.png
│ ├── 009.png
│ ├── e101.png
│ ├── e102.png
│ ├── e103.png
│ ├── e104.png
│ ├── e201.png
│ ├── e202.png
│ ├── e203.png
│ ├── e204.png
│ ├── e301.png
│ ├── e302.png
│ ├── e303.png
│ ├── e304.png
│ ├── e401.png
│ ├── e402.png
│ ├── e403.png
│ ├── e404.png
│ ├── e501.png
│ ├── e502.png
│ ├── e503.png
│ ├── e504.png
│ ├── e601.png
│ ├── e602.png
│ ├── e603.png
│ ├── e604.png
│ ├── e701.png
│ ├── e702.png
│ ├── e703.png
│ ├── e704.png
│ ├── e801.png
│ ├── e802.png
│ ├── e803.png
│ ├── e804.png
│ ├── e901.png
│ ├── e902.png
│ ├── e903.png
│ └── e904.png
├── networking
├── Client.cpp
├── Client.h
├── Server.cpp
├── Server.h
├── ServerStatistics.cpp
├── ServerStatistics.h
├── build_proto.py
├── evtc_rpc.proto
├── evtc_rpc_messages.h
├── networking.vcxproj
├── networking.vcxproj.filters
└── networking.vcxproj.user
├── release.py
├── resource.h
├── simpleini
├── LICENCE.txt
└── SimpleIni.h
├── src
├── AddonVersion.h
├── AgentTable.cpp
├── AgentTable.h
├── AggregatedStats.cpp
├── AggregatedStats.h
├── AggregatedStatsCollection.cpp
├── AggregatedStatsCollection.h
├── Common.h
├── EventProcessor.cpp
├── EventProcessor.h
├── EventSequencer.cpp
├── EventSequencer.h
├── Exports.h
├── GUI.cpp
├── GUI.h
├── ImGuiEx.cpp
├── ImGuiEx.h
├── Log.cpp
├── Log.h
├── Options.cpp
├── Options.h
├── PlayerStats.cpp
├── PlayerStats.h
├── Skills.cpp
├── Skills.h
├── SpecializationData.h
├── State.h
├── UpdateGUI.cpp
├── UpdateGUI.h
├── Utilities.h
└── dllmain.cpp
├── test
├── ConfigTest.cpp
├── EnvironmentTest.cpp
├── EventProcessorTest.cpp
├── GUITest.cpp
├── LocalStatsTest.cpp
├── NetworkTest.cpp
├── StressTest.cpp
├── UtilitiesTest.cpp
├── main.cpp
├── test.vcxproj
├── test.vcxproj.user
└── xevtc_logs
│ ├── berserker_solo.xevtc
│ ├── druid_MO.xevtc
│ ├── druid_solo.xevtc
│ ├── null_names.xevtc
│ └── renegade_solo.xevtc
├── vcpkg-configuration.json
├── vcpkg.json
├── vcpkg_install_dependencies
├── vcpkg_install_dependencies.vcxproj
├── vcpkg_install_dependencies.vcxproj.filters
└── vcpkg_install_dependencies.vcxproj.user
└── xmake.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | x64
3 | x86
4 | build
5 | .xmake
6 | ~AutoRecover*
7 | *.ini
8 | *.mock
9 | *.log
10 | *.aps
11 | *.txt
12 | *.user
13 | packages
14 | vcpkg_installed
--------------------------------------------------------------------------------
/BUILDING.md:
--------------------------------------------------------------------------------
1 | # Building the project
2 | ### Prerequisites:
3 | Visual Studio 2022, Version 17.6.3 was working for me at the time of writing
4 | vcpkg (installed with visual studio integration), Version 2023.04.15 was working for me at the time of writing
5 | Python, Version 3.9 was working for me at the time of writing
6 |
7 | ### Clone the project
8 | --recursive is important so that it clones submodules
9 | ```
10 | git clone --recursive https://github.com/Krappa322/arcdps_healing_stats
11 | ```
12 |
13 | ### Build the addon
14 | Open up arcdps_personal_stats.sln with Visual Studio. Choose mode (Release/Debug) and build the solution. You might have to rebuild only the "vcpkg_install_dependencies" project first before building the whole solution, to ensure that some of the tools needed for building (grpc, protobuf) are downloaded first.
15 |
16 | ### Running tests
17 | Set test.vcxproj as startup project, and run "Local Windows Debugger". You can also run test.exe from in the output directory
18 |
--------------------------------------------------------------------------------
/Example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/Example.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2022 Kappa322
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcDPS Healing Stats
2 | [](https://github.com/Krappa322/arcdps_healing_stats/releases/latest)
3 |
4 | Show healing statistics based on your local stats (i.e. your own healing output).
5 |
6 | This includes outgoing healing per agent and per skill, as well as filtering to only include your own subgroup/squad or to exclude minions. Format of the window title and contents are fully configurable and windows can be configured to show different data (targets healed, skills used to heal, total healing).
7 |
8 | If live stats sharing is enabled, this addon also allows you to see other players in your squads healing stats (and them to see yours)
9 |
10 | Also logs healing to the arcdps evtc, allowing evtc parsers to show healing stats.
11 |
12 | ## Installation
13 | Requires [ArcDPS](https://www.deltaconnected.com/arcdps/).
14 |
15 | Download the latest [arcdps_healing_stats.dll](https://github.com/Krappa322/arcdps_healing_stats/releases/latest) from the Releases page. Drag and drop arcdps_healing_stats.dll into the same directory as arcdps (d3d11.dll)
16 |
17 | ## Usage
18 | Toggle any window under the "Heal Stats" menu in the arcdps main menu. Right-click the window to see more options and hover over options to see what they do. All configuration is done per window, and there can be up to 10 different windows.
19 |
20 | To enable live-sharing of healing stats, allowing you to see others healing stats (and them to see yours), go to "Heal Stats Options" and check the checkbox for "enable live stats sharing"
21 |
22 | ## Issues and requests
23 | Please report issues and requests using the github issue tracker
24 |
25 | ## Pictures
26 | 
27 |
28 | ## Technical information
29 | This addon uses the local stats provided by ArcDPS to count healing done. This information is only available for the local player, i.e. the server does not notify about healing done by other players to other players. As such it is not possible to extend the addon to include everyone's healing without every player in the instance having the addon installed.
30 |
31 | ## Planned features
32 | - Displaying healing done per time skill was cast
33 | - Track overhealing. This is kind of hard because it would require simulating healing events and how much they should be healing (which requires knowing all modifiers and such). Please open an issue with suggestions for how to do this if you know of any :)
34 | - Store history similar to that available in the vanilla ArcDPS dps window (i.e. show statistics for previous encounters)
35 | - Display a graph showing healing done over time (allowing visualisation of when healing pressure is high)
36 | - More statistics than just healing. Confusion comes to mind, since the 10 man log method mentioned above could in the case of confusion create a log that shows true dps done for bosses where self stats and area stats don't match (such as Soulless Horror)
37 |
38 | ## Copyright Notice
39 | This project is licensed under the MIT license (see the LICENSE file for more details). It makes use of the following third party libraries (they are all statically linked):
40 | ### arcdps-extension
41 | [arcdps-extension](https://github.com/knoxfighter/arcdps-extension), licensed under the MIT license and included in this project as a git submodule of [arcdps_mock](/arcdps_mock).
42 | ### Dear ImGui
43 | [Dear ImGui](https://github.com/ocornut/imgui), licensed under the MIT license and included in this project as a git submodule [imgui](/imgui).
44 | ### GoogleTest
45 | [GoogleTest](https://github.com/google/googletest), licensed under the BSD-3-Clause license and included in this project as a linking/header dependency (provided through vcpkg).
46 | ### gRPC
47 | [gRPC](https://github.com/grpc/grpc), licensed under the Apache-2.0 license and included in this project as a linking/header dependency (provided through vcpkg).
48 | ### JSON for Modern C++
49 | [JSON for Modern C++](https://github.com/nlohmann/json), licensed under the MIT license and included in this project as a linking/header dependency (provided through vcpkg).
50 | ### Prometheus Client Library for Modern C++
51 | [prometheus-cpp](https://github.com/jupp0r/prometheus-cpp), licensed under the MIT license and included in this project as a linking/header dependency (provided through vcpkg).
52 | ### Protocol Buffers
53 | [Protocol Buffers](https://github.com/protocolbuffers/protobuf), licensed under the BSD-3-Clause license and included in this project as a linking/header dependency (provided through vcpkg).
54 | ### simpleini
55 | [simpleini](https://github.com/brofield/simpleini), licensed under the MIT license and included in this project as a directory [simpleini](/simpleini).
56 | ### spdlog
57 | [spdlog](https://github.com/gabime/spdlog), licensed under the MIT license and included in this project as a linking/header dependency (provided through vcpkg).
58 |
--------------------------------------------------------------------------------
/arcdps_healing_stats/arcdps_healing_stats.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 |
23 |
24 | Header Files
25 |
26 |
27 |
28 |
29 | Resource Files
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Resource Files
38 |
39 |
40 | Resource Files
41 |
42 |
43 | Resource Files
44 |
45 |
46 | Resource Files
47 |
48 |
49 | Resource Files
50 |
51 |
52 | Resource Files
53 |
54 |
55 | Resource Files
56 |
57 |
58 | Resource Files
59 |
60 |
61 | Resource Files
62 |
63 |
64 | Resource Files
65 |
66 |
67 | Resource Files
68 |
69 |
70 | Resource Files
71 |
72 |
73 | Resource Files
74 |
75 |
76 | Resource Files
77 |
78 |
79 | Resource Files
80 |
81 |
82 | Resource Files
83 |
84 |
85 | Resource Files
86 |
87 |
88 | Resource Files
89 |
90 |
91 | Resource Files
92 |
93 |
94 | Resource Files
95 |
96 |
97 | Resource Files
98 |
99 |
100 | Resource Files
101 |
102 |
103 | Resource Files
104 |
105 |
106 | Resource Files
107 |
108 |
109 | Resource Files
110 |
111 |
112 | Resource Files
113 |
114 |
115 | Resource Files
116 |
117 |
118 | Resource Files
119 |
120 |
121 | Resource Files
122 |
123 |
124 | Resource Files
125 |
126 |
127 | Resource Files
128 |
129 |
130 | Resource Files
131 |
132 |
133 | Resource Files
134 |
135 |
136 | Resource Files
137 |
138 |
139 | Resource Files
140 |
141 |
142 | Resource Files
143 |
144 |
145 | Resource Files
146 |
147 |
148 | Resource Files
149 |
150 |
151 | Resource Files
152 |
153 |
154 | Resource Files
155 |
156 |
157 | Resource Files
158 |
159 |
160 | Resource Files
161 |
162 |
163 | Resource Files
164 |
165 |
166 | Resource Files
167 |
168 |
169 | Resource Files
170 |
171 |
172 | Resource Files
173 |
174 |
175 |
--------------------------------------------------------------------------------
/arcdps_healing_stats/arcdps_healing_stats.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
--------------------------------------------------------------------------------
/arcdps_healing_stats/dllmain.cpp:
--------------------------------------------------------------------------------
1 | // dllmain.cpp : Defines the entry point for the DLL application.
2 | #include "Exports.h"
3 | #include "Log.h"
4 | #include "Windows.h"
5 |
6 | BOOL APIENTRY DllMain( HMODULE hModule,
7 | DWORD ul_reason_for_call,
8 | LPVOID lpReserved
9 | )
10 | {
11 | switch (ul_reason_for_call)
12 | {
13 | case DLL_PROCESS_ATTACH:
14 | GlobalObjects::SELF_HANDLE = hModule;
15 | break;
16 | case DLL_PROCESS_DETACH:
17 | break;
18 | case DLL_THREAD_ATTACH:
19 | case DLL_THREAD_DETACH:
20 | break;
21 | }
22 | return TRUE;
23 | }
24 |
25 | // This triggers the linker to pick up the two exported functions from the static library
26 | void UnusedFunctionToHelpTheLinker()
27 | {
28 | get_init_addr(nullptr, nullptr, nullptr, NULL, nullptr, nullptr, 0);
29 | get_release_addr();
30 | }
--------------------------------------------------------------------------------
/arcdps_personal_stats.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #include "resource.h"
4 |
5 | #define APSTUDIO_READONLY_SYMBOLS
6 | /////////////////////////////////////////////////////////////////////////////
7 | //
8 | // Generated from the TEXTINCLUDE 2 resource.
9 | //
10 | #include "winres.h"
11 |
12 | /////////////////////////////////////////////////////////////////////////////
13 | #undef APSTUDIO_READONLY_SYMBOLS
14 |
15 | /////////////////////////////////////////////////////////////////////////////
16 | // English (United States) resources
17 |
18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
19 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
20 | #pragma code_page(1252)
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // CERTIFICATE
51 | //
52 |
53 | IDR_ROOT_CERTIFICATES CERTIFICATE "vcpkg_installed\\x64-windows-static\\x64-windows\\share\\grpc\\roots.pem"
54 |
55 |
56 | /////////////////////////////////////////////////////////////////////////////
57 | //
58 | // Version
59 | //
60 |
61 | VS_VERSION_INFO VERSIONINFO
62 | FILEVERSION 2,15,1,1
63 | PRODUCTVERSION 2,15,1,1
64 | FILEFLAGSMASK 0x3fL
65 | #ifdef _DEBUG
66 | FILEFLAGS 0x1L
67 | #else
68 | FILEFLAGS 0x0L
69 | #endif
70 | FILEOS 0x40004L
71 | FILETYPE 0x0L
72 | FILESUBTYPE 0x0L
73 | BEGIN
74 | BLOCK "StringFileInfo"
75 | BEGIN
76 | BLOCK "200004b0"
77 | BEGIN
78 | VALUE "FileDescription", "arcdps_healing_stats"
79 | VALUE "FileVersion", "2.15.1.1"
80 | VALUE "InternalName", "arcdps_healing_stats"
81 | VALUE "LegalCopyright", "Copyright (C) 2022"
82 | VALUE "OriginalFilename", "arcdps_healing_stats.dll"
83 | VALUE "ProductName", "arcdps_healing_stats"
84 | VALUE "ProductVersion", "2.15.1.1"
85 | END
86 | END
87 | BLOCK "VarFileInfo"
88 | BEGIN
89 | VALUE "Translation", 0x2000, 1200
90 | END
91 | END
92 |
93 |
94 | /////////////////////////////////////////////////////////////////////////////
95 | //
96 | // PNG
97 | //
98 |
99 | IDB_PNG_SPEC_GUARDIAN PNG "icons\\specs\\001.png"
100 |
101 | IDB_PNG_SPEC_WARRIOR PNG "icons\\specs\\002.png"
102 |
103 | IDB_PNG_SPEC_ENGINEER PNG "icons\\specs\\003.png"
104 |
105 | IDB_PNG_SPEC_RANGER PNG "icons\\specs\\004.png"
106 |
107 | IDB_PNG_SPEC_THIEF PNG "icons\\specs\\005.png"
108 |
109 | IDB_PNG_SPEC_ELEMENTALIST PNG "icons\\specs\\006.png"
110 |
111 | IDB_PNG_SPEC_MESMER PNG "icons\\specs\\007.png"
112 |
113 | IDB_PNG_SPEC_NECROMANCER PNG "icons\\specs\\008.png"
114 |
115 | IDB_PNG_SPEC_REVENANT PNG "icons\\specs\\009.png"
116 |
117 | IDB_PNG_SPEC_DRAGONHUNTER PNG "icons\\specs\\e101.png"
118 |
119 | IDB_PNG_SPEC_FIREBRAND PNG "icons\\specs\\e102.png"
120 |
121 | IDB_PNG_SPEC_WILLBENDER PNG "icons\\specs\\e103.png"
122 |
123 | IDB_PNG_SPEC_BERSERKER PNG "icons\\specs\\e201.png"
124 |
125 | IDB_PNG_SPEC_SPELLBREAKER PNG "icons\\specs\\e202.png"
126 |
127 | IDB_PNG_SPEC_BLADESWORN PNG "icons\\specs\\e203.png"
128 |
129 | IDB_PNG_SPEC_SCRAPPER PNG "icons\\specs\\e301.png"
130 |
131 | IDB_PNG_SPEC_HOLOSMITH PNG "icons\\specs\\e302.png"
132 |
133 | IDB_PNG_SPEC_MECHANIST PNG "icons\\specs\\e303.png"
134 |
135 | IDB_PNG_SPEC_DRUID PNG "icons\\specs\\e401.png"
136 |
137 | IDB_PNG_SPEC_SOULBEAST PNG "icons\\specs\\e402.png"
138 |
139 | IDB_PNG_SPEC_UNTAMED PNG "icons\\specs\\e403.png"
140 |
141 | IDB_PNG_SPEC_DAREDEVIL PNG "icons\\specs\\e501.png"
142 |
143 | IDB_PNG_SPEC_DEADEYE PNG "icons\\specs\\e502.png"
144 |
145 | IDB_PNG_SPEC_SPECTER PNG "icons\\specs\\e503.png"
146 |
147 | IDB_PNG_SPEC_TEMPEST PNG "icons\\specs\\e601.png"
148 |
149 | IDB_PNG_SPEC_WEAVER PNG "icons\\specs\\e602.png"
150 |
151 | IDB_PNG_SPEC_CATALYST PNG "icons\\specs\\e603.png"
152 |
153 | IDB_PNG_SPEC_CHRONOMANCER PNG "icons\\specs\\e701.png"
154 |
155 | IDB_PNG_SPEC_MIRAGE PNG "icons\\specs\\e702.png"
156 |
157 | IDB_PNG_SPEC_VIRTUOSO PNG "icons\\specs\\e703.png"
158 |
159 | IDB_PNG_SPEC_REAPER PNG "icons\\specs\\e801.png"
160 |
161 | IDB_PNG_SPEC_SCOURGE PNG "icons\\specs\\e802.png"
162 |
163 | IDB_PNG_SPEC_HARBINGER PNG "icons\\specs\\e803.png"
164 |
165 | IDB_PNG_SPEC_HERALD PNG "icons\\specs\\e901.png"
166 |
167 | IDB_PNG_SPEC_RENEGADE PNG "icons\\specs\\e902.png"
168 |
169 | IDB_PNG_SPEC_VINDICATOR PNG "icons\\specs\\e903.png"
170 |
171 | IDB_PNG_SPEC_NONE PNG "icons\\specs\\000.png"
172 |
173 | IDB_PNG_SPEC_LUMINARY PNG "icons\\specs\\e104.png"
174 |
175 | IDB_PNG_SPEC_PARAGON PNG "icons\\specs\\e204.png"
176 |
177 | IDB_PNG_SPEC_AMALGAM PNG "icons\\specs\\e304.png"
178 |
179 | IDB_PNG_SPEC_GALESHOT PNG "icons\\specs\\e404.png"
180 |
181 | IDB_PNG_SPEC_ANTIQUARY PNG "icons\\specs\\e504.png"
182 |
183 | IDB_PNG_SPEC_EVOKER PNG "icons\\specs\\e604.png"
184 |
185 | IDB_PNG_SPEC_TROUBADOUR PNG "icons\\specs\\e704.png"
186 |
187 | IDB_PNG_SPEC_RITUALIST PNG "icons\\specs\\e804.png"
188 |
189 | IDB_PNG_SPEC_CONDUIT PNG "icons\\specs\\e904.png"
190 |
191 | #endif // English (United States) resources
192 | /////////////////////////////////////////////////////////////////////////////
193 |
194 |
195 |
196 | #ifndef APSTUDIO_INVOKED
197 | /////////////////////////////////////////////////////////////////////////////
198 | //
199 | // Generated from the TEXTINCLUDE 3 resource.
200 | //
201 |
202 |
203 | /////////////////////////////////////////////////////////////////////////////
204 | #endif // not APSTUDIO_INVOKED
205 |
206 |
--------------------------------------------------------------------------------
/arcdps_personal_stats.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 | Source Files
29 |
30 |
31 | Source Files
32 |
33 |
34 | Source Files
35 |
36 |
37 | Source Files
38 |
39 |
40 | Source Files
41 |
42 |
43 | Source Files
44 |
45 |
46 | Source Files
47 |
48 |
49 | Source Files
50 |
51 |
52 | Source Files
53 |
54 |
55 | Source Files
56 |
57 |
58 |
59 |
60 | Header Files
61 |
62 |
63 | Header Files
64 |
65 |
66 | Header Files
67 |
68 |
69 | Header Files
70 |
71 |
72 | Header Files
73 |
74 |
75 | Header Files
76 |
77 |
78 | Header Files
79 |
80 |
81 | Header Files
82 |
83 |
84 | Header Files
85 |
86 |
87 | Header Files
88 |
89 |
90 | Header Files
91 |
92 |
93 | Header Files
94 |
95 |
96 | Header Files
97 |
98 |
99 | Header Files
100 |
101 |
102 | Header Files
103 |
104 |
105 | Header Files
106 |
107 |
108 | Header Files
109 |
110 |
111 | Header Files
112 |
113 |
114 | Header Files
115 |
116 |
117 |
--------------------------------------------------------------------------------
/arcdps_personal_stats.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
7 | NativeOnly
8 | WindowsLocalDebugger
9 |
10 |
11 | NativeOnly
12 | WindowsLocalDebugger
13 |
14 |
--------------------------------------------------------------------------------
/arcdps_personal_stats_build_all.proj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Configuration=Debug
5 |
6 |
7 | Configuration=Release
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/evtc_rpc_server/evtc_rpc_server.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 |
26 |
27 | Header Files
28 |
29 |
30 |
--------------------------------------------------------------------------------
/evtc_rpc_server/evtc_rpc_server.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | localhost:50052
5 | WindowsLocalDebugger
6 |
7 |
8 | true
9 |
10 |
--------------------------------------------------------------------------------
/evtc_rpc_server/linux_versions_auto.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #define DEPENDENCY_VERSIONS \
3 | "abseil:x64-linux 20240116.2\n"\
4 | "c-ares:x64-linux 1.18.1\n"\
5 | "civetweb:x64-linux 1.15#1\n"\
6 | "cpr:x64-linux 1.10.5#2\n"\
7 | "curl:x64-linux 8.7.1\n"\
8 | "fmt:x64-linux 9.1.0\n"\
9 | "grpc:x64-linux 1.51.1#3\n"\
10 | "gtest:x64-linux 1.14.0#1\n"\
11 | "jemalloc:x64-linux 5.3.0#1\n"\
12 | "nlohmann-json:x64-linux 3.11.3\n"\
13 | "openssl:x64-linux 3.3.1\n"\
14 | "prometheus-cpp:x64-linux 1.2.4\n"\
15 | "protobuf:x64-linux 3.21.12#3\n"\
16 | "re2:x64-linux 2024-04-01#2\n"\
17 | "spdlog:x64-linux 1.14.1\n"\
18 | "upb:x64-linux 2022-06-21#1\n"\
19 | "vcpkg-cmake-config:x64-linux 2024-05-23\n"\
20 | "vcpkg-cmake-get-vars:x64-linux 2023-12-31\n"\
21 | "vcpkg-cmake:x64-linux 2024-04-23\n"\
22 | "zlib:x64-linux 1.3.1\n"
--------------------------------------------------------------------------------
/evtc_rpc_server/main.cpp:
--------------------------------------------------------------------------------
1 | #include "linux_versions_auto.h"
2 |
3 | #include "../networking/Server.h"
4 | #include "../src/Log.h"
5 |
6 | #ifdef LINUX
7 | #include
8 | #include
9 | #endif
10 |
11 | #include
12 | #include
13 |
14 | constexpr static auto MALLOC_STATS_INTERVAL = std::chrono::seconds(600);
15 |
16 | static std::unique_ptr SERVER;
17 | static std::thread SERVER_THREAD;
18 | static std::thread MONITOR_THREAD;
19 |
20 | static std::atomic_bool MONITOR_THREAD_SHUTDOWN = false;
21 |
22 | static void signal_handler_shutdown(int pSignal)
23 | {
24 | LogI("Signal {}", pSignal);
25 | SERVER->Shutdown();
26 | }
27 |
28 | static void install_signal_handler()
29 | {
30 | #ifdef LINUX
31 | struct sigaction sa = {};
32 | sa.sa_handler = &signal_handler_shutdown;
33 | sigemptyset(&sa.sa_mask);
34 | sigaddset(&sa.sa_mask, SIGINT);
35 | sigaddset(&sa.sa_mask, SIGTERM);
36 | sigaction(SIGINT, &sa, nullptr);
37 | sigaction(SIGTERM, &sa, nullptr);
38 | #endif
39 | }
40 |
41 | static void uninstall_signal_handler()
42 | {
43 | #ifdef LINUX
44 | struct sigaction sa = {};
45 | sa.sa_handler = SIG_DFL;
46 | sigaction(SIGINT, &sa, nullptr);
47 | sigaction(SIGTERM, &sa, nullptr);
48 | #endif
49 | }
50 |
51 | static void MallocStats(void*, const char* pData)
52 | {
53 | LogI("{}", pData);
54 | }
55 |
56 | static void monitor_thread_entry()
57 | {
58 | #ifdef LINUX
59 | pthread_setname_np(pthread_self(), "evtc-rpc-mon");
60 | #elif defined(_WIN32)
61 | SetThreadDescription(GetCurrentThread(), L"evtc-rpc-mon");
62 | #endif
63 |
64 | std::chrono::steady_clock::time_point last_report; // epoch
65 | while (MONITOR_THREAD_SHUTDOWN.load(std::memory_order_relaxed) == false)
66 | {
67 | std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
68 | if (last_report + MALLOC_STATS_INTERVAL <= now)
69 | {
70 | #ifdef LINUX
71 | malloc_stats_print(&MallocStats, NULL, NULL);
72 | #endif
73 | last_report = now;
74 | }
75 | std::this_thread::sleep_for(std::chrono::seconds(1));
76 | }
77 | }
78 |
79 | int main(int pArgumentCount, char** pArgumentVector)
80 | {
81 | Log_::InitMultiSink(false, "logs/evtc_rpc_server_debug.txt", "logs/evtc_rpc_server_info.txt");
82 | Log_::SetLevel(spdlog::level::debug);
83 | LogI("Start. Dependency versions:\n{}", DEPENDENCY_VERSIONS);
84 |
85 | if (pArgumentCount != 3)
86 | {
87 | fprintf(stderr, "Invalid argument count\nusage: %s \n", pArgumentVector[0]);
88 | return 1;
89 | }
90 |
91 | SERVER = std::make_unique(pArgumentVector[1], pArgumentVector[2], nullptr);
92 | SERVER_THREAD = std::thread(evtc_rpc_server::ThreadStartServe, SERVER.get());
93 | MONITOR_THREAD = std::thread(monitor_thread_entry);
94 |
95 | // Set thread name after this thread is done cloning itself for other threads
96 | #ifdef LINUX
97 | pthread_setname_np(pthread_self(), "evtc-rpc-main");
98 | #elif defined(_WIN32)
99 | SetThreadDescription(GetCurrentThread(), L"evtc-rpc-main");
100 | #endif
101 |
102 | install_signal_handler();
103 |
104 | SERVER_THREAD.join();
105 | MONITOR_THREAD_SHUTDOWN.store(true, std::memory_order_relaxed);
106 | MONITOR_THREAD.join();
107 |
108 | uninstall_signal_handler();
109 | SERVER = nullptr;
110 |
111 | LogI("Exited normally");
112 | Log_::Shutdown();
113 |
114 | return 0;
115 | }
--------------------------------------------------------------------------------
/get_dependency_versions.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | from typing import List, NamedTuple
4 |
5 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
6 |
7 | VCPKG_ROOT = "/local/vcpkg/"
8 | VCPKG_PATH = os.path.join(VCPKG_ROOT, "vcpkg")
9 | TRIPLET = "x64-linux"
10 | MANIFEST_ROOT = SCRIPT_DIR
11 | INSTALL_ROOT = os.path.join(MANIFEST_ROOT, "vcpkg_installed", TRIPLET)
12 | VERSIONS_FILE = os.path.join(SCRIPT_DIR, "evtc_rpc_server", "linux_versions_auto.h")
13 |
14 | class DependencyInfo(NamedTuple):
15 | name: str
16 | version: str
17 | description: str
18 |
19 | def get_dependency_versions() -> List[DependencyInfo]:
20 | vcpkg_output = subprocess.run(
21 | [VCPKG_PATH, "list", "--x-wait-for-lock", "--triplet", TRIPLET, "--vcpkg-root", VCPKG_ROOT, "--x-manifest-root", MANIFEST_ROOT, "--x-install-root", INSTALL_ROOT],
22 | capture_output=True, universal_newlines=True)
23 | assert vcpkg_output.returncode == 0
24 |
25 | res = []
26 | for line in vcpkg_output.stdout.splitlines():
27 | split = line.split()
28 |
29 | # Features are shown as separate entries but do not have versions, skip them
30 | if "[" in split[0]:
31 | continue
32 |
33 | res.append(DependencyInfo(
34 | name=split[0],
35 | version=split[1],
36 | description=split[2] if len(split) >= 3 else ""))
37 | return res
38 |
39 | def generate_versions_file(deps: List[DependencyInfo]):
40 | longest_dep_name = max([len(dep.name) for dep in deps])
41 | with open(VERSIONS_FILE, "w") as file:
42 | file.write("#pragma once\n")
43 | file.write("#define DEPENDENCY_VERSIONS ")
44 | for dep in deps:
45 | file.write("\\\n")
46 | file.write(f"\"{dep.name: <{longest_dep_name}} {dep.version}\\n\"")
47 |
48 |
49 |
50 | generate_versions_file(get_dependency_versions())
--------------------------------------------------------------------------------
/icons/specs/000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/000.png
--------------------------------------------------------------------------------
/icons/specs/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/001.png
--------------------------------------------------------------------------------
/icons/specs/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/002.png
--------------------------------------------------------------------------------
/icons/specs/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/003.png
--------------------------------------------------------------------------------
/icons/specs/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/004.png
--------------------------------------------------------------------------------
/icons/specs/005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/005.png
--------------------------------------------------------------------------------
/icons/specs/006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/006.png
--------------------------------------------------------------------------------
/icons/specs/007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/007.png
--------------------------------------------------------------------------------
/icons/specs/008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/008.png
--------------------------------------------------------------------------------
/icons/specs/009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/009.png
--------------------------------------------------------------------------------
/icons/specs/e101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e101.png
--------------------------------------------------------------------------------
/icons/specs/e102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e102.png
--------------------------------------------------------------------------------
/icons/specs/e103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e103.png
--------------------------------------------------------------------------------
/icons/specs/e104.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e104.png
--------------------------------------------------------------------------------
/icons/specs/e201.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e201.png
--------------------------------------------------------------------------------
/icons/specs/e202.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e202.png
--------------------------------------------------------------------------------
/icons/specs/e203.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e203.png
--------------------------------------------------------------------------------
/icons/specs/e204.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e204.png
--------------------------------------------------------------------------------
/icons/specs/e301.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e301.png
--------------------------------------------------------------------------------
/icons/specs/e302.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e302.png
--------------------------------------------------------------------------------
/icons/specs/e303.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e303.png
--------------------------------------------------------------------------------
/icons/specs/e304.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e304.png
--------------------------------------------------------------------------------
/icons/specs/e401.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e401.png
--------------------------------------------------------------------------------
/icons/specs/e402.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e402.png
--------------------------------------------------------------------------------
/icons/specs/e403.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e403.png
--------------------------------------------------------------------------------
/icons/specs/e404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e404.png
--------------------------------------------------------------------------------
/icons/specs/e501.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e501.png
--------------------------------------------------------------------------------
/icons/specs/e502.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e502.png
--------------------------------------------------------------------------------
/icons/specs/e503.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e503.png
--------------------------------------------------------------------------------
/icons/specs/e504.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e504.png
--------------------------------------------------------------------------------
/icons/specs/e601.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e601.png
--------------------------------------------------------------------------------
/icons/specs/e602.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e602.png
--------------------------------------------------------------------------------
/icons/specs/e603.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e603.png
--------------------------------------------------------------------------------
/icons/specs/e604.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e604.png
--------------------------------------------------------------------------------
/icons/specs/e701.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e701.png
--------------------------------------------------------------------------------
/icons/specs/e702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e702.png
--------------------------------------------------------------------------------
/icons/specs/e703.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e703.png
--------------------------------------------------------------------------------
/icons/specs/e704.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e704.png
--------------------------------------------------------------------------------
/icons/specs/e801.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e801.png
--------------------------------------------------------------------------------
/icons/specs/e802.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e802.png
--------------------------------------------------------------------------------
/icons/specs/e803.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e803.png
--------------------------------------------------------------------------------
/icons/specs/e804.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e804.png
--------------------------------------------------------------------------------
/icons/specs/e901.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e901.png
--------------------------------------------------------------------------------
/icons/specs/e902.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e902.png
--------------------------------------------------------------------------------
/icons/specs/e903.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e903.png
--------------------------------------------------------------------------------
/icons/specs/e904.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Krappa322/arcdps_healing_stats/5552300db4e358258973237475779313fc304021/icons/specs/e904.png
--------------------------------------------------------------------------------
/networking/Client.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #ifdef __clang__
6 | #pragma clang diagnostic push
7 | #pragma clang diagnostic ignored "-Weverything"
8 | #elif _WIN32
9 | #pragma warning(push, 0)
10 | #pragma warning(disable : 4127)
11 | #pragma warning(disable : 4702)
12 | #pragma warning(disable : 5054)
13 | #pragma warning(disable : 6385)
14 | #pragma warning(disable : 6387)
15 | #pragma warning(disable : 26451)
16 | #pragma warning(disable : 26495)
17 | #endif
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #ifdef __clang__
26 | #pragma clang diagnostic pop
27 | #elif _WIN32
28 | #pragma warning(pop)
29 | #pragma warning(default : 4702)
30 | #endif
31 |
32 | #include
33 | #include
34 | #include
35 | #include
36 |
37 | struct evtc_rpc_client_status
38 | {
39 | bool Connected = false;
40 | bool Encrypted = false;
41 | std::chrono::steady_clock::time_point ConnectTime;
42 | std::string Endpoint;
43 | };
44 |
45 | class evtc_rpc_client
46 | {
47 | typedef void(*PeerCombatCallbackSignature)(cbtevent* pEvent);
48 |
49 | struct PeerInfo
50 | {
51 | uint16_t InstanceId = 0;
52 | std::string AccountName;
53 | };
54 |
55 | struct ConnectionContext
56 | {
57 | bool ForceDisconnected = false;
58 | bool WritePending = false;
59 | uint16_t RegisteredInstanceId = 0;
60 | std::map RegisteredPeers;
61 |
62 | grpc::ClientContext ClientContext;
63 | std::shared_ptr Channel;
64 | std::unique_ptr> Stream;
65 | std::unique_ptr Stub;
66 | };
67 |
68 | enum class CallDataType
69 | {
70 | Connect,
71 | WritesDone,
72 | Finish,
73 | ReadMessage,
74 | RegisterSelf,
75 | AddPeer,
76 | RemovePeer,
77 | CombatEvent,
78 | Disconnect,
79 |
80 | Invalid
81 | };
82 |
83 | struct CallDataBase
84 | {
85 | CallDataBase(CallDataType pType, std::shared_ptr&& pContext)
86 | : Type{pType}
87 | , Context{std::move(pContext)}
88 | {
89 | }
90 |
91 | const CallDataType Type;
92 | std::shared_ptr Context;
93 |
94 | bool IsWrite();
95 | void Destruct();
96 | };
97 |
98 | struct ConnectCallData : public CallDataBase
99 | {
100 | ConnectCallData(std::shared_ptr&& pContext)
101 | : CallDataBase{CallDataType::Connect, std::move(pContext)}
102 | {
103 | }
104 | };
105 |
106 | struct FinishCallData : public CallDataBase
107 | {
108 | FinishCallData(std::shared_ptr&& pContext)
109 | : CallDataBase{CallDataType::Finish, std::move(pContext)}
110 | {
111 | }
112 |
113 | grpc::Status ReturnedStatus;
114 | };
115 |
116 | struct WritesDoneCallData : public CallDataBase
117 | {
118 | WritesDoneCallData(std::shared_ptr&& pContext)
119 | : CallDataBase{CallDataType::WritesDone, std::move(pContext)}
120 | {
121 | }
122 | };
123 |
124 | struct ReadMessageCallData : public CallDataBase
125 | {
126 | ReadMessageCallData(std::shared_ptr&& pContext)
127 | : CallDataBase{CallDataType::ReadMessage, std::move(pContext)}
128 | {
129 | }
130 |
131 | evtc_rpc::Message Message;
132 | };
133 |
134 | struct RegisterSelfCallData : public CallDataBase
135 | {
136 | RegisterSelfCallData(std::shared_ptr&& pContext, uint16_t pSelfInstanceId, std::string&& pSelfAccountName)
137 | : CallDataBase{CallDataType::RegisterSelf, std::move(pContext)}
138 | , SelfInstanceId{pSelfInstanceId}
139 | , SelfAccountName{std::move(pSelfAccountName)}
140 | {
141 | }
142 |
143 | const uint16_t SelfInstanceId;
144 | const std::string SelfAccountName;
145 | };
146 |
147 | struct AddPeerCallData : public CallDataBase
148 | {
149 | AddPeerCallData(std::shared_ptr&& pContext, uint16_t pPeerInstanceId, std::string&& pPeerAccountName)
150 | : CallDataBase{CallDataType::AddPeer, std::move(pContext)}
151 | , PeerInstanceId{pPeerInstanceId}
152 | , PeerAccountName{std::move(pPeerAccountName)}
153 | {
154 | }
155 |
156 | const uint16_t PeerInstanceId;
157 | const std::string PeerAccountName;
158 | };
159 |
160 | struct RemovePeerCallData : public CallDataBase
161 | {
162 | RemovePeerCallData(std::shared_ptr&& pContext, uint16_t pPeerInstanceId)
163 | : CallDataBase{CallDataType::RemovePeer, std::move(pContext)}
164 | , PeerInstanceId{pPeerInstanceId}
165 | {
166 | }
167 |
168 | const uint16_t PeerInstanceId;
169 | };
170 |
171 |
172 | struct CombatEventCallData : public CallDataBase
173 | {
174 | CombatEventCallData(const cbtevent& pEvent)
175 | : CallDataBase{CallDataType::CombatEvent, nullptr}
176 | , Event{pEvent}
177 | {
178 | }
179 |
180 | const cbtevent Event;
181 | };
182 |
183 | struct DisconnectCallData : public CallDataBase
184 | {
185 | DisconnectCallData(std::shared_ptr&& pContext)
186 | : CallDataBase{CallDataType::Disconnect, std::move(pContext)}
187 | {
188 | }
189 | };
190 |
191 | public:
192 | evtc_rpc_client(std::function&& pEndpointCallback, std::function&& pRootCertsCallback, std::function&& pCombatEventCallback);
193 |
194 | evtc_rpc_client_status GetStatus();
195 | void SetEnabledStatus(bool pEnabledStatus);
196 | void SetBudgetMode(bool pBudgetMode);
197 | void SetDisableEncryption(bool pDisableEncryption);
198 |
199 | uintptr_t ProcessLocalEvent(cbtevent* pEvent, ag* pSourceAgent, ag* pDestinationAgent, const char* pSkillname, uint64_t pId, uint64_t pRevision);
200 | uintptr_t ProcessAreaEvent(cbtevent* pEvent, ag* pSourceAgent, ag* pDestinationAgent, const char* pSkillname, uint64_t pId, uint64_t pRevision);
201 |
202 | static void ThreadStartServe(void* pThis);
203 | void Serve();
204 | void Shutdown();
205 |
206 | void FlushEvents(size_t pAcceptableQueueSize = 0);
207 |
208 | #ifndef TEST
209 | private:
210 | #endif
211 | bool QueueEvent(CallDataBase* pCallData, bool pIsImportant);
212 | CallDataBase* TryGetPeerEvent();
213 |
214 | void ForceDisconnect(const std::shared_ptr& pContext, const char* pErrorMessage);
215 | void HandleReadMessage(ReadMessageCallData* pCallData);
216 | void SendEvent(CallDataBase* pCallData);
217 |
218 | const std::function mEndpointCallback;
219 | const std::function mRootCertificatesCallback;
220 | const std::function mCombatEventCallback;
221 |
222 | std::mutex mQueuedEventsLock;
223 | std::queue mQueuedEvents;
224 |
225 | std::atomic_bool mDisabled{false};
226 | std::atomic_bool mBudgetMode{false};
227 | std::atomic_bool mDisableEncryption{false};
228 | std::atomic_bool mShouldShutdown{false};
229 | bool mShutdown = false;
230 | std::chrono::steady_clock::time_point mLastConnectionAttempt;
231 |
232 | std::shared_ptr mConnectionContext;
233 | grpc::CompletionQueue mCompletionQueue;
234 |
235 | std::mutex mSelfInfoLock;
236 | std::string mAccountName;
237 | uint16_t mInstanceId = 0;
238 |
239 | std::mutex mPeerInfoLock;
240 | std::map mPeers;
241 |
242 | std::mutex mStatusLock;
243 | evtc_rpc_client_status mStatus;
244 | };
--------------------------------------------------------------------------------
/networking/Server.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "ServerStatistics.h"
3 |
4 | #ifdef __clang__
5 | #pragma clang diagnostic push
6 | #pragma clang diagnostic ignored "-Weverything"
7 | #elif _WIN32
8 | #pragma warning(push, 0)
9 | #pragma warning(disable : 4127)
10 | #pragma warning(disable : 4702)
11 | #pragma warning(disable : 5054)
12 | #pragma warning(disable : 6385)
13 | #pragma warning(disable : 6387)
14 | #pragma warning(disable : 26451)
15 | #pragma warning(disable : 26495)
16 | #endif
17 | #include
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #ifdef __clang__
28 | #pragma clang diagnostic pop
29 | #elif _WIN32
30 | #pragma warning(pop)
31 | #endif
32 |
33 | #include "evtc_rpc_messages.h"
34 |
35 | #include
36 | #include
37 | #include
38 |
39 | class evtc_rpc_server
40 | {
41 | struct ConnectionContext
42 | {
43 | std::map>::iterator Iterator{}; // Protected by mRegisteredAgentsLock on the server that owns this ConnectionContext
44 | uint16_t InstanceId = 0; // Protected by mRegisteredAgentsLock on the server that owns this ConnectionContext
45 | std::map Peers; // Protected by mRegisteredAgentsLock on the server that owns this ConnectionContext
46 |
47 | std::atomic LastCallTime;
48 |
49 | grpc::ServerContext ServerContext;
50 | // Reads against the stream are protected since they are serialized, writes are protected with WriteLock
51 | grpc::ServerAsyncReaderWriter Stream{&ServerContext};
52 |
53 | std::mutex WriteLock;
54 | bool ForceDisconnected = false; // Protected by WriteLock
55 | bool WritePending = false; // Protected by WriteLock
56 | std::deque QueuedEvents; // Protected by WriteLock
57 | };
58 |
59 | struct CallDataBase
60 | {
61 | CallDataBase(CallDataType pType, std::shared_ptr&& pContext)
62 | : Type{pType}, Context{pContext}
63 | {
64 | }
65 |
66 | const CallDataType Type;
67 | std::shared_ptr Context;
68 | };
69 |
70 | struct FinishCallData : public CallDataBase
71 | {
72 | FinishCallData(std::shared_ptr&& pContext)
73 | : CallDataBase{CallDataType::Finish, std::move(pContext)}
74 | {
75 | }
76 | };
77 |
78 | struct ConnectCallData : public CallDataBase
79 | {
80 | ConnectCallData(std::shared_ptr&& pContext)
81 | : CallDataBase{CallDataType::Connect, std::move(pContext)}
82 | {
83 | }
84 | };
85 |
86 | struct ReadMessageCallData : public CallDataBase
87 | {
88 | ReadMessageCallData(std::shared_ptr&& pContext)
89 | : CallDataBase{CallDataType::ReadMessage, std::move(pContext)}
90 | {
91 | }
92 |
93 | evtc_rpc::Message Message;
94 | };
95 |
96 | struct WriteEventCallData : public CallDataBase
97 | {
98 | WriteEventCallData(std::shared_ptr&& pContext)
99 | : CallDataBase{CallDataType::WriteEvent, std::move(pContext)}
100 | {
101 | }
102 | };
103 |
104 | struct DisconnectCallData : public CallDataBase
105 | {
106 | DisconnectCallData(std::shared_ptr&& pContext)
107 | : CallDataBase{CallDataType::Disconnect, std::move(pContext)}
108 | {
109 | }
110 | };
111 |
112 | struct WakeUpCallData : public CallDataBase
113 | {
114 | WakeUpCallData()
115 | : CallDataBase{CallDataType::WakeUp, nullptr}
116 | , Alarm{new grpc::Alarm}
117 | {
118 | }
119 |
120 | std::unique_ptr Alarm;
121 | };
122 |
123 | enum class ShutdownState
124 | {
125 | Online,
126 | ShouldShutdown,
127 | ShuttingDown
128 | };
129 |
130 | public:
131 | evtc_rpc_server(const char* pListeningEndpoint, const char* pPrometheusEndpoint, const grpc::SslServerCredentialsOptions* pCredentialsOptions);
132 | ~evtc_rpc_server();
133 |
134 | ServerStatisticsSample GetStatistics();
135 |
136 | static void ThreadStartServe(void* pThis);
137 | void Serve();
138 | void Shutdown();
139 |
140 | #ifndef TEST
141 | private:
142 | #endif
143 | void HandleConnect(ConnectCallData* pCallData);
144 | void HandleReadMessage(ReadMessageCallData* pCallData);
145 | void HandleWriteEvent(WriteEventCallData* pCallData);
146 |
147 | const char* HandleRegisterSelf(uint16_t pInstanceId, std::string_view pAccountName, std::shared_ptr& pClient);
148 | const char* HandleSetSelfId(uint16_t pInstanceId, std::shared_ptr& pClient);
149 | const char* HandleAddPeer(uint16_t pInstanceId, std::string_view pAccountName, std::shared_ptr& pClient);
150 | const char* HandleRemovePeer(uint16_t pInstanceId, std::shared_ptr& pClient);
151 | const char* HandleCombatEvent(const cbtevent& pEvent, std::shared_ptr& pClient);
152 |
153 | void SendEvent(const evtc_rpc::messages::CombatEvent& pEvent, WriteEventCallData* pCallData, const std::shared_ptr& pClient);
154 | void ForceDisconnect(const char* pErrorMessage, const std::shared_ptr& pClient);
155 | void ForceDisconnectInternal(const char* pErrorMessage, const std::shared_ptr& pClient, bool pRemovedFromTable);
156 |
157 | std::mutex mRegisteredAgentsLock;
158 | std::map> mRegisteredAgents;
159 |
160 | std::shared_ptr mStatistics;
161 | prometheus::Exposer mPrometheusExposer;
162 |
163 | evtc_rpc::evtc_rpc::AsyncService mService;
164 | std::unique_ptr mServer;
165 | std::unique_ptr mCompletionQueue;
166 |
167 | std::shared_mutex mShutdownLock;
168 | std::atomic mShutdownState = ShutdownState::Online;
169 |
170 | std::atomic mConflictingClientDisconnectThresholdMs = 30000;
171 | };
172 |
--------------------------------------------------------------------------------
/networking/ServerStatistics.cpp:
--------------------------------------------------------------------------------
1 | #include "ServerStatistics.h"
2 | #include "Server.h"
3 | #include "../src/Log.h"
4 |
5 | namespace
6 | {
7 | constexpr const char* CallDataTypeToString(CallDataType pType)
8 | {
9 | switch (pType)
10 | {
11 | case CallDataType::Connect:
12 | return "Connect";
13 | case CallDataType::Finish:
14 | return "Finish";
15 | case CallDataType::ReadMessage:
16 | return "ReadMessage";
17 | case CallDataType::WriteEvent:
18 | return "WriteEvent";
19 | case CallDataType::Disconnect:
20 | return "Disconnect";
21 | case CallDataType::WakeUp:
22 | return "WakeUp";
23 | default:
24 | return "";
25 | };
26 | }
27 |
28 | constexpr const char* EvtcRpcMessageTypeToString(evtc_rpc::messages::Type pType)
29 | {
30 | using evtc_rpc::messages::Type;
31 |
32 | switch (pType)
33 | {
34 | case Type::Invalid:
35 | return "Invalid";
36 | case Type::RegisterSelf:
37 | return "RegisterSelf";
38 | case Type::SetSelfId:
39 | return "SetSelfId";
40 | case Type::AddPeer:
41 | return "AddPeer";
42 | case Type::RemovePeer:
43 | return "RemovePeer";
44 | case Type::CombatEvent:
45 | return "CombatEvent";
46 | default:
47 | return "";
48 | };
49 | }
50 | }; // anonymous namespace
51 |
52 | ServerStatistics::ServerStatistics(evtc_rpc_server& pParent)
53 | : PrometheusRegistry(std::make_shared())
54 | , mParent(pParent)
55 | {
56 | auto& call_data = prometheus::BuildCounter()
57 | .Name("evtc_rpc_server_call_data")
58 | .Register(*PrometheusRegistry);
59 | auto& message_type = prometheus::BuildCounter()
60 | .Name("evtc_rpc_server_message_type")
61 | .Register(*PrometheusRegistry);
62 |
63 | for (size_t i = 0; i < CallData.size(); i++)
64 | {
65 | CallData[i] = &call_data.Add({{"type", CallDataTypeToString(static_cast(i))}});
66 | }
67 |
68 | for (size_t i = 0; i < MessageTypeReceive.size(); i++)
69 | {
70 | MessageTypeReceive[i] = &message_type.Add({
71 | {"type", EvtcRpcMessageTypeToString(static_cast(i))},
72 | {"direction", "receive"}});
73 | }
74 |
75 | for (size_t i = 0; i < MessageTypeTransmit.size(); i++)
76 | {
77 | MessageTypeTransmit[i] = &message_type.Add({
78 | {"type", EvtcRpcMessageTypeToString(static_cast(i))},
79 | {"direction", "transmit"}});
80 | }
81 | }
82 |
83 | std::vector ServerStatistics::Collect() const
84 | {
85 | using namespace std::chrono;
86 |
87 | LogI("ServerStatistics::Collect!");
88 |
89 | auto data = mParent.GetStatistics();
90 | int64_t now = duration_cast(system_clock::now().time_since_epoch()).count();
91 |
92 | std::vector result;
93 | {
94 | auto& family = result.emplace_back();
95 | family.name = "evtc_rpc_server_registered_players";
96 | family.help = "";
97 | family.type = prometheus::MetricType::Gauge;
98 |
99 | auto& metric = family.metric.emplace_back();
100 | metric.gauge.value = static_cast(data.RegisteredPlayers);
101 | metric.timestamp_ms = now;
102 | }
103 |
104 | {
105 | auto& family = result.emplace_back();
106 | family.name = "evtc_rpc_server_known_peers";
107 | family.help = "";
108 | family.type = prometheus::MetricType::Gauge;
109 |
110 | auto& metric = family.metric.emplace_back();
111 | metric.gauge.value = static_cast(data.KnownPeers);
112 | metric.timestamp_ms = now;
113 | }
114 |
115 | {
116 | auto& family = result.emplace_back();
117 | family.name = "evtc_rpc_server_registered_peers";
118 | family.help = "";
119 | family.type = prometheus::MetricType::Gauge;
120 |
121 | auto& metric = family.metric.emplace_back();
122 | metric.gauge.value = static_cast(data.RegisteredPeers);
123 | metric.timestamp_ms = now;
124 | }
125 |
126 |
127 | return result;
128 | }
129 |
--------------------------------------------------------------------------------
/networking/ServerStatistics.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "evtc_rpc_messages.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | enum class CallDataType : uint32_t
11 | {
12 | Connect,
13 | Finish,
14 | ReadMessage,
15 | WriteEvent,
16 | Disconnect,
17 | WakeUp, // Sent internally only
18 | Max,
19 | };
20 |
21 | struct ServerStatisticsSample
22 | {
23 | size_t RegisteredPlayers;
24 | size_t RegisteredPeers;
25 | size_t KnownPeers;
26 | };
27 |
28 | class evtc_rpc_server;
29 | class ServerStatistics final : public prometheus::Collectable
30 | {
31 | public:
32 | ServerStatistics(evtc_rpc_server& pParent);
33 |
34 | std::vector Collect() const;
35 |
36 | std::array(CallDataType::Max)> CallData = {};
37 | std::array(evtc_rpc::messages::Type::Max)> MessageTypeReceive = {};
38 | std::array(evtc_rpc::messages::Type::Max)> MessageTypeTransmit = {};
39 | std::shared_ptr PrometheusRegistry;
40 |
41 | private:
42 | evtc_rpc_server& mParent; // Works around the fact that prometheus is trying to force usage of std::shared_ptr - very weird interface tbh
43 | };
44 |
45 |
--------------------------------------------------------------------------------
/networking/build_proto.py:
--------------------------------------------------------------------------------
1 | # Doing builds with an intermediate python file means the custom build step looks a bit cleaner, and we can also
2 | # provide visual studio parseable errors so that they show up in the error list (instead of having to scroll through
3 | # the build output)
4 | import re, subprocess, sys
5 |
6 | triplet = sys.argv[1]
7 | protoc_path = r"..\vcpkg_installed\{}\tools\protobuf\protoc.exe".format(triplet)
8 | grpc_plugin_path = r"..\vcpkg_installed\{}\tools\grpc\grpc_cpp_plugin.exe".format(triplet)
9 | filename = sys.argv[2]
10 | output_path = sys.argv[3]
11 |
12 | args = [protoc_path, "--cpp_out={}".format(output_path), "--grpc_out={}".format(output_path), "--plugin=protoc-gen-grpc={}".format(grpc_plugin_path), filename]
13 | #print(" ".join(args), file=sys.stderr)
14 | result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
15 |
16 | for line in result.stderr.splitlines():
17 | match = re.match(r"(.*?):(.*?):(.*?):(.*)", line)
18 | if match is not None:
19 | print("{}({},{}) : error PROTO : {}".format(match.group(1), match.group(2), match.group(3), match.group(4)), file=sys.stderr)
20 |
21 | exit(result.returncode)
22 |
--------------------------------------------------------------------------------
/networking/evtc_rpc.proto:
--------------------------------------------------------------------------------
1 |
2 | syntax = "proto3";
3 | package evtc_rpc;
4 |
5 | // Interface exported by the server.
6 | service evtc_rpc
7 | {
8 | // Called once at the start of the connection. Returns a client id which should be attached in
9 | // the initial metadata of all subsequent requests.
10 | rpc Connect(stream Message) returns (stream Message) {}
11 | }
12 |
13 | message Message
14 | {
15 | bytes blob = 1;
16 | }
17 |
--------------------------------------------------------------------------------
/networking/evtc_rpc_messages.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #pragma pack(push, 1)
6 | namespace evtc_rpc
7 | {
8 | namespace messages
9 | {
10 | enum class Type : uint32_t
11 | {
12 | Invalid = 0,
13 | RegisterSelf = 1,
14 | SetSelfId = 2,
15 | AddPeer = 3,
16 | RemovePeer = 4,
17 | CombatEvent = 5,
18 | Max
19 | };
20 |
21 | struct Header
22 | {
23 | uint32_t MessageVersion;
24 | Type MessageType;
25 | };
26 | static_assert(sizeof(Header) == 8, "");
27 |
28 | struct RegisterSelf
29 | {
30 | uint16_t SelfId;
31 | uint8_t SelfAccountNameLength;
32 | //char SelfAccountName[];
33 | };
34 | static_assert(sizeof(RegisterSelf) == 3, "");
35 |
36 | struct SetSelfId
37 | {
38 | uint16_t SelfId;
39 | };
40 | static_assert(sizeof(SetSelfId) == 2, "");
41 |
42 | struct AddPeer
43 | {
44 | uint16_t PeerId;
45 | uint8_t PeerAccountNameLength;
46 | // char PeerAccountName[];
47 | };
48 | static_assert(sizeof(AddPeer) == 3, "");
49 |
50 | struct RemovePeer
51 | {
52 | uint16_t PeerId;
53 | };
54 | static_assert(sizeof(RemovePeer) == 2, "");
55 |
56 | /*
57 | struct CombatEvent
58 | {
59 | cbtevent Event;
60 |
61 | uintptr_t SourceAgentId;
62 | uintptr_t DestinationAgentId;
63 |
64 | Prof SourceAgentProfession;
65 | Prof DestinationAgentProfession;
66 |
67 | uint32_t SourceAgentElite;
68 | uint32_t DestinationAgentElite;
69 |
70 | uint32_t SourceAgentSelf;
71 | uint32_t DestinationAgentSelf;
72 |
73 | uint16_t SourceAgentTeam;
74 | uint16_t DestinationAgentTeam;
75 |
76 | uint8_t SourceAgentNameLength; // UINT8_MAX => SourceAgentName is nullptr
77 | uint8_t DestinationAgentNameLength; // UINT8_MAX => DestinationAgentName is nullptr
78 |
79 | // char SourceAgentName[];
80 | // char DestinationAgentName[];
81 | };
82 | static_assert(sizeof(CombatEvent) == 110, "");
83 | */
84 |
85 | struct CombatEvent
86 | {
87 | cbtevent Event;
88 | uint16_t SenderInstanceId; // 0 when sent from client
89 | };
90 | static_assert(sizeof(CombatEvent) == 66, "");
91 |
92 | };
93 | };
94 | #pragma pack(pop)
--------------------------------------------------------------------------------
/networking/networking.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 |
29 |
30 | Header Files
31 |
32 |
33 | Header Files
34 |
35 |
36 | Header Files
37 |
38 |
39 | Header Files
40 |
41 |
42 |
43 |
44 | Resource Files
45 |
46 |
47 |
48 |
49 | Resource Files
50 |
51 |
52 |
--------------------------------------------------------------------------------
/networking/networking.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
--------------------------------------------------------------------------------
/release.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import datetime
3 | import os
4 | import re
5 | import shutil
6 | import string
7 | import subprocess
8 | from typing import List
9 |
10 | MSBUILD_PATH = r"C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\msbuild.exe"
11 | SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
12 | RELEASES_PATH = os.path.join(SCRIPT_PATH, "..", "arcdps_personal_stats_releases")
13 | BUILD_PATH = os.path.join(SCRIPT_PATH, "x64") # Gets cleaned automatically, be careful what path you put in
14 | BUILD_ALL_PROJECT = "arcdps_personal_stats_build_all.proj"
15 | ARCHVING_TARGETS = ["arcdps_healing_stats.dll", "arcdps_healing_stats.pdb"]
16 | TEST_BINARY_NAME = "test.exe"
17 |
18 |
19 | CONFIGURATIONS = ["Debug", "Release"]
20 | START = datetime.datetime.now()
21 |
22 | def Progress(pStatus: str):
23 | time_diff = datetime.datetime.now() - START
24 | print("{:03}.{:06} {}".format(int(time_diff.total_seconds()), time_diff.microseconds, pStatus))
25 |
26 | def ExtractInternalVersion(pVersionString: str) -> List[int]:
27 | result: List[int] = []
28 | for token_raw in pVersionString.split("."):
29 | token = "".join([char for char in token_raw if char in string.digits])
30 | result.append(int(token))
31 | assert len(result) == 3, "Version must include 3 tokens"
32 | Progress("Parsed version")
33 | return result
34 |
35 | def ChangeResourceVersion(pVersion: List[int]) -> None:
36 | with open(os.path.join(SCRIPT_PATH, "arcdps_personal_stats.rc"), "r+") as file:
37 | fulldata = file.read()
38 |
39 | fulldata, replace_count = re.subn(
40 | r" FILEVERSION \d+,\d+,\d+,\d+",
41 | r" FILEVERSION {},{},{},1".format(pVersion[0], pVersion[1], pVersion[2]),
42 | fulldata)
43 | assert replace_count == 1
44 |
45 | fulldata, replace_count = re.subn(
46 | r" PRODUCTVERSION \d+,\d+,\d+,\d+",
47 | r" PRODUCTVERSION {},{},{},1".format(pVersion[0], pVersion[1], pVersion[2]),
48 | fulldata)
49 | assert replace_count == 1
50 |
51 | fulldata, replace_count = re.subn(
52 | r' VALUE "FileVersion", "\d+.\d+.\d+.\d+"',
53 | r' VALUE "FileVersion", "{}.{}.{}.1"'.format(pVersion[0], pVersion[1], pVersion[2]),
54 | fulldata)
55 | assert replace_count == 1
56 |
57 | fulldata, replace_count = re.subn(
58 | r' VALUE "ProductVersion", "\d+.\d+.\d+.\d+"',
59 | r' VALUE "ProductVersion", "{}.{}.{}.1"'.format(pVersion[0], pVersion[1], pVersion[2]),
60 | fulldata)
61 | assert replace_count == 1
62 |
63 | file.seek(0)
64 | file.write(fulldata)
65 | file.truncate()
66 | Progress("Changed version in resource file")
67 |
68 | # Returns an absolute path to the newly created directory
69 | def CreateReleaseDirectory(pVersionString: str) -> str:
70 | path = os.path.join(RELEASES_PATH, pVersionString)
71 | os.mkdir(path)
72 | return path
73 |
74 | def Build(pReleaseDirectory: str, pRebuild: bool):
75 | if pRebuild == True:
76 | Progress("Cleaning build")
77 | try:
78 | shutil.rmtree(BUILD_PATH)
79 | except FileNotFoundError:
80 | pass
81 |
82 | Progress("Starting build")
83 | build_output = subprocess.run(
84 | [MSBUILD_PATH, os.path.join(SCRIPT_PATH, BUILD_ALL_PROJECT), "-maxCpuCount", "-verbosity:diagnostic", "-consoleLoggerParameters:PerformanceSummary"],
85 | capture_output=True)
86 | Progress("Finished build")
87 |
88 | os.makedirs(os.path.join(pReleaseDirectory, "BuildLogs"), exist_ok=True)
89 | with open(os.path.join(pReleaseDirectory, "BuildLogs", "release_build_stdout.log"), "wb") as file:
90 | file.write(build_output.stdout)
91 | with open(os.path.join(pReleaseDirectory, "BuildLogs", "release_build_stderr.log"), "wb") as file:
92 | file.write(build_output.stderr)
93 |
94 | if (len(build_output.stderr) > 0):
95 | print(build_output.stderr.decode("utf8"))
96 | assert build_output.returncode == 0, "Build failed"
97 |
98 | Progress("Finished saving build logs")
99 |
100 | def SetupTestDirectory(pConfiguration: str):
101 | shutil.copytree(
102 | os.path.join(SCRIPT_PATH, "test", "xevtc_logs"),
103 | os.path.join(BUILD_PATH, pConfiguration, "xevtc_logs"),
104 | dirs_exist_ok=True
105 | )
106 | Progress("Setup test directory for {}".format(pConfiguration))
107 |
108 | async def _RunTests(pReleaseDirectory: str, pConfiguration: str):
109 | process = await asyncio.create_subprocess_exec(
110 | os.path.join(BUILD_PATH, pConfiguration, TEST_BINARY_NAME),
111 | cwd=os.path.join(BUILD_PATH, pConfiguration),
112 | stdout=asyncio.subprocess.PIPE,
113 | stderr=asyncio.subprocess.PIPE)
114 | Progress("Started tests for {}".format(pConfiguration))
115 | stdout, stderr = await process.communicate()
116 | Progress("Finished tests for {}".format(pConfiguration))
117 |
118 | with open(os.path.join(pReleaseDirectory, "TestLogs", "{}_tests_stdout.log".format(pConfiguration)), "wb") as file:
119 | file.write(stdout)
120 | with open(os.path.join(pReleaseDirectory, "TestLogs", "{}_tests_stderr.log".format(pConfiguration)), "wb") as file:
121 | file.write(stderr)
122 |
123 | Progress("Finished saving test logs for {}".format(pConfiguration))
124 | assert process.returncode == 0, "Tests failed for {}".format(pConfiguration)
125 |
126 | async def Test(pReleaseDirectory: str):
127 | os.makedirs(os.path.join(pReleaseDirectory, "TestLogs"), exist_ok=True)
128 |
129 | for configuration in CONFIGURATIONS:
130 | SetupTestDirectory(configuration)
131 | await _RunTests(pReleaseDirectory, configuration)
132 |
133 | # Can't run in parallel since the tests bind to ports and thus interfere with eachother
134 | #await asyncio.gather(*[_RunTests(pReleaseDirectory, configuration) for configuration in CONFIGURATIONS])
135 |
136 | def Archive(pReleaseDirectory: str):
137 | Progress("Archiving binaries")
138 | for configuration in CONFIGURATIONS:
139 | os.mkdir(os.path.join(pReleaseDirectory, configuration))
140 | for target in ARCHVING_TARGETS:
141 | shutil.copy2(
142 | os.path.join(BUILD_PATH, configuration, target),
143 | os.path.join(pReleaseDirectory, configuration, target))
144 |
145 | Progress("Archiving complete")
146 |
147 | def Do_Release(pVersionString: str):
148 | version_internal = ExtractInternalVersion(pVersionString)
149 | release_directory = CreateReleaseDirectory(pVersionString)
150 | ChangeResourceVersion(version_internal)
151 | Build(release_directory, True)
152 | asyncio.run(Test(release_directory))
153 | Archive(release_directory)
154 | Progress("Release done")
155 |
156 | def Do_Test():
157 | Build(BUILD_PATH, False)
158 | asyncio.run(Test(BUILD_PATH))
159 | Progress("Do_Test done")
160 |
161 | #Do_Test()
162 | Do_Release("v2.15.rc1")
163 |
--------------------------------------------------------------------------------
/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by arcdps_personal_stats.rc
4 | //
5 | #define IDR_ROOT_CERTIFICATES 101
6 | #define IDB_PNG_SPEC_GUARDIAN 102
7 | #define IDB_PNG_SPEC_WARRIOR 103
8 | #define IDB_PNG_SPEC_ENGINEER 104
9 | #define IDB_PNG_SPEC_RANGER 105
10 | #define IDB_PNG_SPEC_THIEF 106
11 | #define IDB_PNG_SPEC_ELEMENTALIST 107
12 | #define IDB_PNG_SPEC_MESMER 108
13 | #define IDB_PNG_SPEC_NECROMANCER 109
14 | #define IDB_PNG_SPEC_REVENANT 110
15 | #define IDB_PNG_SPEC_DRAGONHUNTER 111
16 | #define IDB_PNG_SPEC_FIREBRAND 112
17 | #define IDB_PNG_SPEC_WILLBENDER 113
18 | #define IDB_PNG_SPEC_BERSERKER 114
19 | #define IDB_PNG_SPEC_SPELLBREAKER 115
20 | #define IDB_PNG_SPEC_BLADESWORN 116
21 | #define IDB_PNG_SPEC_SCRAPPER 117
22 | #define IDB_PNG_SPEC_HOLOSMITH 118
23 | #define IDB_PNG_SPEC_MECHANIST 119
24 | #define IDB_PNG_SPEC_DRUID 120
25 | #define IDB_PNG_SPEC_SOULBEAST 121
26 | #define IDB_PNG_SPEC_UNTAMED 122
27 | #define IDB_PNG_SPEC_DAREDEVIL 123
28 | #define IDB_PNG_SPEC_DEADEYE 124
29 | #define IDB_PNG_SPEC_SPECTER 125
30 | #define IDB_PNG_SPEC_TEMPEST 126
31 | #define IDB_PNG_SPEC_WEAVER 127
32 | #define IDB_PNG_SPEC_CATALYST 128
33 | #define IDB_PNG_SPEC_CHRONOMANCER 129
34 | #define IDB_PNG_SPEC_MIRAGE 130
35 | #define IDB_PNG_SPEC_VIRTUOSO 131
36 | #define IDB_PNG_SPEC_REAPER 132
37 | #define IDB_PNG_SPEC_SCOURGE 133
38 | #define IDB_PNG_SPEC_HARBINGER 134
39 | #define IDB_PNG_SPEC_HERALD 135
40 | #define IDB_PNG_SPEC_RENEGADE 136
41 | #define IDB_PNG_SPEC_VINDICATOR 137
42 | #define IDB_PNG_SPEC_NONE 138
43 | #define IDB_PNG_SPEC_LUMINARY 139
44 | #define IDB_PNG_SPEC_PARAGON 140
45 | #define IDB_PNG_SPEC_AMALGAM 141
46 | #define IDB_PNG_SPEC_GALESHOT 142
47 | #define IDB_PNG_SPEC_ANTIQUARY 143
48 | #define IDB_PNG_SPEC_EVOKER 144
49 | #define IDB_PNG_SPEC_TROUBADOUR 145
50 | #define IDB_PNG_SPEC_RITUALIST 146
51 | #define IDB_PNG_SPEC_CONDUIT 147
52 |
53 | // Next default values for new objects
54 | //
55 | #ifdef APSTUDIO_INVOKED
56 | #ifndef APSTUDIO_READONLY_SYMBOLS
57 | #define _APS_NEXT_RESOURCE_VALUE 148
58 | #define _APS_NEXT_COMMAND_VALUE 40001
59 | #define _APS_NEXT_CONTROL_VALUE 1001
60 | #define _APS_NEXT_SYMED_VALUE 101
61 | #endif
62 | #endif
63 |
--------------------------------------------------------------------------------
/simpleini/LICENCE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2006-2013 Brodie Thiesfield
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/AddonVersion.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #define HEALING_STATS_ADDON_SIGNATURE 0x9c9b3c99U
6 | #define HEALING_STATS_EVTC_REVISION 2U
7 | #define LEGACY_INI_CONFIG_PATH "addons\\arcdps\\arcdps_healing_stats.ini"
8 | #define JSON_CONFIG_PATH "addons\\arcdps\\arcdps_healing_stats.json"
9 |
10 | #define VERSION_EVENT_SIGNATURE 0x00000000U
11 | struct EvtcVersionHeader
12 | {
13 | uint32_t Signature;
14 | uint32_t EvtcRevision : 24;
15 | uint32_t VersionStringLength : 8;
16 | };
17 |
18 | enum HealingEventFlags : uint8_t
19 | {
20 | // The target of the event is downed. Only gets set for buff damage/healing events since arcdps overrides pad61, for
21 | // direct damage/healing, look at the lower bits of is_offcycle instead (as documented in arcdps evtc documentation)
22 | HealingEventFlags_TargetIsDowned = 1 << 5,
23 | // Signifies that the agent identified by dst_agent/dst_instid generated the event. This lets an event parser figure
24 | // out which agents are sending events, and as a result also for which players the healing data is complete
25 | HealingEventFlags_EventCameFromDestination = 1 << 6,
26 | // Signifies that the agent identified by src_agent/src_instid generated the event. This lets an event parser figure
27 | // out which agents are sending events, and as a result also for which players the healing data is complete
28 | HealingEventFlags_EventCameFromSource = 1 << 7
29 | };
30 |
--------------------------------------------------------------------------------
/src/AgentTable.cpp:
--------------------------------------------------------------------------------
1 | #include "AgentTable.h"
2 | #include "Log.h"
3 |
4 | #include
5 |
6 | HealedAgent::HealedAgent(uint16_t pInstanceId, const char* pAgentName, const char* pAccountName, uint16_t pSubgroup, bool pIsMinion, bool pIsPlayer, Prof pProfession, uint32_t pElite)
7 | : InstanceId{pInstanceId}
8 | , Name{pAgentName}
9 | , AccountName{pAccountName != nullptr ? pAccountName : ""}
10 | , Subgroup{pSubgroup}
11 | , IsMinion{pIsMinion}
12 | , IsPlayer{pIsPlayer}
13 | , Profession{pProfession}
14 | , Elite{pElite}
15 | {
16 | }
17 |
18 | HealedAgent::HealedAgent(const char* pAgentName)
19 | : InstanceId{0}
20 | , Name{pAgentName}
21 | , AccountName{}
22 | , Subgroup{0}
23 | , IsMinion{false}
24 | , IsPlayer{false}
25 | , Profession{Prof::PROF_UNKNOWN}
26 | , Elite{0xFFFFFFFF}
27 | {
28 | }
29 |
30 | HealedAgent::HealedAgent(std::string&& pAgentName)
31 | : InstanceId{0}
32 | , Name{std::move(pAgentName)}
33 | , AccountName{}
34 | , Subgroup{0}
35 | , IsMinion{false}
36 | , IsPlayer{false}
37 | , Profession{Prof::PROF_UNKNOWN}
38 | , Elite{0xFFFFFFFF}
39 | {
40 | }
41 |
42 | bool HealedAgent::operator==(const HealedAgent& pRight) const
43 | {
44 | return std::tie(InstanceId, Name, AccountName, Subgroup, IsMinion, IsPlayer, Profession, Elite) == std::tie(pRight.InstanceId, pRight.Name, pRight.AccountName, pRight.Subgroup, pRight.IsMinion, pRight.IsPlayer, pRight.Profession, pRight.Elite);
45 | }
46 |
47 | bool HealedAgent::operator!=(const HealedAgent& pRight) const
48 | {
49 | return (*this == pRight) == false;
50 | }
51 |
52 | void AgentTable::AddAgent(uintptr_t pUniqueId, uint16_t pInstanceId, const char* pAgentName, const char* pAccountName, std::optional pSubgroup, std::optional pIsMinion, std::optional pIsPlayer, Prof pProfession, uint32_t pElite)
53 | {
54 | assert(pAgentName != nullptr);
55 | LOG("Inserting new agent %llu %hu %s %hu %s %s", pUniqueId, pInstanceId, pAgentName, pSubgroup.value_or(0), BOOL_STR(pIsMinion.value_or(false)), BOOL_STR(pIsPlayer.value_or(false)));
56 |
57 | std::lock_guard lock(mLock);
58 |
59 | auto [agent, agentInserted] = mAgents.try_emplace(pUniqueId, pInstanceId, pAgentName, pAccountName, pSubgroup.value_or(0), pIsMinion.value_or(false), pIsPlayer.value_or(false), pProfession, pElite);
60 | if (agentInserted == false)
61 | {
62 | if ((strcmp(agent->second.Name.c_str(), pAgentName) != 0)
63 | || (pAccountName != nullptr && strcmp(agent->second.AccountName.c_str(), pAccountName) != 0)
64 | || (agent->second.InstanceId != pInstanceId)
65 | || (pSubgroup.has_value() && agent->second.Subgroup != *pSubgroup)
66 | || (pIsMinion.has_value() && agent->second.IsMinion != *pIsMinion)
67 | || (pIsPlayer.has_value() && agent->second.IsPlayer != *pIsPlayer)
68 | || (agent->second.Profession != pProfession)
69 | || (agent->second.Elite != pElite))
70 | {
71 | LOG("Unique id %llu already exists - replacing existing entry %hu %s %hu %s %s",
72 | pUniqueId, agent->second.InstanceId, agent->second.Name.c_str(), agent->second.Subgroup, BOOL_STR(agent->second.IsMinion), BOOL_STR(agent->second.IsPlayer));
73 |
74 | agent->second = HealedAgent{
75 | pInstanceId,
76 | pAgentName,
77 | pAccountName != nullptr ? pAccountName : agent->second.AccountName.c_str(),
78 | pSubgroup.value_or(agent->second.Subgroup),
79 | pIsMinion.value_or(agent->second.IsMinion),
80 | pIsPlayer.value_or(agent->second.IsPlayer),
81 | pProfession,
82 | pElite};
83 | }
84 | }
85 |
86 | auto [iter, instidInserted] = mInstanceIds.try_emplace(pInstanceId, agent);
87 | if (instidInserted == false && iter->second != agent)
88 | {
89 | LOG("Instance id %hu already exists - replacing existing entry %llu %s %hu %s %s",
90 | pInstanceId, iter->second->first, iter->second->second.Name.c_str(), iter->second->second.Subgroup, BOOL_STR(iter->second->second.IsMinion), BOOL_STR(iter->second->second.IsPlayer));
91 | iter->second = agent;
92 | }
93 | }
94 |
95 | std::optional AgentTable::GetUniqueId(uint16_t pInstanceId, bool pAllowNonPlayer)
96 | {
97 | std::lock_guard lock(mLock);
98 |
99 | auto iter = mInstanceIds.find(pInstanceId);
100 | if (iter == mInstanceIds.end())
101 | {
102 | LOG("Couldn't find instance id %hu", pInstanceId);
103 | return std::nullopt;
104 | }
105 |
106 | if (pAllowNonPlayer == false && iter->second->second.IsPlayer == false)
107 | {
108 | LOG("Mapped %hu to %llu but it is not a player (%hu %s %hu %s %s)", pInstanceId, iter->second->first,
109 | iter->second->second.InstanceId, iter->second->second.Name.c_str(), iter->second->second.Subgroup, BOOL_STR(iter->second->second.IsMinion), BOOL_STR(iter->second->second.IsPlayer));
110 | return std::nullopt;
111 | }
112 |
113 | LOG("Mapping %hu to %llu", pInstanceId, iter->second->first);
114 | return iter->second->first;
115 | }
116 |
117 | std::optional AgentTable::GetName(uintptr_t pUniqueId)
118 | {
119 | std::lock_guard lock(mLock);
120 |
121 | auto iter = mAgents.find(pUniqueId);
122 | if (iter == mAgents.end())
123 | {
124 | LOG("Couldn't find unique id %llu", pUniqueId);
125 | return std::nullopt;
126 | }
127 |
128 | DEBUGLOG("Mapping %llu to %s", pUniqueId, iter->second.Name.c_str());
129 | return iter->second.Name;
130 | }
131 |
132 | std::optional AgentTable::GetAgentData(uintptr_t pUniqueId)
133 | {
134 | std::lock_guard lock(mLock);
135 |
136 | auto iter = mAgents.find(pUniqueId);
137 | if (iter == mAgents.end())
138 | {
139 | LogD("Couldn't find unique id {}", pUniqueId);
140 | return std::nullopt;
141 | }
142 |
143 | LogT("Mapping {} to {}", pUniqueId, iter->second.Name.c_str());
144 | return iter->second;
145 | }
146 |
147 | std::map AgentTable::GetState()
148 | {
149 | std::map result;
150 | {
151 | std::lock_guard lock(mLock);
152 | result = mAgents;
153 | }
154 |
155 | return result;
156 | }
157 |
--------------------------------------------------------------------------------
/src/AgentTable.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include