Compare commits

..

15 Commits

Author SHA1 Message Date
Tixx a9faac25fc Bump version to v2.8.1 2026-08-11 22:19:49 +02:00
Tixx f532e58ee0 avoid exposing game to partially written mod files during first download (#250)
This avoids mod loading failure messages caused by partially copied mod
files after initial download, the same way it is already avoided while
loading files from cache.

Addresses some instances of
https://github.com/BeamMP/BeamMP-Launcher/issues/91

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-08-02 22:22:48 +02:00
Katharine Chui 8b4fefd518 avoid exposing game to partially written mod files during first download
This avoids mod loading failure messages caused by partially
copied mod files after initial download, the same way it is
already avoided while loading files from cache.
2026-08-02 18:52:26 +02:00
Tixx 5fc8f580f4 Fix download size int casting (#244)
Fixed an int casting issue that caused weird download failures, improve
error handling and added missing return statement which unintentionally
forwarded data to the game (without the socket even being there)

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-08-02 13:54:54 +02:00
Tixx 6334a0a915 prevent GameSend send() call on invalid socket (#240)
prevents WSAENOTSOCK by not allowing a `send()` call on an
invalid/closed socket when the game client isn't connected

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-08-02 13:53:30 +02:00
Tixx 44211e7235 Fix incorrect string_view usage in log message
and I somehow forgot the semicolon
2026-08-02 13:18:59 +02:00
Tixx 901b7df809 Log debug message on GameSend connection check 2026-08-02 13:06:33 +02:00
Tixx a6e3db6b08 Merge branch 'master' into download-fix 2026-08-02 13:04:25 +02:00
Tixx 92381aa048 handle incomplete MSG_WAITALL during tcp receive (#248)
Addresses https://github.com/BeamMP/BeamMP-Launcher/issues/185 by
handling incomplete TCP recv despite MSG_WAITALL

Fixes running the launcher inside of wine as well as the rare instances
where this happens natively.

```
       MSG_WAITALL (since Linux 2.2)
              This flag requests that the operation block until the full request is satisfied.
              However, the call may still return less data than requested if a signal is caught,
              an error or disconnect occurs, or the next data to  be  re‐ceived is of a different type
              than that returned.  This flag has no effect for datagram sockets.

```

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-06-13 22:16:20 +02:00
Katharine Chui 30639abb88 enforce expected recv size during event and server info tcp recv, remove unused instance of CheckBytes function 2026-05-11 12:47:44 +02:00
Katharine Chui 23760da53b do not discard received data during RecvWaitAll when the other side closes the socket cleanly 2026-04-30 20:35:51 +02:00
Katharine Chui 809080c9aa use RecvWaitAll outside of VehicleEvent.cpp as well 2026-04-30 19:58:05 +02:00
Katharine Chui f1ec753ca3 handle incomplete MSG_WAITALL during tcp receive 2026-04-28 12:43:33 +02:00
Tixx eeca50b37e Fix download size int casting 2026-04-21 17:14:42 +02:00
galacticlemonade 2a92a08587 guard GameSend when game is not connected
prevents WSAENOTSOCK
2026-04-08 15:05:58 -06:00
6 changed files with 82 additions and 34 deletions
+2 -1
View File
@@ -42,7 +42,7 @@ extern std::string magic;
int KillSocket(uint64_t Dead); int KillSocket(uint64_t Dead);
void UUl(const std::string& R); void UUl(const std::string& R);
void UDPSend(std::string Data); void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes); bool CheckBytes(int32_t Bytes, int32_t Expected = -1);
void GameSend(std::string_view Data); void GameSend(std::string_view Data);
void SendLarge(std::string Data); void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock); std::string TCPRcv(uint64_t Sock);
@@ -56,3 +56,4 @@ void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning(); bool SecurityWarning();
void CoreSend(std::string data); void CoreSend(std::string data);
int RecvWaitAll(int sockfd, char *buf, int len);
+8 -9
View File
@@ -143,29 +143,28 @@ void GetServerInfo(std::string Data) {
const std::string buffer = ([&]() -> std::string { const std::string buffer = ([&]() -> std::string {
int32_t Header; int32_t Header;
std::vector<char> data(sizeof(Header)); std::vector<char> data(sizeof(Header));
int Temp = recv(ISock, data.data(), sizeof(Header), MSG_WAITALL); int Temp = RecvWaitAll(ISock, data.data(), sizeof(Header));
auto checkBytes = ([&](const int32_t bytes) -> bool { auto checkBytes = ([&](const int32_t bytes, const int32_t expected = -1) -> bool {
if (bytes == 0) { if (bytes == 0) {
return false; return false;
} else if (bytes < 0) { } else if (bytes < 0) {
return false; return false;
} }
if (expected != -1 && bytes != expected) {
return false;
}
return true; return true;
}); });
if (!checkBytes(Temp)) { if (!checkBytes(Temp, sizeof(Header))) {
return ""; return "";
} }
memcpy(&Header, data.data(), sizeof(Header)); memcpy(&Header, data.data(), sizeof(Header));
if (!checkBytes(Temp)) {
return "";
}
data.resize(Header, 0); data.resize(Header, 0);
Temp = recv(ISock, data.data(), Header, MSG_WAITALL); Temp = RecvWaitAll(ISock, data.data(), Header);
if (!checkBytes(Temp)) { if (!checkBytes(Temp, Header)) {
return ""; return "";
} }
return std::string(data.data(), Header); return std::string(data.data(), Header);
+5 -11
View File
@@ -50,18 +50,11 @@ int KillSocket(uint64_t Dead) {
return a; return a;
} }
bool CheckBytes(uint32_t Bytes) {
if (Bytes == 0) {
debug("(Proxy) Connection closing");
return false;
} else if (Bytes < 0) {
debug("(Proxy) send failed with error: " + std::to_string(WSAGetLastError()));
return false;
}
return true;
}
void GameSend(std::string_view Data) { void GameSend(std::string_view Data) {
if (!GConnected) {
debug("Tried to call GameSend but socket was not connected. Data: " + std::string(Data));
return;
}
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
auto ToSend = Utils::PrependHeader<std::string_view>(Data); auto ToSend = Utils::PrependHeader<std::string_view>(Data);
@@ -206,6 +199,7 @@ void ParserAsync(std::string_view Data) {
return; return;
case 'U': case 'U':
magic = Data.substr(1); magic = Data.substr(1);
return;
default: default:
break; break;
} }
+37 -6
View File
@@ -163,8 +163,13 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
int i = 0; int i = 0;
do { do {
// receive at most some MB at a time // receive at most some MB at a time
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024); uint64_t Len = std::min<uint64_t>((Size - Rcv), 1 * 1024 * 1024);
int Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL); if (Len == 0) {
error("Download size miscalculation");
break;
}
int Temp = RecvWaitAll(Sock, &File[Rcv], Len);
if (Temp == -1 || Temp == 0) { if (Temp == -1 || Temp == 0) {
debug("Recv returned: " + std::to_string(Temp)); debug("Recv returned: " + std::to_string(Temp));
if (Temp == -1) { if (Temp == -1) {
@@ -302,7 +307,7 @@ void InvalidResource(const std::string& File) {
} }
struct ModInfo { struct ModInfo {
static std::pair<bool, std::vector<ModInfo>> ParseModInfosFromPacket(const std::string& packet) { static std::optional<std::pair<bool, std::vector<ModInfo>>> ParseModInfosFromPacket(const std::string& packet) {
bool success = false; bool success = false;
std::vector<ModInfo> modInfos; std::vector<ModInfo> modInfos;
try { try {
@@ -323,6 +328,12 @@ struct ModInfo {
modInfo.Protected = entry["protected"]; modInfo.Protected = entry["protected"];
} }
if (auto fsFile = std::filesystem::path(modInfo.FileName);
!fsFile.has_filename() || fsFile.filename().string() != modInfo.FileName ||
!fsFile.filename().has_extension() || fsFile.filename().extension() != ".zip"){
return std::nullopt;
}
modInfos.push_back(modInfo); modInfos.push_back(modInfo);
success = true; success = true;
} }
@@ -578,8 +589,17 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
c = ::tolower(c); c = ::tolower(c);
} }
#endif #endif
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName;
auto tmp_name = name;
tmp_name += ".tmp";
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing); std::error_code ec;
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing, ec);
if (ec) {
error(beammp_wide("Error in copy_file during download of ") + beammp_fs_string(PathToSaveTo) + beammp_wide(": ") + Utils::ToWString(ec.message()));
break;
}
fs::rename(tmp_name, name);
UpdateModUsage(FName); UpdateModUsage(FName);
} }
WaitForConfirm(); WaitForConfirm();
@@ -602,7 +622,13 @@ void SyncResources(SOCKET Sock) {
if (Ret.starts_with("R")) { if (Ret.starts_with("R")) {
debug("This server is likely outdated, not trying to parse new mod info format"); debug("This server is likely outdated, not trying to parse new mod info format");
} else { } else {
auto [success, modInfo] = ModInfo::ParseModInfosFromPacket(Ret); auto ParsedInfo = ModInfo::ParseModInfosFromPacket(Ret);
if (!ParsedInfo.has_value()) {
error("Invalid mod info");
Terminate = true;
return;
}
auto [success, modInfo] = ParsedInfo.value();
if (success) { if (success) {
NewSyncResources(Sock, Ret, modInfo); NewSyncResources(Sock, Ret, modInfo);
@@ -738,7 +764,12 @@ void SyncResources(SOCKET Sock) {
} }
#endif #endif
fs::copy_file(PathToSaveTo, GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(FName), fs::copy_options::overwrite_existing); auto name = GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(FName);
auto tmp_name = name;
tmp_name += L".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
UpdateModUsage(FN->substr(pos)); UpdateModUsage(FN->substr(pos));
} }
WaitForConfirm(); WaitForConfirm();
+29 -6
View File
@@ -28,9 +28,9 @@ int LastPort;
std::string LastIP; std::string LastIP;
SOCKET TCPSock = -1; SOCKET TCPSock = -1;
bool CheckBytes(int32_t Bytes) { bool CheckBytes(int32_t Bytes, int32_t Expected) {
if (Bytes == 0) { if (Bytes == 0) {
debug("(TCP) Connection closing... CheckBytes(16)"); debug("(TCP) Connection closing...");
Terminate = true; Terminate = true;
return false; return false;
} else if (Bytes < 0) { } else if (Bytes < 0) {
@@ -39,6 +39,11 @@ bool CheckBytes(int32_t Bytes) {
Terminate = true; Terminate = true;
return false; return false;
} }
if (Expected != -1 && Bytes != Expected) {
debug(std::format("(TCP) Short recv detected, expected {} bytes, got {} bytes", Expected, Bytes));
Terminate = true;
return false;
}
return true; return true;
} }
void UUl(const std::string& R) { void UUl(const std::string& R) {
@@ -75,6 +80,24 @@ void TCPSend(const std::string& Data, uint64_t Sock) {
} while (Sent < Size); } while (Sent < Size);
} }
int RecvWaitAll(int sockfd, char *buf, int len) {
// handle MSG_WAITALL not actually filling the whole buffer
// happens frequently in wine, and can also happen natively when the OS pauses the execution for various reasons
int offset = 0;
while (offset < len) {
int recv_status = recv(sockfd, &buf[offset], len - offset, MSG_WAITALL);
if (recv_status == 0) {
// do not discard received data when the other side closes the socket cleanly
return offset;
}
if (recv_status == -1) {
return -1;
}
offset += recv_status;
}
return offset;
}
std::string TCPRcv(SOCKET Sock) { std::string TCPRcv(SOCKET Sock) {
if (Sock == -1) { if (Sock == -1) {
Terminate = true; Terminate = true;
@@ -84,8 +107,8 @@ std::string TCPRcv(SOCKET Sock) {
int32_t Header; int32_t Header;
int Temp; int Temp;
std::vector<char> Data(sizeof(Header)); std::vector<char> Data(sizeof(Header));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL); Temp = RecvWaitAll(Sock, Data.data(), sizeof(Header));
if (!CheckBytes(Temp)) { if (!CheckBytes(Temp, sizeof(Header))) {
UUl("Socket Closed Code 3"); UUl("Socket Closed Code 3");
return ""; return "";
} }
@@ -97,8 +120,8 @@ std::string TCPRcv(SOCKET Sock) {
} }
Data.resize(Header, 0); Data.resize(Header, 0);
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL); Temp = RecvWaitAll(Sock, Data.data(), Header);
if (!CheckBytes(Temp)) { if (!CheckBytes(Temp, Header)) {
UUl("Socket Closed Code 5"); UUl("Socket Closed Code 5");
return ""; return "";
} }
+1 -1
View File
@@ -86,7 +86,7 @@ std::string GetVer() {
return "2.8"; return "2.8";
} }
std::string GetPatch() { std::string GetPatch() {
return ".0"; return ".1";
} }
beammp_fs_string GetEP(const beammp_fs_char* P) { beammp_fs_string GetEP(const beammp_fs_char* P) {