CKPE Plugin - Perchik71/Creation-Kit-Platform-Extended GitHub Wiki
Important
This version of the wiki is only relevant for version v0.6 and newer. If you need information about creating plugins for older versions, please refer to the editorial history.
Note
All plugins must be in the folder: <GAMEDIR>\CKPEPlugins\.
Your debug log is there too.
CKPE supports plugins. You can create your own project in C/C++, but it is best to study or use a ready-made project TestPlugin.
Important
You are required to have knowledge of С/C++, ASM x64, RE (Reverse Engineering). It makes no sense to start if you don't have these skills, or you can try to learn new things. As a debugger, perchik71 use x64dbg tool.
Create a project dynamic library .dll in MS Visual Studio 2022 or newer.
Include path should be: $(SolutionDir)CKPE\Include;$(SolutionDir)CKPE.PluginAPI\Include;$(SolutionDir)CKPE.Common\Include;$(IncludePath).
Note
Depending on the location of your project, it may be different, but you need Includes of these libraries for initialization in CKPE.
Library path should be: $(SolutionDir)$(Platform). And add a list of libraries: CKPE.lib;CKPE.Common.lib;CKPE.PluginAPI.lib;%(AdditionalDependencies)
Note
Depending on the location of your project, it may be different. Before you start creating the plugin, you must compile CKPE and specify the path to the .lib files.
Also, depending on your needs, you may need to additionally Include and .lib the specific game you want to support. For example, if you need EditorAPI assets.
At the beginning dllmain.cpp write the following:
#include "windows.h"
#include <CKPE.Module.h>
#include <CKPE.MessageBox.h>
#include <CKPE.StringUtils.h>
#include <CKPE.PathUtils.h>
#include <CKPE.PluginAPI.PluginAPI.h>
using namespace CKPE;Next specify the export functions and data that will be processed in CKPE.
extern "C"
{
__declspec(dllexport) PluginAPI::CKPEPluginVersionData CKPEPlugin_Version;
__declspec(dllexport) bool CKPEPlugin_HasDependencies() noexcept;
__declspec(dllexport) uint32_t CKPEPlugin_GetDependCount() noexcept;
__declspec(dllexport) const char* CKPEPlugin_GetDependAt(uint32_t id) noexcept;
__declspec(dllexport) uint32_t CKPEPlugin_Load(const PluginAPI::CKPEPluginInterface* intf) noexcept;
}Let's tell about them in more detail:
-
__declspec(dllexport) PluginAPI::CKPEPluginVersionData CKPEPlugin_Version;. Export data contains short information about your plugin, the structure is described in detail here.
The simplest example:
__declspec(dllexport) PluginAPI::CKPEPluginVersionData CKPEPlugin_Version =
{
CKPE::PluginAPI::CKPEPluginVersionData::kVersion,
1,
"Test plugin",
"Perchik71",
CKPE::PluginAPI::CKPEPluginVersionData::kGameSkyrimSE,
0,
{ MAKE_EXE_VERSION_EX(1, 5, 73, 0), 0 },
0
};This example will tell CKPE that this plugin is only for Skyrim and version 1.5.73. In other versions, CKPE will notify you of an incompatible plugin and ask user to continue running without the plugin or stop running.
Tip
The kAnyGames and kNoLinkedVersionGame flags are available, where kAnyGames will skip checking what the game is, and kNoLinkedVersionGame will skip checking valid versions. If you support the Skyrim game, but this is for any version that supports CKPE itself, then the kNoLinkedVersionGame flag is suitable for you.
-
__declspec(dllexport) bool CKPEPlugin_HasDependencies() noexcept;. If your plugin clearly has a dependency on the installed patches in CKPE, you must returntruein this function, after which CKPE will call__declspec(dllexport) uint32_t CKPEPlugin_GetDependCount() noexcept;, where you must return the explicit number of dependent patches, and__declspec(dllexport) const char* CKPEPlugin_GetDependAt(uint32_t id) noexcept;you must return the name of the dependent patch.
The simplest example:
__declspec(dllexport) bool CKPEPlugin_HasDependencies() noexcept(true)
{
return true;
}
__declspec(dllexport) std::uint32_t CKPEPlugin_GetDependCount() noexcept(true)
{
return 1;
}
__declspec(dllexport) const char* CKPEPlugin_GetDependAt(std::uint32_t id) noexcept(true)
{
return "Console";
}This example says that you need an output patch to the application console, usually there are errors and the process that CK is doing at the moment and eventually ends up in the .log file. These messages are displayed in the Console window.
Important
If you write your plugin to the .log file, use the namespace PluginAPI for the example PluginAPI::_ERROR("error message"). If you write CKPE::_ERROR, you will see this message in the .log file of CKPE itself.
Note
In fact, you don't need a patch to output to the console, this is a CKPE system function, disabling the Console patch will not cause the window to malfunction, just the entire output of the CK itself will be in the standard 1k of dumb windows messages.
Tip
In order for your message to send there, use: _CONSOLE("best message %d", 1) or _CONSOLEVA("best message %d", ap_list) and add include #include <CKPE.Common.LogWindow.h>.
-
__declspec(dllexport) uint32_t CKPEPlugin_Load(const PluginAPI::CKPEPluginInterface* intf) noexcept;. It is called at the very end, here you directly write all the logic of initializing your patch and what it should do.
__declspec(dllexport) uint32_t CKPEPlugin_Load(const PluginAPI::CKPEPluginInterface* intf) noexcept(true)
{
auto path = std::make_unique<wchar_t[]>(MAX_PATH);
if (!path) return false;
// init plugin .log file
auto sfname = PathUtils::GetCKPELogsPluginPath() + L"TestPlugin.log";
PathUtils::CreateFolder(PathUtils::ExtractFilePath(PathUtils::Normalize(sfname)));
if (!PluginAPI::UserPluginLogger.Open(sfname))
{
CKPE::_ERROR(L"Couldn't create \"%s\" file", sfname.c_str());
return false;
}
/*
...
<BEST YOUR CODE>
...
*/
return true;
}If everything is successful, you must return a non-zero value, otherwise return 0.
Caution
CKPE actually expects bool, so use boolean values false or true.
The structure of PluginAPI::CKPEPluginInterface is described here, it has a QueryInterface() function, which does not return anything useful yet, and no interfaces have been implemented. The GetPluginHandle() function returns the index of the plugin, but this is relevant
only within the limits of the CKPEPlugin_Load() function, after its completion, the index is irrelevant.
You can get the path to yours .dll is in the most main function, however, you cannot use the CKPE interfaces at this stage, as you have not received them yet.
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
// Getting the file name of this dll
GetModuleFileNameA(hModule, szPluginDllFileName, MAX_PATH);
// Here, your code, if you need it here...
}
return TRUE;
}