Loading example/hldsinfo.cpp +9 −2 Original line number Diff line number Diff line #include <iostream> #include "hldsview.h" #include "exception.h" int main(int argc, char *argv[]){ if(argc < 3){ std::cerr << "Usage: " << argv[0] << " <address> <port>" << std::endl; return 1; } try{ gameinfo::HldsView hlds(argv[1], atoi(argv[2])); Loading @@ -23,7 +28,9 @@ int main(int argc, char *argv[]){ std::cout << "Players: " << hldsdata.Players << "(" << hldsdata.BotsAmount << ")" << "/" << hldsdata.MaxPlayers << std::endl; }catch(char &e){ std::cerr << e << std::endl; }catch(gameinfo::GameInfoException &e){ std::cerr << "GameInfo Error: " << e.what() << std::endl; return 1; } return 0; } src/hldsview.cpp +71 −71 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include <algorithm> #include <cstring> #include <thread> #include <sstream> #include <iomanip> #include <netplus/exception.h> Loading Loading @@ -79,17 +81,17 @@ void gameinfo::HldsView::refresh(HldsData &info) { auto start = std::chrono::steady_clock::now(); auto deadline = start + std::chrono::milliseconds(1000); auto recvWithRetry = [&](netplus::buffer& buf) -> size_t { auto recvWithRetry = [&](netplus::buffer& buf, size_t capacity) -> size_t { while (std::chrono::steady_clock::now() < deadline) { try { buf.size = capacity; // reset to full capacity before each recv return clientSocket->recvData(buf); } catch (netplus::NetException& e) { if (e.getErrorType() == netplus::NetException::Note) { // kurz warten und erneut probieren std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } throw; // echte Fehler throw; } } netplus::NetException e; Loading @@ -105,30 +107,24 @@ void gameinfo::HldsView::refresh(HldsData &info) { clientSocket->sendData(send); netplus::buffer hrecv(1400); size_t rcv = recvWithRetry(hrecv); if (rcv > 8 && (uint8_t)hrecv.data.buf[4] == 0x41) { std::memcpy(challange, hrecv.data.buf + 5, 4); } const size_t hrecv_capacity = 1400; for (;;) { if (challange[0] != 0) { size_t rcv = recvWithRetry(hrecv, hrecv_capacity); if (rcv >= 9 && (uint8_t)hrecv.data.buf[4] == 0x41) { // Challenge response: store challenge and resend with it std::memcpy(challange, hrecv.data.buf + 5, 4); std::memcpy(send.data.buf, payload_template, A2S_INFO_PAYLOAD_LEN_WITHOUT_CHALLENGE); std::memcpy(send.data.buf + A2S_INFO_PAYLOAD_CHALLENGE_OFFSET, challange, sizeof(challange)); send.size = A2S_INFO_PAYLOAD_LEN_WITH_CHALLENGE; } else { send.size = A2S_INFO_PAYLOAD_LEN_WITHOUT_CHALLENGE; } clientSocket->sendData(send); rcv = recvWithRetry(hrecv); if (rcv > 8 && (uint8_t)hrecv.data.buf[4] == 0x41) { std::memcpy(challange, hrecv.data.buf + 5, 4); continue; // erneut senden continue; } // Should be an A2S_INFO response (0x49) _parse(info, hrecv.data.buf, rcv); return; } Loading @@ -150,94 +146,98 @@ void gameinfo::HldsView::_parse(HldsData &info,const char* data, size_t len){ info.Port=sport; info.header=0x00; for(int i=0; i<len; ++i){ if(data[i]==0x49){ info.header=0x49; data+=++i; len-=i; } } // Helper: hex dump for diagnostics auto hexdump = [](const char* buf, size_t n) -> std::string { std::ostringstream oss; size_t limit = std::min(n, (size_t)64); for(size_t i = 0; i < limit; ++i) oss << std::hex << std::setfill('0') << std::setw(2) << ((unsigned int)(uint8_t)buf[i]) << ' '; if(n > limit) oss << "..."; return oss.str(); }; if(info.header!=0x49){ // A2S_INFO response format: FF FF FF FF 49 <payload...> // Minimum: 5 byte header + 1 byte protocol = 6 bytes if(len < 6 || (uint8_t)data[0] != 0xFF || (uint8_t)data[1] != 0xFF || (uint8_t)data[2] != 0xFF || (uint8_t)data[3] != 0xFF || (uint8_t)data[4] != 0x49){ GameInfoException e; e[GameInfoException::Error] << "_parse: no hlds server!"; e[GameInfoException::Error] << "_parse: no hlds server! len=" << (int)len << " hex: " << hexdump(data, len); throw e; } info.header=0x49; const char* origData = data; size_t origLen = len; data+=5; len-=5; info.protocol=data[0]; data=data+1; len-=1; for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.ServerName.assign(data,data+i); data+=++i; len-=i; break; } } for(int i=0; i<len; ++i){ // Helper: read a null-terminated string and advance auto readString = [&](std::string &dest) -> bool { for(size_t i=0; i<len; ++i){ if(data[i]=='\0'){ info.MapName.assign(data,data+i); data+=++i; dest.assign(data,data+i); ++i; data+=i; len-=i; break; return true; } } return false; }; for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.ModName.assign(data,data+i); data+=++i; len-=i; break; } if(!readString(info.ServerName) || !readString(info.MapName) || !readString(info.ModName) || !readString(info.GameName)){ GameInfoException e; e[GameInfoException::Error] << "_parse: truncated packet (strings) remaining=" << (int)len << " hex(full): " << hexdump(origData, origLen); throw e; } for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.GameName.assign(data,data+i); data+=++i; len-=i; break; } // Fixed fields: steamid(2) + players(1) + maxplayers(1) + bots(1) // + servertype(1) + env(1) + visibility(1) + vac(1) = 9 bytes if(len < 9){ GameInfoException e; e[GameInfoException::Error] << "_parse: truncated packet (fields)"; throw e; } memcpy(&info.steamid,data,sizeof(short)); data=data+sizeof(short); len-=sizeof(short); info.Players=data[0]; info.Players=(uint8_t)data[0]; data=data+1; len-=1; info.MaxPlayers=data[0]; info.MaxPlayers=(uint8_t)data[0]; data=data+1; len-=1; info.BotsAmount=data[0]; info.BotsAmount=(uint8_t)data[0]; data=data+1; len-=1; info.servertype=data[0]; info.servertype=(uint8_t)data[0]; data=data+1; len-=1; info.env=data[0]; info.env=(uint8_t)data[0]; data=data+1; len-=1; if(data[0]==1) info.pwProtected=true; else info.pwProtected=false; info.pwProtected = (data[0] == 1); data=data+1; len-=1; info.vac=data[0]; info.vac=(uint8_t)data[0]; data=data+1; len-=1; } Loading
example/hldsinfo.cpp +9 −2 Original line number Diff line number Diff line #include <iostream> #include "hldsview.h" #include "exception.h" int main(int argc, char *argv[]){ if(argc < 3){ std::cerr << "Usage: " << argv[0] << " <address> <port>" << std::endl; return 1; } try{ gameinfo::HldsView hlds(argv[1], atoi(argv[2])); Loading @@ -23,7 +28,9 @@ int main(int argc, char *argv[]){ std::cout << "Players: " << hldsdata.Players << "(" << hldsdata.BotsAmount << ")" << "/" << hldsdata.MaxPlayers << std::endl; }catch(char &e){ std::cerr << e << std::endl; }catch(gameinfo::GameInfoException &e){ std::cerr << "GameInfo Error: " << e.what() << std::endl; return 1; } return 0; }
src/hldsview.cpp +71 −71 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include <algorithm> #include <cstring> #include <thread> #include <sstream> #include <iomanip> #include <netplus/exception.h> Loading Loading @@ -79,17 +81,17 @@ void gameinfo::HldsView::refresh(HldsData &info) { auto start = std::chrono::steady_clock::now(); auto deadline = start + std::chrono::milliseconds(1000); auto recvWithRetry = [&](netplus::buffer& buf) -> size_t { auto recvWithRetry = [&](netplus::buffer& buf, size_t capacity) -> size_t { while (std::chrono::steady_clock::now() < deadline) { try { buf.size = capacity; // reset to full capacity before each recv return clientSocket->recvData(buf); } catch (netplus::NetException& e) { if (e.getErrorType() == netplus::NetException::Note) { // kurz warten und erneut probieren std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } throw; // echte Fehler throw; } } netplus::NetException e; Loading @@ -105,30 +107,24 @@ void gameinfo::HldsView::refresh(HldsData &info) { clientSocket->sendData(send); netplus::buffer hrecv(1400); size_t rcv = recvWithRetry(hrecv); if (rcv > 8 && (uint8_t)hrecv.data.buf[4] == 0x41) { std::memcpy(challange, hrecv.data.buf + 5, 4); } const size_t hrecv_capacity = 1400; for (;;) { if (challange[0] != 0) { size_t rcv = recvWithRetry(hrecv, hrecv_capacity); if (rcv >= 9 && (uint8_t)hrecv.data.buf[4] == 0x41) { // Challenge response: store challenge and resend with it std::memcpy(challange, hrecv.data.buf + 5, 4); std::memcpy(send.data.buf, payload_template, A2S_INFO_PAYLOAD_LEN_WITHOUT_CHALLENGE); std::memcpy(send.data.buf + A2S_INFO_PAYLOAD_CHALLENGE_OFFSET, challange, sizeof(challange)); send.size = A2S_INFO_PAYLOAD_LEN_WITH_CHALLENGE; } else { send.size = A2S_INFO_PAYLOAD_LEN_WITHOUT_CHALLENGE; } clientSocket->sendData(send); rcv = recvWithRetry(hrecv); if (rcv > 8 && (uint8_t)hrecv.data.buf[4] == 0x41) { std::memcpy(challange, hrecv.data.buf + 5, 4); continue; // erneut senden continue; } // Should be an A2S_INFO response (0x49) _parse(info, hrecv.data.buf, rcv); return; } Loading @@ -150,94 +146,98 @@ void gameinfo::HldsView::_parse(HldsData &info,const char* data, size_t len){ info.Port=sport; info.header=0x00; for(int i=0; i<len; ++i){ if(data[i]==0x49){ info.header=0x49; data+=++i; len-=i; } } // Helper: hex dump for diagnostics auto hexdump = [](const char* buf, size_t n) -> std::string { std::ostringstream oss; size_t limit = std::min(n, (size_t)64); for(size_t i = 0; i < limit; ++i) oss << std::hex << std::setfill('0') << std::setw(2) << ((unsigned int)(uint8_t)buf[i]) << ' '; if(n > limit) oss << "..."; return oss.str(); }; if(info.header!=0x49){ // A2S_INFO response format: FF FF FF FF 49 <payload...> // Minimum: 5 byte header + 1 byte protocol = 6 bytes if(len < 6 || (uint8_t)data[0] != 0xFF || (uint8_t)data[1] != 0xFF || (uint8_t)data[2] != 0xFF || (uint8_t)data[3] != 0xFF || (uint8_t)data[4] != 0x49){ GameInfoException e; e[GameInfoException::Error] << "_parse: no hlds server!"; e[GameInfoException::Error] << "_parse: no hlds server! len=" << (int)len << " hex: " << hexdump(data, len); throw e; } info.header=0x49; const char* origData = data; size_t origLen = len; data+=5; len-=5; info.protocol=data[0]; data=data+1; len-=1; for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.ServerName.assign(data,data+i); data+=++i; len-=i; break; } } for(int i=0; i<len; ++i){ // Helper: read a null-terminated string and advance auto readString = [&](std::string &dest) -> bool { for(size_t i=0; i<len; ++i){ if(data[i]=='\0'){ info.MapName.assign(data,data+i); data+=++i; dest.assign(data,data+i); ++i; data+=i; len-=i; break; return true; } } return false; }; for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.ModName.assign(data,data+i); data+=++i; len-=i; break; } if(!readString(info.ServerName) || !readString(info.MapName) || !readString(info.ModName) || !readString(info.GameName)){ GameInfoException e; e[GameInfoException::Error] << "_parse: truncated packet (strings) remaining=" << (int)len << " hex(full): " << hexdump(origData, origLen); throw e; } for(int i=0; i<len; ++i){ if(data[i]=='\0'){ info.GameName.assign(data,data+i); data+=++i; len-=i; break; } // Fixed fields: steamid(2) + players(1) + maxplayers(1) + bots(1) // + servertype(1) + env(1) + visibility(1) + vac(1) = 9 bytes if(len < 9){ GameInfoException e; e[GameInfoException::Error] << "_parse: truncated packet (fields)"; throw e; } memcpy(&info.steamid,data,sizeof(short)); data=data+sizeof(short); len-=sizeof(short); info.Players=data[0]; info.Players=(uint8_t)data[0]; data=data+1; len-=1; info.MaxPlayers=data[0]; info.MaxPlayers=(uint8_t)data[0]; data=data+1; len-=1; info.BotsAmount=data[0]; info.BotsAmount=(uint8_t)data[0]; data=data+1; len-=1; info.servertype=data[0]; info.servertype=(uint8_t)data[0]; data=data+1; len-=1; info.env=data[0]; info.env=(uint8_t)data[0]; data=data+1; len-=1; if(data[0]==1) info.pwProtected=true; else info.pwProtected=false; info.pwProtected = (data[0] == 1); data=data+1; len-=1; info.vac=data[0]; info.vac=(uint8_t)data[0]; data=data+1; len-=1; }