-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Hello! I'm a newbee in C++ programming about ra2yr. Thanks for your effort for creating "ra2yrcpp" and "pyra2yr" projects.
TL;DR
The "libfmt" library does not work properly on my PC. Disabling all logging statements in "libra2yrcpp.dll" or writing logs to a file solves the problem, regardless of whether the library is debug or release build.
Environment
- OS: Windows 11 Pro 24H2
- Game: YR 1.001
- Spawner: CI build on CnCNet/yr-patches@368ca11
The Crash
Recently I tried to launch the game with "libra2yrcpp.dll" but failed. The game successfully loaded. I was able to see my MCV on the starting location and the money is 143. Then, the game exited without telling me anything.
The "client.log" file of CnCNet:
...
... Launch executable: ...\gamemd-spawn.ra2yr.exe
... Launch arguments: -SPAWN
... GameProcessLogic: Process started.
... Waiting for qres.dat or gamemd-spawn.ra2yr.exe to exit.
...
... GameProcessLogic: Process exited.
... GameProcessExited: Parsing statistics.
...
Looks normally.
There were some logs in Windows Event Viewer, but they looked useless to me...
I've tried all "hardened/non-hardened" and "debug/release" spawners but still didn't work.
After some searching, I downloaded IDA Pro and WinDbg and had found something meaningful.
The Call Stack
I've built the debug DLL by CMAKE_BUILD_TYPE=Debug ./scripts/tools.sh docker-build. I just hoped that I can see more information in the debugger.
I was using the debug build spawner because I needed to pause the game during its loading ("Quick edit mode" 1).
My steps were as follows:
- Launched the game by CnCNet client. Left-clicked the CLI dialog of the spawner to pause the loading.
- Launched WinDbg. Attached to the game process and entered
gto continue the execution. - Right-clicked that CLI dialog to resume the loading.
- WinDbg breaked the process. Entered
kto see the call stack.
> k
# ChildEBP RetAddr
00 00e0c390 774829f4 ntdll!NtTerminateProcess+0xc
01 00e0c468 75ec9fa2 ntdll!RtlExitUserProcess+0x184
02 00e0c47c 76338017 KERNEL32!ExitProcessImplementation+0x12
03 00e0c488 763385bf msvcrt!__crtExitProcess+0x17
04 00e0c4cc 76337fa1 msvcrt!doexit+0x135
05 00e0c4e0 7632d488 msvcrt!_exit+0x11
06 00e0c810 6f1a816c msvcrt!abort+0xe8
WARNING: Stack unwind information not available. Following frames may be wrong.
07 00e0c8e0 6ef04c6b libra2yrcpp!ExeRun+0x3c93f3
08 00e0cd20 6ef04d6e libra2yrcpp!ExeRun+0x125ef2
09 00e0cf80 6efeca73 libra2yrcpp!ExeRun+0x125ff5
0a 00e0d0c0 6efed827 libra2yrcpp!ExeRun+0x20dcfa
0b 00e0d0f0 6ede2507 libra2yrcpp!ExeRun+0x20eaae
0c 00e0d140 6ede3b83 libra2yrcpp!ExeRun+0x378e
0d 00e0d180 6efee804 libra2yrcpp!ExeRun+0x4e0a
0e 00e0d1e0 6efeecca libra2yrcpp!ExeRun+0x20fa8b
0f 00e0d230 6eddecc0 libra2yrcpp!ExeRun+0x20ff51
10 00e0d280 031e2011 libra2yrcpp!GameLoopBegin+0x3a
11 00e0f0a8 006be066 0x31e2011
12 00e0fee8 007cd8ef gamemd_spawn_ra2yr+0x2be066
13 00e0ff74 75eb5d49 gamemd_spawn_ra2yr+0x3cd8ef
14 00e0ff84 774bbd6b KERNEL32!BaseThreadInitThunk+0x19
15 00e0ffdc 774bbcf1 ntdll!__RtlUserThreadStart+0x2b
16 00e0ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
Then, I opened IDA. I re-launched a new game and attached to it with IDA. I set a breakpoint in libra2yrcpp:
> bp libra2yrcpp!ExeRun+0x3c93f3
> bl
0 e x86 00000000`6f1a816c 0001 (0001) 0:**** libra2yrcpp!ExeRun+0x3c93f3
The code was:
.text:6F1A816C loc_6F1A816C: ; CODE XREF: __gnu_cxx::__verbose_terminate_handler(void)+DF↑j
.text:6F1A816C mov [esp+2Ch+lpmangled], edi ; Block
I continued the execution, and then successfully hit the breakpoint! I clicked the "RetAddr" of each frame to reveal the call stack:
> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00e0c8e0 6ef04c6b libra2yrcpp!ExeRun+0x3c93f3 ; call fmt::v9::detail::fwrite_fully(void const*,uint,uint,_iobuf *)
00e0cd20 6ef04d6e libra2yrcpp!ExeRun+0x125ef2 ; call fmt::v9::detail::print(_iobuf *,fmt::v9::basic_string_view<char>)
00e0cf80 6efeca73 libra2yrcpp!ExeRun+0x125ff5 ; call fmt::v9::vprint(_iobuf *,fmt::v9::basic_string_view<char>,fmt::v9::basic_format_args<fmt::v9::basic_format_context<fmt::v9::appender,char>>)
00e0d0c0 6efed827 libra2yrcpp!ExeRun+0x20dcfa ; call ra2yrcpp::logging::print_message<int>(_iobuf *,ra2yrcpp::logging::Level,char const*,char const*,char const*,int,int)
00e0d0f0 6ede2507 libra2yrcpp!ExeRun+0x20eaae ; call ra2yrcpp::logging::eerror<char const*,char const*,char const*,int,int>(char const*,char const*,char const*,int,int)
00e0d140 6ede3b83 libra2yrcpp!ExeRun+0x378e ; call ra2::TypeClassParser::parse(void)
00e0d180 6efee804 libra2yrcpp!ExeRun+0x4e0a ; call ra2::parse_AbstractTypeClasses(google::protobuf::RepeatedPtrField<ra2yrproto::ra2yr::ObjectTypeClass> *,ra2::abi::ABIGameMD *)
00e0d1e0 6efeecca libra2yrcpp!ExeRun+0x20fa8b ; call StateSave::state_to_protobuf(bool)
00e0d230 6eddecc0 libra2yrcpp!ExeRun+0x20ff51 ; call StateSave::execute(void)
00e0d280 03282011 libra2yrcpp!GameLoopBegin+0x3a
...
So...
Libfmt Broke the Game?
I wanted to see those messages:
Lines 25 to 36 in 1af2a45
| template <typename... Args> | |
| inline void print_message(FILE* fp, Level level, const char* fmt_s, | |
| const char* file, const char* func, int line, | |
| Args... args) { | |
| fmt::print( | |
| fp, "{}: [thread {} TS: {}]: {}:{}:{} {}\n", | |
| levels[static_cast<int>(level)], | |
| std::hash<std::thread::id>{}(std::this_thread::get_id()), | |
| static_cast<std::uint64_t>( | |
| std::chrono::high_resolution_clock::now().time_since_epoch().count()), | |
| file, func, line, fmt::format(fmt_s, args...)); | |
| } |
diff --git a/src/logging.hpp b/src/logging.hpp
index 43cc69f..79caec3 100644
--- a/src/logging.hpp
+++ b/src/logging.hpp
@@ -26,6 +26,7 @@ template <typename... Args>
inline void print_message(FILE* fp, Level level, const char* fmt_s,
const char* file, const char* func, int line,
Args... args) {
+ fp = fopen("libra2yrcpp.dll.stderr", "a");
fmt::print(
fp, "{}: [thread {} TS: {}]: {}:{}:{} {}\n",
levels[static_cast<int>(level)],
@@ -33,6 +34,7 @@ inline void print_message(FILE* fp, Level level, const char* fmt_s,
static_cast<std::uint64_t>(
std::chrono::high_resolution_clock::now().time_since_epoch().count()),
file, func, line, fmt::format(fmt_s, args...));
+ fclose(fp);
}
Then the game was playable!!
The log in the "stderr" file was as follows:
INFO: [thread 1753268367 TS: 1737894946797726900]: /home/user/project/src/is_context.cpp:create_hook:192 name=TunnelSendTo,target=0x7b3d6f,size_bytes=6
DEBUG: [thread 1753268367 TS: 1737894946798151100]: /home/user/project/src/hook.cpp:patch_code:13 address=0x3016000, bytes=6
DEBUG: [thread 1753268367 TS: 1737894946798327500]: /home/user/project/src/hook.cpp:JumpTo:42 Trampoline size=6
...
DEBUG: [thread 2076165573 TS: 1737894946807120300]: /home/user/project/src/command/command_manager.hpp:worker:345 Spawn worker
DEBUG: [thread 1753268367 TS: 1737894946807579700]: /home/user/project/src/websocket_server.cpp:start:210 init asio, port=14521
ERROR: [thread 1753268367 TS: 1737894951060563400]: /home/user/project/src/ra2/state_parser.cpp:parse:246 unknown TypeClass: 10
...
ERROR: [thread 1753268367 TS: 1737894951062879400]: /home/user/project/src/ra2/state_parser.cpp:parse:246 unknown TypeClass: 28
...
ERROR: [thread 1753268367 TS: 1737894951063554100]: /home/user/project/src/ra2/state_parser.cpp:parse:246 unknown TypeClass: 14
...
ERROR: [thread 1753268367 TS: 1737894951065716900]: /home/user/project/src/ra2/state_parser.cpp:parse:246 unknown TypeClass: 32
...
<too many "unknown TypeClass" lines>
...
The library indeed wanted to write to STDERR, but why it crashed? I don't know. The official docs says that fmt::print(stderr is a usage: https://fmt.dev/9.1.0/api.html#_CPPv4IDpEN3fmt5printEvPNSt4FILEE13format_stringIDp1TEDpRR1T
Message "cannot write to file: Bad file descriptor"
I had seen this error when I commented out the eprintf() in TypeClassParser::parse() without other modification, the game was also playable, but some of my commands sent by pyra2yr got errors with the "cannot write to file: Bad file descriptor" message.
Slow Start and End of the Game if fopen("stderr", "w")
If I only changed the print_message() function like this:
diff --git a/src/logging.hpp b/src/logging.hpp
index 43cc69f..45f9f7f 100644
--- a/src/logging.hpp
+++ b/src/logging.hpp
@@ -26,6 +26,8 @@ template <typename... Args>
inline void print_message(FILE* fp, Level level, const char* fmt_s,
const char* file, const char* func, int line,
Args... args) {
+ // fp = fopen("libra2yrcpp.dll.stderr", "a");
+ fp = fopen("stderr", "w");
fmt::print(
fp, "{}: [thread {} TS: {}]: {}:{}:{} {}\n",
levels[static_cast<int>(level)],
@@ -33,6 +35,7 @@ inline void print_message(FILE* fp, Level level, const char* fmt_s,
static_cast<std::uint64_t>(
std::chrono::high_resolution_clock::now().time_since_epoch().count()),
file, func, line, fmt::format(fmt_s, args...));
+ fclose(fp);
}
The game was playable, but it stuck seconds during its start and end. No log was showed in the CLI dialog of the spawner.
Footnotes
-
Windows 10 console stops running if I click in the console window - Super User | https://superuser.com/q/1442941 ↩