diff --git a/README.md b/README.md
index c0f07ea..7a56b7c 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,39 @@
## openspy-client
-openspy-client is a universal modification for old PC games to replace the now-defunct GameSpy Arcade service with an open-source alternative.
+**openspy-client** is a universal modification for old PC games to replace the now-defunct GameSpy Arcade service with an open-source alternative.
This allows the multiplayer feature of those games to be used again.
-The openspy client module is unobtrusive, meaning no game files need to be altered. It is loaded into the game's memory and redirects the requests from GameSpy to OpenSpy on the fly. It is very light and does not carry any performance penalty.
+The **openspy-client** module is unobtrusive, meaning no game files need to be altered. It is loaded into the game's memory and redirects the requests from GameSpy to OpenSpy on the fly. It is very light and does not carry any performance penalty.
-On this early stage, only a few games are listed as supported. However, due to the generic implementation, many games should be already supported.
+In this early stage, only a few games are listed as supported. However, due to the generic implementation, many games should be already supported.
-Help us by [reporting the games you found working](https://github.com/anzz1/openspy-client/issues/new?labels=working+game&template=report-working-game.yml&title=%5Bworking-game%5D+GAME+NAME) so we can add them to the list and [reporting the ones which don't](https://github.com/anzz1/openspy-client/issues/new?labels=game+request&template=request-for-game-support.yml&title=%5Bgame-request%5D+GAME+NAME) so we can work on adding the support.
+Help us by [reporting the games you found working](https://github.com/anzz1/openspy-client/issues/new?labels=working+game&template=report-working-game.yml&title=%5Bworking-game%5D+GAME+NAME) so we can add them to the list, and [reporting the ones which don't](https://github.com/anzz1/openspy-client/issues/new?labels=game+request&template=request-for-game-support.yml&title=%5Bgame-request%5D+GAME+NAME) so we can work on adding the support.
-Aim is to eventually support 100% of GameSpy Arcade titles.
+The goal is to eventually support 100% of GameSpy Arcade titles.
-Compatible with Windows XP / Vista / 7 / 8 / 8.1 / 10 / 11 and Server 2003 / 2003 R2 / 2008 / 2008 R2 / 2012 / 2012 R2 / 2016 / 2019 / 2022
+Compatible with Windows XP / Vista / 7 / 8 / 8.1 / 10 / 11 and Server 2003 / 2003 R2 / 2008 / 2008 R2 / 2012 / 2012 R2 / 2016 / 2019 / 2022.
## How to install
-1. Download the [latest release](https://github.com/anzz1/openspy-client/releases/latest/download/openspy.zip)
+1. Download the [latest release](https://github.com/anzz1/openspy-client/releases/latest/download/openspy.zip).
-2. Install
- 1. 32-bit games
+2. Install:
+ 1. For 32-bit games:
1. Extract the `openspy.x86.dll` file to the game folder, next to the game executable.
- 2. Rename the `openspy.x86.dll` to what is listed in the [supported games list](#supported-games-list)
+ 2. Rename the `openspy.x86.dll` to the name listed in the [supported games list](#supported-games-list).
- 2. 64-bit games
+ 2. For 64-bit games:
1. Extract the `openspy.x64.dll` file to the game folder, next to the game executable.
- 2. Rename the `openspy.x64.dll` to what is listed in the [supported games list](#supported-games-list)
+ 2. Rename the `openspy.x64.dll` to the name listed in the [supported games list](#supported-games-list).
-3. Play !
+3. Play!
## Account creation
-If the game requires an account to play, you can create one in game.
-If the game does not have an option to create an account, you can [create one here](http://account.openspy.net/).
+If the game requires an account to play, you can create one in-game.
-Please note that the GameSpy protocol is old and does not meet modern password encryption security standards, so do not use a password which you have used elsewhere.
+Please note that the GameSpy protocol is old and does not meet modern password encryption security standards, so **do not** use a password which you have used elsewhere.
## Supported games list
@@ -96,6 +95,8 @@ Please note that the GameSpy protocol is old and does not meet modern password e
| Serious Sam 2 |
| `dsound.dll` | n/a | |
| Sniper Elite |
| `dinput8.dll` | n/a | |
| Startopia |
| `dsound.dll` | n/a | |
+| Star Trek: Armada II |
| `dsound.dll` | n/a | [1] Latest [v1.1 update](http://armadafiles.com/files/armada-2/official-releases/patches/star-trek-armada-ii-patch-11/details) is recommended **RETAIL**
[2] Also works with free [Demo](http://armadafiles.com/files/armada-2/official-releases/demo/star-trek-armada-ii-demo/details) |
+| Star Trek: Armada II Demo |
| `dsound.dll` | n/a | [1] [Free download](http://armadafiles.com/files/armada-2/official-releases/demo/star-trek-armada-ii-demo/details) |
| Star Trek: Bridge Commander |
| `dinput.dll` | n/a | |
| Star Trek: Legacy |
| `dinput8.dll` | n/a | [1] Latest [v1.2 update](https://taco.cab/files/games/stlegacy/STLegacyv1.2.zip) is recommended |
| Star Wars: Republic Commando |
| `version.dll` | n/a | |
@@ -141,32 +142,33 @@ Please note that the GameSpy protocol is old and does not meet modern password e
| [report unsupported game](https://github.com/anzz1/openspy-client/issues/new?labels=game+request&template=request-for-game-support.yml&title=%5Bgame-request%5D+GAME+NAME) |
## Remarks
-To uninstall, simply delete the `openspy.dll` file.
+To uninstall, simply delete the renamed `openspy.dll` file from the game directory.
-If a game is not listed, you can try renaming it as the different variations openspy-client currently supports:
+If a game is not listed, you can try renaming it as the different variations **openspy-client** currently supports:
- `dinput.dll`
- `dinput8.dll`
- `dsound.dll`
- `version.dll`
- `winmm.dll`
-Please report back with your findings to help us fill the supported games list.
+Please report back with your findings to help us add to the [supported games list](#supported-games-list).
-Generally it does not matter how the openspy-client module is loaded into the game. This means that in addition to the variations supported, you can also use any other method to load the module into the game.
+Generally, it does not matter how the **openspy-client** module is loaded into the game. This means that in addition to the DLL variations supported, you can also use any other method to load the module into the game.
For example, if you use an [ASI loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) to load mods like [Widescreen Fix](https://thirteenag.github.io/wfp) for your game, you can simply rename the OpenSpy module to `openspy.asi` and place it next to the `WideScreenFix.asi` and it will be loaded that way.
---
-:earth_americas: [Web](https://beta.openspy.net/)
-
[Discord](http://discord.gg/sMaWdbt)
+🌎 [Web](https://openspy.net/)
-This component is a part of the [OpenSpy](https://beta.openspy.net/) project
+
[Discord](http://discord.gg/sMaWdbt)
-- [openspy-core-v2](https://github.com/chc/openspy-core-v2)
-- [openspy-web-backend](https://github.com/chc/openspy-web-backend)
-- [openspy-natneg-helper](https://github.com/chc/openspy-natneg-helper)
-- [openspy-discord-bot](https://github.com/chc/openspy-discord-bot)
+This component is a part of the [OpenSpy](https://openspy.net/) project:
+
+- [openspy-core](https://github.com/openspy/openspy-core)
+- [openspy-web-backend](https://github.com/openspy/openspy-web-backend)
+- [openspy-natneg-helper](https://github.com/openspy/natneg-helper)
+- [openspy-discord-bot](https://github.com/openspy/openspy-discord-bot)
- openspy-client
diff --git a/dllmain.c b/dllmain.c
index 4b469b9..2452414 100644
--- a/dllmain.c
+++ b/dllmain.c
@@ -36,6 +36,7 @@
#include "include/game_thug2.h"
#include "include/game_rof.h"
#include "include/game_hd2.h"
+ #include "include/game_sta2.h"
#include "include/game_stbc.h"
#include "include/game_bfme2.h"
#include "include/game_blood2.h"
@@ -265,6 +266,8 @@ int __stdcall DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID lpReserved) {
patch_rof();
} else if (!__stricmp(p, "hd2.exe") || !__stricmp(p, "hd2ds.exe") || !__stricmp(p, "hd2_sabresquadron.exe") || !__stricmp(p, "hd2ds_sabresquadron.exe")) { // Hidden & Dangerous 2
patch_hd2();
+ } else if (!__stricmp(p, "Armada2.exe") || !__stricmp(p, "Armada2Demo.exe")) { // Star Trek: Armada II
+ patch_sta2();
} else if (!__stricmp(p, "stbc.exe")) { // Star Trek - Bridge Commander
patch_stbc();
} else if (!__stricmp(p, "blood2.exe") || !__stricmp(p, "blood2sv.exe") || !__stricmp(p, "b2nmsrv.exe")) { // Blood II - The Chosen
diff --git a/include/game_sta2.h b/include/game_sta2.h
new file mode 100644
index 0000000..c1ad7c0
--- /dev/null
+++ b/include/game_sta2.h
@@ -0,0 +1,129 @@
+// game_sta2.h
+
+#ifndef __GAME_STA2_H
+#define __GAME_STA2_H
+
+/* According to the game's "NetHelp.txt", the following ports are required:
+
+ DirectPlay 8 Ports
+
+ 2300..2400 UDP inbound and outbound
+ 6073 TCP inbound and outbound
+
+ GameSpy Ports
+
+ 80 TCP, outbound
+ 6500 UDP, inbound
+ 6667 TCP, outbound
+ 13139 UDP, inbound and outbound
+ 27900 UDP, inbound and outbound
+ 28900 TCP, outbound
+ 29900 TCP, outbound
+ 29901 TCP, outbound
+
+The documentation stating port 6073 as TCP seems to be an error. 6073/udp gets dynamically mapped by picoupnp,
+and according to Microsoft documentation [MC-DPLHP], the standard assignment for directplay8 uses 6073/udp.
+
+NAT-PMP is used to map the GameSpy ports, and UPNP is used to map the DirectPlay ports, so both must be enabled on the host's router.
+Clients do not seem to need to have port forwarding in place to be able to join a game and play.
+
+Port 27900 is stated to be required but is not automatically mapped, and does not seem to be required for multiplayer functionality.
+Even with a manually created NAT rule for 27900/udp, there are no hits.
+
+~ Ligushka 2/7/2025
+*/
+
+#include "include/global.h"
+#include "iathook/iathook.h"
+
+int __stdcall hk_bind(SOCKET s, struct sockaddr* addr, int namelen);
+LPHOSTENT __stdcall hk_gethostbyname(const char* name);
+
+LPHOSTENT __stdcall sta2_hk_gethostbyname(const char* name) {
+ // The game's "gamespy.cfg" specifies this server address several times, to "re-direct through activision".
+ // Any other GameSpy references are handled by openspy-client's globally hooked gethostbyname function.
+ if (name && !__strcmp(name, "STArmada2.activision.com"))
+ return ogethostbyname("master.openspy.net");
+ // Speed up the lobby connection by avoiding a DNS lookup timeout by redirecting away from defunct MOTD domain to OpenSpy's.
+ else if (name && !__strcmp(name, "www.armada2.com"))
+ return ogethostbyname("motd.openspy.net");
+ else
+ return hk_gethostbyname(name);
+}
+
+__forceinline static void sta2_hook_gs(HMODULE mod) {
+ HOOK_FUNC(0, gethostbyname, sta2_hk_gethostbyname, "ws2_32.dll", 52, TRUE); // the only call to gethostbyname in armada2.exe is in Transport::GetLocalIPAddresses, so this may not be needed
+ HOOK_FUNC(mod, gethostbyname, sta2_hk_gethostbyname, "ws2_32.dll", 52, TRUE);
+ HOOK_FUNC(mod, bind, hk_bind, "ws2_32.dll", 2, TRUE);
+}
+
+// DirectPlay Voice initialization crashes the game on modern operating systems, just like Star Trek: Legacy.
+// Even the GOG version still needs this, since their bypasses are only for TCP/IP LAN games.
+__forceinline static void sta2_disable_net_voice(HMODULE mod) {
+
+ // in Network::DirectPlay::GameCreate, nop call to Network::Lobby::GetVoiceSettings and jz -> jmp past the voice check
+ BYTE search[] = { 0xE8, 0xF8, 0x04, 0x01, 0x00, 0x8A, 0x48, 0x28 };
+ BYTE patch[] = { 0xE9, 0xA4, 0x00, 0x00 };
+
+ // nop call to Network::Lobby::GetVoiceSettings
+ WORD search2[] = { _ANY, _ANY, _ANY, _ANY, 0x00, 0x8A, 0x48, 0x28, 0x51 };
+
+ // jnz -> jmp in Network::Lobby::GetVoiceSettings
+ BYTE search3[] = { 0x85, 0xC0, 0x75, 0x34, 0x56, 0x6A, 0x44, 0xE8 };
+ BYTE patch3[] = { 0xEB }; // jnz -> jmp
+
+ // jnz -> jmp in Network::Lobby::GetVoiceSettingsAvailable to always return 0
+ BYTE search4[] = { 0x74, 0x5E, 0x56, 0x8B, 0x35, 0x14 };
+
+ BYTE* ptr = find_pattern_mem((ULONG_PTR)mod, search, search + 7, TRUE);
+ if (ptr)
+ nop_mem(ptr, 10);
+ write_mem(ptr + 10, patch, 4);
+
+ ptr = find_pattern_mem_wildcard((ULONG_PTR)mod, search2, search2 + 8, TRUE);
+ if (ptr)
+ nop_mem(ptr, 8);
+
+ ptr = find_pattern_mem((ULONG_PTR)mod, search3, search3 + 7, TRUE);
+ if (ptr)
+ write_mem(ptr + 2, patch3, 1);
+
+ ptr = find_pattern_mem((ULONG_PTR)mod, search4, search4 + 5, TRUE);
+ if (ptr)
+ write_mem(ptr, patch3, 1);
+}
+
+// Fix crash in Program::SystemOpen when the system path environment variable is too long.
+// Crash affects versions 1.1, 1.0, and demo.
+// Patched in GOG version and community "Patch Project 1.2.5".
+__forceinline static void sta2_fix_path_crash() {
+ BYTE search[] = { 0x85, 0xC0, 0x74, 0x3F, 0x68 };
+ BYTE patch[] = { 0xEB };
+
+ BYTE* ptr = find_pattern_mem(0, search, search + 4, TRUE);
+ if (ptr)
+ write_mem(ptr + 2, patch, 1);
+}
+
+// GOG version patches out a portion of this condition check of the return value of Transport::GetStatus in order to prevent entering the GameSpy lobby altogether.
+__forceinline static void sta2_gog_restore_state_check() {
+ BYTE search[] = { 0x83, 0xF8, 0x16, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x83, 0xF8, 0x3A, 0x0F, 0x8D, 0x94, 0x00 };
+ BYTE patch[] = { 0x0F, 0x8E, 0x9D, 0x00, 0x00, 0x00 };
+
+ BYTE* ptr = find_pattern_mem(0, search, search + 15, TRUE);
+ if (ptr)
+ write_mem(ptr + 3, patch, 6);
+}
+
+__noinline static void patch_sta2() {
+ sta2_fix_path_crash();
+
+ HMODULE mod = GetModuleHandleA("NetworkManager.dll");
+ if (mod) {
+ sta2_hook_gs(mod);
+ sta2_disable_net_voice(mod);
+ sta2_gog_restore_state_check();
+ }
+}
+
+#endif // __GAME_STA2_H