diff --git a/CHANGELOG.md b/CHANGELOG.md
index 721af3c..c84ab69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## 5.X.Y
* Feat: Replaced `punycode` package usage with `url.domainToASCII` (#630).
* Feat: World of Padman (2007) - Added support (#636)
+* Feat: Satisfactory - Added support (By @Smidy13 #645)
* Feat: Update Soldat protocol (#642)
* Feat: TOXIKK (2016) - Added support (#641)
diff --git a/GAMES_LIST.md b/GAMES_LIST.md
index ee78fb0..c9ec156 100644
--- a/GAMES_LIST.md
+++ b/GAMES_LIST.md
@@ -257,6 +257,7 @@
| rune | Rune | |
| rust | Rust | [Valve Protocol](#valve) |
| s2ats | Savage 2: A Tortured Soul | |
+| satisfactory | Satisfactory | [Notes](#satisfactory) |
| sdtd | 7 Days to Die | [Valve Protocol](#valve) |
| serioussam | Serious Sam | |
| serioussam2 | Serious Sam 2 | |
@@ -492,5 +493,9 @@ EOS does not provide players data.
Palworld support can be unstable, the devs mention the api is currently experimental.
To query Palworld servers, the `RESTAPIEnabled` setting must be `True` in the configuration file, and you need to pass the `username` (currently always `admin`) and the `adminpassword` (from the server config) as the `password` parameter.
+### Satisfactory
+Satisfactory servers unless specified use self-signed certificates for the HTTPS API. If you are using a self-signed certificate you will need to set the `rejectUnauthorized` flag in options to `false` in order to connect.
+For more information on setting a user certificate refer to the [Satisfactory Dedicated server/HTTPS API wiki documentation](https://satisfactory.wiki.gg/wiki/Dedicated_servers/HTTPS_API).
+
### Soldat
Requires `Allow_Download` and `Logging` to be `1` in the server config.
diff --git a/lib/games.js b/lib/games.js
index ddfe220..d45c4be 100644
--- a/lib/games.js
+++ b/lib/games.js
@@ -2546,6 +2546,17 @@ export const games = {
old_id: '7d2d'
}
},
+ satisfactory: {
+ name: 'Satisfactory',
+ release_year: 2019,
+ options: {
+ port: 7777,
+ protocol: 'satisfactory'
+ },
+ extra: {
+ doc_notes: 'satisfactory'
+ }
+ },
spaceengineers: {
name: 'Space Engineers',
release_year: 2019,
diff --git a/protocols/index.js b/protocols/index.js
index cd54f12..d1ccd4b 100644
--- a/protocols/index.js
+++ b/protocols/index.js
@@ -39,7 +39,9 @@ import quake2 from './quake2.js'
import quake3 from './quake3.js'
import rfactor from './rfactor.js'
import samp from './samp.js'
+import satisfactory from './satisfactory.js'
import savage2 from './savage2.js'
+import soldat from './soldat.js'
import starmade from './starmade.js'
import starsiege from './starsiege.js'
import teamspeak2 from './teamspeak2.js'
@@ -63,13 +65,11 @@ import xonotic from './xonotic.js'
import altvmp from './altvmp.js'
import vintagestorymaster from './vintagestorymaster.js'
import vintagestory from './vintagestory.js'
-import soldat from './soldat.js'
export {
armagetron, ase, asa, assettocorsa, battlefield, buildandshoot, cs2d, discord, doom3, eco, epic, factorio, farmingsimulator, ffow,
fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, gtasao, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft,
minecraftbedrock, minecraftvanilla, minetest, mumble, mumbleping, nadeo, openttd, palworld, quake1, quake2, quake3, rfactor, ragemp, samp,
- savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, toxikk, tribes1, tribes1master, unreal2, ut3, valve,
- vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster,
- vintagestory, soldat
+ satisfactory, soldat, savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, toxikk, tribes1, tribes1master, unreal2, ut3, valve,
+ vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster, vintagestory
}
diff --git a/protocols/satisfactory.js b/protocols/satisfactory.js
new file mode 100644
index 0000000..56beace
--- /dev/null
+++ b/protocols/satisfactory.js
@@ -0,0 +1,82 @@
+import Core from './core.js'
+
+export default class satisfactory extends Core {
+ constructor () {
+ super()
+
+ // Don't use the tcp ping probing
+ this.usedTcp = true
+
+ }
+
+ async run (state) {
+
+ /**
+ * To get information about the Satisfactory game server, you need to first obtain a client authenticationToken.
+ * https://satisfactory.wiki.gg/wiki/Dedicated_servers/HTTPS_API
+ */
+
+ const tokenRequestJson = {
+ function: 'PasswordlessLogin',
+ data: {
+ MinimumPrivilegeLevel: 'Client'
+ }
+ }
+
+ const queryJson = {
+ function: 'QueryServerState'
+ }
+
+ let headers = {
+ 'Content-Type': 'application/json'
+ }
+
+ /**
+ * Satisfactory servers unless specified use self-signed certificates for the HTTPS API.
+ * Because of this we default the `rejectUnauthorized` flag to `false` unless set.
+ * For more information see GAMES_LIST.md
+ */
+ if (!this.options.rejectUnauthorized) this.options.rejectUnauthorized = false
+
+ let tokenRequestResponse = await this.queryInfo(tokenRequestJson, headers)
+
+ headers.Authorization = `Bearer ${tokenRequestResponse.data.authenticationToken}`
+
+ let queryResponse = await this.queryInfo(queryJson, headers)
+
+ /**
+ * Satisfactory API cannot pull Server Name at the moment, see QA and vote for fix here
+ * https://questions.satisfactorygame.com/post/66ebebad772a987f4a8b9ef8
+ */
+
+ state.numplayers = queryResponse.data.serverGameState.numConnectedPlayers
+ state.maxplayers = queryResponse.data.serverGameState.playerLimit
+ state.raw = queryResponse
+
+ }
+
+ async queryInfo (json, headers) {
+
+ const url = `https://${this.options.host}:${this.options.port}/api/v1/`
+
+ this.logger.debug(`POST: ${url}`)
+
+ const response = await this.request({
+ url,
+ json,
+ headers,
+ method: 'POST',
+ responseType: 'json',
+ https: {
+ minVersion: 'TLSv1.2',
+ rejectUnauthorized: this.options.rejectUnauthorized
+ }
+ })
+
+ if (response.data == null) {
+ throw new Error('Unable to retrieve data from server')
+ } else {
+ return response
+ }
+ }
+}