diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58d6794 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +.vs +.vscode/settings.json +build +cmake-build* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8227c16 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,128 @@ +# It's recommended to set a minimum CMake version. +# If you use CMake features from higher versions, update this to match. +cmake_minimum_required(VERSION 3.23) +message("Using toolchain file ${CMAKE_TOOLCHAIN_FILE}.") + +######################################################################################################################## +## Define project +######################################################################################################################## + +# Set your project name. This will be the name of your SKSE .dll file. +project(F4SE_HTTP VERSION 0.0.1 LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + +include(cmake/common.cmake) + +option(DBUILD_SHARED_LIBS OFF) +option(BUILD_SHARED_LIBS OFF) +option(BUILD_STATIC_CURL ON) +option(BUILD_STATIC_LIBS ON) + +set(SOURCES + src/TypedDictionary.cpp + src/SKSE_HTTP_TypedDictionary.cpp + src/plugin.cpp) + +source_group( + TREE ${CMAKE_CURRENT_SOURCE_DIR} + FILES + ${sources}) + +include(FetchContent) +FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 3b15fa82ea74739b574d705fea44959b58142eb8) # Replace with your desired git commit from: https://github.com/libcpr/cpr/releases +FetchContent_MakeAvailable(cpr) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_MakeAvailable(json) + +# If you're not using a mod manager, you probably want the SKSE plugin to go +# inside of your Skyrim "Data" folder. +# +# To do this automatically, set the `SKYRIM_FOLDER` environment variable +# to the path of your Skyrim Special Edition folder +if(DEFINED ENV{SKYRIM_FOLDER} AND IS_DIRECTORY "$ENV{SKYRIM_FOLDER}/Data") + set(OUTPUT_FOLDER "$ENV{SKYRIM_FOLDER}/Data") +endif() + +# If you're using Mod Organizer 2 or Vortex, you might want this to go inside +# of your "mods" folder, inside of a subfolder named "". +# +# To do this automatically, set the `MO2_MODS_FOLDER` environment variable +# to the path of your "mods" folder +if(DEFINED ENV{MO2_MODS_FOLDER} AND IS_DIRECTORY "$ENV{MO2_MODS_FOLDER}") + set(OUTPUT_FOLDER "$ENV{MO2_MODS_FOLDER}/${PROJECT_NAME}") +endif() + +# Otherwise, you can set OUTPUT_FOLDER to any place you'd like :) +# set(OUTPUT_FOLDER "C:/path/to/any/folder") + +######################################################################################################################## +## Configure target DLL +######################################################################################################################## + +add_library( + "${PROJECT_NAME}" + SHARED + ${SOURCES} +) + +target_compile_features( + "${PROJECT_NAME}" + PRIVATE + cxx_std_23 +) + +add_library("${PROJECT_NAME}::${PROJECT_NAME}" ALIAS "${PROJECT_NAME}") + +target_include_directories(${PROJECT_NAME} + PRIVATE + $ + $ + $) + +target_include_directories(${PROJECT_NAME} + PUBLIC + $) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + cpr::cpr) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + nlohmann_json::nlohmann_json) + +include(cmake/addCommonLibF4NG.cmake) + +target_precompile_headers(${PROJECT_NAME} PRIVATE src/PCH.h) # <--- PCH.h is required! + +# When your SKSE .dll is compiled, this will automatically copy the .dll into your mods folder. +# Only works if you configure DEPLOY_ROOT above (or set the MO2_MODS_FOLDER environment variable) +if(DEFINED OUTPUT_FOLDER) + # If you specify an (including via environment variables) + # then we'll copy your mod files into Skyrim or a mod manager for you! + + # Copy the SKSE plugin .dll files into the F4SE/Plugins/ folder + set(DLL_FOLDER "${OUTPUT_FOLDER}/F4SE/Plugins") + + message(STATUS "SKSE plugin output folder: ${DLL_FOLDER}") + + add_custom_command( + TARGET "${PROJECT_NAME}" + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "${DLL_FOLDER}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${DLL_FOLDER}/$" + VERBATIM + ) + + # If you perform a "Debug" build, also copy .pdb file (for debug symbols) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_custom_command( + TARGET "${PROJECT_NAME}" + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${DLL_FOLDER}/$" + VERBATIM + ) + endif() +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..bb7b262 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,41 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "installDir": "${sourceDir}/install/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_MAKE_PROGRAM":"C:/PROGRAM FILES/MICROSOFT VISUAL STUDIO/2022/COMMUNITY/COMMON7/IDE/COMMONEXTENSIONS/MICROSOFT/CMAKE/Ninja/ninja.exe", + "CMAKE_CXX_COMPILER": "cl.exe", + "CMAKE_CXX_FLAGS": "/permissive- /Zc:preprocessor /EHsc /MP /W4 -DWIN32_LEAN_AND_MEAN -DNOMINMAX -DUNICODE -D_UNICODE", + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x64-windows-static-md", + "VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/cmake", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + }, + "environment": { + "MO2_MODS_FOLDER": "C:\\Users\\Leidtier\\AppData\\Local\\ModOrganizer\\Fallout 4 Development\\mods" + } + }, + { + "name": "debug", + "inherits": [ "base" ], + "displayName": "Debug", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "release", + "inherits": [ "base" ], + "displayName": "Release", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 405459f..6ef3316 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A F4SE plugin to connect to a local http server and exchange strongly typed JSON - [x] cpr - [x] nlohmann/json - [x] Make sure this works on - - [ ] Fallout 4 + - [x] Fallout 4 - [ ] Fallout VR ## Usage @@ -51,12 +51,13 @@ endFunction Receive the reply from the http server ```Papyrus event OnInit() - ;Register for the event *F4SE_HTTP_OnHttpReplyReceived* using a custom event - RegisterForModEvent("F4SE_HTTP_OnHttpReplyReceived","OnHttpReplyReceived") + ;Register for the event *OnHttpReplyReceived* or/and *OnHttpErrorReceived* using a custom event + RegisterForExternalEvent("OnHttpReplyReceived","OnHttpReplyReceived") + RegisterForExternalEvent("OnHttpErrorReceived","OnHttpErrorReceived") endEvent -;*F4SE_HTTP_OnHttpReplyReceived* provides a handle to a dictionary that contains the contents of the reply -event OnHttpReplyReceived(int typedDictionaryHandle) +;*OnHttpReplyReceived* provides a handle to a dictionary that contains the contents of the reply +function OnHttpReplyReceived(int typedDictionaryHandle) ;retrieve string from dictionary using key "replytype". The last parameter is a default that will be used if the value does not exist string replyType = F4SE_HTTP.getString(typedDictionaryHandle, "replytype", "Error: No reply type received") string npc = F4SE_HTTP.getString(typedDictionaryHandle, "npc", "Error: No npc to say stuff") @@ -67,9 +68,11 @@ event OnHttpReplyReceived(int typedDictionaryHandle) string current_location = F4SE_HTTP.getString(contextHandle, "location", "Only the gods know where") int time = F4SE_HTTP.getInt(contextHandle, "time", 0) DoSomethingWithAllThisStuff() -endEvent +endFunction ``` -Uses https://github.com/alandtse/CommonLibF4 + +Uses https://github.com/alandtse/CommonLibF4 Many thanks! + Based on https://github.com/SkyrimScripting/SKSE_Templates Many thanks! diff --git a/Scripts/Source/User/F4SE_HTTP.psc b/Scripts/Source/User/F4SE_HTTP.psc index 83cdd73..a40b4cf 100644 --- a/Scripts/Source/User/F4SE_HTTP.psc +++ b/Scripts/Source/User/F4SE_HTTP.psc @@ -4,29 +4,12 @@ function sendLocalhostHttpRequest(int typedDictionaryHandle, int port, string ro event OnHttpReplyReceived(int typedDictionaryHandle) native event OnHttpErrorReceived(int typedDictionaryHandle) native -; function raiseOnHttpReplyReceived(int typedDictionaryHandle) global -; int handle = ModEvent.Create("SKSE_HTTP_OnHttpReplyReceived") -; if (handle) -; ModEvent.PushInt(handle, typedDictionaryHandle) -; ModEvent.Send(handle) -; endIf -; endFunction - -; function raiseOnHttpErrorReceived(int typedDictionaryHandle) global -; int handle = ModEvent.Create("SKSE_HTTP_OnHttpErrorReceived") -; if (handle) -; ModEvent.PushInt(handle, typedDictionaryHandle) -; ModEvent.Send(handle) -; endIf -; endFunction ; Dictionary Int function createDictionary() global native function clearAllDictionaries() global native -;/ Returns the value associated with the @key. If not, returns @default value -/; String function getString(Int object, String key, String default="") global native Int function getInt(Int object, String key, Int default=0) global native Float function getFloat(Int object, String key, Float default=0.0) global native @@ -38,8 +21,6 @@ String[] function getStringArray(Int object, String key) global native Bool[] function getBoolArray(Int object, String key) global native Int[] function getNestedDictionariesArray(Int object, String key) global native -;/ Inserts @key: @value pair. Replaces existing pair with the same @key -/; function setString(Int object, String key, String value) global native function setInt(Int object, String key, Int value) global native function setFloat(Int object, String key, Float value) global native @@ -51,11 +32,6 @@ function setStringArray(Int object, String key, String[] value) global native function setBoolArray(Int object, String key, Bool[] value) global native function setNestedDictionariesArray(Int object, String key, Int[] value) global native -;/ Returns true, if the container has @key: value pair -/; Bool function hasKey(Int object, String key) global native -;/ Returns type of the value associated with the @key. - 0 - no value, 1 - none, 2 - int, 3 - float, 4 - form, 5 - object, 6 - string -/; Int function valueType(Int object, String key) global native \ No newline at end of file diff --git a/cmake/addCommonLibF4NG.cmake b/cmake/addCommonLibF4NG.cmake new file mode 100644 index 0000000..4f1447b --- /dev/null +++ b/cmake/addCommonLibF4NG.cmake @@ -0,0 +1,81 @@ +macro(find_commonlib_path) + if (CommonLibName AND NOT ${CommonLibName} STREQUAL "") + # Check extern + find_path(CommonLibPath + include/REL/Relocation.h + PATHS ${ROOT_DIR}/extern/${CommonLibName}/${CommonLibName}/) + if (${CommonLibPath} STREQUAL "CommonLibPath-NOTFOUND") + #Check path + set_from_environment(${CommonLibName}Path) + set(CommonLibPath ${${CommonLibName}Path}) + endif() + endif() +endmacro() +set(CommonLibName "CommonLibF4") +set_root_directory() + +find_commonlib_path() +message( + STATUS + "Building ${PROJECT_NAME} ${PROJECT_VERSION} for ${FalloutVersion} with ${CommonLibName} at ${CommonLibPath}." +) + +if (DEFINED CommonLibPath AND NOT ${CommonLibPath} STREQUAL "" AND IS_DIRECTORY ${CommonLibPath}) + add_subdirectory(${CommonLibPath} ${CommonLibName}) +else () + message( + FATAL_ERROR + "Variable ${CommonLibName}Path is not set or in external/." + ) +endif() + +find_package(Boost + MODULE + REQUIRED + COMPONENTS + nowide + #stacktrace_windbg +) +#IF (Boost_FOUND) +# INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) +# ADD_DEFINITIONS( "-DHAS_BOOST" ) +#ENDIF() +find_package(binary_io REQUIRED CONFIG) +find_package(fmt REQUIRED CONFIG) +find_package(frozen REQUIRED CONFIG) +find_package(spdlog REQUIRED CONFIG) +find_package(mmio REQUIRED CONFIG) +find_package(xbyak REQUIRED CONFIG) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +target_link_libraries( + "${PROJECT_NAME}" + PRIVATE + Boost::headers + Boost::nowide + #Boost::stacktrace_windbg + Dbghelp.lib + binary_io::binary_io + CommonLibF4::CommonLibF4 + fmt::fmt + frozen::frozen + mmio::mmio + spdlog::spdlog + xbyak::xbyak +) + +#target_link_libraries( +# "${PROJECT_NAME}" +# PRIVATE +# Dbghelp.lib +# CommonLibF4::CommonLibF4 +# mmio::mmio +# spdlog::spdlog +# xbyak::xbyak +#) + +#target_compile_definitions( +# CommonLibF4 +# PUBLIC +# F4SE_SUPPORT_XBYAK +#) diff --git a/cmake/common.cmake b/cmake/common.cmake new file mode 100644 index 0000000..e23f830 --- /dev/null +++ b/cmake/common.cmake @@ -0,0 +1,78 @@ +option(COPY_OUTPUT "copy the output of build operations to the game directory" OFF) + +macro(set_from_environment VARIABLE) + if(NOT DEFINED ${VARIABLE} AND DEFINED ENV{${VARIABLE}}) + set(${VARIABLE} $ENV{${VARIABLE}}) + endif() +endmacro() + +function(set_root_directory) + set(ROOT_DIR "${PROJECT_SOURCE_DIR}") + cmake_path(NORMAL_PATH ROOT_DIR) + if("${ROOT_DIR}" MATCHES "[\\/]$") + string(LENGTH "${ROOT_DIR}" ROOT_DIR_LENGTH) + math(EXPR ROOT_DIR_LENGTH "${ROOT_DIR_LENGTH} - 1") + string(SUBSTRING "${ROOT_DIR}" 0 "${ROOT_DIR_LENGTH}" ROOT_DIR) + endif() + set(ROOT_DIR "${ROOT_DIR}" PARENT_SCOPE) +endfunction() + +macro(handle_data_files) + set(_PREFIX handle_data_files) + + set(_OPTIONS) + set(_ONE_VALUE_ARGS + DESTINATION + ) + set(_MULTI_VALUE_ARGS + FILES + ) + + set(_REQUIRED + FILES + ) + + cmake_parse_arguments( + "${_PREFIX}" + "${_OPTIONS}" + "${_ONE_VALUE_ARGS}" + "${_MULTI_VALUE_ARGS}" + "${ARGN}" + ) + + foreach(_ARG ${_REQUIRED}) + if(NOT DEFINED "${_PREFIX}_${_ARG}") + message(FATAL_ERROR "Argument is required to be defined: ${_ARG}") + endif() + endforeach() + + if(DEFINED ${_PREFIX}_DESTINATION) + set(${_PREFIX}_INSTALL_DESTINATION "${${_PREFIX}_DESTINATION}") + set(${_PREFIX}_COPY_DESTINATION "${${_PREFIX}_DESTINATION}") + else() + set(${_PREFIX}_INSTALL_DESTINATION "/") + set(${_PREFIX}_COPY_DESTINATION "") + endif() + + install( + FILES ${${_PREFIX}_FILES} + DESTINATION "${${_PREFIX}_INSTALL_DESTINATION}" + COMPONENT "main" + ) + + if("${COPY_OUTPUT}") + foreach(_FILE ${${_PREFIX}_FILES}) + add_custom_command( + TARGET "${PROJECT_NAME}" + POST_BUILD + COMMAND + "${CMAKE_COMMAND}" + -E + copy_if_different + "${_FILE}" + "${Fallout4Path}/Data/${${_PREFIX}_COPY_DESTINATION}" + VERBATIM + ) + endforeach() + endif() +endmacro() \ No newline at end of file diff --git a/fallout4.ppj b/fallout4.ppj index 443d512..80f88a4 100644 --- a/fallout4.ppj +++ b/fallout4.ppj @@ -13,9 +13,9 @@ - + - + @GameFolder\Data\Scripts\Source\Base diff --git a/src/plugin.cpp b/src/plugin.cpp index c4f9b53..5d3f75e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -107,13 +107,7 @@ int generateDictionaryFromJson(json jsonToUse) int sendHttpRequestResultToSkyrimEvent(std::string completeReply, std::string papyrusFunctionToCall) { json reply = json::parse(completeReply); int newHandle = generateDictionaryFromJson(reply); - //auto* vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); - //auto eventArgs = RE::MakeFunctionArguments((int)newHandle); - //auto scrapFunc = (Papyrus::FunctionArgs{vm, newHandle}).get(); - //RE::BSTSmartPointer callback; - //RE::BSFixedString Skse_Http = "SKSE_HTTP"; SendPapyrusEvent(papyrusFunctionToCall, newHandle); - //vm->DispatchStaticCall(Skse_Http, papyrusFunctionToCall, scrapFunc, callback); return 0; }; @@ -122,7 +116,6 @@ void postCallbackMethod(cpr::Response response) if (response.status_code == 200) { std::string onHttpReplyReceived = "OnHttpReplyReceived"; - //RE::BSFixedString onHttpReplyReceived = "raiseOnHttpReplyReceived"; sendHttpRequestResultToSkyrimEvent(response.text, onHttpReplyReceived); } else @@ -130,7 +123,6 @@ void postCallbackMethod(cpr::Response response) json jsonToUse; jsonToUse["F4SE_HTTP_error"] = response.error.message; std::string onHttpErrorReceived = "OnHttpErrorReceived"; - //RE::BSFixedString onHttpErrorReceived = "raiseOnHttpErrorReceived"; sendHttpRequestResultToSkyrimEvent(jsonToUse.dump(), onHttpErrorReceived); } } @@ -302,18 +294,6 @@ bool Bind(RE::BSScript::IVirtualMachine* vm) { return true; }; -//SKSEPluginLoad(const SKSE::LoadInterface* skse) { -// SKSE::Init(skse); -// SKSE::GetPapyrusInterface()->Register(Bind); -// return true; -//}; - -//F4SEPlugin_Load(const F4SE::LoadInterface* a_f4se) { -// F4SE::Init(a_f4se); -// F4SE::GetPapyrusInterface()->Register(Bind); -// return true; -//} - extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Query(const F4SE::QueryInterface* a_f4se, F4SE::PluginInfo* a_info) { a_info->infoVersion = F4SE::PluginInfo::kVersion; a_info->name = "F4SE_HTTP"; diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..3e0d0f5 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,7 @@ +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg.git", + "baseline": "cc288af760054fa489574bd8e22d05aa8fa01e5c" + } +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..e9aad45 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,20 @@ +{ + "name": "f4se-http", + "version-semver": "0.1.0", + "dependencies": [ + "args", + "boost-nowide", + "boost-stacktrace", + "boost-stl-interfaces", + "fmt", + "frozen", + "nowide", + "rapidcsv", + "rsm-binary-io", + "rsm-mmio", + "srell", + "spdlog", + "xbyak" + ] +} +