Arizona_Steve Posted July 11, 2020 Posted July 11, 2020 Hoping there are some C++ experts here that can help. Some may know me as the "Random Sex" guy. Random Sex makes use of an SKSE plugin to figure out which animations can be used and where to place NPCs and creatures within the selected animation. I currently build SKSE as a static library (not a dll) and link my Random Sex dll against that library. I suspect this is not the correct way to do things. The reason I ask is this. The Random Sex dll has a secondary function where it handles the notifications that are put on the screen as to who is doing who. I have added some calls to the SKSE translation code to retrieve translation message templates from which I can add the actor names and generate the notification message. The issue that I have is that the translation strings do not appear to be available when I try to pull the translation templates. Digging around, it looks like the translation strings are loaded at SKSE start-up. I'm guessing that because I am linking Random Sex to an SKSE static library that Random Sex is getting loaded before SKSE and the translation files are loaded. I had hoped that SKSE loaded first then called the SKSEPlugin_Query() and SKSEPlugin_Load() functions in the SexLabRandomSex.dll. Initializing the translation table from SKSEPlugin_Load() gives me an empty (or null) translation table. A little confused here, but I suspect my use of a static library for SKSE is screwing things up. How do people normally link SKSE with their plugins?
Fotogen Posted July 11, 2020 Posted July 11, 2020 Skee64 is not SKSE its a regular mod. Source for skee64.dll: https://github.com/expired6978/SKSE64Plugins/tree/master/skee Start with main.cpp. OK, I never made .dll for SE, but I did for LE. I think the idea is the same. You just use SKSE source. Add .h and .cpp that you need to your project. Not all, just those you need. No need to make SKSE.lib. Maybe making lib would be OK, but don't add everything to that lib that contains "main". Its what I did and it worked. Add .h to project so it compiles. Add to project .cpp so it links.
Guest Posted July 11, 2020 Posted July 11, 2020 Going to answer this against my better judgement (many in these forums get an answer from me and then disappear without ever notifying me of whether my answer was helpful or not, wasting my time in the process), but since I don't think you will get an answer on this... Let's start with the fact that you cannot dynamically link with SKSE. There are no exportable functions for you to make use of. It wouldn't make any sense anyway, since SKSE is the one dynamically linking with your plugin. Some internal SKSE functionality can be accessed via the SKSEInterface pointer passed to your plugin on calls to SKSEPlugin_Query and SKSEPlugin_Load, but that's it. If your plugin is using SKSE code that bitches about symbol resolution at link time, you MUST statically link with skse.lib. Now, you've made some incorrect assumptions, for starters, SKSE is the one that calls LoadLibrary on your DLL, therefore, it obviously gets loaded first. Without SKSE your DLL won't be loaded into the process at all. As to how to handle SKSE functionality not accessible at compile time, you can attach a debugger, get SKSE's physical address of the function (as an example), declare the function with the same return type, parameters and conventions within your plugin and at runtime use GetModuleHandle to get the base address of where the SKSE DLL is loaded into the virtual memory space and define your function pointer with that address plus the offset (physical address) of the function you got from the debugger. using MySKSEFunctionPtr = bool (__cdecl *)(int param1, float param2); MySKSEFunctionPtr MySKSEFunction; __declspec(dllexport) bool SKSEPlugin_Load(const SKSEInterface* skse) { MySKSEFunction = (MySKSEFunctionPtr)((UInt32)GetModuleHandle("skse_1_9_32.dll") + offset); } In your case, you don't need to do any of that. The translator object is stored within memory allocated by the TESV.exe image and SKSE provides you with an easy way to access it. GFxLoader* loader = GFxLoader::GetSingleton(); BSScaleformTranslator* translator = loader->stateBag->GetTranslator(); TranslationTableItem* item = translator->translations.Find(key); item->Dump(); Localization files are read, parsed and cached on GFxLoader's ctor (via a hook), so I would recommend you to start querying the translator after your plugin has received both kMessage_DataLoaded and kMessage_InputLoaded from SKSE. __declspec(dllexport) bool SKSEPlugin_Load(const SKSEInterface* skse) { if (SKSEMessagingInterface* messagingInterface = static_cast<SKSEMessagingInterface*>(skse->QueryInterface(kInterface_Messaging))) { return messagingInterface->RegisterListener(skse->GetPluginHandle(), "SKSE", YourFunctionListeningToSKSEMessages)); } return false; } static void YourFunctionListeningToSKSEMessages(SKSEMessagingInterface::Message* msg) { static UInt8 status = 0; if (msg == nullptr) return; switch (msg->type) { case SKSEMessagingInterface::kMessage_DataLoaded: status |= 1; break; case SKSEMessagingInterface::kMessage_InputLoaded: status |= 2; break; default: return; break; } if (status != 3) return; do something with the translator }
Arizona_Steve Posted July 11, 2020 Author Posted July 11, 2020 @Hawk9969, really appreciate the detailed explanation. While I did have the BSScaleFormTranslator code that you have above, it turns out that I am completely unaware of the events being sent in from SKSE. So that last code snippet above fills in the gap perfectly. Should be easy to implement in my codebase. Thanks again!
Grey Cloud Posted July 11, 2020 Posted July 11, 2020 3 hours ago, Arizona_Steve said: Some may know me as the "Random Sex" guy. Yup. Great little mod.
Guest Posted July 11, 2020 Posted July 11, 2020 56 minutes ago, Arizona_Steve said: @Hawk9969, really appreciate the detailed explanation. While I did have the BSScaleFormTranslator code that you have above, it turns out that I am completely unaware of the events being sent in from SKSE. So that last code snippet above fills in the gap perfectly. Should be easy to implement in my codebase. Thanks again! For the record, engine and Papyrus related functions (such as getting a character's name) will return an UTF-8 const char*, while the translator stores them as const wchar_t* (which on Windows the implementation for wchar_t is UTF-16 LE). This is because Scaleform (which uses wchar_t) is not truly part of the Creation Engine, ala, it's a third party late addition, where Gamebryo/Creation Engine uses UTF-8 for its strings. You can construct a std::wstring from an UTF-8 string by using std::wstring_convert. The std::wstring_convert::from_bytes page has an example of how you can do that.
Arizona_Steve Posted July 11, 2020 Author Posted July 11, 2020 Like this... std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter; return converter.to_bytes(item->translation); That part I figured out some time ago. I plugged in your RegisterListener code, and while I don't get a CTD, it doesn't like the registration and spits out setListener=false: // Also tried with static void skseMessagingCallback(SKSEMessagingInterface::Message* msg) { // Stuff } __declspec(dllexport) bool SKSEPlugin_Load(const SKSEInterface* skse) { // Is there a reason why <static_cast> is better than below? Yes, I'm a Java programmer... SKSEMessagingInterface* messaging = (SKSEMessagingInterface*)skse->QueryInterface(kInterface_Messaging); if (messaging == nullptr) { _MESSAGE("Failed to retrieve SKSE messaging interface"); return false; } PluginHandle pluginHandle = skse->GetPluginHandle(); bool setListener = messaging->RegisterListener(pluginHandle, "SKSE", skseMessagingCallback); if (!setListener) { _MESSAGE("Failed to set to activity data listener"); return false; } // Other stuff return true; }
Guest Posted July 11, 2020 Posted July 11, 2020 I see nothing wrong. Are you filling info->infoVersion, info->name and info->version on SKSEPlugin_Query? Declaring the function as static in that context means that the function will only be visible within this file. As for the cast, (type)obj is a C cast, which in C++ can perform static_cast, reinterpret_cast and const_cast, and as such, it's good practice to only use it for primitive types (char, int, float, etc) where you don't have to worry about type casting safety. *((int*)0); // Compile and crashes at runtime. *(static_cast<int*>(0)); // Compile error. I recommend you to read on the various type castings for C++.
Arizona_Steve Posted July 12, 2020 Author Posted July 12, 2020 Turns out I messed something up and SKSE wasn't recognizing my DLL as loadable (although it was hitting the SKSEPlugin_Load function). After messing around with things all afternoon I reverted back to an earlier build of the main file and reapplied my changes. Looks like everything is working as intended now.
Recommended Posts
Archived
This topic is now archived and is closed to further replies.