diff --git a/.gitignore b/.gitignore index 53f5ec2..d65c822 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,13 @@ *.out *.app +Binaries/ +Intermediates/ BuildFiles/ -Output/ \ No newline at end of file +vsxmake2022/ +Output/ +build/ +.xmake/ +RE-UE4SS/ +Mods/* +!xmake.lua \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e7734f3..0000000 --- a/.gitmodules +++ /dev/null @@ -1,5 +0,0 @@ -[submodule "RE-UE4SS"] - path = RE-UE4SS - url = https://github.com/UE4SS-RE/RE-UE4SS.git - branch = main - ignore = dirty diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index a678b51..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.18) - -project(MyCPPMods) - -# requirements -add_subdirectory(RE-UE4SS) -add_subdirectory(MyCPPMods) diff --git a/Mods/xmake.lua b/Mods/xmake.lua new file mode 100644 index 0000000..c7becde --- /dev/null +++ b/Mods/xmake.lua @@ -0,0 +1 @@ +includes("*/xmake.lua") \ No newline at end of file diff --git a/MyCPPMods/CMakeLists.txt b/MyCPPMods/CMakeLists.txt deleted file mode 100644 index 5026b83..0000000 --- a/MyCPPMods/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(MyExampleMod) diff --git a/MyCPPMods/MyExampleMod/CMakeLists.txt b/MyCPPMods/MyExampleMod/CMakeLists.txt deleted file mode 100644 index a6f6564..0000000 --- a/MyCPPMods/MyExampleMod/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.18) - -set(TARGET MyExampleMod) -project(${TARGET}) - -add_library(${TARGET} SHARED "src/dllmain.cpp") -target_include_directories(${TARGET} PRIVATE .) - -target_link_libraries(${TARGET} PRIVATE UE4SS) diff --git a/MyCPPMods/MyExampleMod/src/dllmain.cpp b/MyCPPMods/MyExampleMod/src/dllmain.cpp deleted file mode 100644 index 0739745..0000000 --- a/MyCPPMods/MyExampleMod/src/dllmain.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include -#include -#include - -namespace MyMods -{ - using namespace RC; - using namespace Unreal; - - /** - * MyExampleMod: UE4SS c++ mod class defintion - */ - class MyExampleMod : public RC::CppUserModBase { - public: - - // constructor - MyExampleMod() { - ModVersion = STR("0.1"); - ModName = STR("MyExampleMod"); - ModAuthors = STR("UE4SS"); - ModDescription = STR("A basic template C++ mod"); - // Do not change this unless you want to target a UE4SS version - // other than the one you're currently building with somehow. - //ModIntendedSDKVersion = STR("2.6"); - - Output::send(STR("[MyExampleMod]: Init.\n")); - } - - // destructor - ~MyExampleMod() override { - // fill when required - } - - auto on_program_start() -> void override - { - } - - auto on_dll_load(std::wstring_view dll_name) -> void override - { - } - - auto on_unreal_init() -> void override - { - // You are allowed to use the 'Unreal' namespace in this function and anywhere else after this function has fired. - auto Object = UObjectGlobals::StaticFindObject(nullptr, nullptr, STR("/Script/CoreUObject.Object")); - Output::send(STR("Object Name: {}\n"), Object->GetFullName()); - } - - };//class -} - -/** -* export the start_mod() and uninstall_mod() functions to -* be used by the core ue4ss system to load in our dll mod -*/ -#define MOD_EXPORT __declspec(dllexport) -extern "C" { - MOD_EXPORT RC::CppUserModBase* start_mod(){ return new MyMods::MyExampleMod(); } - MOD_EXPORT void uninstall_mod(RC::CppUserModBase* mod) { delete mod; } -} - - - - - diff --git a/RE-UE4SS b/RE-UE4SS deleted file mode 160000 index ce074a9..0000000 --- a/RE-UE4SS +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ce074a9b8c411733cefa19fc00b0562d400e1914 diff --git a/README.md b/README.md index a03e1ce..ae9610e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ # UE4SSCPPTemplate - + +This repository includes some custom `xmake` tasks to assist in your mod development. + +- `xmake newmod` +- `xmake installmod` +- `xmake bi` +- `xmake ue4ss` + +## `xmake newmod [-r, --regen] name` + +The `newmod` command will ensure you have the UE4SS repository cloned and then bootstrap new mod creation. + +Running `xmake newmod CoolMod` will generate `/Mods/CoolMod/...` in your repository and automatically link it with the xmake system. If you have VS2022 installed then the `newmod` command will also automatically generate a VS project which can be located at `/vsxmake2022/UE4SSCppTemplate.sln`. + +## `xmake installmod [-d, --exedir] name` + +The `installmod` command will copy your build output to a specific game directory. The configuration of the build you want to install is derived from the currently configured xmake mode (`xmake f -m `). This configured mode is global to the `RE-UE4SS` repo and all the mods in the `Mods` folder. If you want to specify a specific configuration to install, then you can change the xmake configuration by running `xmake f -m "Game__Debug__Win64"`. + +Running `xmake installmod --exedir="Path\To\Your\Game\Dir" CoolMod` will copy the build output and install `CoolMod` in the correct subdir in the game's mod folder. + +Note that running `xmake installmod` does not automatically build your mod. You have to manually build it with `xmake build` or use the `xmake bi` task to build and install your mod. + +## `xmake bi [-d, --exedir] name` + +This is a shorthand task that will run `xmake build -y ` followed by the `xmake installmod` command. This one-liner command facilitates rapid testing/iterating of your mod. + +## `xmake ue4ss [-r, --remote] [-u, --update]` + +By default, the `xmake newmod` command will attempt to checkout the latest release tag of UE4SS to build your mods against. If you want to build your mods against a different remote then you can specify a branch or tag as the `--remote=` parameter. + +`xmake ue4ss --remote="v3.1.0"` will attempt to use tag "v3.1.0" as the checked out UE4SS version. + +You can use the latest release tag by specifying `xmake ue4ss --remote="latest"`. + +You can also specify branches instead of tags to use. `xmake ue4ss --remote=main` will checkout the `main` branch of UE4SS. + +If you want to get upstream changes from remote branches then you can use the `--update` arg to pull remote changes. + +`xmake ue4ss --remote=main --update` will pull the latest remote changes from the `main` branch locally. + +You can also run `xmake ue4ss --update` and xmake will try to pull changes from whatever branch your local UE4SS is currently on. \ No newline at end of file diff --git a/Scripts/build_and_install.lua b/Scripts/build_and_install.lua new file mode 100644 index 0000000..09d6b05 --- /dev/null +++ b/Scripts/build_and_install.lua @@ -0,0 +1,7 @@ +import("core.base.option") +import("core.base.task") + +function main() + task.run("build", {yes={}}, option.get("name")) + task.run("installmod", {exedir=option.get("exedir"), name=option.get("name")}) +end diff --git a/Scripts/install_mod.lua b/Scripts/install_mod.lua new file mode 100644 index 0000000..33b6077 --- /dev/null +++ b/Scripts/install_mod.lua @@ -0,0 +1,49 @@ +import("core.base.option") +import("core.base.task") +import("core.project.config") +import("core.project.project") + +function main() + -- validate? + local mod_name = option.get("name") + local mod_dir = path.join(option.get("exedir"), "Mods", mod_name) + + -- Get the current build mode. ex: Game__Shipping__Win64 + config.load() + local mode = config.get("mode") + print("Using the mode from xmake config: %s", mode) + + -- Load the dll location from the xmake target for the mod. + local target = project.target(mod_name) + + if not target then + print("Target %s not found.", mod_name) + os.exit() + end + + -- Step 1: Check if we have build output. + if not os.exists(target:targetfile()) then + print("Build output not detected at %s", target:targetfile()) + os.exit() + end + + -- Step 2: Copy to the mods folder. + local dll_dir = path.join(mod_dir, "dlls") + if not os.exists(dll_dir) then + print("%s not found. Creating...", dll_dir) + os.mkdir(dll_dir) + end + + os.cp(target:targetfile(), path.join(dll_dir, "main.dll")) + + if os.exists(target:symbolfile()) then + print("Symbol file detected. Copying to mod directory...") + os.cp(target:symbolfile(), path.join(dll_dir, "main.pdb")) + end + + -- Step 3: Create the enabled.txt in the mods folder. + if not os.exists(path.join(mod_dir, "enabled.txt")) then + print("enabled.txt not found. Creating...") + io.writefile(path.join(mod_dir, "enabled.txt")) + end +end \ No newline at end of file diff --git a/Scripts/new_mod.lua b/Scripts/new_mod.lua new file mode 100644 index 0000000..5695fd3 --- /dev/null +++ b/Scripts/new_mod.lua @@ -0,0 +1,63 @@ +import("core.base.option") +import("devel.git") +import("devel.git.submodule") +import("core.base.task") +import("detect.sdks.find_vstudio") +import("core.base.task") +import("core.base.semver") + +function main() + + -- validate? + local mod_name = option.get("name") + + if not mod_name then + print("Please specify a mod name.") + os.exit() + end + + -- Step 1: Initialise or update RE-UE4SS repository on the latest release tag. + if not os.exists(path.join("$(projectdir)", "RE-UE4SS")) then + print("UE4SS repository not yet cloned. Automatically cloning...") + task.run("ue4ss", {update={}, remote="latest"}) + end + + -- Step 2: Create mod directory and files. + local mods_dir = path.join("$(projectdir)", "Mods") + + if os.exists(mod_name) then + if option.get("regen") then + print("Regenerating mod directory and files.") + CreateModFiles(mod_name, mods_dir) + else + print("Mod directory already exists. Run with the --regen flag to regenerate mod files.") + os.exit() + end + else + CreateModFiles(mod_name, mods_dir) + end + + -- Step 3: Generate VS project if VS2022 is installed + local vs_versions = find_vstudio() + if(vs_versions and vs_versions["2022"]) then + -- Equivalent to xmake project -k vsxmake2022 -y + task.run("project", {kind="vsxmake2022", yes={}}) + end +end + +function CreateModFiles(mod_name, mods_dir) + print("Creating mod directory and files...") + local mod_dir = path.join(mods_dir, mod_name) + os.mkdir(mod_dir) + + local mod_xmake = path.join(mod_dir, "xmake.lua") + os.cp(path.join("$(projectdir)", "xmake_template.lua"), mod_xmake) + + io.replace(mod_xmake, "MyAwesomeMod", mod_name) + + local mod_cpp = path.join(mod_dir, "dllmain.cpp") + os.cp(path.join("$(projectdir)", "dllmain_template.cpp"), mod_cpp) + io.replace(mod_cpp, "MyAwesomeMod", mod_name) + io.replace(mod_cpp, "MY_AWESOME_MOD_API", string.upper(mod_name).."_API") +end + diff --git a/Scripts/ue4ss_git.lua b/Scripts/ue4ss_git.lua new file mode 100644 index 0000000..90447f6 --- /dev/null +++ b/Scripts/ue4ss_git.lua @@ -0,0 +1,78 @@ +import("core.base.option") +import("devel.git") +import("devel.git.submodule") +import("core.base.semver") + +local ue4ss_url = "https://github.com/UE4SS-RE/RE-UE4SS.git" +local ue4ss_dir = "RE-UE4SS" + +function main() + local remote = option.get("remote") + local update = option.get("update") + + if (remote == nil and update == nil) then + print("No arguments. Run 'xmake ue4ss --help'") + os.exit() + end + + local ue4ss_repo = path.join("$(projectdir)", ue4ss_dir) + if not os.exists(ue4ss_repo) then + print("Cloning UE4SS Repository...") + git.clone(ue4ss_url) + end + + if remote then + -- Special handling if the user wants to find the 'latest' release tag. + if remote == "latest" then + print("Attempting to find latest release tag...") + local tags = git.tags(ue4ss_url) + local latest_tag + for _,tag in ipairs(tags) do + if semver.is_valid(tag) then + if not latest_tag then + latest_tag = tag + end + + if semver.compare(tag, latest_tag) == 1 then + latest_tag = tag + end + end + end + remote = latest_tag + end + + -- Returns a matching version/tag/branch based on the provided "remote" string. + -- Uses semantic versioning or branch naming. + local remote, type = semver.select(remote, nil, git.tags(ue4ss_url), git.branches(ue4ss_url)) + if not remote then + print("%s was not found as a valid git remote.") + os.exit() + end + + if type == "tag" then + -- Special logic to ensure we don't check out a tag that doesn't have xmake. + -- This would put the user's local repo into an unrecoverable state. + if not semver.satisfies(remote, ">3.0.1") then + print("The latest tag %s is not compatible with xmake. Using 'main' branch instead of a tag.", remote) + remote = "main" + type = "branch" + end + end + + print("Checking out %s %s...", type, remote) + git.checkout(remote,{repodir=ue4ss_dir}) + end + + if update then + -- If the user specifies a tag, then we don't need to pull/update. + local cur_branch = git.branch({repodir = ue4ss_dir}) + if cur_branch then + git.pull({remote = "origin", tags = true, repodir = ue4ss_dir}) + else + print("UE4SS is not currently a branch. No remote changes to pull.") + end + + print("Updating submodule") + submodule.update({repodir = ue4ss_dir, recursive = true, init=true}) + end +end \ No newline at end of file diff --git a/Scripts/xmake.lua b/Scripts/xmake.lua new file mode 100644 index 0000000..90816d8 --- /dev/null +++ b/Scripts/xmake.lua @@ -0,0 +1,47 @@ +task("ue4ss") + on_run("ue4ss_git") + set_menu { + usage = "xmake ue4ss [options]", + description = "Change the version of ue4ss that is fetched from github.", + options = + { + {'r', 'remote', "v", "latest", "Tag/branch of UE4SS to use. Ex: 'latest', 'v3.0.1', 'experimental', 'main'"}, + {'u', 'update', "k", nil, "Fetches and downloads any remote changes. Only works when the --remote arg is a git branch."}, + } + } + +task("newmod") + on_run("new_mod") + set_menu { + usage = "xmake newmod [options] [modname]", + description = "Bootstrap a new mod", + options = + { + {'r', 'regen', "k", nil, "Forcibly regenerate the mod."}, + {nil, 'name', "v", nil, "Name of the new mod to create."}, + } + } + +task("installmod") + on_run("install_mod") + set_menu { + usage = "xmake installmod [options] [modname]", + description = "Installs a mod into the specified game directory.", + options = + { + {'d', 'exedir', "v", nil, "Path of the game's executable folder"}, + {nil, 'name', "v", nil, "Name of the mod to install."}, + } + } + +task("bi") + on_run("build_and_install") + set_menu { + usage = "xmake bi [options] [modname]", + description = "Build and install a mod. Equivalent to 'xmake build' followed by 'xmake installmod'", + options = + { + {'d', 'exedir', "v", nil, "Path of the game's executable folder"}, + {nil, 'name', "v", nil, "Name of the mod to build and install."}, + } + } \ No newline at end of file diff --git a/build_mods_shipping.bat b/build_mods_shipping.bat deleted file mode 100644 index 6bc00c9..0000000 --- a/build_mods_shipping.bat +++ /dev/null @@ -1,5 +0,0 @@ -if not exist "BuildFiles" mkdir BuildFiles -cd BuildFiles -cmake -B Output -G"Visual Studio 17 2022" .. -cmake --build . --config Game__Shipping__Win64 -pause \ No newline at end of file diff --git a/dllmain_template.cpp b/dllmain_template.cpp new file mode 100644 index 0000000..bc84b1f --- /dev/null +++ b/dllmain_template.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +using namespace RC; +using namespace RC::Unreal; + +class MyAwesomeMod : public CppUserModBase +{ +public: + MyAwesomeMod() : CppUserModBase() + { + ModName = STR("MyAwesomeMod"); + ModVersion = STR("1.0"); + ModDescription = STR("This is my awesome mod"); + ModAuthors = STR("UE4SS Team"); + // Do not change this unless you want to target a UE4SS version + // other than the one you're currently building with somehow. + //ModIntendedSDKVersion = STR("2.6"); + + Output::send(STR("MyAwesomeMod says hello\n")); + } + + ~MyAwesomeMod() override {} + + auto on_update() -> void override {} + + auto on_unreal_init() -> void override + { + auto Object = UObjectGlobals::StaticFindObject(nullptr, nullptr, STR("/Script/CoreUObject.Object")); + Output::send(STR("Object Name: {}\n"), Object->GetFullName()); + } +}; + +#define MY_AWESOME_MOD_API __declspec(dllexport) +extern "C" +{ + MY_AWESOME_MOD_API CppUserModBase* start_mod() + { + return new MyAwesomeMod(); + } + + MY_AWESOME_MOD_API void uninstall_mod(CppUserModBase* mod) + { + delete mod; + } +} diff --git a/generate_project_files.bat b/generate_project_files.bat deleted file mode 100644 index af7a471..0000000 --- a/generate_project_files.bat +++ /dev/null @@ -1,4 +0,0 @@ -if not exist "BuildFiles" mkdir BuildFiles -cd BuildFiles -cmake -G"Visual Studio 17 2022" .. -pause \ No newline at end of file diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..dbc9741 --- /dev/null +++ b/xmake.lua @@ -0,0 +1,3 @@ +includes("RE-UE4SS") +includes("Mods") +includes("Scripts") diff --git a/xmake_template.lua b/xmake_template.lua new file mode 100644 index 0000000..743b3b3 --- /dev/null +++ b/xmake_template.lua @@ -0,0 +1,27 @@ +local projectName = "MyAwesomeMod" + +target(projectName) + set_kind("shared") + set_languages("cxx20") + set_exceptions("cxx") + + add_includedirs(".") + + add_files("dllmain.cpp") + + add_deps("UE4SS") + + on_load(function (target) + import("build_configs", { rootdir = get_config("scriptsRoot") }) + build_configs:set_output_dir(target) + end) + + on_config(function (target) + import("build_configs", { rootdir = get_config("scriptsRoot") }) + build_configs:config(target) + end) + + after_clean(function (target) + import("build_configs", { rootdir = get_config("scriptsRoot") }) + build_configs:clean_output_dir(target) + end)