From decff8231895632e302e228648a93516c0c9399c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 3 Dec 2023 20:51:43 +0200 Subject: [PATCH 01/49] feat: add Post Scriptum support (#163) * feat: add post scriptum support * Add/Update badge --------- Co-authored-by: GitHub Action --- .github/badges/node.svg | 8 ++++---- CHANGELOG.md | 1 + GAMES.md | 13 +++++++------ crates/lib/src/games/definitions.rs | 1 + crates/lib/src/games/valve.rs | 1 + 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index d968517..df3be38 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 14.06% + + Node game coverage: 14.02% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2050d..fb66ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Games: - [Valheim](https://store.steampowered.com/app/892970/Valheim/) support. - [The Front](https://store.steampowered.com/app/2285150/The_Front/) support. - [Conan Exiles](https://store.steampowered.com/app/440900/Conan_Exiles/) support. +- [Post Scriptum](https://store.steampowered.com/app/736220/Post_Scriptum/) support. - Added a valve protocol query example. Protocols: diff --git a/GAMES.md b/GAMES.md index 5d7bad6..59ab947 100644 --- a/GAMES.md +++ b/GAMES.md @@ -65,12 +65,13 @@ Beware of the `Notes` column, as it contains information about query port offset | Valheim | VALHEIM | Valve | Query Port offset: 1. Does not respond to the A2S rules. | | The Front | THEFRONT | Valve | Responds with wrong values on `name` (gives out a SteamID instead of the server name) and `players_maximum` (always 200). | | Conan Exiles | CONANEXILES | Valve | Does not respond to the players query. | -| Darkest Hour: Europe '44-'45 | DARKESTHOUR | Unreal2 | Query port offset: 1 | -| Devastation | DEVASTATION | Unreal2 | Query port offset: 1 | -| Killing Floor | KILLINGFLOOR | Unreal2 | Query port offset: 1 | -| Red Orchestra | REDORCHESTRA | Unreal2 | Query port offset: 1 | -| Unreal Tournament 2003 | UT2003 | Unreal2 | Query port offset: 1 | -| Unreal Tournament 2004 | UT2004 | Unreal2 | Query port offset: 1 | +| Darkest Hour: Europe '44-'45 | DARKESTHOUR | Unreal2 | Query port offset: 1 | +| Devastation | DEVASTATION | Unreal2 | Query port offset: 1 | +| Killing Floor | KILLINGFLOOR | Unreal2 | Query port offset: 1 | +| Red Orchestra | REDORCHESTRA | Unreal2 | Query port offset: 1 | +| Unreal Tournament 2003 | UT2003 | Unreal2 | Query port offset: 1 | +| Unreal Tournament 2004 | UT2004 | Unreal2 | Query port offset: 1 | +| Post Scriptum | POSTSCRIPTUM | Valve | | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 7f55a65..8f4754b 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -81,6 +81,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "l4d2" => game!("Left 4 Dead 2", 27015, Protocol::Valve(Engine::new(550))), "ohd" => game!("Operation: Harsh Doorstop", 27005, Protocol::Valve(Engine::new_with_dedicated(736_590, 950_900))), "onset" => game!("Onset", 7776, Protocol::Valve(Engine::new(1_105_810))), + "postscriptum" => game!("Post Scriptum", 10037, Protocol::Valve(Engine::new(736220))), "projectzomboid" => game!("Project Zomboid", 16261, Protocol::Valve(Engine::new(108_600))), "quake1" => game!("Quake 1", 27500, Protocol::Quake(QuakeVersion::One)), "quake2" => game!("Quake 2", 27910, Protocol::Quake(QuakeVersion::Two)), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index acd612c..bfb9728 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -100,6 +100,7 @@ game_query_mod!( 27005 ); game_query_mod!(onset, "Onset", Engine::new(1_105_810), 7776); +game_query_mod!(postscriptum, "Post Scriptum", Engine::new(736220), 10037); game_query_mod!( projectzomboid, "Project Zomboid", From 45f05aec139e361e5a1966b5fdafdf8a67ce1ed0 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 3 Dec 2023 20:59:46 +0200 Subject: [PATCH 02/49] feat: add Squad support (#164) * feat: add Squad support * Add/Update badge --------- Co-authored-by: GitHub Action --- .github/badges/node.svg | 8 ++++---- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 1 + crates/lib/src/games/valve.rs | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index df3be38..79faad7 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 14.02% + + Node game coverage: 14.33% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fb66ad9..7b4a1a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Games: - [The Front](https://store.steampowered.com/app/2285150/The_Front/) support. - [Conan Exiles](https://store.steampowered.com/app/440900/Conan_Exiles/) support. - [Post Scriptum](https://store.steampowered.com/app/736220/Post_Scriptum/) support. +- [Squad](https://store.steampowered.com/app/393380/Squad/) support. - Added a valve protocol query example. Protocols: diff --git a/GAMES.md b/GAMES.md index 59ab947..e8333f8 100644 --- a/GAMES.md +++ b/GAMES.md @@ -72,6 +72,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Unreal Tournament 2003 | UT2003 | Unreal2 | Query port offset: 1 | | Unreal Tournament 2004 | UT2004 | Unreal2 | Query port offset: 1 | | Post Scriptum | POSTSCRIPTUM | Valve | | +| Squad | SQUAD | Valve | | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 8f4754b..18e033b 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -92,6 +92,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "sdtd" => game!("7 Days to Die", 26900, Protocol::Valve(Engine::new(251_570))), "sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)), "serioussam" => game!("Serious Sam", 25601, Protocol::Gamespy(GameSpyVersion::One)), + "squad" => game!("Squad", 27165, Protocol::Valve(Engine::new(393380))), "theforest" => game!("The Forest", 27016, Protocol::Valve(Engine::new(556_450))), "thefront" => game!("The Front", 27015, Protocol::Valve(Engine::new(2_285_150))), "teamfortress2" => game!("Team Fortress 2", 27015, Protocol::Valve(Engine::new(440))), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index bfb9728..a2aafe9 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -111,6 +111,7 @@ game_query_mod!(ror2, "Risk of Rain 2", Engine::new(632_360), 27016); game_query_mod!(rust, "Rust", Engine::new(252_490), 27015); game_query_mod!(sco, "Sven Co-op", Engine::new_gold_src(false), 27015); game_query_mod!(sdtd, "7 Days to Die", Engine::new(251_570), 26900); +game_query_mod!(squad, "Squad", Engine::new(393380), 27165); game_query_mod!(teamfortress2, "Team Fortress 2", Engine::new(440), 27015); game_query_mod!( tfc, From 04da29f2a6b11157524c170097db14232ef32813 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:03:27 +0200 Subject: [PATCH 03/49] Bump actions/labeler from 4 to 5 in /.github/workflows (#165) Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/labeler dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 0dda4ce..9fe17f5 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true From dd037daa048f3d8e75040552e61b168ef37e7c47 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:51:45 +0000 Subject: [PATCH 04/49] Revert "Bump actions/labeler from 4 to 5 in /.github/workflows (#165)" (#167) This reverts commit 04da29f2a6b11157524c170097db14232ef32813. --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 9fe17f5..0dda4ce 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/labeler@v5 + - uses: actions/labeler@v4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true From af8e1e9b1a8765fd095e1613a61e82185aa61546 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 10 Dec 2023 19:39:26 +0200 Subject: [PATCH 05/49] feat: add savage 2 support (#169) * feat: savage 2 support * fix: add savage 2 to definitions * chore: run rustfmt * fix: config serde use * fix: remove needless borrow * docs: add savage 2 to protocols.md --- CHANGELOG.md | 1 + GAMES.md | 1 + PROTOCOLS.md | 15 ++-- RESPONSES.md | 104 ++++++++++++----------- crates/lib/src/games/definitions.rs | 1 + crates/lib/src/games/mod.rs | 5 ++ crates/lib/src/games/savage2/mod.rs | 8 ++ crates/lib/src/games/savage2/protocol.rs | 37 ++++++++ crates/lib/src/games/savage2/types.rs | 30 +++++++ crates/lib/src/protocols/types.rs | 3 + 10 files changed, 148 insertions(+), 57 deletions(-) create mode 100644 crates/lib/src/games/savage2/mod.rs create mode 100644 crates/lib/src/games/savage2/protocol.rs create mode 100644 crates/lib/src/games/savage2/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4a1a4..82d736d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Games: - [Conan Exiles](https://store.steampowered.com/app/440900/Conan_Exiles/) support. - [Post Scriptum](https://store.steampowered.com/app/736220/Post_Scriptum/) support. - [Squad](https://store.steampowered.com/app/393380/Squad/) support. +- [Savage 2](https://savage2.net/) support. - Added a valve protocol query example. Protocols: diff --git a/GAMES.md b/GAMES.md index e8333f8..fe88259 100644 --- a/GAMES.md +++ b/GAMES.md @@ -73,6 +73,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Unreal Tournament 2004 | UT2004 | Unreal2 | Query port offset: 1 | | Post Scriptum | POSTSCRIPTUM | Valve | | | Squad | SQUAD | Valve | | +| Savage 2 | SAVAGE2 | Proprietary | | ## Planned to add support: _ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 6b1ef0c..78c49ba 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,13 +1,14 @@ A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| Name | For | Proprietary? | Documentation reference | Notes | -|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. | -| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | -| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. | -| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | | -| Unreal2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | +| Name | For | Proprietary? | Documentation reference | Notes | +|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | +| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. | +| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | | +| Unreal2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | +| Savage 2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) | | ## Planned to add support: _ diff --git a/RESPONSES.md b/RESPONSES.md index 14415fe..a9c3174 100644 --- a/RESPONSES.md +++ b/RESPONSES.md @@ -5,53 +5,57 @@ In the case that a field that performs the same function exists in the current c # Response table -| Field | Generic | GameSpy(1) | GameSpy(2) | GameSpy(3) | Minecraft(Java) | Minecraft(Bedrock) | Valve | Quake | Unreal2 | Proprietary: FFOW | Proprietary: TheShip | Proprietary: JC2MP | -|----------------------|------------------|---------------------------|---------------|---------------------------|-----------------------|--------------------|-----------------------------------|---------------------------|--------------------------------|-------------------|---------------------------|--------------------| -| name | `Option` | `String` | `String` | `String` | | `String` | `String` | `String` | `String` | `String` | `String` | `String` | -| description | `Option` | | | | `String` | | | | | `String` | | `String` | -| game_mode | `Option` | `String` | | `String` | | `Option` | `String` | | `String` | `String` | `String` | | -| game_version | `Option` | `String` | | `String` | `String` | | `String` | `String` | | `String` | `String` | `String` | -| map | `Option` | `String` | `String` | `String` | | `Option` | `String` | `String` | `String` | `String` | `String` | | -| players_maxmimum | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | -| players_online | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | -| players_bots | `Option` | | | | | | `u8` | | | | `u8` | | -| has_password | `Option` | `bool` | `bool` | `bool` | | | `bool` | | | `bool` | `bool` | `bool` | -| players_minimum | | `Option` | `Option` | `Option` | | | | | | | | | -| players | | `Vec` | `Vec` | `Vec` | `Option>` | | `Option>` | `Vec

` | `Vec` | | `Vec` | `Vec` | -| tournament | | `bool` | | `bool` | | | | | | | | | -| unused_entries | | `Hashmap` | | `HashMap` | | | | `HashMap` | | | | | -| teams | | | `Vec` | `Vec` | | | | | | | | | -| protocol_version | | | | | `i32` | `String` | `u8` | | | `u8` | `u8` | | -| server_type | | | | | `Server` | `Server` | `Server` | | | | `Server` | | -| rules | | | | | | | `Option>` | | `HashMap>` | | `HashMap` | | -| environment_type | | | | | | | `Environment` | | | `Environment` | | | -| vac_secured | | | | | | | `bool` | | | `bool` | `bool` | | -| map_title | | `Option` | | | | | | | | | | | -| admin_contact | | `Option` | | | | | | | | | | | -| admin_name | | `Option` | | | | | | | | | | | -| favicon | | | | | `Option` | | | | | | | | -| previews_chat | | | | | `Option` | | | | | | | | -| enforces_secure_chat | | | | | `Option` | | | | | | | | -| edition | | | | | | `String` | | | | | | | -| id | | | | | | `String` | | | `String` | | | | -| the_ship | | | | | | | `Option` | | | | | | -| is_mod | | | | | | | `bool` | | | | | | -| extra_data | | | | | | | `Option` | | | | | | -| mod_data | | | | | | | `Option` | | | | | | -| folder | | | | | | | `String` | | | | | | -| appid | | | | | | | `u32` | | | | | | -| active_mod | | | | | | | | | | `String` | | | -| round | | | | | | | | | | `u8` | | | -| rounds_maximum | | | | | | | | | | `u8` | | | -| time_left | | | | | | | | | | `u16` | | | -| port | | | | | | | | | `u32` | | `Option` | | -| steam_id | | | | | | | | | | | `Option` | | -| tv_port | | | | | | | | | | | `Option` | | -| tv_name | | | | | | | | | | | `Option` | | -| keywords | | | | | | | | | | | `Option` | | -| mode | | | | | | | | | | | `u8` | | -| witnesses | | | | | | | | | | | `u8` | | -| duration | | | | | | | | | | | `u8` | | -| query_port | | | | | | | | | `u32` | | | | -| ip | | | | | | | | | `String` | | | | -| mutators | | | | | | | | | `HashSet` | | | | +| Field | Generic | GameSpy(1) | GameSpy(2) | GameSpy(3) | Minecraft(Java) | Minecraft(Bedrock) | Valve | Quake | Unreal2 | Proprietary: FFOW | Proprietary: TheShip | Proprietary: JC2MP | Proprietary: Savage 2 | +|----------------------|----------|------------|------------|------------|-----------------|--------------------|---------------|-----------|------------|-------------------|----------------------|--------------------|-----------------------| +| name | `Option` | `String` | `String` | `String` | | `String` | `String` | `String` | `String` | `String` | `String` | `String` | `String` | +| description | `Option` | | | | `String` | | | | | `String` | | `String` | | +| game_mode | `Option` | `String` | | `String` | | `Option` | `String` | | `String` | `String` | `String` | | `String` | +| game_version | `Option` | `String` | | `String` | `String` | | `String` | `String` | | `String` | `String` | `String` | | +| map | `Option` | `String` | `String` | `String` | | `Option` | `String` | `String` | `String` | `String` | `String` | | `String` | +| players_maxmimum | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | `u8` | +| players_online | `u32` | `u32` | `u32` | `u32` | `u32` | `u32` | `u8` | `u8` | `u32` | `u8` | `u8` | `u32` | `u8` | +| players_bots | `Option` | | | | | | `u8` | | | | `u8` | | | +| has_password | `Option` | `bool` | `bool` | `bool` | | | `bool` | | | `bool` | `bool` | `bool` | | +| players_minimum | | `Option` | `Option` | `Option` | | | | | | | | | `u8` | +| players | | `Vec` | `Vec` | `Vec` | `Option>` | | `Option>` | `Vec ` | `Vec` | | `Vec` | `Vec` | | +| tournament | | `bool` | | `bool` | | | | | | | | | | +| unused_entries | | `Hashmap` | | `HashMap` | | | | `HashMap` | | | | | | +| teams | | | `Vec` | `Vec` | | | | | | | | | | +| protocol_version | | | | | `i32` | `String` | `u8` | | | `u8` | `u8` | | `String` | +| server_type | | | | | `Server` | `Server` | `Server` | | | | `Server` | | | +| rules | | | | | | | `Option>` | | `HashMap>` | | `HashMap` | | | +| environment_type | | | | | | | `Environment` | | | `Environment` | | | | +| vac_secured | | | | | | | `bool` | | | `bool` | `bool` | | | +| map_title | | `Option` | | | | | | | | | | | | +| admin_contact | | `Option` | | | | | | | | | | | | +| admin_name | | `Option` | | | | | | | | | | | | +| favicon | | | | | `Option` | | | | | | | | | +| previews_chat | | | | | `Option` | | | | | | | | | +| enforces_secure_chat | | | | | `Option` | | | | | | | | | +| edition | | | | | | `String` | | | | | | | | +| id | | | | | | `String` | | | `String` | | | | | +| the_ship | | | | | | | `Option` | | | | | | | +| is_mod | | | | | | | `bool` | | | | | | | +| extra_data | | | | | | | `Option` | | | | | | | +| mod_data | | | | | | | `Option` | | | | | | | +| folder | | | | | | | `String` | | | | | | | +| appid | | | | | | | `u32` | | | | | | | +| active_mod | | | | | | | | | | `String` | | | | +| round | | | | | | | | | | `u8` | | | | +| rounds_maximum | | | | | | | | | | `u8` | | | | +| time_left | | | | | | | | | | `u16` | | | | +| port | | | | | | | | | `u32` | | `Option` | | | +| steam_id | | | | | | | | | | | `Option` | | | +| tv_port | | | | | | | | | | | `Option` | | | +| tv_name | | | | | | | | | | | `Option` | | | +| keywords | | | | | | | | | | | `Option` | | | +| mode | | | | | | | | | | | `u8` | | | +| witnesses | | | | | | | | | | | `u8` | | | +| duration | | | | | | | | | | | `u8` | | | +| query_port | | | | | | | | | `u32` | | | | | +| ip | | | | | | | | | `String` | | | | | +| mutators | | | | | | | | | `HashSet` | | | | | +| next_map | | | | | | | | | | | | | `String` | +| location | | | | | | | | | | | | | `String` | +| level_minimum | | | | | | | | | | | | | `String` | +| time | | | | | | | | | | | | | `String` | diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 18e033b..e157f99 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -88,6 +88,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "q3a" => game!("Quake 3 Arena", 27960, Protocol::Quake(QuakeVersion::Three)), "ror2" => game!("Risk of Rain 2", 27016, Protocol::Valve(Engine::new(632_360))), "rust" => game!("Rust", 27015, Protocol::Valve(Engine::new(252_490))), + "savage2" => game!("Savage 2", 11235, Protocol::PROPRIETARY(ProprietaryProtocol::Savage2)), "sco" => game!("Sven Co-op", 27015, Protocol::Valve(Engine::new_gold_src(false))), "sdtd" => game!("7 Days to Die", 26900, Protocol::Valve(Engine::new(251_570))), "sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)), diff --git a/crates/lib/src/games/mod.rs b/crates/lib/src/games/mod.rs index e8e5641..add1f19 100644 --- a/crates/lib/src/games/mod.rs +++ b/crates/lib/src/games/mod.rs @@ -21,6 +21,8 @@ pub mod ffow; pub mod jc2m; /// Minecraft pub mod minecraft; +/// Savage 2 +pub mod savage2; /// The Ship pub mod theship; @@ -117,6 +119,9 @@ pub fn query_with_timeout_and_extra_settings( } Protocol::PROPRIETARY(protocol) => { match protocol { + ProprietaryProtocol::Savage2 => { + savage2::query_with_timeout(address, port, timeout_settings).map(Box::new)? + } ProprietaryProtocol::TheShip => { theship::query_with_timeout(address, port, timeout_settings).map(Box::new)? } diff --git a/crates/lib/src/games/savage2/mod.rs b/crates/lib/src/games/savage2/mod.rs new file mode 100644 index 0000000..88d57f2 --- /dev/null +++ b/crates/lib/src/games/savage2/mod.rs @@ -0,0 +1,8 @@ +/// The implementation. +/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/crates/lib/src/games/savage2/protocol.rs b/crates/lib/src/games/savage2/protocol.rs new file mode 100644 index 0000000..dbb1eb8 --- /dev/null +++ b/crates/lib/src/games/savage2/protocol.rs @@ -0,0 +1,37 @@ +use crate::buffer::{Buffer, Utf8Decoder}; +use crate::games::savage2::types::Response; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, UdpSocket}; +use crate::GDResult; +use byteorder::LittleEndian; +use std::net::{IpAddr, SocketAddr}; + +pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, None) } + +pub fn query_with_timeout( + address: &IpAddr, + port: Option, + timeout_settings: Option, +) -> GDResult { + let addr = &SocketAddr::new(*address, port.unwrap_or(11235)); + let mut socket = UdpSocket::new(addr, &timeout_settings)?; + socket.send(&[0x01])?; + let data = socket.receive(None)?; + let mut buffer = Buffer::::new(&data); + + buffer.move_cursor(12)?; + + Ok(Response { + name: buffer.read_string::(None)?, + players_online: buffer.read::()?, + players_maximum: buffer.read::()?, + time: buffer.read_string::(None)?, + map: buffer.read_string::(None)?, + next_map: buffer.read_string::(None)?, + location: buffer.read_string::(None)?, + players_minimum: buffer.read::()?, + game_mode: buffer.read_string::(None)?, + protocol_version: buffer.read_string::(None)?, + level_minimum: buffer.read::()?, + }) +} diff --git a/crates/lib/src/games/savage2/types.rs b/crates/lib/src/games/savage2/types.rs new file mode 100644 index 0000000..d72068b --- /dev/null +++ b/crates/lib/src/games/savage2/types.rs @@ -0,0 +1,30 @@ +use crate::protocols::types::CommonResponse; +use crate::protocols::GenericResponse; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct Response { + pub name: String, + pub players_online: u8, + pub players_maximum: u8, + pub players_minimum: u8, + pub time: String, + pub map: String, + pub next_map: String, + pub location: String, + pub game_mode: String, + pub protocol_version: String, + pub level_minimum: u8, +} + +impl CommonResponse for Response { + fn as_original(&self) -> GenericResponse { GenericResponse::Savage2(self) } + + fn name(&self) -> Option<&str> { Some(&self.name) } + fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) } + fn map(&self) -> Option<&str> { Some(&self.map) } + fn players_maximum(&self) -> u32 { self.players_maximum.into() } + fn players_online(&self) -> u32 { self.players_online.into() } +} diff --git a/crates/lib/src/protocols/types.rs b/crates/lib/src/protocols/types.rs index 4249d5c..9bd4b53 100644 --- a/crates/lib/src/protocols/types.rs +++ b/crates/lib/src/protocols/types.rs @@ -18,6 +18,7 @@ pub enum ProprietaryProtocol { Minecraft(Option), FFOW, JC2M, + Savage2, } /// Enumeration of all valid protocol types @@ -48,6 +49,8 @@ pub enum GenericResponse<'a> { FFOW(&'a crate::games::ffow::Response), #[cfg(feature = "games")] JC2M(&'a crate::games::jc2m::Response), + #[cfg(feature = "games")] + Savage2(&'a crate::games::savage2::Response), } /// All player types From fb6f22b801debb572b661a5105427b1ba2f64762 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 10 Dec 2023 17:39:46 +0000 Subject: [PATCH 06/49] Add/Update badge --- .github/badges/node.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index 79faad7..69f3425 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 14.33% + + Node game coverage: 14.64% @@ -13,8 +13,8 @@ \ No newline at end of file From dd204936f029a7c3b5c7415a0ea2d2a9436880c6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 10 Dec 2023 19:41:28 +0200 Subject: [PATCH 07/49] chore: fix unreal 2 entry in protocols.md --- PROTOCOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 78c49ba..219a33a 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -7,7 +7,7 @@ A protocol is defined as proprietary if it is being used only for a single scope | Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | | GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. | | Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | | -| Unreal2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | +| Unreal 2 | Games | No | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/unreal2.js) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | | Savage 2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) | | ## Planned to add support: From 55f498d45a178eb20559ba1ae49bf407d021083e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 10 Dec 2023 19:43:32 +0200 Subject: [PATCH 08/49] fix: add jc2m in protocols.md --- PROTOCOLS.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 219a33a..ba4d942 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,14 +1,15 @@ A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| Name | For | Proprietary? | Documentation reference | Notes | -|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. | -| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | -| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. | -| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | | -| Unreal 2 | Games | No | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/unreal2.js) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | -| Savage 2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) | | +| Name | For | Proprietary? | Documentation reference | Notes | +|----------------------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | In some cases, the players details query might contain some 0-length named players. Multi-packet decompression not tested. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | +| GameSpy | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/gamespy3.js) | These protocols are not really standardized, gamedig tries to get the most common fields amongst its supported games, if there are parsing problems, use the `query_vars` function. | +| Quake | Games | No | One: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake1.js) Two: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake2.js) Three: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/quake3.js) | | +| Just Cause 2: Multiplayer | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/jc2mp.js) | +| Unreal 2 | Games | No | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/unreal2.js) | Sometimes servers send strings that node-gamedig would treat as latin1 that are UTF-8 encoded, when this happens the remove color code breaks because latin1 decodes the colour sequences differently. Some games provide additional info at the end of the server info packet, this is not currently handled (see the node implementation). Some games use a bot player to denote the team names, this is not currently handled. | +| Savage 2 | Games | Yes | [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/savage2.js) | | ## Planned to add support: _ From bc2b69d1836cda80f7981892d6037f5a985534a9 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 10 Dec 2023 19:46:12 +0200 Subject: [PATCH 09/49] fix: remove unwrapping in an assert --- crates/lib/src/errors/result.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/errors/result.rs b/crates/lib/src/errors/result.rs index 4156e26..b7bdef1 100644 --- a/crates/lib/src/errors/result.rs +++ b/crates/lib/src/errors/result.rs @@ -12,7 +12,7 @@ mod tests { #[test] fn test_gdresult_ok() { let result: GDResult = Ok(42); - assert_eq!(result.unwrap(), 42); + assert_eq!(result, Ok(42)); } // Testing Err variant of the GDResult type From e0cc2a2420db75f83ece46430d753096e9573f07 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 10 Dec 2023 23:32:12 +0200 Subject: [PATCH 10/49] fix: readme examples folder returning not found since monorepo merge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cda8c46..dddbc11 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Response (note that some games have a different structure): } ``` -Want to see more examples? Checkout the [examples](examples) folder. +Want to see more examples? Checkout the [examples](crates/lib/examples) folder. ## Documentation The documentation is available at [docs.rs](https://docs.rs/gamedig/latest/gamedig/). From 1dc3c6dade5d6834ae8b2cf6ac5bf56cc89beeab Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:19:51 +0200 Subject: [PATCH 11/49] chore: replace the closure with the method itself --- crates/lib/src/services/valve_master_server/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/services/valve_master_server/service.rs b/crates/lib/src/services/valve_master_server/service.rs index ee62c24..f9e992c 100644 --- a/crates/lib/src/services/valve_master_server/service.rs +++ b/crates/lib/src/services/valve_master_server/service.rs @@ -18,7 +18,7 @@ pub fn default_master_address() -> SocketAddr { fn construct_payload(region: Region, filters: &Option, last_ip: &str, last_port: u16) -> Vec { let filters_bytes: Vec = filters .as_ref() - .map_or_else(|| vec![0x00], |f| f.to_bytes()); + .map_or_else(|| vec![0x00], SearchFilters::to_bytes); let region_byte = &[region as u8]; From 8b4f6083f1213bcc5047561b4dfd274d4341202c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:21:39 +0200 Subject: [PATCH 12/49] chore: replace ''.to_string with String::new --- crates/lib/src/protocols/valve/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/protocols/valve/protocol.rs b/crates/lib/src/protocols/valve/protocol.rs index 5d18cba..daef2e1 100644 --- a/crates/lib/src/protocols/valve/protocol.rs +++ b/crates/lib/src/protocols/valve/protocol.rs @@ -271,7 +271,7 @@ impl ValveProtocol { has_password, vac_secured, the_ship: None, - game_version: "".to_string(), // a version field only for the mod + game_version: String::new(), // a version field only for the mod extra_data: None, is_mod, mod_data, From febba25a91b18b74b20015fc3bbc55815055193f Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:25:29 +0200 Subject: [PATCH 13/49] chore: instead of .map().unwrap_or_else() use .map_or_else() --- crates/lib/src/protocols/valve/protocol.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/lib/src/protocols/valve/protocol.rs b/crates/lib/src/protocols/valve/protocol.rs index daef2e1..f655fae 100644 --- a/crates/lib/src/protocols/valve/protocol.rs +++ b/crates/lib/src/protocols/valve/protocol.rs @@ -129,8 +129,7 @@ impl ValveProtocol { let socket = UdpSocket::new(address, &timeout_settings)?; let retry_count = timeout_settings .as_ref() - .map(|t| t.get_retries()) - .unwrap_or_else(|| TimeoutSettings::default().get_retries()); + .map_or_else(|| TimeoutSettings::default().get_retries(), |t| t.get_retries()); Ok(Self { socket, From 177d22e4b266f14463cbfb8e65e016e1e7d39459 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:26:55 +0200 Subject: [PATCH 14/49] chore: instead of .map().unwrap_or_else() use .map_or_else() --- crates/lib/src/protocols/unreal2/protocol.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/lib/src/protocols/unreal2/protocol.rs b/crates/lib/src/protocols/unreal2/protocol.rs index e5535ca..9b6e70c 100644 --- a/crates/lib/src/protocols/unreal2/protocol.rs +++ b/crates/lib/src/protocols/unreal2/protocol.rs @@ -35,8 +35,7 @@ impl Unreal2Protocol { let socket = UdpSocket::new(address, &timeout_settings)?; let retry_count = timeout_settings .as_ref() - .map(|t| t.get_retries()) - .unwrap_or_else(|| TimeoutSettings::default().get_retries()); + .map_or_else(|| TimeoutSettings::default().get_retries(), TimeoutSettings::get_retries); Ok(Self { socket, From 81e028e1a016808f03140e3337ee2efff17cbaab Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:29:07 +0200 Subject: [PATCH 15/49] perf: dereference &&str to use specialized ToString implementation --- crates/lib/src/protocols/quake/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/protocols/quake/client.rs b/crates/lib/src/protocols/quake/client.rs index 972d64a..d3f3db6 100644 --- a/crates/lib/src/protocols/quake/client.rs +++ b/crates/lib/src/protocols/quake/client.rs @@ -78,7 +78,7 @@ fn get_server_values(bufferer: &mut Buffer) -> GDResult Date: Mon, 11 Dec 2023 03:31:18 +0200 Subject: [PATCH 16/49] perf: dereference &&str to use specialized ToString implementation --- crates/lib/src/protocols/gamespy/protocols/three/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/protocols/gamespy/protocols/three/protocol.rs b/crates/lib/src/protocols/gamespy/protocols/three/protocol.rs index 1dfb623..6e70ab9 100644 --- a/crates/lib/src/protocols/gamespy/protocols/three/protocol.rs +++ b/crates/lib/src/protocols/gamespy/protocols/three/protocol.rs @@ -282,7 +282,7 @@ fn parse_players_and_teams(packets: Vec>) -> GDResult<(Vec, Vec< } let entry_data = data.get_mut(offset).ok_or(PacketBad)?; - entry_data.insert(field_name.to_string(), item); + entry_data.insert((*field_name).to_string(), item); offset += 1; } From f1094e0e686e94226b07ad7c3ada384c9a511bfa Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:31:57 +0200 Subject: [PATCH 17/49] chore: replace the closure with the method itself --- crates/lib/src/protocols/gamespy/protocols/one/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/protocols/gamespy/protocols/one/protocol.rs b/crates/lib/src/protocols/gamespy/protocols/one/protocol.rs index a84c93d..32350ce 100644 --- a/crates/lib/src/protocols/gamespy/protocols/one/protocol.rs +++ b/crates/lib/src/protocols/gamespy/protocols/one/protocol.rs @@ -55,7 +55,7 @@ fn get_server_values_impl(socket: &mut UdpSocket) -> GDResult Date: Mon, 11 Dec 2023 03:34:05 +0200 Subject: [PATCH 18/49] chore: fixed some long literal lacking separators --- crates/lib/src/games/definitions.rs | 4 ++-- crates/lib/src/games/valve.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index e157f99..1ec66b1 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -81,7 +81,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "l4d2" => game!("Left 4 Dead 2", 27015, Protocol::Valve(Engine::new(550))), "ohd" => game!("Operation: Harsh Doorstop", 27005, Protocol::Valve(Engine::new_with_dedicated(736_590, 950_900))), "onset" => game!("Onset", 7776, Protocol::Valve(Engine::new(1_105_810))), - "postscriptum" => game!("Post Scriptum", 10037, Protocol::Valve(Engine::new(736220))), + "postscriptum" => game!("Post Scriptum", 10037, Protocol::Valve(Engine::new(736_220))), "projectzomboid" => game!("Project Zomboid", 16261, Protocol::Valve(Engine::new(108_600))), "quake1" => game!("Quake 1", 27500, Protocol::Quake(QuakeVersion::One)), "quake2" => game!("Quake 2", 27910, Protocol::Quake(QuakeVersion::Two)), @@ -93,7 +93,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "sdtd" => game!("7 Days to Die", 26900, Protocol::Valve(Engine::new(251_570))), "sof2" => game!("Soldier of Fortune 2", 20100, Protocol::Quake(QuakeVersion::Three)), "serioussam" => game!("Serious Sam", 25601, Protocol::Gamespy(GameSpyVersion::One)), - "squad" => game!("Squad", 27165, Protocol::Valve(Engine::new(393380))), + "squad" => game!("Squad", 27165, Protocol::Valve(Engine::new(393_380))), "theforest" => game!("The Forest", 27016, Protocol::Valve(Engine::new(556_450))), "thefront" => game!("The Front", 27015, Protocol::Valve(Engine::new(2_285_150))), "teamfortress2" => game!("Team Fortress 2", 27015, Protocol::Valve(Engine::new(440))), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index a2aafe9..c188ee2 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -100,7 +100,7 @@ game_query_mod!( 27005 ); game_query_mod!(onset, "Onset", Engine::new(1_105_810), 7776); -game_query_mod!(postscriptum, "Post Scriptum", Engine::new(736220), 10037); +game_query_mod!(postscriptum, "Post Scriptum", Engine::new(736_220), 10037); game_query_mod!( projectzomboid, "Project Zomboid", @@ -111,7 +111,7 @@ game_query_mod!(ror2, "Risk of Rain 2", Engine::new(632_360), 27016); game_query_mod!(rust, "Rust", Engine::new(252_490), 27015); game_query_mod!(sco, "Sven Co-op", Engine::new_gold_src(false), 27015); game_query_mod!(sdtd, "7 Days to Die", Engine::new(251_570), 26900); -game_query_mod!(squad, "Squad", Engine::new(393380), 27165); +game_query_mod!(squad, "Squad", Engine::new(393_380), 27165); game_query_mod!(teamfortress2, "Team Fortress 2", Engine::new(440), 27015); game_query_mod!( tfc, From 5d0834ac78da43a7d5dfa4ce508f8ae7d68aadff Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:34:38 +0200 Subject: [PATCH 19/49] chore: run rustfmt --- crates/lib/src/protocols/unreal2/protocol.rs | 7 ++++--- crates/lib/src/protocols/valve/protocol.rs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/lib/src/protocols/unreal2/protocol.rs b/crates/lib/src/protocols/unreal2/protocol.rs index 9b6e70c..b1326b8 100644 --- a/crates/lib/src/protocols/unreal2/protocol.rs +++ b/crates/lib/src/protocols/unreal2/protocol.rs @@ -33,9 +33,10 @@ pub(crate) struct Unreal2Protocol { impl Unreal2Protocol { pub fn new(address: &SocketAddr, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, &timeout_settings)?; - let retry_count = timeout_settings - .as_ref() - .map_or_else(|| TimeoutSettings::default().get_retries(), TimeoutSettings::get_retries); + let retry_count = timeout_settings.as_ref().map_or_else( + || TimeoutSettings::default().get_retries(), + TimeoutSettings::get_retries, + ); Ok(Self { socket, diff --git a/crates/lib/src/protocols/valve/protocol.rs b/crates/lib/src/protocols/valve/protocol.rs index f655fae..b4ec3c6 100644 --- a/crates/lib/src/protocols/valve/protocol.rs +++ b/crates/lib/src/protocols/valve/protocol.rs @@ -127,9 +127,10 @@ static PACKET_SIZE: usize = 6144; impl ValveProtocol { pub fn new(address: &SocketAddr, timeout_settings: Option) -> GDResult { let socket = UdpSocket::new(address, &timeout_settings)?; - let retry_count = timeout_settings - .as_ref() - .map_or_else(|| TimeoutSettings::default().get_retries(), |t| t.get_retries()); + let retry_count = timeout_settings.as_ref().map_or_else( + || TimeoutSettings::default().get_retries(), + |t| t.get_retries(), + ); Ok(Self { socket, From f431508418401795a517ae596868436ca5226e53 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:41:41 +0200 Subject: [PATCH 20/49] chore: use map_or_else instead of if let Some() ... else --- crates/lib/src/socket.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/lib/src/socket.rs b/crates/lib/src/socket.rs index 8ae6bef..223c94c 100644 --- a/crates/lib/src/socket.rs +++ b/crates/lib/src/socket.rs @@ -34,11 +34,10 @@ pub struct TcpSocket { impl Socket for TcpSocket { fn new(address: &SocketAddr, timeout_settings: &Option) -> GDResult { - let socket = if let Some(timeout) = TimeoutSettings::get_connect_or_default(timeout_settings) { - net::TcpStream::connect_timeout(address, timeout) - } else { - net::TcpStream::connect(address) - }; + let socket = TimeoutSettings::get_connect_or_default(timeout_settings).map_or_else( + || net::TcpStream::connect(address), + |timeout| net::TcpStream::connect_timeout(address, timeout), + ); let socket = Self { socket: socket.map_err(|e| SocketConnect.context(e))?, From 486abbd9f733ba0c6f8baef0cd38252f54c66fcf Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:45:54 +0200 Subject: [PATCH 21/49] chore: use Self where possible --- crates/lib/src/protocols/types.rs | 12 ++++++------ crates/lib/src/protocols/unreal2/types.rs | 6 +++--- crates/lib/src/protocols/valve/types.rs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/lib/src/protocols/types.rs b/crates/lib/src/protocols/types.rs index 9bd4b53..cb01517 100644 --- a/crates/lib/src/protocols/types.rs +++ b/crates/lib/src/protocols/types.rs @@ -231,33 +231,33 @@ impl TimeoutSettings { /// Get the number of retries if there are timeout settings else fall back /// to the default - pub const fn get_retries_or_default(timeout_settings: &Option) -> usize { + pub const fn get_retries_or_default(timeout_settings: &Option) -> usize { if let Some(timeout_settings) = timeout_settings { timeout_settings.get_retries() } else { - TimeoutSettings::const_default().get_retries() + Self::const_default().get_retries() } } /// Get the read and write durations if there are timeout settings else fall /// back to the defaults pub const fn get_read_and_write_or_defaults( - timeout_settings: &Option, + timeout_settings: &Option, ) -> (Option, Option) { if let Some(timeout_settings) = timeout_settings { (timeout_settings.get_read(), timeout_settings.get_write()) } else { - let default = TimeoutSettings::const_default(); + let default = Self::const_default(); (default.get_read(), default.get_write()) } } /// Get the connect duration given timeout settings or get the default. - pub const fn get_connect_or_default(timeout_settings: &Option) -> Option { + pub const fn get_connect_or_default(timeout_settings: &Option) -> Option { if let Some(timeout_settings) = timeout_settings { timeout_settings.get_connect() } else { - TimeoutSettings::const_default().get_connect() + Self::const_default().get_connect() } } diff --git a/crates/lib/src/protocols/unreal2/types.rs b/crates/lib/src/protocols/unreal2/types.rs index 468e9e8..bab95c0 100644 --- a/crates/lib/src/protocols/unreal2/types.rs +++ b/crates/lib/src/protocols/unreal2/types.rs @@ -50,7 +50,7 @@ pub struct ServerInfo { impl ServerInfo { pub fn parse(buffer: &mut Buffer) -> GDResult { - Ok(ServerInfo { + Ok(Self { server_id: buffer.read()?, ip: buffer.read_string::(None)?, game_port: buffer.read()?, @@ -118,7 +118,7 @@ impl Players { /// Pre-allocate the vectors inside the players struct based on the provided /// capacity. pub fn with_capacity(capacity: usize) -> Self { - Players { + Self { players: Vec::with_capacity(capacity), // Allocate half as many bots as we don't expect there to be as many bots: Vec::with_capacity(capacity / 2), @@ -234,7 +234,7 @@ impl GatheringSettings { } impl Default for GatheringSettings { - fn default() -> Self { GatheringSettings::default() } + fn default() -> Self { Self::default() } } impl From for GatheringSettings { diff --git a/crates/lib/src/protocols/valve/types.rs b/crates/lib/src/protocols/valve/types.rs index ebe46ad..b0d9b92 100644 --- a/crates/lib/src/protocols/valve/types.rs +++ b/crates/lib/src/protocols/valve/types.rs @@ -310,7 +310,7 @@ impl GatheringSettings { } impl Default for GatheringSettings { - fn default() -> Self { GatheringSettings::default() } + fn default() -> Self { Self::default() } } impl From for GatheringSettings { From 21205fc3cb98c550b1d5bd556cb1a0a369ff0536 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:47:31 +0200 Subject: [PATCH 22/49] fix: also add Eq to savage2::Response --- crates/lib/src/games/savage2/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/games/savage2/types.rs b/crates/lib/src/games/savage2/types.rs index d72068b..ceedd98 100644 --- a/crates/lib/src/games/savage2/types.rs +++ b/crates/lib/src/games/savage2/types.rs @@ -4,7 +4,7 @@ use crate::protocols::GenericResponse; use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Response { pub name: String, pub players_online: u8, From e1bffd2045428ab5d8969d74e2eae5e932a7c9d6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:49:18 +0200 Subject: [PATCH 23/49] pref: apply const to applicable functions --- crates/lib/src/games/minecraft/types.rs | 2 +- crates/lib/src/protocols/types.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/lib/src/games/minecraft/types.rs b/crates/lib/src/games/minecraft/types.rs index db64792..4e6ed01 100644 --- a/crates/lib/src/games/minecraft/types.rs +++ b/crates/lib/src/games/minecraft/types.rs @@ -115,7 +115,7 @@ impl Default for RequestSettings { impl RequestSettings { /// Make a new *RequestSettings* with just the hostname, the protocol /// version defaults to -1 - pub fn new_just_hostname(hostname: String) -> Self { + pub const fn new_just_hostname(hostname: String) -> Self { Self { hostname, protocol_version: -1, diff --git a/crates/lib/src/protocols/types.rs b/crates/lib/src/protocols/types.rs index cb01517..182d594 100644 --- a/crates/lib/src/protocols/types.rs +++ b/crates/lib/src/protocols/types.rs @@ -340,22 +340,22 @@ impl ExtraRequestSettings { } /// [Sets protocol /// version](ExtraRequestSettings#structfield.protocol_version) - pub fn set_protocol_version(mut self, protocol_version: i32) -> Self { + pub const fn set_protocol_version(mut self, protocol_version: i32) -> Self { self.protocol_version = Some(protocol_version); self } /// [Sets gather players](ExtraRequestSettings#structfield.gather_players) - pub fn set_gather_players(mut self, gather_players: bool) -> Self { + pub const fn set_gather_players(mut self, gather_players: bool) -> Self { self.gather_players = Some(gather_players); self } /// [Sets gather rules](ExtraRequestSettings#structfield.gather_rules) - pub fn set_gather_rules(mut self, gather_rules: bool) -> Self { + pub const fn set_gather_rules(mut self, gather_rules: bool) -> Self { self.gather_rules = Some(gather_rules); self } /// [Sets check app ID](ExtraRequestSettings#structfield.check_app_id) - pub fn set_check_app_id(mut self, check_app_id: bool) -> Self { + pub const fn set_check_app_id(mut self, check_app_id: bool) -> Self { self.check_app_id = Some(check_app_id); self } From 9c3e6cb51fb83675a99d09979aa4deeb76f428f5 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:53:18 +0200 Subject: [PATCH 24/49] perf: use of ok_or followed by a function call --- crates/lib/src/games/jc2m.rs | 2 +- crates/lib/src/protocols/gamespy/common.rs | 2 +- crates/lib/src/protocols/unreal2/protocol.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/lib/src/games/jc2m.rs b/crates/lib/src/games/jc2m.rs index 3ad22b2..52647aa 100644 --- a/crates/lib/src/games/jc2m.rs +++ b/crates/lib/src/games/jc2m.rs @@ -90,7 +90,7 @@ pub fn query_with_timeout( let packets = client.get_server_packets()?; let data = packets .get(0) - .ok_or(PacketBad.context("First packet missing"))?; + .ok_or_else(|| PacketBad.context("First packet missing"))?; let (mut server_vars, remaining_data) = data_to_map(data)?; let players = parse_players_and_teams(&remaining_data)?; diff --git a/crates/lib/src/protocols/gamespy/common.rs b/crates/lib/src/protocols/gamespy/common.rs index 9f78e17..18b672d 100644 --- a/crates/lib/src/protocols/gamespy/common.rs +++ b/crates/lib/src/protocols/gamespy/common.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; pub fn has_password(server_vars: &mut HashMap) -> GDResult { let password_value = server_vars .remove("password") - .ok_or(GDErrorKind::PacketBad.context("Missing password (exists) field"))? + .ok_or_else(|| GDErrorKind::PacketBad.context("Missing password (exists) field"))? .to_lowercase(); if let Ok(has) = password_value.parse::() { diff --git a/crates/lib/src/protocols/unreal2/protocol.rs b/crates/lib/src/protocols/unreal2/protocol.rs index b1326b8..beeed44 100644 --- a/crates/lib/src/protocols/unreal2/protocol.rs +++ b/crates/lib/src/protocols/unreal2/protocol.rs @@ -209,7 +209,7 @@ impl StringDecoder for Unreal2StringDecoder { let mut ucs2 = false; let mut length: usize = (*data .first() - .ok_or(PacketBad.context("Tried to decode string without length"))?) + .ok_or_else(|| PacketBad.context("Tried to decode string without length"))?) .into(); let mut start = 0; From f746fad157186a8cb958873e7c5948c50cb179f7 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:54:04 +0200 Subject: [PATCH 25/49] perf: use of ok_or followed by a function call --- crates/lib/src/games/jc2m.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/games/jc2m.rs b/crates/lib/src/games/jc2m.rs index 52647aa..6fe3505 100644 --- a/crates/lib/src/games/jc2m.rs +++ b/crates/lib/src/games/jc2m.rs @@ -97,7 +97,7 @@ pub fn query_with_timeout( let players_maximum = server_vars .remove("maxplayers") - .ok_or(PacketBad.context("Server variables missing maxplayers"))? + .ok_or_else(|| PacketBad.context("Server variables missing maxplayers"))? .parse() .map_err(|e| TypeParse.context(e))?; let players_online = match server_vars.remove("numplayers") { From 0aa498b30be7d3e135b5493c88c3f068e8efd936 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:56:14 +0200 Subject: [PATCH 26/49] chore: simplify condition to use equals operator --- crates/lib/src/protocols/unreal2/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/protocols/unreal2/protocol.rs b/crates/lib/src/protocols/unreal2/protocol.rs index beeed44..1c47e1a 100644 --- a/crates/lib/src/protocols/unreal2/protocol.rs +++ b/crates/lib/src/protocols/unreal2/protocol.rs @@ -225,7 +225,7 @@ impl StringDecoder for Unreal2StringDecoder { // For UCS-2 strings, some unreal 2 games randomly insert an extra 0x01 here, // not included in the length. Skip it if present (hopefully this never happens // legitimately) - if let Some(1) = data[start ..].first() { + if data[start ..].first() == Some(&1) { start += 1; } } From a4bc430868cd116aa2fc53d4bf84fe69000a3466 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 03:58:40 +0200 Subject: [PATCH 27/49] perf: use of or followed by a function call --- crates/lib/src/games/mod.rs | 2 +- crates/lib/src/protocols/quake/client.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/lib/src/games/mod.rs b/crates/lib/src/games/mod.rs index add1f19..98b2a6e 100644 --- a/crates/lib/src/games/mod.rs +++ b/crates/lib/src/games/mod.rs @@ -85,7 +85,7 @@ pub fn query_with_timeout_and_extra_settings( &socket_addr, *engine, extra_settings - .or(Option::from(game.request_settings.clone())) + .or_else(|| Option::from(game.request_settings.clone())) .map(ExtraRequestSettings::into), timeout_settings, ) diff --git a/crates/lib/src/protocols/quake/client.rs b/crates/lib/src/protocols/quake/client.rs index d3f3db6..e905097 100644 --- a/crates/lib/src/protocols/quake/client.rs +++ b/crates/lib/src/protocols/quake/client.rs @@ -116,23 +116,23 @@ pub fn client_query( Ok(Response { name: server_vars .remove("hostname") - .or(server_vars.remove("sv_hostname")) + .or_else(|| server_vars.remove("sv_hostname")) .ok_or(GDErrorKind::PacketBad)?, map: server_vars .remove("mapname") - .or(server_vars.remove("map")) + .or_else(|| server_vars.remove("map")) .ok_or(GDErrorKind::PacketBad)?, players_online: players.len() as u8, players_maximum: server_vars .remove("maxclients") - .or(server_vars.remove("sv_maxclients")) + .or_else(|| server_vars.remove("sv_maxclients")) .ok_or(GDErrorKind::PacketBad)? .parse() .map_err(|e| TypeParse.context(e))?, players, game_version: server_vars .remove("version") - .or(server_vars.remove("*version")), + .or_else(|| server_vars.remove("*version")), unused_entries: server_vars, }) } From 44abf6ec71706375d534255c457418bdf3f830b6 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 04:00:31 +0200 Subject: [PATCH 28/49] chore: use Option::map_or_else instead of an if let/else --- crates/cli/src/main.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 526f227..e908ec2 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -85,13 +85,14 @@ fn find_game(game_id: &str) -> Result<&'static Game> { /// * `Result` - On sucess returns a resolved IP address; on failure /// returns an [Error::InvalidHostname] error. fn resolve_ip_or_domain(host: &str, extra_options: &mut Option) -> Result { - if let Ok(parsed_ip) = host.parse() { - Ok(parsed_ip) - } else { - set_hostname_if_missing(host, extra_options); + host.parse().map_or_else( + |_| { + set_hostname_if_missing(host, extra_options); - resolve_domain(host) - } + resolve_domain(host) + }, + |parsed_ip| Ok(parsed_ip), + ) } /// Resolve a domain name to one of its IP addresses (the first one returned). From 8c52ca6ad314231519ba141bdb01a58ea7243f6e Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Mon, 11 Dec 2023 04:01:41 +0200 Subject: [PATCH 29/49] chore: remove redundant closure --- crates/cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e908ec2..69c1e15 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -91,7 +91,7 @@ fn resolve_ip_or_domain(host: &str, extra_options: &mut Option Date: Fri, 15 Dec 2023 20:19:15 +0100 Subject: [PATCH 30/49] chore: extract jc2m into multiple files (#171) * chore: extract jc2m into multiple files * docs: add jc2m fields public --- CHANGELOG.md | 1 + crates/lib/src/games/jc2m/mod.rs | 8 +++ .../src/games/{jc2m.rs => jc2m/protocol.rs} | 66 ++----------------- crates/lib/src/games/jc2m/types.rs | 50 ++++++++++++++ 4 files changed, 65 insertions(+), 60 deletions(-) create mode 100644 crates/lib/src/games/jc2m/mod.rs rename crates/lib/src/games/{jc2m.rs => jc2m/protocol.rs} (52%) create mode 100644 crates/lib/src/games/jc2m/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d736d..48c92a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Games: - [Squad](https://store.steampowered.com/app/393380/Squad/) support. - [Savage 2](https://savage2.net/) support. - Added a valve protocol query example. +- Made all of Just Cause 2: Multiplayer Response and Player fields public. Protocols: - Added the unreal2 protocol and its associated games: Darkest Hour, Devastation, Killing Floor, Red Orchestra, Unreal Tournament 2003, Unreal Tournament 2004 (by @Douile). diff --git a/crates/lib/src/games/jc2m/mod.rs b/crates/lib/src/games/jc2m/mod.rs new file mode 100644 index 0000000..642b155 --- /dev/null +++ b/crates/lib/src/games/jc2m/mod.rs @@ -0,0 +1,8 @@ +/// The implementation. +/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/jc2mp.js) +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/crates/lib/src/games/jc2m.rs b/crates/lib/src/games/jc2m/protocol.rs similarity index 52% rename from crates/lib/src/games/jc2m.rs rename to crates/lib/src/games/jc2m/protocol.rs index 6fe3505..b6e0461 100644 --- a/crates/lib/src/games/jc2m.rs +++ b/crates/lib/src/games/jc2m/protocol.rs @@ -1,61 +1,13 @@ use crate::buffer::{Buffer, Utf8Decoder}; +use crate::jc2m::{Player, Response}; use crate::protocols::gamespy::common::has_password; use crate::protocols::gamespy::three::{data_to_map, GameSpy3}; -use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings}; -use crate::protocols::GenericResponse; +use crate::protocols::types::TimeoutSettings; use crate::GDErrorKind::{PacketBad, TypeParse}; -use crate::{GDErrorKind, GDResult}; +use crate::GDResult; use byteorder::BigEndian; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Player { - name: String, - steam_id: String, - ping: u16, -} - -impl CommonPlayer for Player { - fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) } - - fn name(&self) -> &str { &self.name } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Response { - game_version: String, - description: String, - name: String, - has_password: bool, - players: Vec, - players_maximum: u32, - players_online: u32, -} - -impl CommonResponse for Response { - fn as_original(&self) -> GenericResponse { GenericResponse::JC2M(self) } - - fn game_version(&self) -> Option<&str> { Some(&self.game_version) } - fn description(&self) -> Option<&str> { Some(&self.description) } - fn name(&self) -> Option<&str> { Some(&self.name) } - fn has_password(&self) -> Option { Some(self.has_password) } - fn players_maximum(&self) -> u32 { self.players_maximum } - fn players_online(&self) -> u32 { self.players_online } - - fn players(&self) -> Option> { - Some( - self.players - .iter() - .map(|p| p as &dyn CommonPlayer) - .collect(), - ) - } -} - fn parse_players_and_teams(packet: &[u8]) -> GDResult> { let mut buf = Buffer::::new(packet); @@ -112,15 +64,9 @@ pub fn query_with_timeout( } as u32; Ok(Response { - game_version: server_vars - .remove("version") - .ok_or(GDErrorKind::PacketBad)?, - description: server_vars - .remove("description") - .ok_or(GDErrorKind::PacketBad)?, - name: server_vars - .remove("hostname") - .ok_or(GDErrorKind::PacketBad)?, + game_version: server_vars.remove("version").ok_or(PacketBad)?, + description: server_vars.remove("description").ok_or(PacketBad)?, + name: server_vars.remove("hostname").ok_or(PacketBad)?, has_password: has_password(&mut server_vars)?, players, players_maximum, diff --git a/crates/lib/src/games/jc2m/types.rs b/crates/lib/src/games/jc2m/types.rs new file mode 100644 index 0000000..0f14a58 --- /dev/null +++ b/crates/lib/src/games/jc2m/types.rs @@ -0,0 +1,50 @@ +use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer}; +use crate::protocols::GenericResponse; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Player { + pub name: String, + pub steam_id: String, + pub ping: u16, +} + +impl CommonPlayer for Player { + fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) } + + fn name(&self) -> &str { &self.name } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Response { + pub game_version: String, + pub description: String, + pub name: String, + pub has_password: bool, + pub players: Vec, + pub players_maximum: u32, + pub players_online: u32, +} + +impl CommonResponse for Response { + fn as_original(&self) -> GenericResponse { GenericResponse::JC2M(self) } + + fn game_version(&self) -> Option<&str> { Some(&self.game_version) } + fn description(&self) -> Option<&str> { Some(&self.description) } + fn name(&self) -> Option<&str> { Some(&self.name) } + fn has_password(&self) -> Option { Some(self.has_password) } + fn players_maximum(&self) -> u32 { self.players_maximum } + fn players_online(&self) -> u32 { self.players_online } + + fn players(&self) -> Option> { + Some( + self.players + .iter() + .map(|p| p as &dyn CommonPlayer) + .collect(), + ) + } +} From bdcf64facf7b3f976ff9beb7a91024b18c28bac4 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 19 Dec 2023 20:58:15 +0100 Subject: [PATCH 31/49] chore: extract the ship into multiple files (#172) * chore: extract the ship into multiple files * fix: actual the ship reference link * fix: revert last commit and replace in ts --- crates/lib/src/games/theship/mod.rs | 8 +++++ crates/lib/src/games/theship/protocol.rs | 23 +++++++++++++ .../games/{theship.rs => theship/types.rs} | 34 +++---------------- 3 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 crates/lib/src/games/theship/mod.rs create mode 100644 crates/lib/src/games/theship/protocol.rs rename crates/lib/src/games/{theship.rs => theship/types.rs} (82%) diff --git a/crates/lib/src/games/theship/mod.rs b/crates/lib/src/games/theship/mod.rs new file mode 100644 index 0000000..b37a291 --- /dev/null +++ b/crates/lib/src/games/theship/mod.rs @@ -0,0 +1,8 @@ +/// The implementation. +/// Reference: [server queries](https://developer.valvesoftware.com/wiki/Server_queries) +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/crates/lib/src/games/theship/protocol.rs b/crates/lib/src/games/theship/protocol.rs new file mode 100644 index 0000000..c041ceb --- /dev/null +++ b/crates/lib/src/games/theship/protocol.rs @@ -0,0 +1,23 @@ +use crate::games::theship::types::Response; +use crate::protocols::types::TimeoutSettings; +use crate::protocols::valve; +use crate::protocols::valve::Engine; +use crate::GDResult; +use std::net::{IpAddr, SocketAddr}; + +pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, None) } + +pub fn query_with_timeout( + address: &IpAddr, + port: Option, + timeout_settings: Option, +) -> GDResult { + let valve_response = valve::query( + &SocketAddr::new(*address, port.unwrap_or(27015)), + Engine::new(2400), + None, + timeout_settings, + )?; + + Response::new_from_valve_response(valve_response) +} diff --git a/crates/lib/src/games/theship.rs b/crates/lib/src/games/theship/types.rs similarity index 82% rename from crates/lib/src/games/theship.rs rename to crates/lib/src/games/theship/types.rs index 5084200..9df679c 100644 --- a/crates/lib/src/games/theship.rs +++ b/crates/lib/src/games/theship/types.rs @@ -1,17 +1,10 @@ -use crate::{ - protocols::{ - types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings}, - valve::{self, get_optional_extracted_data, Server, ServerPlayer}, - GenericResponse, - }, - GDErrorKind::PacketBad, - GDResult, -}; -use std::net::{IpAddr, SocketAddr}; - +use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer}; +use crate::protocols::valve::{get_optional_extracted_data, Server, ServerPlayer}; +use crate::protocols::{valve, GenericResponse}; +use crate::GDErrorKind::PacketBad; +use crate::GDResult; use std::collections::HashMap; -use crate::protocols::valve::Engine; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -127,20 +120,3 @@ impl Response { }) } } - -pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, None) } - -pub fn query_with_timeout( - address: &IpAddr, - port: Option, - timeout_settings: Option, -) -> GDResult { - let valve_response = valve::query( - &SocketAddr::new(*address, port.unwrap_or(27015)), - Engine::new(2400), - None, - timeout_settings, - )?; - - Response::new_from_valve_response(valve_response) -} From 10169c9107348a4f18ea52d9929f9ccca78d7816 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Tue, 19 Dec 2023 22:04:45 +0100 Subject: [PATCH 32/49] chore: extract ffow into multiple files (#173) * chore: extract ffow into multiple files * fix: serde uses not being conditional --- crates/lib/src/games/ffow/mod.rs | 8 +++ .../src/games/{ffow.rs => ffow/protocol.rs} | 57 +------------------ crates/lib/src/games/ffow/types.rs | 56 ++++++++++++++++++ 3 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 crates/lib/src/games/ffow/mod.rs rename crates/lib/src/games/{ffow.rs => ffow/protocol.rs} (50%) create mode 100644 crates/lib/src/games/ffow/types.rs diff --git a/crates/lib/src/games/ffow/mod.rs b/crates/lib/src/games/ffow/mod.rs new file mode 100644 index 0000000..db37a19 --- /dev/null +++ b/crates/lib/src/games/ffow/mod.rs @@ -0,0 +1,8 @@ +/// The implementation. +/// Reference: [Node-GameGig](https://github.com/gamedig/node-gamedig/blob/master/protocols/ffow.js) +pub mod protocol; +/// All types used by the implementation. +pub mod types; + +pub use protocol::*; +pub use types::*; diff --git a/crates/lib/src/games/ffow.rs b/crates/lib/src/games/ffow/protocol.rs similarity index 50% rename from crates/lib/src/games/ffow.rs rename to crates/lib/src/games/ffow/protocol.rs index 9cbbdfc..9eb7423 100644 --- a/crates/lib/src/games/ffow.rs +++ b/crates/lib/src/games/ffow/protocol.rs @@ -1,64 +1,11 @@ use crate::buffer::{Buffer, Utf8Decoder}; -use crate::protocols::types::{CommonResponse, TimeoutSettings}; +use crate::games::ffow::types::Response; +use crate::protocols::types::TimeoutSettings; use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol}; -use crate::protocols::GenericResponse; use crate::GDResult; use byteorder::LittleEndian; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; -/// The query response. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Response { - /// Protocol used by the server. - pub protocol_version: u8, - /// Name of the server. - pub name: String, - /// Map name. - pub active_mod: String, - /// Running game mode. - pub game_mode: String, - /// The version that the server is running on. - pub game_version: String, - /// Description of the server. - pub description: String, - /// Current map. - pub map: String, - /// Number of players on the server. - pub players_online: u8, - /// Maximum number of players the server reports it can hold. - pub players_maximum: u8, - /// Dedicated, NonDedicated or SourceTV - pub server_type: Server, - /// The Operating System that the server is on. - pub environment_type: Environment, - /// Indicates whether the server requires a password. - pub has_password: bool, - /// Indicates whether the server uses VAC. - pub vac_secured: bool, - /// Current round index. - pub round: u8, - /// Maximum amount of rounds. - pub rounds_maximum: u8, - /// Time left for the current round in seconds. - pub time_left: u16, -} - -impl CommonResponse for Response { - fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) } - - fn name(&self) -> Option<&str> { Some(&self.name) } - fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) } - fn description(&self) -> Option<&str> { Some(&self.description) } - fn game_version(&self) -> Option<&str> { Some(&self.game_version) } - fn map(&self) -> Option<&str> { Some(&self.map) } - fn has_password(&self) -> Option { Some(self.has_password) } - fn players_maximum(&self) -> u32 { self.players_maximum.into() } - fn players_online(&self) -> u32 { self.players_online.into() } -} - pub fn query(address: &IpAddr, port: Option) -> GDResult { query_with_timeout(address, port, None) } pub fn query_with_timeout( diff --git a/crates/lib/src/games/ffow/types.rs b/crates/lib/src/games/ffow/types.rs new file mode 100644 index 0000000..4a8d852 --- /dev/null +++ b/crates/lib/src/games/ffow/types.rs @@ -0,0 +1,56 @@ +use crate::protocols::types::CommonResponse; +use crate::protocols::valve::{Environment, Server}; +use crate::protocols::GenericResponse; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The query response. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Response { + /// Protocol used by the server. + pub protocol_version: u8, + /// Name of the server. + pub name: String, + /// Map name. + pub active_mod: String, + /// Running game mode. + pub game_mode: String, + /// The version that the server is running on. + pub game_version: String, + /// Description of the server. + pub description: String, + /// Current map. + pub map: String, + /// Number of players on the server. + pub players_online: u8, + /// Maximum number of players the server reports it can hold. + pub players_maximum: u8, + /// Dedicated, NonDedicated or SourceTV + pub server_type: Server, + /// The Operating System that the server is on. + pub environment_type: Environment, + /// Indicates whether the server requires a password. + pub has_password: bool, + /// Indicates whether the server uses VAC. + pub vac_secured: bool, + /// Current round index. + pub round: u8, + /// Maximum amount of rounds. + pub rounds_maximum: u8, + /// Time left for the current round in seconds. + pub time_left: u16, +} + +impl CommonResponse for Response { + fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) } + + fn name(&self) -> Option<&str> { Some(&self.name) } + fn game_mode(&self) -> Option<&str> { Some(&self.game_mode) } + fn description(&self) -> Option<&str> { Some(&self.description) } + fn game_version(&self) -> Option<&str> { Some(&self.game_version) } + fn map(&self) -> Option<&str> { Some(&self.map) } + fn has_password(&self) -> Option { Some(self.has_password) } + fn players_maximum(&self) -> u32 { self.players_maximum.into() } + fn players_online(&self) -> u32 { self.players_online.into() } +} From 87ed02420e3af7527944a9ddb9da13b30bbbac56 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Tue, 19 Dec 2023 21:20:19 +0000 Subject: [PATCH 33/49] feat: Add best effort test to validate game ID rules (#111) * [Test] Add best effort test to validate game ID rules An attempt to implement the rules specified in #108 as a programmatic test. * [Test] Refactor ID rules to check if a mod exists after - in game name This allows fivem to pass the check following rule 8, but could also cause a false pass in some cases. * [Test] Add unit tests for ID rule checker Adds unit tests based on the examples in CONTRIBUTING.md to confirm in those cases we would allow ID to pass. However these tests don't check any error cases. * test/id: Correctly extract protocol names * games/defs: Fix unreal tournament IDs * tests: Require game definitions to run ID tests * tests: Improve comments on ID tests * tests/id: Combine - seperated numbers * games/defs: Fix darkest hour ID * Add/Update badge --------- Co-authored-by: GitHub Action --- .github/badges/node.svg | 8 +- CHANGELOG.md | 3 + crates/lib/Cargo.toml | 4 + crates/lib/src/games/definitions.rs | 6 +- tests/game_ids.rs | 556 ++++++++++++++++++++++++++++ 5 files changed, 570 insertions(+), 7 deletions(-) create mode 100644 tests/game_ids.rs diff --git a/.github/badges/node.svg b/.github/badges/node.svg index 69f3425..ce84f0a 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 14.64% + + Node game coverage: 13.71% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c92a5..1273198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ Game: - - Left 4 Dead: `left4dead` -> `l4d`. - - 7 Days to Die: `7d2d` in definitions and `sd2d` in game declaration -> `sdtd`. - - Quake 3 Arena: `quake3arena` -> `q3a`. +- - Unreal tournament 2003: `ut2003` -> `unrealtournament2003` +- - Unreal tournament 2004: `ut2004` -> `unrealtournament2004` +- - Darkest Hour: Europe '44-'45: `darkesthour` -> `dhe4445` - Minecraft: - - Legacy 1.5 and 1.3 were renamed to 1.4 and beta 1.8 respectively to show the lowest version they support, this change includes Structs, Enum and game id renames, also removed the "v" from the game definition name. - - Moved the Minecraft protocol implementation in the games folder as its proprietary. diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 5e24b5f..59cdbc8 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -37,6 +37,10 @@ phf = { version = "0.11", optional = true, features = ["macros"] } clap = { version = "4.1.11", optional = true, features = ["derive"] } +[dev-dependencies] +number_to_words = "0.1" +roman_numeral = "0.1" + # Examples [[example]] name = "minecraft" diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 1ec66b1..0fe5cb9 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -109,10 +109,10 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "vrising" => game!("V Rising", 27016, Protocol::Valve(Engine::new(1_604_030))), "jc2m" => game!("Just Cause 2: Multiplayer", 7777, Protocol::PROPRIETARY(ProprietaryProtocol::JC2M)), "warsow" => game!("Warsow", 44400, Protocol::Quake(QuakeVersion::Three)), - "darkesthour" => game!("Darkest Hour: Europe '44-'45 (2008)", 7758, Protocol::Unreal2), + "dhe4445" => game!("Darkest Hour: Europe '44-'45 (2008)", 7758, Protocol::Unreal2), "devastation" => game!("Devastation (2003)", 7778, Protocol::Unreal2), "killingfloor" => game!("Killing Floor", 7708, Protocol::Unreal2), "redorchestra" => game!("Red Orchestra", 7759, Protocol::Unreal2), - "ut2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2), - "ut2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2), + "unrealtournament2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2), + "unrealtournament2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2), }; diff --git a/tests/game_ids.rs b/tests/game_ids.rs new file mode 100644 index 0000000..75f170f --- /dev/null +++ b/tests/game_ids.rs @@ -0,0 +1,556 @@ +#![cfg(all(test, feature = "game_defs"))] + +use std::{collections::HashMap, fs, io::Read}; + +use gamedig::GAMES; + +use utils::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IDRule { + IDsMustBeLowerCase, + NumbersAreTheirOwnWord, + IfFirstWordNumberNoDigits, + IfLastWordNumberMustBeAppended, + ConvertRomanNumeralsToArabic, + TwoWordsOrLessUseFullWords, + MoreThanTwoWordsMakeAcronym, + IfIDDuplicateSameGameAppendYearToNewer, + IfIDDuplicateSameGameAppendProtocol, + IfIDDuplicateNoAcronym, + IfModForQueriesProcessOnlyModName, + NoDuplicates, +} + +#[derive(Clone, Debug)] +pub struct IDFail { + pub game_id: String, + pub game_name: String, + pub expected_id: String, + pub rule_stack: Vec, +} + +impl IDFail { + fn new(game_id: String, game_name: String, expected_id: String, rule_stack: Vec) -> Self { + Self { + game_id, + game_name, + expected_id, + rule_stack, + } + } +} + +/// Test a single game against the rules +pub fn test_game_name_rule( + seen_ids: &mut HashMap>, + id: &str, + mut game: GameNameParsed, + is_mod_name: bool, +) -> Vec { + let mut wrong_ids = Vec::new(); + + let mut rule_stack = Vec::new(); + if is_mod_name { + rule_stack.push(IDRule::IfModForQueriesProcessOnlyModName); + } + + let mut suffix = String::new(); + + // A game's identification is a lowercase alphanumeric string will and be forged + // following these rules: + if id.to_lowercase().ne(id) { + wrong_ids.push(IDFail::new( + id.to_owned(), + game.name.to_owned(), + id.to_lowercase(), + vec![IDRule::IDsMustBeLowerCase], + )); + } + + // 5. Roman numbering will be converted to arabic numbering (XIV -> 14). + game.words = { + let mut is_first = true; + game.words + .into_iter() + .map(|w| { + // First word will never be a numeral + if is_first { + is_first = false; + w + } else if let Ok(number) = roman_numeral::RomanNumeral::from_string(&w) { + rule_stack.push(IDRule::ConvertRomanNumeralsToArabic); + number.get_u32().to_string() + } else { + w + } + }) + .collect() + }; + + // 6. Unless numbers are at the end of a name, they will be considered words, + // but digits will always be used instead of the acronym (counter to #2) + // (Left 4 Dead -> l4d) unless they at the start position (7 Days to Die -> + // sdtd), if they are at the end (such as sequel number or the year), always + // append them (Team Fortress 2 -> teamfortress2, Unreal Tournament 2003 -> + // unrealtournament2003). + game.words = game + .words + .into_iter() + .flat_map(|w| { + let n = split_on_switch_between_alpha_numeric(&w); + if n.len() > 1 { + rule_stack.push(IDRule::NumbersAreTheirOwnWord); + } + n + }) + .collect(); + + // If first word is number make text + if !game.words.is_empty() && game.words[0].chars().next().unwrap().is_ascii_digit() { + game.words[0] = number_to_words::number_to_words(game.words[0].parse::().unwrap(), false); + rule_stack.push(IDRule::IfFirstWordNumberNoDigits); + } + + // If last word is number append full number + if let Some(last_word) = game.words.last() { + if last_word.chars().all(|c| c.is_ascii_digit()) { + suffix += &game.words.pop().unwrap(); + rule_stack.push(IDRule::IfLastWordNumberMustBeAppended); + } + } + + let main = if game.words.len() <= 2 { + // 1. Names composed of a maximum of two words (unless #4 applies) will result + // in an id where the words are concatenated (Dead Cells -> deadcells), + // acronyms in the name count as a single word (S.T.A.L.K.E.R. -> stalker). + + rule_stack.push(IDRule::TwoWordsOrLessUseFullWords); + + game.words + .iter() + .map(|w| w.trim_matches('-').to_owned()) + .collect::>() + .join("") + } else { + // 2. Names of more than two words shall be made into an acronym made of the + // initial letters (The Binding of Isaac -> tboi), hypenation composed words + // don't count as a single word, but of how many parts they are made of (Dino + // D-Day, 3 words, so ddd). + + rule_stack.push(IDRule::MoreThanTwoWordsMakeAcronym); + + game.words + .iter() + .map(|w| w.chars().next().unwrap()) + .filter(|c| c.is_alphanumeric()) + .collect() + }; + + let mut expected_id = format!("{}{}", main, suffix).to_lowercase(); + + if let Some(other_game_name_words) = seen_ids.get(&expected_id) { + let mut game_names_same = other_game_name_words.len() == game.words.len(); + // Check all words in game name are the same + if game_names_same { + for i in 0 .. game.words.len() { + if game.words[i].to_lowercase() != other_game_name_words[i].to_lowercase() { + game_names_same = false; + break; + } + } + } + + if game_names_same { + if let Some(year) = game.year { + // 3. If a game has the exact name as a previously existing id's game (Star Wars + // Battlefront 2, the 2005 and 2017 one), append the release year to the + // newer id (2005 would be swbf2 (suppose we already have this one supported) + // and 2017 would be swbf22017). + + rule_stack.push(IDRule::IfIDDuplicateSameGameAppendYearToNewer); + expected_id = format!("{}{}", expected_id, year).to_lowercase(); + } else if let Some(protocol) = game.optional_parts.first() { + // 7. If a game supports multiple protocols, multiple entries will be done for + // said game where the edition/protocol name (first disposable in this order) + // will be appended to the game name (Minecraft is divided by 2 editions, + // Java and Bedrock which will be minecraftjava and minecraftbedrock + // respectively) and one more entry can be added by the base name of the game + // which queries in a group said supported protocols to make generic queries + // easier and disposable. + + rule_stack.push(IDRule::IfIDDuplicateSameGameAppendProtocol); + + // Parse the protocol as a game name so we can remove all non-valid characters + let protocol_parsed = extract_game_parts_from_name(protocol); + + expected_id = format!("{}{}", expected_id, protocol_parsed.words.concat(),); + } + } + } + + // 4. If a new id (Day of Dragons -> dod) results in an id that already exists + // (Day of Defeat -> dod), then the new name should ignore rule #2 (Day of + // Dragons -> dayofdragons). + if seen_ids.contains_key(&expected_id) { + rule_stack.push(IDRule::IfIDDuplicateNoAcronym); + + let main = game + .words + .iter() + .map(|w| w.trim_matches('-').to_owned()) + .collect::>() + .join(""); + + expected_id = format!("{}{}", main, suffix).to_lowercase(); + } + + // 8. If its actually about a mod that adds the ability for queries to be + // performed, process only the mod name. + if !is_mod_name && id != expected_id { + if let Some((_, mod_game)) = game.name.split_once('-') { + let mut result = test_game_name_rule(seen_ids, id, extract_game_parts_from_name(mod_game), true); + + if result.is_empty() { + return result; + } else { + wrong_ids.append(&mut result); + } + } + } + + let duplicate = if seen_ids.insert(expected_id.clone(), game.words).is_some() { + rule_stack.push(IDRule::NoDuplicates); + true + } else { + false + }; + + // Check ID matches + if id != expected_id || duplicate { + wrong_ids.push(IDFail::new( + id.to_owned(), + game.name.to_owned(), + expected_id, + rule_stack, + )); + } + + wrong_ids +} + +#[derive(Clone, Debug)] +pub struct GameNameParsed<'a> { + name: &'a str, + words: Vec, + optional_parts: Vec<&'a str>, + year: Option, +} + +pub fn extract_game_parts_from_name(game: &str) -> GameNameParsed { + // Separate game name into words + // NOTE: we have to leave "-" in to prevent hyphenated prefixes being parsed as + // numerals + let mut optional_game_name_parts = Vec::new(); + + let (game, paren) = extract_bracketed_suffix(game); + + if let Some(paren) = paren { + optional_game_name_parts.push(paren); + } + + let mut number_accumulator: Option = None; + + // Filter map necessary to move out words + #[allow(clippy::unnecessary_filter_map)] + let game_name_words: Vec<_> = game + // First split all text on space or dash + .split_inclusive(&[' ', '-']) + // Remove whitespace surrounding words (leave in dash because it is important information) + .map(|w| w.trim()) + // If a word is entirely surrounded in brackets move it to optional parts + .filter_map(|w| { + if w.starts_with('(') && w.ends_with(')') { + optional_game_name_parts.push(w); + None + } else { + Some(w) + } + }) + // Remove all characters that aren't alphanumeric or dashses + .map(|w| { + w.replace( + |c: char| !c.is_ascii_digit() && !c.is_alphabetic() && c != '-', + "", + ) + }) + // Remove words that are empty (discounting strings that are just dashes) + .filter(|w| !w.trim_matches('-').is_empty()) + // Combine numbers that are seperated by dashes + // e.g. 44-45 = 4445 + // Panics if there is text after number with trailing dash (44-text) + .filter_map(|w| { + if number_accumulator.is_some() { + if let Some(maybe_number) = w.strip_suffix('-') { + if maybe_number.chars().all(|c| c.is_ascii_digit()) { + number_accumulator.as_mut().unwrap().push_str(maybe_number); + return None; + } else { + panic!("Text after number-"); + } + } else if w.chars().all(|c| c.is_ascii_digit()) { + let mut accumulator = number_accumulator.as_ref().unwrap().clone(); + number_accumulator = None; + accumulator.push_str(&w); + return Some(accumulator); + } else { + panic!("Text after number-"); + } + } else if let Some(maybe_number) = w.strip_suffix('-') { + if maybe_number.chars().all(|c| c.is_ascii_digit()) { + number_accumulator = Some(maybe_number.to_string()); + return None; + } + } + + Some(w) + }) + .collect(); + + let mut game_year: Option = None; + for optional_part in &optional_game_name_parts { + if let Some(game_year_text) = optional_part + .strip_prefix('(') + .and_then(|s| s.strip_suffix(')')) + { + if let Ok(year) = game_year_text.parse() { + game_year = Some(year); + break; + } + } else if let Ok(year) = optional_part.parse() { + game_year = Some(year); + break; + } + } + + GameNameParsed { + name: game, + words: game_name_words, + optional_parts: optional_game_name_parts, + year: game_year, + } +} + +/// Iterate game entries and validate the id matches current rules +pub fn test_game_name_rules<'a, I: Iterator>(games: I) -> Vec { + let mut wrong_ids = Vec::with_capacity(games.size_hint().0); + + let mut seen_ids: HashMap> = HashMap::new(); + + // We must sort games by year so that rule 3 is applied correctly + let mut sorted_games: Vec<_> = games + .map(|(id, game)| { + let game = extract_game_parts_from_name(game); + + (id, game) + }) + .collect(); + + sorted_games.sort_by(|(_, a_game), (_, b_game)| { + a_game + .year + .cmp(&b_game.year) + .then(a_game.name.len().cmp(&b_game.name.len())) + }); + + let game_count = sorted_games.len(); + + for (id, game) in sorted_games { + wrong_ids.append(&mut test_game_name_rule(&mut seen_ids, id, game, false)) + } + + if !wrong_ids.is_empty() { + for fail in &wrong_ids { + println!("{:#?}", fail); + } + let percentage = (wrong_ids.len() * 100) / game_count; + println!( + "{} ({}%) IDs didn't match naming rules", + wrong_ids.len(), + percentage + ); + } + + wrong_ids +} + +#[test] +fn check_definitions_match_name_rules() { + let wrong = test_game_name_rules(GAMES.entries().map(|(id, game)| (id.to_owned(), game.name))); + assert!(wrong.is_empty()); +} + +#[test] +#[ignore = "Don't test node by default"] +fn check_node_definitions_match_name_rules() { + let mut file = fs::OpenOptions::new() + .read(true) + .open("./node-gamedig/games.txt") + .unwrap(); + + let mut text = String::new(); + file.read_to_string(&mut text).unwrap(); + + let games = text + .split('\n') + .map(|line| line.trim()) + .filter(|line| !line.starts_with('#') && !line.is_empty()) + .filter_map(|line| { + let parts: Vec<_> = line.splitn(3, '|').collect(); + if parts.len() > 1 { + Some((parts[0].split(',').next().unwrap(), parts[1])) + } else { + None + } + }); + + let wrong = test_game_name_rules(games); + assert!(wrong.is_empty()); +} + +fn test_single_game_rule(id: &str, name: &str) -> Vec { test_game_name_rules(std::iter::once((id, name))) } + +mod id_tests { + use super::{test_game_name_rules, test_single_game_rule}; + #[test] + fn id_rule_one() { + assert!(test_single_game_rule("testgame", "Test Game").is_empty()); + assert!(test_single_game_rule("testgame", "TestGame").is_empty()); + + assert!(test_single_game_rule("deadcells", "Dead Cells").is_empty()); + assert!(test_single_game_rule("stalker", "S.T.A.L.K.E.R").is_empty()); + } + + #[test] + fn id_rule_two() { + assert!(test_single_game_rule("tgt", "Test Game Three").is_empty()); + assert!(test_single_game_rule("tgt", "Test Game-Three").is_empty()); + + assert!(test_single_game_rule("tboi", "The Binding of Isaac").is_empty()); + assert!(test_single_game_rule("ddd", "Dino D-Day").is_empty()); + } + + #[test] + fn id_rule_three() { + let games = vec![ + ("swb22017", "Star Wars Battlefront 2 (2017)"), + ("swb2", "Star Wars Battlefront 2 (2015)"), + ]; + assert!(test_game_name_rules(games.into_iter()).is_empty()); + } + + #[test] + fn id_rule_four() { + let games = vec![("dod", "Day of Defeat"), ("dayofdragons", "Day of Dragons")]; + assert!(test_game_name_rules(games.into_iter()).is_empty()); + } + + #[test] + fn id_rule_five() { + assert!(test_single_game_rule("gta14", "Grand Theft Auto XIV").is_empty()); + } + + #[test] + fn id_rule_six() { + assert!(test_single_game_rule("l4d", "Left 4 Dead").is_empty()); + assert!(test_single_game_rule("sdtd", "7 Days to Die").is_empty()); + assert!(test_single_game_rule("teamfortress2", "Team Fortress 2").is_empty()); + assert!(test_single_game_rule("unrealtournament2003", "Unreal Tournament 2003").is_empty()); + assert!(test_single_game_rule("dhe4445", "Darkest Hour: Europe '44-'45").is_empty()); + } + + #[test] + fn id_rule_seven() { + let games = vec![ + ("minecraft", "Minecraft"), + ("minecraftjava", "Minecraft (java)"), + ("minecraftbedrock", "Minecraft (bedrock)"), + ]; + assert!(test_game_name_rules(games.into_iter()).is_empty()); + } + + #[test] + fn id_rule_eight() { + assert!(test_single_game_rule("fivem", "Grand Theft Auto V - FiveM (2013)").is_empty()); + assert!(test_single_game_rule("jc3m", "Just Cause 3 - Multiplayer").is_empty()); + } +} + +mod utils { + /// Split a str when characters swap between being digits and not digits. + pub fn split_on_switch_between_alpha_numeric(text: &str) -> Vec { + if text.is_empty() { + return vec![]; + } + + let mut parts = Vec::with_capacity(text.len()); + let mut current = Vec::with_capacity(text.len()); + + let mut iter = text.chars(); + let c = iter.next().unwrap(); + let mut last_was_numeric = c.is_ascii_digit(); + current.push(c); + + for c in iter { + if c.is_ascii_digit() == last_was_numeric { + current.push(c); + } else { + parts.push(current.iter().collect()); + current.clear(); + current.push(c); + last_was_numeric = !last_was_numeric; + } + } + + parts.push(current.into_iter().collect()); + + parts + } + + #[test] + fn split_correctly() { + assert_eq!( + split_on_switch_between_alpha_numeric("2D45A"), + &["2", "D", "45", "A"] + ); + } + + #[test] + fn split_symbol_broken_numbers() { + let game_name = super::extract_game_parts_from_name("Darkest Hour: Europe '44-'45"); + assert_eq!(game_name.words, &["Darkest", "Hour", "Europe", "4445"]); + } + + /// Extract parts at end of string enclosed in brackets. + pub fn extract_bracketed_suffix(text: &str) -> (&str, Option<&str>) { + if let Some(text) = text.strip_suffix(')') { + if let Some((text, extra)) = text.rsplit_once('(') { + return (text, Some(extra)); + } + } + + (text, None) + } + + #[test] + fn extract_brackets_correctly() { + assert_eq!( + extract_bracketed_suffix("no brackets here"), + ("no brackets here", None) + ); + assert_eq!( + extract_bracketed_suffix("Game name (with protocol here)"), + ("Game name ", Some("with protocol here")) + ); + } +} From 0e241056bff84a67a6e638fe711de0d9f9d1e239 Mon Sep 17 00:00:00 2001 From: Cain <75994858+cainthebest@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:01:08 +0000 Subject: [PATCH 34/49] fix: remove unused manifest key in `cargo.toml` --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4dd9c76..70ccb3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [workspace] -name = "gamedig-workspace" members = ["crates/cli", "crates/lib"] # Edition 2021, uses resolver = 2 From a7ee331dc36826ecdeaca9721a85f38ea91a7d16 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 30 Dec 2023 17:31:22 +0200 Subject: [PATCH 35/49] feat: add Rising World support --- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 5 +++++ crates/lib/src/games/valve.rs | 1 + 4 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1273198..daff5e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Games: - [Post Scriptum](https://store.steampowered.com/app/736220/Post_Scriptum/) support. - [Squad](https://store.steampowered.com/app/393380/Squad/) support. - [Savage 2](https://savage2.net/) support. +- [Rising World](https://store.steampowered.com/app/324080/Rising_World/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. diff --git a/GAMES.md b/GAMES.md index fe88259..6cf7926 100644 --- a/GAMES.md +++ b/GAMES.md @@ -74,6 +74,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Post Scriptum | POSTSCRIPTUM | Valve | | | Squad | SQUAD | Valve | | | Savage 2 | SAVAGE2 | Proprietary | | +| Rising World | RISINGWORLD | Valve | Query port offset: -1 | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 0fe5cb9..4ac3e8e 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -86,6 +86,11 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "quake1" => game!("Quake 1", 27500, Protocol::Quake(QuakeVersion::One)), "quake2" => game!("Quake 2", 27910, Protocol::Quake(QuakeVersion::Two)), "q3a" => game!("Quake 3 Arena", 27960, Protocol::Quake(QuakeVersion::Three)), + "risingworld" => game!("Rising World", 4254, Protocol::Valve(Engine::new(324_080)), GatheringSettings { + players: true, + rules: false, + check_app_id: true, + }.into_extra()), "ror2" => game!("Risk of Rain 2", 27016, Protocol::Valve(Engine::new(632_360))), "rust" => game!("Rust", 27015, Protocol::Valve(Engine::new(252_490))), "savage2" => game!("Savage 2", 11235, Protocol::PROPRIETARY(ProprietaryProtocol::Savage2)), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index c188ee2..93266ee 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -107,6 +107,7 @@ game_query_mod!( Engine::new(108_600), 16261 ); +game_query_mod!(risingworld, "Rising World", Engine::new(324_080), 4254); game_query_mod!(ror2, "Risk of Rain 2", Engine::new(632_360), 27016); game_query_mod!(rust, "Rust", Engine::new(252_490), 27015); game_query_mod!(sco, "Sven Co-op", Engine::new_gold_src(false), 27015); From 483d728ac8ae4d7f1209773b6cf16c2c9b7cdae0 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 30 Dec 2023 15:32:01 +0000 Subject: [PATCH 36/49] Add/Update badge --- .github/badges/node.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index ce84f0a..df3be38 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 13.71% + + Node game coverage: 14.02% @@ -13,8 +13,8 @@ \ No newline at end of file From bd3727d7fe8be6dd287a2e20acafe655b0538db3 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Mon, 1 Jan 2024 22:17:43 +0000 Subject: [PATCH 37/49] chore: Tidy up some out of place types (#160) * tidy: Move TimeoutSettings out of protocol types * tidy: Move game type to a types file * tidy: Move generic query functions to own file * tidy: Move ExtraRequestSettings to game types * tidy: Move generic query functions into games module * Revert "tidy: Move ExtraRequestSettings to game types" This reverts commit aa0d23fc2acc12d68f03265a9caff6f98bf25054. * Revert "tidy: Move TimeoutSettings out of protocol types" This reverts commit 7ee10711ed11f17cc06565d4cc9102040d53a319. * tidy: Re-export types needed for query at the root * Add/Update badge --------- Co-authored-by: GitHub Action --- .github/badges/node.svg | 8 +- crates/cli/src/main.rs | 5 +- crates/lib/examples/generic.rs | 4 +- crates/lib/examples/valve_protocol_query.rs | 2 +- crates/lib/src/games/mod.rs | 132 +------------------- crates/lib/src/games/query.rs | 117 +++++++++++++++++ crates/lib/src/games/types.rs | 20 +++ crates/lib/src/lib.rs | 7 +- 8 files changed, 156 insertions(+), 139 deletions(-) create mode 100644 crates/lib/src/games/query.rs create mode 100644 crates/lib/src/games/types.rs diff --git a/.github/badges/node.svg b/.github/badges/node.svg index df3be38..dcc0833 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 14.02% + + Node game coverage: 13.98% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 69c1e15..3628e8d 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,10 +1,7 @@ use std::net::{IpAddr, ToSocketAddrs}; use clap::{Parser, ValueEnum}; -use gamedig::{ - games::*, - protocols::types::{CommonResponse, ExtraRequestSettings, TimeoutSettings}, -}; +use gamedig::{games::*, protocols::types::CommonResponse, ExtraRequestSettings, TimeoutSettings}; mod error; diff --git a/crates/lib/examples/generic.rs b/crates/lib/examples/generic.rs index 2b47ffa..d54a0b3 100644 --- a/crates/lib/examples/generic.rs +++ b/crates/lib/examples/generic.rs @@ -1,8 +1,10 @@ use gamedig::{ - protocols::types::{CommonResponse, ExtraRequestSettings, TimeoutSettings}, + protocols::types::CommonResponse, query_with_timeout_and_extra_settings, + ExtraRequestSettings, GDResult, Game, + TimeoutSettings, GAMES, }; diff --git a/crates/lib/examples/valve_protocol_query.rs b/crates/lib/examples/valve_protocol_query.rs index a77b41f..f6ca6ff 100644 --- a/crates/lib/examples/valve_protocol_query.rs +++ b/crates/lib/examples/valve_protocol_query.rs @@ -1,6 +1,6 @@ -use gamedig::protocols::types::TimeoutSettings; use gamedig::protocols::valve; use gamedig::protocols::valve::{Engine, GatheringSettings}; +use gamedig::TimeoutSettings; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; diff --git a/crates/lib/src/games/mod.rs b/crates/lib/src/games/mod.rs index 98b2a6e..32467b5 100644 --- a/crates/lib/src/games/mod.rs +++ b/crates/lib/src/games/mod.rs @@ -1,8 +1,5 @@ //! Currently supported games. -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - pub mod gamespy; pub mod quake; pub mod unreal2; @@ -26,135 +23,14 @@ pub mod savage2; /// The Ship pub mod theship; -use crate::protocols::gamespy::GameSpyVersion; -use crate::protocols::quake::QuakeVersion; -use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, TimeoutSettings}; -use crate::protocols::{self, Protocol}; -use crate::GDResult; -use std::net::{IpAddr, SocketAddr}; +pub mod types; +pub use types::*; -/// Definition of a game -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Game { - /// Full name of the game - pub name: &'static str, - /// Default port used by game - pub default_port: u16, - /// The protocol the game's query uses - pub protocol: Protocol, - /// Request settings. - pub request_settings: ExtraRequestSettings, -} +pub mod query; +pub use query::*; #[cfg(feature = "game_defs")] mod definitions; #[cfg(feature = "game_defs")] pub use definitions::GAMES; - -/// Make a query given a game definition -#[inline] -pub fn query(game: &Game, address: &IpAddr, port: Option) -> GDResult> { - query_with_timeout_and_extra_settings(game, address, port, None, None) -} - -/// Make a query given a game definition and timeout settings -#[inline] -pub fn query_with_timeout( - game: &Game, - address: &IpAddr, - port: Option, - timeout_settings: Option, -) -> GDResult> { - query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None) -} - -/// Make a query given a game definition, timeout settings, and extra settings -pub fn query_with_timeout_and_extra_settings( - game: &Game, - address: &IpAddr, - port: Option, - timeout_settings: Option, - extra_settings: Option, -) -> GDResult> { - let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port)); - Ok(match &game.protocol { - Protocol::Valve(engine) => { - protocols::valve::query( - &socket_addr, - *engine, - extra_settings - .or_else(|| Option::from(game.request_settings.clone())) - .map(ExtraRequestSettings::into), - timeout_settings, - ) - .map(Box::new)? - } - Protocol::Gamespy(version) => { - match version { - GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, timeout_settings).map(Box::new)?, - GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, timeout_settings).map(Box::new)?, - GameSpyVersion::Three => { - protocols::gamespy::three::query(&socket_addr, timeout_settings).map(Box::new)? - } - } - } - Protocol::Quake(version) => { - match version { - QuakeVersion::One => protocols::quake::one::query(&socket_addr, timeout_settings).map(Box::new)?, - QuakeVersion::Two => protocols::quake::two::query(&socket_addr, timeout_settings).map(Box::new)?, - QuakeVersion::Three => protocols::quake::three::query(&socket_addr, timeout_settings).map(Box::new)?, - } - } - Protocol::Unreal2 => { - protocols::unreal2::query( - &socket_addr, - &extra_settings - .map(ExtraRequestSettings::into) - .unwrap_or_default(), - timeout_settings, - ) - .map(Box::new)? - } - Protocol::PROPRIETARY(protocol) => { - match protocol { - ProprietaryProtocol::Savage2 => { - savage2::query_with_timeout(address, port, timeout_settings).map(Box::new)? - } - ProprietaryProtocol::TheShip => { - theship::query_with_timeout(address, port, timeout_settings).map(Box::new)? - } - ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?, - ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?, - ProprietaryProtocol::Minecraft(version) => { - match version { - Some(minecraft::Server::Java) => { - minecraft::protocol::query_java( - &socket_addr, - timeout_settings, - extra_settings.map(ExtraRequestSettings::into), - ) - .map(Box::new)? - } - Some(minecraft::Server::Bedrock) => { - minecraft::protocol::query_bedrock(&socket_addr, timeout_settings).map(Box::new)? - } - Some(minecraft::Server::Legacy(group)) => { - minecraft::protocol::query_legacy_specific(*group, &socket_addr, timeout_settings) - .map(Box::new)? - } - None => { - minecraft::protocol::query( - &socket_addr, - timeout_settings, - extra_settings.map(ExtraRequestSettings::into), - ) - .map(Box::new)? - } - } - } - } - } - }) -} diff --git a/crates/lib/src/games/query.rs b/crates/lib/src/games/query.rs new file mode 100644 index 0000000..d37cdd8 --- /dev/null +++ b/crates/lib/src/games/query.rs @@ -0,0 +1,117 @@ +//! Generic query functions + +use std::net::{IpAddr, SocketAddr}; + +use crate::games::types::Game; +use crate::games::{ffow, jc2m, minecraft, savage2, theship}; +use crate::protocols; +use crate::protocols::gamespy::GameSpyVersion; +use crate::protocols::quake::QuakeVersion; +use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, Protocol, TimeoutSettings}; +use crate::GDResult; + +/// Make a query given a game definition +#[inline] +pub fn query(game: &Game, address: &IpAddr, port: Option) -> GDResult> { + query_with_timeout_and_extra_settings(game, address, port, None, None) +} + +/// Make a query given a game definition and timeout settings +#[inline] +pub fn query_with_timeout( + game: &Game, + address: &IpAddr, + port: Option, + timeout_settings: Option, +) -> GDResult> { + query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None) +} + +/// Make a query given a game definition, timeout settings, and extra settings +pub fn query_with_timeout_and_extra_settings( + game: &Game, + address: &IpAddr, + port: Option, + timeout_settings: Option, + extra_settings: Option, +) -> GDResult> { + let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port)); + Ok(match &game.protocol { + Protocol::Valve(engine) => { + protocols::valve::query( + &socket_addr, + *engine, + extra_settings + .or_else(|| Option::from(game.request_settings.clone())) + .map(ExtraRequestSettings::into), + timeout_settings, + ) + .map(Box::new)? + } + Protocol::Gamespy(version) => { + match version { + GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, timeout_settings).map(Box::new)?, + GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, timeout_settings).map(Box::new)?, + GameSpyVersion::Three => { + protocols::gamespy::three::query(&socket_addr, timeout_settings).map(Box::new)? + } + } + } + Protocol::Quake(version) => { + match version { + QuakeVersion::One => protocols::quake::one::query(&socket_addr, timeout_settings).map(Box::new)?, + QuakeVersion::Two => protocols::quake::two::query(&socket_addr, timeout_settings).map(Box::new)?, + QuakeVersion::Three => protocols::quake::three::query(&socket_addr, timeout_settings).map(Box::new)?, + } + } + Protocol::Unreal2 => { + protocols::unreal2::query( + &socket_addr, + &extra_settings + .map(ExtraRequestSettings::into) + .unwrap_or_default(), + timeout_settings, + ) + .map(Box::new)? + } + Protocol::PROPRIETARY(protocol) => { + match protocol { + ProprietaryProtocol::Savage2 => { + savage2::query_with_timeout(address, port, timeout_settings).map(Box::new)? + } + ProprietaryProtocol::TheShip => { + theship::query_with_timeout(address, port, timeout_settings).map(Box::new)? + } + ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?, + ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?, + ProprietaryProtocol::Minecraft(version) => { + match version { + Some(minecraft::Server::Java) => { + minecraft::protocol::query_java( + &socket_addr, + timeout_settings, + extra_settings.map(ExtraRequestSettings::into), + ) + .map(Box::new)? + } + Some(minecraft::Server::Bedrock) => { + minecraft::protocol::query_bedrock(&socket_addr, timeout_settings).map(Box::new)? + } + Some(minecraft::Server::Legacy(group)) => { + minecraft::protocol::query_legacy_specific(*group, &socket_addr, timeout_settings) + .map(Box::new)? + } + None => { + minecraft::protocol::query( + &socket_addr, + timeout_settings, + extra_settings.map(ExtraRequestSettings::into), + ) + .map(Box::new)? + } + } + } + } + } + }) +} diff --git a/crates/lib/src/games/types.rs b/crates/lib/src/games/types.rs new file mode 100644 index 0000000..3868e21 --- /dev/null +++ b/crates/lib/src/games/types.rs @@ -0,0 +1,20 @@ +//! Game related types + +use crate::protocols::types::{ExtraRequestSettings, Protocol}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Definition of a game +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Game { + /// Full name of the game + pub name: &'static str, + /// Default port used by game + pub default_port: u16, + /// The protocol the game's query uses + pub protocol: Protocol, + /// Request settings. + pub request_settings: ExtraRequestSettings, +} diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 7cafba7..b17777c 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -15,7 +15,7 @@ //! //! ## Using a game definition //! ``` -//! use gamedig::games::{GAMES, query}; +//! use gamedig::{GAMES, query}; //! //! let game = GAMES.get("teamfortress2").unwrap(); // Get a game definition, the full list can be found in src/games/mod.rs //! let response = query(game, &"127.0.0.1".parse().unwrap(), None); // None will use the default port @@ -48,5 +48,10 @@ mod utils; pub use errors::*; #[cfg(feature = "games")] pub use games::*; +#[cfg(feature = "games")] +pub use query::*; #[cfg(feature = "services")] pub use services::*; + +// Re-export types needed to call games::query::query in the root +pub use protocols::types::{ExtraRequestSettings, TimeoutSettings}; From ae9a38907fd411524588aa209750f7eb84c75ef8 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 7 Jan 2024 23:15:35 +0200 Subject: [PATCH 38/49] feat(games): add atlas support --- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 1 + crates/lib/src/games/valve.rs | 1 + 4 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index daff5e1..ab5756c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Games: - [Squad](https://store.steampowered.com/app/393380/Squad/) support. - [Savage 2](https://savage2.net/) support. - [Rising World](https://store.steampowered.com/app/324080/Rising_World/) support. +- [ATLAS](https://store.steampowered.com/app/834910/ATLAS/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. diff --git a/GAMES.md b/GAMES.md index 6cf7926..68d2e22 100644 --- a/GAMES.md +++ b/GAMES.md @@ -75,6 +75,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Squad | SQUAD | Valve | | | Savage 2 | SAVAGE2 | Proprietary | | | Rising World | RISINGWORLD | Valve | Query port offset: -1 | +| ATLAS | ATLAS | Valve | Query port offset: 51800 | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 4ac3e8e..ea997ca 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -44,6 +44,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "a2oa" => game!("ARMA 2: Operation Arrowhead", 2304, Protocol::Valve(Engine::new(33930))), "ase" => game!("ARK: Survival Evolved", 27015, Protocol::Valve(Engine::new(346_110))), "asrd" => game!("Alien Swarm: Reactive Drop", 2304, Protocol::Valve(Engine::new(563_560))), + "atlas" => game!("ATLAS", 57561, Protocol::Valve(Engine::new(563_560))), //game_query_mod!(atlas, "ATLAS", Engine::new(834_910), 57561); "avorion" => game!("Avorion", 27020, Protocol::Valve(Engine::new(445_220))), "barotrauma" => game!("Barotrauma", 27016, Protocol::Valve(Engine::new(602_960))), "battalion1944" => game!("Battalion 1944", 7780, Protocol::Valve(Engine::new(489_940))), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index 93266ee..3400c65 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -17,6 +17,7 @@ game_query_mod!( Engine::new(563_560), 2304 ); +game_query_mod!(atlas, "ATLAS", Engine::new(834_910), 57561); game_query_mod!(avorion, "Avorion", Engine::new(445_220), 27020); game_query_mod!( ballisticoverkill, From d9c0a63e8ca3b7dd16b17f35c54b9d01f31c3005 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 7 Jan 2024 21:16:08 +0000 Subject: [PATCH 39/49] Add/Update badge --- .github/badges/node.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index dcc0833..4d9844c 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 13.98% + + Node game coverage: 14.29% @@ -13,8 +13,8 @@ \ No newline at end of file From 1333655d53fa88755d76e09cf775ea65786a7052 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 7 Jan 2024 23:22:17 +0200 Subject: [PATCH 40/49] fix(games): atlas definitions steam app id --- crates/lib/src/games/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index ea997ca..69ff791 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -44,7 +44,7 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "a2oa" => game!("ARMA 2: Operation Arrowhead", 2304, Protocol::Valve(Engine::new(33930))), "ase" => game!("ARK: Survival Evolved", 27015, Protocol::Valve(Engine::new(346_110))), "asrd" => game!("Alien Swarm: Reactive Drop", 2304, Protocol::Valve(Engine::new(563_560))), - "atlas" => game!("ATLAS", 57561, Protocol::Valve(Engine::new(563_560))), //game_query_mod!(atlas, "ATLAS", Engine::new(834_910), 57561); + "atlas" => game!("ATLAS", 57561, Protocol::Valve(Engine::new(834_910))), "avorion" => game!("Avorion", 27020, Protocol::Valve(Engine::new(445_220))), "barotrauma" => game!("Barotrauma", 27016, Protocol::Valve(Engine::new(602_960))), "battalion1944" => game!("Battalion 1944", 7780, Protocol::Valve(Engine::new(489_940))), From 109a3db13e43681bf48d2878085aa6a9797318ba Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 7 Jan 2024 23:34:26 +0200 Subject: [PATCH 41/49] feat(games): add americas army proving grounds support --- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 5 +++++ crates/lib/src/games/valve.rs | 5 +++++ 4 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab5756c..5c9478e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Games: - [Savage 2](https://savage2.net/) support. - [Rising World](https://store.steampowered.com/app/324080/Rising_World/) support. - [ATLAS](https://store.steampowered.com/app/834910/ATLAS/) support. +- [America's Army: Proving Grounds](https://store.steampowered.com/app/203290/Americas_Army_Proving_Grounds/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. diff --git a/GAMES.md b/GAMES.md index 68d2e22..7c43258 100644 --- a/GAMES.md +++ b/GAMES.md @@ -76,6 +76,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Savage 2 | SAVAGE2 | Proprietary | | | Rising World | RISINGWORLD | Valve | Query port offset: -1 | | ATLAS | ATLAS | Valve | Query port offset: 51800 | +| America's Army: Proving Grounds | AAPG | Valve | Query port: 27020. Does not respond to the rules query. | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 69ff791..6272e81 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -39,6 +39,11 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "minecraftlegacy16" => game!("Minecraft (legacy 1.6)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_6))))), "minecraftlegacy14" => game!("Minecraft (legacy 1.4)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::V1_4))))), "minecraftlegacyb18" => game!("Minecraft (legacy b1.8)", 25565, Protocol::PROPRIETARY(ProprietaryProtocol::Minecraft(Some(Server::Legacy(LegacyGroup::VB1_8))))), + "aapg" => game!("America's Army: Proving Grounds", 27020, Protocol::Valve(Engine::new(203_290)), GatheringSettings { + players: true, + rules: false, + check_app_id: true, + }.into_extra()), "alienswarm" => game!("Alien Swarm", 27015, Protocol::Valve(Engine::new(630))), "aoc" => game!("Age of Chivalry", 27015, Protocol::Valve(Engine::new(17510))), "a2oa" => game!("ARMA 2: Operation Arrowhead", 2304, Protocol::Valve(Engine::new(33930))), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index 3400c65..69d6418 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -10,6 +10,11 @@ game_query_mod!( ); game_query_mod!(alienswarm, "Alien Swarm", Engine::new(630), 27015); game_query_mod!(aoc, "Age of Chivalry", Engine::new(17510), 27015); +game_query_mod!(aapg, "America's Army: Proving Grounds", Engine::new(203_290), 27020, GatheringSettings { + players: true, + rules: false, + check_app_id: true, + }); game_query_mod!(ase, "ARK: Survival Evolved", Engine::new(346_110), 27015); game_query_mod!( asrd, From c71e783e1e78d32ba2e9b3330846ff4edb69ef08 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 7 Jan 2024 23:34:46 +0200 Subject: [PATCH 42/49] fix(crate): formatting --- crates/lib/src/games/valve.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index 69d6418..d6ac1e0 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -10,11 +10,17 @@ game_query_mod!( ); game_query_mod!(alienswarm, "Alien Swarm", Engine::new(630), 27015); game_query_mod!(aoc, "Age of Chivalry", Engine::new(17510), 27015); -game_query_mod!(aapg, "America's Army: Proving Grounds", Engine::new(203_290), 27020, GatheringSettings { +game_query_mod!( + aapg, + "America's Army: Proving Grounds", + Engine::new(203_290), + 27020, + GatheringSettings { players: true, rules: false, check_app_id: true, - }); + } +); game_query_mod!(ase, "ARK: Survival Evolved", Engine::new(346_110), 27015); game_query_mod!( asrd, From 12a6c2af58776abb7a65d73c16e993389ce52949 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 7 Jan 2024 21:35:12 +0000 Subject: [PATCH 43/49] Add/Update badge --- .github/badges/node.svg | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index 4d9844c..9da1962 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,20 +1,20 @@ - - Node game coverage: 14.29% + + Node game coverage: 14.6% - + - - + + \ No newline at end of file From 94102d0d7bba42db78061bde20f6dafe45bba12c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sun, 7 Jan 2024 23:44:35 +0200 Subject: [PATCH 44/49] feat(games): add base defense support --- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 5 +++++ crates/lib/src/games/valve.rs | 1 + 4 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9478e..c41da20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Games: - [Rising World](https://store.steampowered.com/app/324080/Rising_World/) support. - [ATLAS](https://store.steampowered.com/app/834910/ATLAS/) support. - [America's Army: Proving Grounds](https://store.steampowered.com/app/203290/Americas_Army_Proving_Grounds/) support. +- [Base Defense](https://store.steampowered.com/app/632730/Base_Defense/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. diff --git a/GAMES.md b/GAMES.md index 7c43258..5331c8d 100644 --- a/GAMES.md +++ b/GAMES.md @@ -77,6 +77,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Rising World | RISINGWORLD | Valve | Query port offset: -1 | | ATLAS | ATLAS | Valve | Query port offset: 51800 | | America's Army: Proving Grounds | AAPG | Valve | Query port: 27020. Does not respond to the rules query. | +| Base Defense | BASEDEFENSE | Valve | Query port: 27015. Does not respond to the rules query. | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 6272e81..284cb5d 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -52,6 +52,11 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "atlas" => game!("ATLAS", 57561, Protocol::Valve(Engine::new(834_910))), "avorion" => game!("Avorion", 27020, Protocol::Valve(Engine::new(445_220))), "barotrauma" => game!("Barotrauma", 27016, Protocol::Valve(Engine::new(602_960))), + "basedefense" => game!("Base Defense", 27015, Protocol::Valve(Engine::new(632_730)), GatheringSettings { + players: true, + rules: false, + check_app_id: true, + }.into_extra()), "battalion1944" => game!("Battalion 1944", 7780, Protocol::Valve(Engine::new(489_940))), "brainbread2" => game!("BrainBread 2", 27015, Protocol::Valve(Engine::new(346_330))), "battlefield1942" => game!("Battlefield 1942", 23000, Protocol::Gamespy(GameSpyVersion::One)), diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index d6ac1e0..18c718c 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -8,6 +8,7 @@ game_query_mod!( Engine::new(33930), 2304 ); +game_query_mod!(basedefense, "Base Defense", Engine::new(632_730), 27015); game_query_mod!(alienswarm, "Alien Swarm", Engine::new(630), 27015); game_query_mod!(aoc, "Age of Chivalry", Engine::new(17510), 27015); game_query_mod!( From b248a7661e7637908ea2e261c41f4d4959eee882 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:31:02 +0000 Subject: [PATCH 45/49] feat: Move ID tests into their own crate with a CLI (#177) * fix: ID tests not in correct directory * refactor: Move game-id test logic into its own crate * id-tests: Add CLI that reads JSON input * id-tests: Update crate docs * Remove node ID test * id-tests: Don't try to parse unneeded info * id-tests: Enable cli feature by default --- Cargo.toml | 2 +- crates/id-tests/Cargo.toml | 29 +++++ .../game_ids.rs => crates/id-tests/src/lib.rs | 115 +----------------- crates/id-tests/src/main.rs | 31 +++++ crates/id-tests/src/utils.rs | 66 ++++++++++ crates/lib/Cargo.toml | 3 +- crates/lib/tests/game_ids.rs | 11 ++ 7 files changed, 144 insertions(+), 113 deletions(-) create mode 100644 crates/id-tests/Cargo.toml rename tests/game_ids.rs => crates/id-tests/src/lib.rs (82%) create mode 100644 crates/id-tests/src/main.rs create mode 100644 crates/id-tests/src/utils.rs create mode 100644 crates/lib/tests/game_ids.rs diff --git a/Cargo.toml b/Cargo.toml index 70ccb3d..76a0c96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/cli", "crates/lib"] +members = ["crates/cli", "crates/lib", "crates/id-tests"] # Edition 2021, uses resolver = 2 resolver = "2" diff --git a/crates/id-tests/Cargo.toml b/crates/id-tests/Cargo.toml new file mode 100644 index 0000000..22b9a90 --- /dev/null +++ b/crates/id-tests/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "gamedig-id-tests" +version = "0.0.1" +edition = "2021" +authors = [ + "rust-GameDig contributors [https://github.com/gamedig/rust-gamedig/contributors]", + "node-GameDig contributors [https://github.com/gamedig/node-gamedig/contributors]", +] +license = "MIT" +description = "Test if IDs match the gamedig rules" +homepage = "https://github.com/gamedig/rust-gamedig/CONTRIBUTING.md#naming" +repository = "https://github.com/gamedig/rust-gamedig" +readme = "README.md" +rust-version = "1.65.0" + +[features] +cli = ["dep:serde_json", "dep:serde"] +default = ["cli"] + +[[bin]] +name = "gamedig-id-tests" +required-features = ["cli"] + +[dependencies] +number_to_words = "0.1" +roman_numeral = "0.1" + +serde_json = { version = "1", optional = true } +serde = { version = "1", optional = true, features = ["derive"] } \ No newline at end of file diff --git a/tests/game_ids.rs b/crates/id-tests/src/lib.rs similarity index 82% rename from tests/game_ids.rs rename to crates/id-tests/src/lib.rs index 75f170f..6cd7d31 100644 --- a/tests/game_ids.rs +++ b/crates/id-tests/src/lib.rs @@ -1,10 +1,7 @@ -#![cfg(all(test, feature = "game_defs"))] +use std::collections::HashMap; -use std::{collections::HashMap, fs, io::Read}; - -use gamedig::GAMES; - -use utils::*; +mod utils; +use utils::{extract_bracketed_suffix, split_on_switch_between_alpha_numeric}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum IDRule { @@ -384,42 +381,9 @@ pub fn test_game_name_rules<'a, I: Iterator>(games: I wrong_ids } -#[test] -fn check_definitions_match_name_rules() { - let wrong = test_game_name_rules(GAMES.entries().map(|(id, game)| (id.to_owned(), game.name))); - assert!(wrong.is_empty()); -} - -#[test] -#[ignore = "Don't test node by default"] -fn check_node_definitions_match_name_rules() { - let mut file = fs::OpenOptions::new() - .read(true) - .open("./node-gamedig/games.txt") - .unwrap(); - - let mut text = String::new(); - file.read_to_string(&mut text).unwrap(); - - let games = text - .split('\n') - .map(|line| line.trim()) - .filter(|line| !line.starts_with('#') && !line.is_empty()) - .filter_map(|line| { - let parts: Vec<_> = line.splitn(3, '|').collect(); - if parts.len() > 1 { - Some((parts[0].split(',').next().unwrap(), parts[1])) - } else { - None - } - }); - - let wrong = test_game_name_rules(games); - assert!(wrong.is_empty()); -} - -fn test_single_game_rule(id: &str, name: &str) -> Vec { test_game_name_rules(std::iter::once((id, name))) } +pub fn test_single_game_rule(id: &str, name: &str) -> Vec { test_game_name_rules(std::iter::once((id, name))) } +#[cfg(test)] mod id_tests { use super::{test_game_name_rules, test_single_game_rule}; #[test] @@ -485,72 +449,3 @@ mod id_tests { assert!(test_single_game_rule("jc3m", "Just Cause 3 - Multiplayer").is_empty()); } } - -mod utils { - /// Split a str when characters swap between being digits and not digits. - pub fn split_on_switch_between_alpha_numeric(text: &str) -> Vec { - if text.is_empty() { - return vec![]; - } - - let mut parts = Vec::with_capacity(text.len()); - let mut current = Vec::with_capacity(text.len()); - - let mut iter = text.chars(); - let c = iter.next().unwrap(); - let mut last_was_numeric = c.is_ascii_digit(); - current.push(c); - - for c in iter { - if c.is_ascii_digit() == last_was_numeric { - current.push(c); - } else { - parts.push(current.iter().collect()); - current.clear(); - current.push(c); - last_was_numeric = !last_was_numeric; - } - } - - parts.push(current.into_iter().collect()); - - parts - } - - #[test] - fn split_correctly() { - assert_eq!( - split_on_switch_between_alpha_numeric("2D45A"), - &["2", "D", "45", "A"] - ); - } - - #[test] - fn split_symbol_broken_numbers() { - let game_name = super::extract_game_parts_from_name("Darkest Hour: Europe '44-'45"); - assert_eq!(game_name.words, &["Darkest", "Hour", "Europe", "4445"]); - } - - /// Extract parts at end of string enclosed in brackets. - pub fn extract_bracketed_suffix(text: &str) -> (&str, Option<&str>) { - if let Some(text) = text.strip_suffix(')') { - if let Some((text, extra)) = text.rsplit_once('(') { - return (text, Some(extra)); - } - } - - (text, None) - } - - #[test] - fn extract_brackets_correctly() { - assert_eq!( - extract_bracketed_suffix("no brackets here"), - ("no brackets here", None) - ); - assert_eq!( - extract_bracketed_suffix("Game name (with protocol here)"), - ("Game name ", Some("with protocol here")) - ); - } -} diff --git a/crates/id-tests/src/main.rs b/crates/id-tests/src/main.rs new file mode 100644 index 0000000..8dfadaa --- /dev/null +++ b/crates/id-tests/src/main.rs @@ -0,0 +1,31 @@ +#![cfg(feature = "cli")] + +use std::collections::HashMap; + +/// Format for input games (the same as used in node-gamedig/lib/games.js). +type GamesInput = HashMap; + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +struct Game { + name: String, +} + +use gamedig_id_tests::test_game_name_rules; + +fn main() { + let games: GamesInput = if let Some(file) = std::env::args_os().skip(1).next() { + let file = std::fs::OpenOptions::new().read(true).open(file).unwrap(); + + serde_json::from_reader(file).unwrap() + } else { + serde_json::from_reader(std::io::stdin().lock()).unwrap() + }; + + let failed = test_game_name_rules( + games + .iter() + .map(|(key, game)| (key.as_str(), game.name.as_str())), + ); + + assert!(failed.is_empty()); +} diff --git a/crates/id-tests/src/utils.rs b/crates/id-tests/src/utils.rs new file mode 100644 index 0000000..71dfe71 --- /dev/null +++ b/crates/id-tests/src/utils.rs @@ -0,0 +1,66 @@ +/// Split a str when characters swap between being digits and not digits. +pub fn split_on_switch_between_alpha_numeric(text: &str) -> Vec { + if text.is_empty() { + return vec![]; + } + + let mut parts = Vec::with_capacity(text.len()); + let mut current = Vec::with_capacity(text.len()); + + let mut iter = text.chars(); + let c = iter.next().unwrap(); + let mut last_was_numeric = c.is_ascii_digit(); + current.push(c); + + for c in iter { + if c.is_ascii_digit() == last_was_numeric { + current.push(c); + } else { + parts.push(current.iter().collect()); + current.clear(); + current.push(c); + last_was_numeric = !last_was_numeric; + } + } + + parts.push(current.into_iter().collect()); + + parts +} + +#[test] +fn split_correctly() { + assert_eq!( + split_on_switch_between_alpha_numeric("2D45A"), + &["2", "D", "45", "A"] + ); +} + +#[test] +fn split_symbol_broken_numbers() { + let game_name = super::extract_game_parts_from_name("Darkest Hour: Europe '44-'45"); + assert_eq!(game_name.words, &["Darkest", "Hour", "Europe", "4445"]); +} + +/// Extract parts at end of string enclosed in brackets. +pub fn extract_bracketed_suffix(text: &str) -> (&str, Option<&str>) { + if let Some(text) = text.strip_suffix(')') { + if let Some((text, extra)) = text.rsplit_once('(') { + return (text, Some(extra)); + } + } + + (text, None) +} + +#[test] +fn extract_brackets_correctly() { + assert_eq!( + extract_bracketed_suffix("no brackets here"), + ("no brackets here", None) + ); + assert_eq!( + extract_bracketed_suffix("Game name (with protocol here)"), + ("Game name ", Some("with protocol here")) + ); +} diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 59cdbc8..563aab8 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -38,8 +38,7 @@ phf = { version = "0.11", optional = true, features = ["macros"] } clap = { version = "4.1.11", optional = true, features = ["derive"] } [dev-dependencies] -number_to_words = "0.1" -roman_numeral = "0.1" +gamedig-id-tests = { path = "../id-tests", no-default-features = true } # Examples [[example]] diff --git a/crates/lib/tests/game_ids.rs b/crates/lib/tests/game_ids.rs new file mode 100644 index 0000000..d7925a9 --- /dev/null +++ b/crates/lib/tests/game_ids.rs @@ -0,0 +1,11 @@ +#![cfg(all(test, feature = "game_defs"))] + +use gamedig::GAMES; + +use gamedig_id_tests::test_game_name_rules; + +#[test] +fn check_definitions_match_name_rules() { + let wrong = test_game_name_rules(GAMES.entries().map(|(id, game)| (id.to_owned(), game.name))); + assert!(wrong.is_empty()); +} From a3bc8b79e5c4738c0be8971e53dff0edf2f9f26c Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Thu, 11 Jan 2024 01:48:05 +0200 Subject: [PATCH 46/49] feat(games): add zombie panic: source support --- CHANGELOG.md | 1 + GAMES.md | 1 + crates/lib/src/games/definitions.rs | 1 + crates/lib/src/games/valve.rs | 1 + 4 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c41da20..2542c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Games: - [ATLAS](https://store.steampowered.com/app/834910/ATLAS/) support. - [America's Army: Proving Grounds](https://store.steampowered.com/app/203290/Americas_Army_Proving_Grounds/) support. - [Base Defense](https://store.steampowered.com/app/632730/Base_Defense/) support. +- [Zombie Panic: Source](https://store.steampowered.com/app/17500/Zombie_Panic_Source/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. diff --git a/GAMES.md b/GAMES.md index 5331c8d..a2ab39a 100644 --- a/GAMES.md +++ b/GAMES.md @@ -78,6 +78,7 @@ Beware of the `Notes` column, as it contains information about query port offset | ATLAS | ATLAS | Valve | Query port offset: 51800 | | America's Army: Proving Grounds | AAPG | Valve | Query port: 27020. Does not respond to the rules query. | | Base Defense | BASEDEFENSE | Valve | Query port: 27015. Does not respond to the rules query. | +| Zombie Panic: Source | ZPS | Valve | Query port: 27015. | ## Planned to add support: _ diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index 284cb5d..df5f748 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -131,4 +131,5 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "redorchestra" => game!("Red Orchestra", 7759, Protocol::Unreal2), "unrealtournament2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2), "unrealtournament2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2), + "zps" => game!("Zombie Panic: Source", 27015, Protocol::Valve(Engine::new(17_500))), }; diff --git a/crates/lib/src/games/valve.rs b/crates/lib/src/games/valve.rs index 18c718c..d70a030 100644 --- a/crates/lib/src/games/valve.rs +++ b/crates/lib/src/games/valve.rs @@ -148,3 +148,4 @@ game_query_mod!( } ); game_query_mod!(vrising, "V Rising", Engine::new(1_604_030), 27016); +game_query_mod!(zps, "Zombie Panic: Source", Engine::new(17_500), 27015); From ba92466ae15e21d719bc37be11accd5ec84ab0d3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 10 Jan 2024 23:48:39 +0000 Subject: [PATCH 47/49] Add/Update badge --- .github/badges/node.svg | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index 9da1962..601625f 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,20 +1,20 @@ - - Node game coverage: 14.6% + + Node game coverage: 21.05% - + - - + + \ No newline at end of file From 07de5168f42a666182b00380e0234d73a2880975 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:53:40 +0000 Subject: [PATCH 48/49] Add support for Mindustry (#178) * buffer: Add UTF8LengthPrefixed string decoder * games: Use expression for default port This allows us to refer to constants for the default ports if we want to (literals will still work). * games: Add support for mindustry --- CHANGELOG.md | 1 + crates/lib/src/buffer.rs | 49 ++++++++++ crates/lib/src/games/definitions.rs | 5 +- crates/lib/src/games/mindustry/mod.rs | 25 +++++ crates/lib/src/games/mindustry/protocol.rs | 58 +++++++++++ crates/lib/src/games/mindustry/types.rs | 108 +++++++++++++++++++++ crates/lib/src/games/mod.rs | 2 + crates/lib/src/games/query.rs | 3 +- crates/lib/src/protocols/types.rs | 3 + 9 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 crates/lib/src/games/mindustry/mod.rs create mode 100644 crates/lib/src/games/mindustry/protocol.rs create mode 100644 crates/lib/src/games/mindustry/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2542c91..fc4dd53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Games: - [Zombie Panic: Source](https://store.steampowered.com/app/17500/Zombie_Panic_Source/) support. - Added a valve protocol query example. - Made all of Just Cause 2: Multiplayer Response and Player fields public. +- [Mindustry](https://mindustrygame.github.io/) support. Protocols: - Added the unreal2 protocol and its associated games: Darkest Hour, Devastation, Killing Floor, Red Orchestra, Unreal Tournament 2003, Unreal Tournament 2004 (by @Douile). diff --git a/crates/lib/src/buffer.rs b/crates/lib/src/buffer.rs index 420be8b..1a0c0aa 100644 --- a/crates/lib/src/buffer.rs +++ b/crates/lib/src/buffer.rs @@ -361,6 +361,55 @@ impl StringDecoder for Utf8Decoder { } } +/// A decoder for UTF-8 encoded strings prefixed by a single byte denoting the +/// string's length. +/// +/// This decoder uses a single null byte (`0x00`) as the default delimiter. +pub struct Utf8LengthPrefixedDecoder; + +impl StringDecoder for Utf8LengthPrefixedDecoder { + type Delimiter = [u8; 1]; + + const DELIMITER: Self::Delimiter = [0x00]; + + /// Decodes a UTF-8 string from the given data, updating the cursor position + /// accordingly. + fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult { + // Find the maximum length of the string + let length = *data + .first() + .ok_or(PacketBad.context("Length of string not found"))?; + + // Find the position of the delimiter in the data. If the delimiter is not + // found, the length is returned. + let position = data + // Create an iterator over the data. + .iter() + .skip(1) + .take(length as usize) + // Find the position of the delimiter + .position(|&b| b == delimiter.as_ref()[0]) + // If the delimiter is not found, use the whole data slice. + .unwrap_or(length as usize); + + // Convert the data until the found position into a UTF-8 string. + let result = std::str::from_utf8( + // Take a slice of data until the position. + &data[1 .. position + 1] + ) + // If the data cannot be converted into a UTF-8 string, return an error + .map_err(|e| PacketBad.context(e))? + // Convert the resulting &str into a String + .to_owned(); + + // Update the cursor position + // The +1 is to skip t length + *cursor += position + 1; + + Ok(result) + } +} + /// A decoder for UTF-16 encoded strings. /// /// This decoder uses a pair of null bytes (`0x00, 0x00`) as the default diff --git a/crates/lib/src/games/definitions.rs b/crates/lib/src/games/definitions.rs index df5f748..18c736d 100644 --- a/crates/lib/src/games/definitions.rs +++ b/crates/lib/src/games/definitions.rs @@ -9,7 +9,7 @@ use crate::protocols::valve::GatheringSettings; use phf::{phf_map, Map}; macro_rules! game { - ($name: literal, $default_port: literal, $protocol: expr) => { + ($name: literal, $default_port: expr, $protocol: expr) => { game!( $name, $default_port, @@ -18,7 +18,7 @@ macro_rules! game { ) }; - ($name: literal, $default_port: literal, $protocol: expr, $extra_request_settings: expr) => { + ($name: literal, $default_port: expr, $protocol: expr, $extra_request_settings: expr) => { Game { name: $name, default_port: $default_port, @@ -132,4 +132,5 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "unrealtournament2003" => game!("Unreal Tournament 2003", 7758, Protocol::Unreal2), "unrealtournament2004" => game!("Unreal Tournament 2004", 7778, Protocol::Unreal2), "zps" => game!("Zombie Panic: Source", 27015, Protocol::Valve(Engine::new(17_500))), + "mindustry" => game!("Mindustry", crate::games::mindustry::DEFAULT_PORT, Protocol::PROPRIETARY(ProprietaryProtocol::Mindustry)), }; diff --git a/crates/lib/src/games/mindustry/mod.rs b/crates/lib/src/games/mindustry/mod.rs new file mode 100644 index 0000000..dc3fb7c --- /dev/null +++ b/crates/lib/src/games/mindustry/mod.rs @@ -0,0 +1,25 @@ +//! Mindustry game ping (v146) +//! +//! [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L225-L259) + +use std::{net::IpAddr, net::SocketAddr}; + +use crate::{GDResult, TimeoutSettings}; + +use self::types::ServerData; + +pub mod types; + +pub mod protocol; + +/// Default mindustry server port +/// +/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/Vars.java#L141-L142) +pub const DEFAULT_PORT: u16 = 6567; + +/// Query a mindustry server. +pub fn query(ip: &IpAddr, port: Option, timeout_settings: &Option) -> GDResult { + let address = SocketAddr::new(*ip, port.unwrap_or(DEFAULT_PORT)); + + protocol::query_with_retries(&address, timeout_settings) +} diff --git a/crates/lib/src/games/mindustry/protocol.rs b/crates/lib/src/games/mindustry/protocol.rs new file mode 100644 index 0000000..a42d69a --- /dev/null +++ b/crates/lib/src/games/mindustry/protocol.rs @@ -0,0 +1,58 @@ +use std::net::SocketAddr; + +use crate::{ + buffer::{self, Buffer}, + socket::{Socket, UdpSocket}, + utils, + GDResult, + TimeoutSettings, +}; + +use super::types::ServerData; + +/// Mindustry max datagram packet size. +pub const MAX_BUFFER_SIZE: usize = 500; + +/// Send a ping packet. +/// +/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L248) +pub fn send_ping(socket: &mut UdpSocket) -> GDResult<()> { socket.send(&[-2i8 as u8, 1i8 as u8]) } + +/// Parse server data. +/// +/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135) +pub fn parse_server_data( + buffer: &mut Buffer, +) -> GDResult { + Ok(ServerData { + host: buffer.read_string::(None)?, + map: buffer.read_string::(None)?, + players: buffer.read()?, + wave: buffer.read()?, + version: buffer.read()?, + version_type: buffer.read_string::(None)?, + gamemode: buffer.read::()?.try_into()?, + player_limit: buffer.read()?, + description: buffer.read_string::(None)?, + mode_name: buffer.read_string::(None).ok(), + }) +} + +/// Query a Mindustry server (without retries). +pub fn query(address: &SocketAddr, timeout_settings: &Option) -> GDResult { + let mut socket = UdpSocket::new(address, timeout_settings)?; + + send_ping(&mut socket)?; + + let socket_data = socket.receive(Some(MAX_BUFFER_SIZE))?; + let mut buffer = Buffer::new(&socket_data); + + parse_server_data::(&mut buffer) +} + +/// Query a Mindustry server. +pub fn query_with_retries(address: &SocketAddr, timeout_settings: &Option) -> GDResult { + let retries = TimeoutSettings::get_retries_or_default(timeout_settings); + + utils::retry_on_timeout(retries, || query(address, timeout_settings)) +} diff --git a/crates/lib/src/games/mindustry/types.rs b/crates/lib/src/games/mindustry/types.rs new file mode 100644 index 0000000..c1e84f4 --- /dev/null +++ b/crates/lib/src/games/mindustry/types.rs @@ -0,0 +1,108 @@ +use crate::{ + protocols::types::{CommonResponse, GenericResponse}, + GDErrorKind, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Mindustry sever data +/// +/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135) +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct ServerData { + pub host: String, + pub map: String, + pub players: i32, + pub wave: i32, + pub version: i32, + pub version_type: String, + pub gamemode: GameMode, + pub player_limit: i32, + pub description: String, + pub mode_name: Option, +} + +/// Mindustry game mode +/// +/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/game/Gamemode.java) +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub enum GameMode { + Survival, + Sandbox, + Attack, + PVP, + Editor, +} + +impl TryFrom for GameMode { + type Error = GDErrorKind; + fn try_from(value: u8) -> Result { + use GameMode::*; + Ok(match value { + 0 => Survival, + 1 => Sandbox, + 2 => Attack, + 3 => PVP, + 4 => Editor, + _ => return Err(GDErrorKind::TypeParse), + }) + } +} + +impl GameMode { + fn as_str(&self) -> &'static str { + use GameMode::*; + match self { + Survival => "survival", + Sandbox => "sandbox", + Attack => "attack", + PVP => "pvp", + Editor => "editor", + } + } +} + +impl CommonResponse for ServerData { + fn as_original(&self) -> GenericResponse { GenericResponse::Mindustry(self) } + + fn players_online(&self) -> u32 { self.players.try_into().unwrap_or(0) } + fn players_maximum(&self) -> u32 { self.player_limit.try_into().unwrap_or(0) } + + fn game_mode(&self) -> Option<&str> { Some(self.gamemode.as_str()) } + + fn map(&self) -> Option<&str> { Some(&self.map) } + fn description(&self) -> Option<&str> { Some(&self.description) } +} + +#[cfg(test)] +mod test { + use crate::protocols::types::CommonResponse; + + use super::ServerData; + + #[test] + fn common_impl() { + let data = ServerData { + host: String::from("host"), + map: String::from("map"), + players: 5, + wave: 2, + version: 142, + version_type: String::from("steam"), + gamemode: super::GameMode::PVP, + player_limit: 20, + description: String::from("description"), + mode_name: Some(String::from("campaign")), + }; + + let common: &dyn CommonResponse = &data; + + assert_eq!(common.players_online(), 5); + assert_eq!(common.players_maximum(), 20); + assert_eq!(common.game_mode(), Some("pvp")); + assert_eq!(common.map(), Some("map")); + assert_eq!(common.description(), Some("description")); + } +} diff --git a/crates/lib/src/games/mod.rs b/crates/lib/src/games/mod.rs index 32467b5..5088ffd 100644 --- a/crates/lib/src/games/mod.rs +++ b/crates/lib/src/games/mod.rs @@ -16,6 +16,8 @@ pub mod battalion1944; pub mod ffow; /// Just Cause 2: Multiplayer pub mod jc2m; +/// Mindustry +pub mod mindustry; /// Minecraft pub mod minecraft; /// Savage 2 diff --git a/crates/lib/src/games/query.rs b/crates/lib/src/games/query.rs index d37cdd8..2d2bf9c 100644 --- a/crates/lib/src/games/query.rs +++ b/crates/lib/src/games/query.rs @@ -3,7 +3,7 @@ use std::net::{IpAddr, SocketAddr}; use crate::games::types::Game; -use crate::games::{ffow, jc2m, minecraft, savage2, theship}; +use crate::games::{ffow, jc2m, mindustry, minecraft, savage2, theship}; use crate::protocols; use crate::protocols::gamespy::GameSpyVersion; use crate::protocols::quake::QuakeVersion; @@ -84,6 +84,7 @@ pub fn query_with_timeout_and_extra_settings( } ProprietaryProtocol::FFOW => ffow::query_with_timeout(address, port, timeout_settings).map(Box::new)?, ProprietaryProtocol::JC2M => jc2m::query_with_timeout(address, port, timeout_settings).map(Box::new)?, + ProprietaryProtocol::Mindustry => mindustry::query(address, port, &timeout_settings).map(Box::new)?, ProprietaryProtocol::Minecraft(version) => { match version { Some(minecraft::Server::Java) => { diff --git a/crates/lib/src/protocols/types.rs b/crates/lib/src/protocols/types.rs index 182d594..7986769 100644 --- a/crates/lib/src/protocols/types.rs +++ b/crates/lib/src/protocols/types.rs @@ -19,6 +19,7 @@ pub enum ProprietaryProtocol { FFOW, JC2M, Savage2, + Mindustry, } /// Enumeration of all valid protocol types @@ -42,6 +43,8 @@ pub enum GenericResponse<'a> { Valve(&'a valve::Response), Unreal2(&'a unreal2::Response), #[cfg(feature = "games")] + Mindustry(&'a crate::games::mindustry::types::ServerData), + #[cfg(feature = "games")] Minecraft(minecraft::VersionedResponse<'a>), #[cfg(feature = "games")] TheShip(&'a crate::games::theship::Response), From 0f9bada4f38788b47b756858b954946dd6696035 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 17 Jan 2024 13:54:10 +0000 Subject: [PATCH 49/49] Add/Update badge --- .github/badges/node.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/badges/node.svg b/.github/badges/node.svg index 601625f..490c822 100644 --- a/.github/badges/node.svg +++ b/.github/badges/node.svg @@ -1,5 +1,5 @@ - - Node game coverage: 21.05% + + Node game coverage: 20.99% @@ -13,8 +13,8 @@ \ No newline at end of file