Skip to content

Commit

Permalink
Merge pull request #53 from CromFr/user-dir
Browse files Browse the repository at this point in the history
Split nwnx4 files between user and install dir
  • Loading branch information
CromFr authored Sep 4, 2022
2 parents ea72757 + 326c09b commit 2de1501
Show file tree
Hide file tree
Showing 20 changed files with 366 additions and 362 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,16 @@ jobs:
- name: "Configure NWNX4"
shell: bash
run: |
mkdir -p /c/nwnx4_user/ /c/nwnx4_user/plugins/
# Install old ini plugin for testing compatibility
cp .github/workflows/xp_ini.dll /c/nwnx4/plugins/
cp .github/workflows/xp_ini.dll /c/nwnx4_user/plugins/
cp .github/workflows/test.ini /c/nwnx4_nwn2server/
cd /c/nwnx4/
cd /c/nwnx4_user/
# Install default config for plugins
cp config.example/* .
cp /c/nwnx4/config.example/* .
# Configure nwnx.ini to load the module
sed -i -E '
Expand Down Expand Up @@ -215,12 +217,11 @@ jobs:
run: |
set -o pipefail
cd /c/nwnx4/
cd /c/nwnx4_user/
echo "> $(date --rfc-3339=seconds --utc) Start NWNX4"
# Start NWNX4_Controller.exe with admin privileges
powershell -Command "Start-Process NWNX4_Controller.exe -ArgumentList '-verbose','-interactive' -Verb RunAs"
# powershell -Command "Start-Process NWNX4_Gui.exe -Verb RunAs"
powershell -Command "Start-Process ../nwnx4/NWNX4_Controller.exe -ArgumentList '-verbose','-interactive' -Verb RunAs"
sleep 20
echo "> $(date --rfc-3339=seconds --utc) Check NWNX4"
Expand Down Expand Up @@ -250,15 +251,15 @@ jobs:
if: always()
shell: bash
run: |
! grep -E "ERROR:|WARN: " /c/nwnx4/*.txt /c/nwn2temp/NWN2/LOGS.0/*.txt
! grep -E "ERROR:|WARN: " /c/nwnx4_user/*.txt /c/nwn2temp/NWN2/LOGS.0/*.txt
- name: "Upload nwn2server logs"
if: always()
uses: actions/upload-artifact@v2
with:
path: |
C:/nwn2temp/NWN2/LOGS.0/*.txt
C:/nwnx4/*.txt
C:/nwnx4_user/*.txt
name: "test-logs-${{matrix.sql}}"

# --------------------------------------------------
Expand Down
83 changes: 54 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,71 @@ You must install:
- [Visual C++ 2015 x86 Runtime](https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x86.exe) <!-- msvc 2019 -->
- [.NET Framework 4.7.2](https://download.visualstudio.microsoft.com/download/pr/1f5af042-d0e4-4002-9c59-9ba66bcf15f6/124d2afe5c8f67dfa910da5f9e3db9c1/ndp472-kb4054531-web.exe) or above <!-- xp_bugfix -->

### First installation
### Installation


1. Download and extract the [NWNX4 zip
file](https://github.com/nwn2dev/nwnx4/releases) in any directory
2. Copy all files from the `config.example/` folder to your nwnx4 folder (i.e.
to their parent directory)
3. Customize the configuration files you just copied to suit your needs:
file](https://github.com/nwn2dev/nwnx4/releases) in `C:\Program Files
(x86)\nwnx4` (recommended). This folder is referenced as the "_Install
dir_"
2. Create a new empty folder somewhere in your user profile, like
`%USERPROFILE%\Documents\nwnx4`. This folder is referenced as the "_User
dir_".
3. Copy all files from `C:\Program Files (x86)\nwnx4\config.example\` to your
_User dir_ (`%USERPROFILE%\Documents\nwnx4`)
4. Customize the configuration files you just copied to suit your needs:
- `nwnx.ini`:
+ `plugin_list`: the list of all nwnx4 plugins that will be loaded.
You may want to add or remove some of them.
+ `nwn2`: full or relative path to the NWN2 install folder. By default
nwnx4 will try to detect an existing NWN2 installation.
You may want to add or remove some of them. Additional plugins
should be installed in the `plugins` folder in _User dir_ (you may
need to create it).
+ `parameters`: nwn2server command line arguments. Examples:
* `-module YourModuleName` to automatically load a module as a
.mod file
* `-moduledir YourModuleName` if your module is a directory
- `xp_*.ini`: these are the plugins configuration files. Most plugins are
shipped with convenient defaults, but you may need to tweak some of
them. Note that the presence of a plugin configuration file does not
mean the plugin will be loaded (see `plugin_list` in `nwnx.ini`).
4. Copy the `.nss` files from the `nwscript/` folder into your
module folder, **or** import `nwscript/nwnx.erf` into your module using the
NWN2 toolset. Overwrite existing files if prompted.
5. Start NWNX4:
+ Run `NWNX4_GUI.exe` for the GUI version
+ Run `NWNX4_Controller.exe -interactive` in a shell for the command-line
version.
mean the plugin is loaded (see `plugin_list` in `nwnx.ini`).

There are 3 methods for launching the server:
- Using `NWNX4_GUI.exe` (graphical interface)
+ Create a shortcut to `NWNX4_GUI.exe`, open its properties and edit the
"Run in" field to set to your _User dir_.
+ Then Double click the shortcut to launch the server
- Using `NWNX4_Controller.exe` (command-line)
+ Run `NWNX4_Controller.exe -userdir "%USERPROFILE%\Documents\nwnx4"
-interactive` in a cmd window. If `-userdir` is not provided, the
current working directory will be used instead.
- Using a Windows service:
+ Run `NWNX4_Controller.exe -userdir "%USERPROFILE%\Documents\nwnx4"
-installservice` in a cmd window to register the service. If `-userdir`
is not provided, the _User dir_ will be the current working directory.
+ Run `NWNX4_Controller.exe -startservice` to start the service and launch
the server


#### Alternative install: single-folder

Before NWNX4 v1.2.0, the _Install dir_ and _User dir_ were the same folder.
This behavior is still supported but discouraged for server setups, as it
makes updating NWNX4 harder.

Double-clicking `NWNX4_GUI.exe` will launch NWNX4 with merged folder.


### Updating NWNX4

1. Download and extract the [NWNX4 zip
file](https://github.com/nwn2dev/nwnx4/releases) into your existing nwnx4
directory, and overwrite everything
2. Only configuration files inside `config.example/` will be updated. The
existing configuration files in the NWNX4 folder are not overwritten, but
**you may need to manually update** those configuration files (see the
[release notes](https://github.com/nwn2dev/nwnx4/releases)).
3. Copy the `.nss` files from the `nwscript/` folder into your
module folder, **or** import `nwscript/nwnx.erf` into your module using the
NWN2 toolset. Overwrite existing files if prompted.
1. Delete or rename your current NWNX4 _Install dir_
2. Replace it with the latest [NWNX4 zip
file](https://github.com/nwn2dev/nwnx4/releases)
3. Compare the new config files in `config.example/` with your existing config
files in the NWNX4 _User dir_. Read the release notes to see exactly what
changed.
4. Copy the `.nss` files from the `nwscript/` folder into your module folder,
**or** import `nwscript/nwnx.erf` into your module using the NWN2 toolset.
Overwrite existing files if prompted.


# Building from sources

Expand Down Expand Up @@ -118,7 +143,7 @@ This section will guide you through the process of creating a folder / zip
file containing everything players will need to quickly run a NWN2 server with
your custom content, for single-player or multi-player usage.

## Skeleton
### Skeleton

Download this repository and copy the `package-skel` directory to any
location. This folder contains some handy scripts, and the base structure for
Expand All @@ -133,7 +158,7 @@ package-skel\
└── start-server.bat Launcher for the server (with nwnx4)
```

## NWNX4
### NWNX4

Download the [nwnx4 zip file](https://github.com/nwn2dev/nwnx4/releases), and
extract it into `package-skel\nwnx4\`.
Expand All @@ -156,7 +181,7 @@ arguments are recommended but not required:
parameters = -home "%NWNX4_DIR%\..\home" -moduledir YourModule -publicserver 0 -maxclients 1 -servervault 1
```

## Game launcher
### Game launcher

Skywing's Client Extension can automatically detect NWN2 installation
directory and provides a better NWN2 player experience. [Download it from the
Expand Down
7 changes: 5 additions & 2 deletions include/nwnx_cplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ struct NWNXCPlugin_InitInfo
{
/// Path to this plugin DLL file
const char* dll_path;
/// Path to the NWNX4 base directory, where nwnx4_controller.exe is located.
const char* nwnx_path;
/// Path to the NWNX4 user directory, where config files are stored and log files should be
/// written.
const char* nwnx_user_path;
/// Path to the NWN2 installation directory, where nwn2server.exe is located.
const char* nwn2_install_path;
/// Path to the NWN2 home folder, usually 'Documents\Neverwinter Nights 2'
Expand All @@ -21,6 +22,8 @@ struct NWNXCPlugin_InitInfo
/// Note: this value depends on the parameters list in nwnx.ini. If the server has not been
/// started with -module or -moduledir, this value is set to NULL.
const char* nwn2_module_path;
/// Path to the NWNX4 user directory, where nwnx4_controller.exe is located.
const char* nwnx_install_path;
};

//
Expand Down
82 changes: 57 additions & 25 deletions src/controller/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
***************************************************************************/

#include "controller.h"
#include "../misc/windows_utils.h"

extern LogNWNX* logger;
extern std::unique_ptr<LogNWNX> logger;

static std::string GetNwn2InstallPath()
/*++
Expand Down Expand Up @@ -160,16 +161,38 @@ Return Value:
}
}


NWNXController::NWNXController(SimpleIniConfig* config)
{
this->config = config;

// Setup temporary directories.
this->setupTempDirectories();

m_nwnx4UserDir = std::filesystem::current_path();

char executablePath[MAX_PATH] = {0};
if(GetModuleFileNameA(nullptr, executablePath, MAX_PATH) == 0){
auto errInfo = GetLastErrorInfo();
logger->Warn("Could not get executable path: Error %d: %s", errInfo.first, errInfo.second);
logger->Warn("Falling back to %s as nwnx4 install dir", m_nwnx4UserDir);
m_nwnx4InstallDir = m_nwnx4UserDir;
}
else{
m_nwnx4InstallDir = std::filesystem::path{executablePath}.parent_path();
}

// Set nwnx4 dir env variable
GetCurrentDirectoryA(MAX_PATH, m_nwnx4Dir);
SetEnvironmentVariableA("NWNX4_DIR", m_nwnx4Dir);
SetEnvironmentVariableA("NWNX4_DIR", m_nwnx4UserDir.string().c_str()); // Kept for compatibility
SetEnvironmentVariableA("NWNX4_USER_DIR", m_nwnx4UserDir.string().c_str());
SetEnvironmentVariableA("NWNX4_INSTALL_DIR", m_nwnx4InstallDir.string().c_str());


// Populate user dir if it's currently empty
if(std::filesystem::is_empty(m_nwnx4UserDir)){
populateUserDir();
}


tick = 0;
initialized = false;
Expand Down Expand Up @@ -207,6 +230,7 @@ NWNXController::NWNXController(SimpleIniConfig* config)
}
}

std::string nwninstalldir;
if (!config->Read("nwn2", &nwninstalldir))
{
try{
Expand All @@ -217,8 +241,9 @@ NWNXController::NWNXController(SimpleIniConfig* config)
return;
}
}
m_nwn2InstallDir = nwninstalldir;

logger->Trace("NWN2 install dir: %s", nwninstalldir.c_str());
logger->Trace("NWN2 install dir: %ls", m_nwn2InstallDir.c_str());
logger->Trace("NWN2 parameters: %s", parameters.c_str());
}

Expand All @@ -230,6 +255,20 @@ NWNXController::~NWNXController()
delete udp;
}

void NWNXController::populateUserDir(){
if(!std::filesystem::exists(m_nwnx4UserDir / "plugins"))
std::filesystem::create_directory(m_nwnx4UserDir / "plugins");

if(!std::filesystem::exists(m_nwnx4UserDir / "nwn2server-dll"))
std::filesystem::create_directory(m_nwnx4UserDir / "nwn2server-dll");

for (auto& file : std::filesystem::directory_iterator(m_nwnx4InstallDir / "config.example")) {
auto filename = file.path().filename();
if(!std::filesystem::exists(m_nwnx4UserDir / filename))
std::filesystem::copy_file(file, m_nwnx4UserDir / filename);
}
}

void NWNXController::setupTempDirectories() {
std::string tempPath;
if (config->Read("nwn2temp", &tempPath))
Expand Down Expand Up @@ -265,31 +304,21 @@ void NWNXController::notifyServiceShutdown()

bool NWNXController::startServerProcessInternal()
{
SHARED_MEMORY shmem;
std::string nwnexe("\\nwn2server.exe");
auto pszHookDLLPath = "NWNX4_Hook.dll";

ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);

auto exePath = nwninstalldir + nwnexe;
logger->Trace("Starting server executable %s in %s", exePath.c_str(), nwninstalldir.c_str());
auto exePath = m_nwn2InstallDir / "nwn2server.exe";
logger->Trace("Starting server executable: %ls", exePath.c_str());

char szDllPath[MAX_PATH];
LPSTR pszFilePart = nullptr;

if (!GetFullPathNameA(pszHookDLLPath, arrayof(szDllPath), szDllPath, &pszFilePart))
{
logger->Info("Error: %s could not be found.", pszHookDLLPath);
return false;
}
const auto hookDllPath = m_nwnx4InstallDir / "NWNX4_Hook.dll";

SECURITY_ATTRIBUTES SecurityAttributes;
SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
SecurityAttributes.bInheritHandle = TRUE;
SecurityAttributes.lpSecurityDescriptor = 0;

SHARED_MEMORY shmem;
shmem.ready_event = CreateEvent(&SecurityAttributes, TRUE, FALSE, nullptr);
if(!shmem.ready_event)
{
Expand All @@ -315,12 +344,12 @@ bool NWNXController::startServerProcessInternal()
return false;
}

logger->Trace("Starting: %s\\%s", nwninstalldir.c_str(), params);
logger->Trace("with %s", szDllPath);
logger->Trace("CLI Arguments: %s", params);
logger->Trace("Injecting DLL %ls", hookDllPath.c_str());

if (!DetourCreateProcessWithDllExA(exePath.c_str(), params,
nullptr, nullptr, TRUE, dwFlags, nullptr, nwninstalldir.c_str(),
&si, &pi, szDllPath, nullptr))
if (!DetourCreateProcessWithDllExA(exePath.string().c_str(), params,
nullptr, nullptr, TRUE, dwFlags, nullptr, m_nwn2InstallDir.string().c_str(),
&si, &pi, hookDllPath.string().c_str(), nullptr))
{
auto err = GetLastError();
logger->Err("DetourCreateProcessWithDll failed: %d", err);
Expand All @@ -342,8 +371,11 @@ bool NWNXController::startServerProcessInternal()
{0xb6, 0xd7, 0x00, 0x60, 0x97, 0xb0, 0x10, 0xe3}
};

strncpy_s(shmem.nwnx_dir, MAX_PATH, m_nwnx4Dir, MAX_PATH);
logger->Debug("Injecting NWNX4 directory as '%s'", shmem.nwnx_dir);
strncpy_s(shmem.nwnx_user_dir, MAX_PATH, m_nwnx4UserDir.string().c_str(), MAX_PATH);
logger->Debug("Injecting NWNX4 user directory as '%s'", shmem.nwnx_user_dir);

strncpy_s(shmem.nwnx_install_dir, MAX_PATH, m_nwnx4InstallDir.string().c_str(), MAX_PATH);
logger->Debug("Injecting NWNX4 install directory as '%s'", shmem.nwnx_install_dir);

if (!DetourCopyPayloadToProcess(pi.hProcess, my_guid, &shmem, sizeof(SHARED_MEMORY))) {
logger->Err("! Error: Could not copy payload to process. Error %d", GetLastError());
Expand Down
7 changes: 5 additions & 2 deletions src/controller/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <cwchar>
#include <detours/detours.h>
#include <processenv.h>
#include <filesystem>
#include "udp.h"
#include "../misc/log.h"
#include "../misc/shmem.h"
Expand Down Expand Up @@ -67,17 +68,19 @@ class NWNXController
private:
SimpleIniConfig* config;

char m_nwnx4Dir[MAX_PATH];
std::filesystem::path m_nwnx4UserDir;
std::filesystem::path m_nwnx4InstallDir;
std::filesystem::path m_nwn2InstallDir;

CUDP *udp;
STARTUPINFOA si;
PROCESS_INFORMATION pi;

unsigned long tick;
std::string nwninstalldir;
bool initialized;
bool shuttingDown;

void populateUserDir();
void setupTempDirectories();

bool startServerProcessInternal();
Expand Down
Loading

0 comments on commit 2de1501

Please sign in to comment.