Skip to content

Commit

Permalink
Merge pull request #70 from nwn2dev/feature/docker-integration
Browse files Browse the repository at this point in the history
Initial Docker image setup; xp_bugfix SSL certificate validation impr…
  • Loading branch information
scottmunday84 authored Apr 26, 2023
2 parents 0bc1336 + 3e25e3c commit ce938ff
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 12 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@
/src/plugins/*/Release/

.idea/
dev/
meson-build-*/
vcpkg_installed/
vcpkg_installed/
dist/
21 changes: 21 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## NWNX4 Docker

NWNX4 Docker is a way to deploy NWNX4 in a Docker Linux container.

## How-to

There is a docker-compose.yml file available in this folder that helps to build and lift
your copy. You will need to buildthe distribution of the NWNX4 application first.

NOTE: Make sure the build directory is using --buildtype=release.

1. Run `meson install --destdir=../dist` from your NWNX4 build directory in a VS 2019
x86 Dev. Command Prompt. If your destdir is within a dist folder at the root of the
repository as in this instruction, you will not need to set the Docker argument
`NWNX4_DIST_DIR` during your `docker-compose build` step.
3. Set the following environment variables:
1. `NWN2_HOME_DIR` i.e. C:\Users\youruser\OneDrive\Documents\Neverwinter Nights 2
2. `NWN2_INSTALL_DIR` i.e. C:\Program Files (x86)\Steam\steamapps\common\Neverwinter Nights 2
3. `NWNX4_USER_DIR` i.e. C:\nwnx4-user
4. Run `docker-compose build` from this directory (./docker).
5. Run `docker-compose up -d` to start the service daemon. By default, the server will be accessible from port 5121.
15 changes: 15 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3"
services:
nwnx4:
container_name: nwnx4
image: nwn2dev/nwnx4:latest
build:
context: ..
dockerfile: ./docker/nwnx4/Dockerfile
volumes:
- "${NWNX4_USER_DIR}:/srv/nwnx4-user"
- "${NWN2_HOME_DIR}:/srv/nwn2-home"
- "${NWN2_INSTALL_DIR}:/srv/nwn2:ro"
ports:
- "${SERVER_PORT:-5121}:5121/udp"
tty: true
75 changes: 75 additions & 0 deletions docker/nwnx4/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
FROM debian:bullseye

# Copy the NWNX4 release into the image
ARG NWNX4_DIST_DIR="dist/"
ARG NWNX4_USER_HOME="/home/nxnx4"

# Install requirements
RUN dpkg --add-architecture i386 \
&& apt-get update \
&& apt-get install -y gosu wget cabextract xvfb openssl \
&& apt-get install -y gnupg2 software-properties-common \
\
&& wget -qO- https://dl.winehq.org/wine-builds/winehq.key | apt-key add - \
&& apt-add-repository https://dl.winehq.org/wine-builds/debian/ \
&& apt-get update \
&& apt-get install -y --install-recommends winehq-stable \
\
&& wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks -O /usr/local/bin/winetricks \
&& chmod +x /usr/local/bin/winetricks \
\
&& apt-get clean \
&& apt-get autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/apt/lists/*

# Setup nwnx4 user
RUN useradd -u 1000 -d $NWNX4_USER_HOME --create-home -ms /bin/bash nwnx4

# Give access to NWNX4 etc folder to nwnx4 user
RUN mkdir -p /etc/nwnx4/plugins \
&& chown nwnx4:nwnx4 -R /etc/nwnx4

# Set to nwnx4 user; allows wine to be configured correctly
USER nwnx4:nwnx4

# Setup Wine/Xvfb
ENV WINEPREFIX="$NWNX4_USER_HOME/.wine32"
ENV WINEARCH="win32"
ENV WINEDLLOVERRIDES="mshtml=;devenum,dxdiagn,granny2=n"
ENV WINEDEBUG="fixme-all"
ENV WINE_NO_AUDIO=1
ENV AUDIODRIVER="none"
ENV DISPLAY=":0"

RUN ln -s /srv/nwn2-home "$NWNX4_USER_HOME/Neverwinter Nights 2" \
&& mkdir -p "$WINEPREFIX/drive_c/users/nwnx4/Temp/NWN2" \
&& ln -s /srv/nwn2-logs "$WINEPREFIX/drive_c/users/nwnx4/Temp/NWN2/LOGS.0" \
\
&& wineboot --init \
&& xvfb-run winetricks -q dotnet48 vcrun2005 vcrun2015 \
\
&& rm -rf /tmp/wine*

VOLUME ["/srv/nwn2-logs"]

# Setup NWN2 stage and registry overrides
COPY --chown=nwnx4:nwnx4 docker/nwnx4/nwn2-stage /opt/nwn2-stage
COPY --chown=nwnx4:nwnx4 docker/nwnx4/nwn2.reg /opt/

# Setup NWNX4 distribution
COPY --chown=nwnx4:nwnx4 $NWNX4_DIST_DIR /opt/nwnx4/

# Add temp. folder for X11
RUN mkdir -p /tmp/.X11-unix \
&& chmod 1777 /tmp/.X11-unix

# Expose UDP port 5121 for port access to NWN2 server
EXPOSE 5121/udp

# Setup entrypoint and command
USER root
WORKDIR /srv/nwnx4-user
COPY docker/nwnx4/docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["wine", "/opt/nwnx4/NWNX4_Controller.exe", "-interactive", "-verbose"]
73 changes: 73 additions & 0 deletions docker/nwnx4/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash

set -euxo pipefail

# If the NWN2 stage file does not exist in NWN2 install directory, drop
for file in $(ls /opt/nwn2-stage); do
if [[ ! -e "/srv/nwn2/$file" ]]; then
rm -f "/opt/nwn2-stage/$file"
fi
done

# If the NWN2 install file does not exist in NWN2 stage directory, add
for file in $(ls /srv/nwn2); do
if [[ ! -e "/opt/nwn2-stage/$file" ]]; then
ln -s "/srv/nwn2/$file" /opt/nwn2-stage/ && chown nwnx4:nwnx4 -h "/opt/nwn2-stage/$file"
fi
done

# Clear all files in plugin folder; do this every startup
rm -Rf /etc/nwnx4/plugins/*;

# Copy a plugin if it exists; prefer /srv/nwnx4-user plugins
for file in $(ls /srv/nwnx4-user/plugins/*.dll | xargs -n 1 basename); do
cp "/srv/nwnx4-user/plugins/$file" /etc/nwnx4/plugins/ && chown nwnx4:nwnx4 "/etc/nwnx4/plugins/$file"
done

# Copy a plugin if it exists; only add /opt/nwnx4 plugins if it doesn't exist in /srv/nwnx4-user
for file in $(ls /opt/nwnx4/plugins/*.dll | xargs -n 1 basename); do
if [[ ! -e "/etc/nwnx4/plugins/$file" ]]; then
cp "/opt/nwnx4/plugins/$file" /etc/nwnx4/plugins/ && chown nwnx4:nwnx4 "/etc/nwnx4/plugins/$file"
fi
done

# Wine doesn't support NCrypt well; building it here through openssl
CERTIFICATE_PATH="/srv/nwnx4-user/NWNCertificate"
HOSTNAME="CN=Neverwinter Nights"
ALGORITHM="sha384"

if [ ! -e "${CERTIFICATE_PATH}.pfx" ]; then
# Generate private key
openssl ecparam -name secp384r1 -genkey -noout -out "${CERTIFICATE_PATH}.key"

# Generate certificate request
openssl req -new -key "${CERTIFICATE_PATH}.key" -subj "/${HOSTNAME}" -out "${CERTIFICATE_PATH}.csr"

# Self-sign certificate
openssl x509 -req -days 365000 -in "${CERTIFICATE_PATH}.csr" -signkey "${CERTIFICATE_PATH}.key" -sha384 -out "${CERTIFICATE_PATH}.crt" -extfile <(
echo "[v3_ca]
basicConstraints = CA:TRUE
subjectAltName = DNS:${HOSTNAME}"
)
openssl x509 -in "${CERTIFICATE_PATH}.crt" -outform DER -out "${CERTIFICATE_PATH}.cer"

# Combine key and certificate into PKCS12 format
openssl pkcs12 -export -in "${CERTIFICATE_PATH}.crt" -inkey "${CERTIFICATE_PATH}.key" -out "${CERTIFICATE_PATH}.pfx" -passout pass:

# Remove certificate request (.csr) and certificate (.crt)
rm "${CERTIFICATE_PATH}.key"
rm "${CERTIFICATE_PATH}.csr"
rm "${CERTIFICATE_PATH}.crt"
fi

# All files in the /srv/nwnx4-user and /srv/nwn2-logs folder must be owned by the nwnx4 user
chown -R nwnx4:nwnx4 /srv/nwnx4-user
chown -R nwnx4:nwnx4 /srv/nwn2-logs

# Setup Xvfb; execute command
gosu nwnx4 bash <<-EOF
Xvfb $DISPLAY -screen 0 1024x768x16 &
wine reg import /opt/nwn2.reg
EOF

exec gosu nwnx4 "$@"
Binary file added docker/nwnx4/nwn2-stage/granny2.dll
Binary file not shown.
11 changes: 11 additions & 0 deletions docker/nwnx4/nwn2.reg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Windows Registry Editor Version 5.00

[HKLM\SOFTWARE\Obsidian\NWN 2\Neverwinter]
"Location"="Z:\\opt\\nwn2-stage"

[HKCU\Software\Wine\Audio]
"Drivers"="none"

[HKCU\Software\Wine\Direct3D]
OffscreenRenderingMode=fbo
VideoMemorySize=0
28 changes: 23 additions & 5 deletions src/plugins/xp_bugfix/NetLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ TlsCertificateHashToString(
return;
}

bool EnableTls()
bool EnableTls(bool skipAuroraServerQueryCreateCertificate)
{
AURORA_SERVER_QUERY_CREATE_CERTIFICATE Cert;
CHAR HostKey[ 256 ];
Expand Down Expand Up @@ -771,15 +771,33 @@ bool EnableTls()
sizeof( Cert.Hostname ),
"CN=Neverwinter Nights" );

if (AuroraServerNetLayerQuery_(
// Most Windows encryption algorithms are missing from Wine, so we must work around it
if (skipAuroraServerQueryCreateCertificate) {
// Assume that you created the PFX file externally
std::string certificatePath = std::string(Cert.CertificatePath) + ".pfx";
std::ifstream file(certificatePath);
if (!file.is_open()) {
logger->Err("! EnableTls: Expected a PFX file to be stored at '%s'. File must be available with read access", certificatePath);

return false;
}

// Read file content into a string
std::string pfxFileContent((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());

// Calculate SHA-512 hash
CryptoPP::SHA512 hash;
hash.CalculateDigest(Cert.Sha512, reinterpret_cast<const byte*>(pfxFileContent.c_str()), pfxFileContent.length());
} else if (AuroraServerNetLayerQuery_(
NULL,
AuroraServerQueryCreateCertificate,
sizeof( Cert ),
NULL,
&Cert ) == FALSE)
{
&Cert ) == FALSE) {
logger->Err("! EnableTls: Failed to create server certificate and store it in directory '%s'. Check that .NET Framework 4.7.2 or newer is enabled. https://support.microsoft.com/en-us/help/4054530/microsoft-net-framework-4-7-2-offline-installer-for-windows",
NWNXHome);
NWNXHome);

return false;
}

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/xp_bugfix/NetLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#pragma once
#endif

#include <cryptopp/sha.h>
#include <cryptopp/hex.h>

//
// Begin snipped from NetLayerWindow.h
//
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/xp_bugfix/bugfix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@
#include <cassert>
#include <map>

#define BUGFIX_VERSION "1.0.73"
#define BUGFIX_VERSION "1.0.74"
#define __NWN2_VERSION_STR(X) #X
#define _NWN2_VERSION_STR(X) __NWN2_VERSION_STR(X)
#define NWN2_VERSION _NWN2_VERSION_STR(NWN2SERVER_VERSION)

#define BUGFIX_LOG_GAMEOBJACCESS 0

extern bool ReplaceNetLayer();
extern bool EnableTls();
extern bool EnableTls(bool skipAuroraServerQueryCreateCertificate);

extern bool TlsActive;
extern bool WindowExtensions;
Expand All @@ -62,6 +62,7 @@ bool nocompress = true;
long GameObjUpdateBurstSize = 102400; // 100K
CHAR NWNXHome[ MAX_PATH + 1 ];
bool CanonicalizeAccountNames = true;
bool SkipAuroraServerCreateCertificate = false;
StringMap AccountNameMap;
std::unique_ptr<LogNWNX> logger;

Expand Down Expand Up @@ -611,6 +612,8 @@ bool BugFix::Init(char* nwnxhome)

config.Read( "CanonicalizeAccountNames", &CanonicalizeAccountNames, CanonicalizeAccountNames );

config.Read("SkipAuroraServerCreateCertificate", &SkipAuroraServerCreateCertificate, SkipAuroraServerCreateCertificate);

#ifdef XP_BUGFIX_AIUPDATE_THROTTLING
config.Read( "AIUpdateThrottle", &ThrottleValue, 0L );
aiUpdateThrottle = (ULONG) ThrottleValue;
Expand Down Expand Up @@ -730,7 +733,7 @@ bool BugFix::Init(char* nwnxhome)
{
logger->Debug("* Attempting to enable TLS.");

if (EnableTls())
if (EnableTls(SkipAuroraServerCreateCertificate))
{
logger->Info("* TLS enabled.");

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/xp_bugfix/meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

shared_library('xp_bugfix',
[
'../../misc/IATHook.cpp',
Expand All @@ -18,6 +17,7 @@ shared_library('xp_bugfix',
dependencies: [
cppcompiler.find_library('dbghelp'),
cppcompiler.find_library('ws2_32'),
cppcompiler.find_library('cryptopp', dirs: lib_dirs)
],
install: true,
install_dir: 'plugins',
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/xp_bugfix/xp_bugfix.ini
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,11 @@ InitialAttachmentPointCount = 1
#

DisableCharacterCreation = 0

#
# Skip the AuroraServerCreateCertificate query
#
# The plugin default is 0
#

SkipAuroraServerCreateCertificate = 0
1 change: 1 addition & 0 deletions vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version-string": "0.1.0",
"builtin-baseline": "1d5f3adf598a877c6870dc37b80b453f4c74d462",
"dependencies": [
"cryptopp",
"detours",
"wxwidgets",
"libmariadb"
Expand Down

0 comments on commit ce938ff

Please sign in to comment.