Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Native Windows Support #1681

Draft
wants to merge 37 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
11de4af
Split cmake file into platform-specific builds.
jhett12321 Jul 11, 2023
0703f42
Add detours external lib.
jhett12321 Jul 11, 2023
7f95637
Add launcher project.
jhett12321 Jul 11, 2023
3393ba6
dllimport all globals.
jhett12321 Jul 11, 2023
7a620ac
Implement platform specific library functions.
jhett12321 Jul 11, 2023
e50bb46
Implement platform specific console commands.
jhett12321 Jul 11, 2023
8bad248
Add platform independent function list for hooks. Update nwnx.hpp
jhett12321 Jul 11, 2023
f141b25
Use platform-independent methods for nwnx core services.
jhett12321 Jul 11, 2023
a741660
NWNXCore: Update to support windows + linux.
jhett12321 Jul 11, 2023
971c833
Fix plugin extensions.
jhett12321 Jul 11, 2023
70da5a3
Convert structs to classes.
jhett12321 Jul 11, 2023
6ad9960
Make members public.
jhett12321 Jul 11, 2023
e03f601
Compile NWNXLib as OBJECT.
jhett12321 Jul 11, 2023
5cbb437
Remove undefined "g_bExitProgram" global on windows.
jhett12321 Jul 11, 2023
a9bb461
Fix Administration plugin compile errors.
jhett12321 Jul 11, 2023
5a6c54a
Fix area plugin compile errors.
jhett12321 Jul 11, 2023
9001008
Fix plugin/nwnx entrypoint.
jhett12321 Jul 11, 2023
e1a60c2
Fix compiler compile errors.
jhett12321 Jul 11, 2023
74e7dc5
Fix appearance plugin compile errors.
jhett12321 Jul 11, 2023
0a052b4
Add test github action.
jhett12321 Jul 11, 2023
1a17a47
Area plugin: workaround for protected methods.
jhett12321 Jul 12, 2023
4d83cc7
Merge branch 'master' into nwnx-windows
jhett12321 Jul 19, 2023
99f83db
Add HookFunction overload to support member function pointers.
jhett12321 Jul 19, 2023
2370963
Add static assert.
jhett12321 Jul 19, 2023
f85b72c
Add platform independent defines for NWNX_IMPORT/NWNX_EXPORT
jhett12321 Jul 19, 2023
2fb3ef2
Fix CExoString hash structure.
jhett12321 Jul 19, 2023
f186eca
Add missing RTLD defines, don't use union cast on linux.
jhett12321 Jul 19, 2023
36d5658
Expose AdvertLUT::m_map
jhett12321 Jul 19, 2023
241cc32
Add missing functions to FunctionsList.
jhett12321 Jul 19, 2023
199c5e4
Use NWNX_IMPORT for imported symbols.
jhett12321 Jul 19, 2023
5b2c68d
Core: Use address of member function where possible.
jhett12321 Jul 19, 2023
ab593a0
Area/Appearance: Use address of member function for function hooks.
jhett12321 Jul 19, 2023
189f67e
Disable SWIG for now.
jhett12321 Jul 19, 2023
05b821b
build.yml: Use windows-2019 for MSVC.
jhett12321 Jul 19, 2023
0beca32
Add lib generation script.
jhett12321 Jul 20, 2023
7585d31
Remove static assert.
jhett12321 Jul 20, 2023
23b8f25
Add nwserver.lib generation.
jhett12321 Jul 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
35 changes: 35 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,41 @@ jobs:
name: "build${{ steps.vars.outputs.nwn_build }}.${{ steps.vars.outputs.nwn_build_revision }}.${{ steps.vars.outputs.nwn_build_postfix }}-HEAD"
artifacts: "NWNX-EE.zip,NWScript.zip"

build-win:
runs-on: windows-2019
steps:
- uses: actions/checkout@v3
- run: git config --system --add safe.directory /__w/unified/unified

- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build

- name: Configure CMake
shell: cmake -P {0}
run: |
set(ENV{PATH} "$ENV{GITHUB_WORKSPACE}:$ENV{PATH}")
execute_process(
COMMAND cmake
-S .
-B build
-D CMAKE_BUILD_TYPE=$ENV{BUILD_TYPE}
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Bad exit status")
endif()

- name: Build
shell: cmake -P {0}
run: |
execute_process(
COMMAND cmake --build build -j 4
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Build failed")
endif()

docker:
runs-on: ubuntu-20.04
if: github.event_name == 'push'
Expand Down
94 changes: 4 additions & 90 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,94 +1,8 @@
cmake_minimum_required(VERSION 3.0.2)
project(NWNX-Unified)

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules)

set(CMAKE_EXPORT_COMPILE_COMMANDS 1)

# Includes the sanitizer package to facilitate debugging.
# If you want to build with this support (I suggest you do), you should pass in one or all of the following:
# -DSANITIZE_UNDEFINED=On
# -DSANITIZE_THREAD=On
# -DSANITIZE_MEMORY=On
# -DSANITIZE_ADDRESS=On
find_package(Sanitizers)

execute_process(COMMAND git rev-parse --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE SHORT_HASH)
set(TARGET_NWN_BUILD 8193)
set(TARGET_NWN_BUILD_REVISION 35)
set(TARGET_NWN_BUILD_POSTFIX 40)
set(NWNX_BUILD_SHA ${SHORT_HASH})
set(PLUGIN_PREFIX NWNX_)

# Adds the provided shared library, then builds it with a NWNX_ prefix.
function(add_plugin target)
add_library(${target} MODULE ${ARGN})
configure_plugin(${target})
endfunction()

function(configure_plugin target)
add_sanitizers(${target})
target_link_libraries(${target} Core)
set_target_properties(${target} PROPERTIES PREFIX "${PLUGIN_PREFIX}")
target_include_directories(${target} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_definitions(${target} PRIVATE "-DPLUGIN_NAME=\"${PLUGIN_PREFIX}${target}\"")
endfunction()

# Sets the output directory for the built targets.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Binaries)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Binaries)

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(WARNING_FLAGS_CXX "-Weverything -Wno-missing-braces -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded \
-Wno-packed -Wno-old-style-cast -Wno-reserved-id-macro -Wno-format-nonliteral -Wno-format-security \
-Wno-gnu-zero-variadic-macro-arguments -Wno-global-constructors -Wno-exit-time-destructors \
-Wno-missing-prototypes -Wno-unused-function -Wno-weak-vtables -Wno-missing-noreturn \
-Wno-non-virtual-dtor -Wno-double-promotion -Wno-covered-switch-default -Wno-unused-macros \
-Wno-register -Wno-pmf-conversions")
if (MSVC)
include(windows.cmake)
else()
set(WARNING_FLAGS_CXX "-Wall -Wextra -Wno-pmf-conversions")
endif()

set(NWNX_STANDARD_FLAGS "-m64 -march=x86-64 -fdiagnostics-show-option -fno-omit-frame-pointer -fPIC -fno-strict-aliasing")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${NWNX_STANDARD_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NWNX_STANDARD_FLAGS} ${WARNING_FLAGS_CXX} -std=c++17")

add_definitions(-DNWNX_PLUGIN_PREFIX="${PLUGIN_PREFIX}")
add_definitions(-DNWNX_TARGET_NWN_BUILD=${TARGET_NWN_BUILD})
add_definitions(-DNWNX_TARGET_NWN_BUILD_REVISION=${TARGET_NWN_BUILD_REVISION})
add_definitions(-DNWNX_TARGET_NWN_BUILD_POSTFIX=${TARGET_NWN_BUILD_POSTFIX})
add_definitions(-DNWNX_BUILD_SHA="${NWNX_BUILD_SHA}")

# Provides the NWN API and other useful things as a static lib.
add_subdirectory(NWNXLib)

# The core shared library.
add_subdirectory(Core)

# The documentation generation.
add_subdirectory(docgen)

# Detect every plugin and store it in plugins . . .
file(GLOB plugins Plugins/*/CMakeLists.txt)

# Allow skipping certain plugins by putting their names in env. variable
foreach(skipped $ENV{NWNX_SKIP_PLUGINS})
file(GLOB skip Plugins/${skipped}/CMakeLists.txt)
list(REMOVE_ITEM plugins ${skip} )
endforeach(skipped)

# . . . Then iterate over it.
foreach(plugin ${plugins})
get_filename_component(pluginPath ${plugin} PATH)
add_subdirectory(${pluginPath})
endforeach(plugin)

# Allow specifying out of tree plugins by putting their paths in an env. variable
foreach(addplugin $ENV{NWNX_ADDITIONAL_PLUGINS})
add_subdirectory(${addplugin} ${CMAKE_BINARY_DIR}/custom)
endforeach(addplugin)
include(linux.cmake)
endif ()
19 changes: 19 additions & 0 deletions Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ set_target_properties(Core PROPERTIES PREFIX "${PLUGIN_PREFIX}")
target_compile_definitions(Core PRIVATE "-DPLUGIN_NAME=\"${PLUGIN_PREFIX}Core\"")
target_link_libraries(Core NWNXLib)

if(MSVC)
file(DOWNLOAD https://nwn.beamdog.net/downloads/nwnee-dedicated-8193.35-40.zip nwserver.zip)
file(ARCHIVE_EXTRACT INPUT nwserver.zip DESTINATION "." PATTERNS "bin/win32/nwserver.exe")

add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/nwserver.lib"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/create_lib.bat
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/create_lib.bat ARGS "bin/win32/nwserver.exe"
)

add_custom_target(NWServerLib DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/nwserver.lib")

add_dependencies(Core NWServerLib)
set_target_properties(Core PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
set_target_properties(Core PROPERTIES BUILD_SHARED_LIBS TRUE)
set_target_properties(Core PROPERTIES ENABLE_EXPORTS TRUE)
target_link_libraries(Core "${CMAKE_CURRENT_BINARY_DIR}/nwserver.lib")
endif ()

# The name defined here will be ignored when loading plugins.
add_definitions(-DNWNX_CORE_PLUGIN_NAME="${PLUGIN_PREFIX}Core")

81 changes: 28 additions & 53 deletions Core/NWNXCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include "API/CServerExoApp.hpp"
#include "API/CNWSModule.hpp"
#include "API/CExoLinkedListInternal.hpp"
#include "API/CExoLinkedListNode.hpp"
#include "API/CExoResMan.hpp"
#include "API/CExoBase.hpp"
#include "API/CExoAliasList.hpp"
Expand All @@ -16,11 +15,10 @@

#include <csignal>
#include <regex>
#include <dirent.h>
#include <unistd.h>
#include <cstdio>
#include <sstream>
#include <dlfcn.h>
#include <filesystem>
#include <string>
#include <iostream>

using namespace NWNXLib;
using namespace NWNXLib::API;
Expand Down Expand Up @@ -69,27 +67,6 @@ extern "C" void nwnx_signal_handler(int sig)
}
}

// Don't allow the -quite flag to close stdout/stderr, we print important info there.
extern "C" FILE *freopen64(const char *filename, const char *mode, FILE *stream)
{
if ((stream == stdout || stream == stderr) && !strcmp(filename, "/dev/null"))
{
if (stream == stdout)
{
std::puts("NWNX overriding -quiet flag. Always keep an eye on stdout.\n"
"Server will continue in non-interactive mode, but with full output.\n");
}
return stream;
}

using Type = FILE*(*)(const char*,const char*,FILE*);
static Type real;
if (!real)
real = (Type)dlsym(RTLD_NEXT, "freopen64");
return real(filename, mode, stream);
}


namespace {

void InitCrashHandlers()
Expand Down Expand Up @@ -123,11 +100,21 @@ static NWNXCore s_core;
NWNXCore* g_core = nullptr; // Used to access the core class in hook or event handlers.
bool g_CoreShuttingDown = false;

#if WIN32
extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}
#endif

NWNXCore::NWNXCore()
: m_ScriptChunkRecursion(0)
{
g_core = this;

// Initialize platform specific stuff (e.g. create windows console window).
Platform::Initialize();

// NOTE: We should do the version check here, but the global in the binary hasn't been initialised yet at this point.
// This will be fixed in a future release of NWNX:EE. For now, the version check will happen *too late* - we may
// crash before the version check happens.
Expand Down Expand Up @@ -300,59 +287,47 @@ void NWNXCore::InitialSetupPlugins()
constexpr static const char* pluginPrefix = NWNX_PLUGIN_PREFIX;
const std::string prefix = pluginPrefix;

char cwd[PATH_MAX];
ASSERT(getcwd(cwd, sizeof(cwd)) != nullptr);

const auto pluginDir = Config::Get<std::string>("LOAD_PATH", cwd);
const auto pluginDir = Config::Get<std::string>("LOAD_PATH", std::filesystem::current_path().string());
const bool skipAllPlugins = Config::Get<bool>("SKIP_ALL", false);

LOG_INFO("Loading plugins from: %s", pluginDir);

std::vector<std::string> files;
if (auto dir = opendir(pluginDir.c_str()))
std::vector<std::filesystem::path> files;

for (const auto& entry : std::filesystem::directory_iterator(pluginDir))
{
while (auto entry = readdir(dir))
if (entry.is_regular_file() || entry.is_symlink() || entry.is_other())
{
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_REG || entry->d_type == DT_LNK)
{
files.emplace_back(entry->d_name);
}
files.emplace_back(entry.path());
}
closedir(dir);
}

// Sort by file name, so at least plugins are loaded in deterministic order.
std::sort(std::begin(files), std::end(files));

for (auto& dynamicLibrary : files)
for (auto& libraryPath : files)
{
const std::string& pluginName = dynamicLibrary;
const std::string pluginNameWithoutExtension = String::Basename(pluginName);
const std::string& extension = libraryPath.extension().string();
const std::string& pluginName = libraryPath.stem().string();

if (pluginNameWithoutExtension.compare(0, prefix.size(), prefix) != 0)
if (pluginName.compare(0, prefix.size(), prefix) != 0 || pluginName == "NWNX_Launcher" || (extension != Platform::PluginExtension()))
{
continue; // Not a plugin.
}

if (pluginNameWithoutExtension == "NWNX_Experimental" && !Config::Get<bool>("LOAD_EXPERIMENTAL_PLUGIN", false))
if (pluginName == "NWNX_Experimental" && !Config::Get<bool>("LOAD_EXPERIMENTAL_PLUGIN", false))
{
continue;
}

auto services = ConstructProxyServices(pluginNameWithoutExtension);

// Always load core.
if (pluginNameWithoutExtension != NWNX_CORE_PLUGIN_NAME && Config::Get<bool>("SKIP", (bool)skipAllPlugins, pluginNameWithoutExtension))
{
LOG_INFO("Skipping plugin %s due to configuration.", pluginNameWithoutExtension);
continue;
}
Plugin::Load(pluginDir + "/" + pluginName, std::move(services));
auto services = ConstructProxyServices(pluginName);
Plugin::Load(pluginDir + Platform::PathSeparator() + pluginName, std::move(services));
}
}

void NWNXCore::InitialSetupResourceDirectories()
{
auto nwnxResDirPath = Config::Get<std::string>("NWNX_RESOURCE_DIRECTORY_PATH", Globals::ExoBase()->m_sUserDirectory.CStr() + std::string("/nwnx"));
auto nwnxResDirPath = Config::Get<std::string>("NWNX_RESOURCE_DIRECTORY_PATH", Globals::ExoBase()->m_sUserDirectory.CStr() + std::string(Platform::PathSeparator() + "nwnx"));
auto nwnxResDirPriority = Config::Get<int32_t>("NWNX_RESOURCE_DIRECTORY_PRIORITY", 70000000);

std::unordered_map<std::string, std::pair<std::string, int32_t>> resourceDirectories;
Expand Down
11 changes: 11 additions & 0 deletions Core/create_lib.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@echo off

dumpbin /exports %1 > nwserver.exports

echo LIBRARY nwserver.exe > nwserver.def
echo EXPORTS >> nwserver.def
for /f "skip=19 tokens=1,4" %%A in (nwserver.exports) do if NOT "%%B" == "" (echo %%B @%%A >> nwserver.def)

lib /def:nwserver.def /out:nwserver.lib /machine:x64 /name:nwserver.exe

del nwserver.exports nwserver.def nwserver.exp
4 changes: 4 additions & 0 deletions Launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add_executable(Launcher WIN32 main.cpp)
target_link_libraries(Launcher PRIVATE detours)

set_target_properties(Launcher PROPERTIES PREFIX "${PLUGIN_PREFIX}")
25 changes: 25 additions & 0 deletions Launcher/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <Windows.h>
#include <detours.h>
#include <string>

static const char exename[] = "nwserver.exe";
static const char dllname[] = "NWNX_Core.dll";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
static char cmd[4192];
snprintf(cmd, sizeof(cmd), "%s %s", exename, lpCmdLine);

STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);

if(!DetourCreateProcessWithDll(NULL, cmd, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi, dllname, NULL))
{
exit(GetLastError());
}

return 0;
}
Loading