Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ elseif ("$ENV{RUNTIME_DISABLE_FLAGS}" STREQUAL "-UENABLE_SKYRIM_SE -UENABLE_SKYR
set(COPY_VR YES)
endif()

if (COPY_SE)
if (COPY_SE AND EXISTS "${SKYRIM_SE_DIR}/Data/SKSE/Plugins")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROJECT_NAME}>
"${SKYRIM_SE_DIR}/Data/SKSE/Plugins/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
)
endif()
if (COPY_VR)
if (COPY_VR AND EXISTS "${SKYRIM_VR_DIR}/Data/SKSE/Plugins")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROJECT_NAME}>
"${SKYRIM_VR_DIR}/Data/SKSE/Plugins/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
Expand Down
13 changes: 9 additions & 4 deletions include/GFxDisplayObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,19 @@ namespace IUI
Invoke("swapDepths", a_depth);
}

void LoadMovie(const std::string_view& a_swfRelativePath)
bool LoadMovie(const std::string_view& a_swfRelativePath)
{
Invoke("loadMovie", a_swfRelativePath.data());
return Invoke("loadMovie", static_cast<RE::GFxValue*>(nullptr), a_swfRelativePath.data());
}

void RemoveMovieClip()
bool RemoveMovieClip()
{
Invoke("removeMovieClip");
return Invoke("removeMovieClip", static_cast<RE::GFxValue*>(nullptr));
}

bool SetName(const std::string_view& a_name)
{
return SetMember("_name", a_name.data());
}

std::pair<RE::GFxMovieDefImpl*, RE::GFxSpriteDef*> GetDefs() const
Expand Down
8 changes: 6 additions & 2 deletions include/GFxMoviePatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ namespace IUI
return std::string(dotPos != std::string::npos ? a_memberPath.substr(0, dotPos) : "_root");
}

void AddInstance(const std::string& a_name, GFxDisplayObject& a_parent, const std::string& a_patchRelativePath);
bool AddInstance(const std::string& a_name, GFxDisplayObject& a_parent, const std::string& a_patchRelativePath);

void ReplaceInstance(const std::string& a_name, GFxDisplayObject& a_originalInstance,
bool ReplaceInstance(const std::string& a_name, GFxDisplayObject& a_originalInstance,
GFxDisplayObject& a_parent, const std::string& a_patchRelativePath);

void AbortReplaceInstance(const std::string& a_name, RE::GFxValue& a_originalInstance, const std::string& a_patchRelativePath) const;

bool IsPatchDisabled(const std::string& a_patchRelativePath) const;
bool LoadInstanceMovie(GFxDisplayObject& a_instance, const std::string& a_patchRelativePath, bool a_dispatchPostPatch = true);
std::string MakeTempInstanceName(const std::string& a_name) const;

// members
RE::IMenu* menu;

Expand Down
9 changes: 8 additions & 1 deletion include/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ namespace settings
{
inline logger::level logLevel = logger::level::err;
}
}

namespace patching
{
inline bool safeReplacement = true;
inline std::uint32_t slowPatchWarningMS = 250;
inline const char* disabledPatchFiles = "";
}
}
202 changes: 180 additions & 22 deletions source/GFxMoviePatcher.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
#include "GFxMoviePatcher.h"

#include "GFxDisplayObject.h"
#include "Settings.h"

#include <algorithm>
#include <chrono>
#include <cctype>
#include <sstream>

namespace IUI
{
//std::unordered_map<RE::GFxMovieRoot*, GFxMoviePatcher> g_moviePatchersList;

namespace
{
using Clock = std::chrono::steady_clock;

std::string NormalizePatchPath(std::string a_path)
{
std::replace(a_path.begin(), a_path.end(), '/', '\\');
std::transform(a_path.begin(), a_path.end(), a_path.begin(), [](unsigned char a_char) {
return static_cast<char>(std::tolower(a_char));
});
return a_path;
}

void Trim(std::string& a_text)
{
const auto start = a_text.find_first_not_of(" \t");
if (start == std::string::npos) {
a_text.clear();
return;
}

const auto end = a_text.find_last_not_of(" \t");
a_text = a_text.substr(start, end - start + 1);
}

std::uint64_t ElapsedMS(const Clock::time_point& a_start)
{
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - a_start).count());
}
}

GFxMoviePatcher::GFxMoviePatcher(RE::IMenu* a_menu, RE::GFxMovieRoot* a_movieRoot)
: menu{ a_menu }, movieRoot{ a_movieRoot }
{
Expand Down Expand Up @@ -38,10 +75,10 @@ namespace IUI
{
std::filesystem::path currentPath = stack.top();
stack.pop();

if (std::filesystem::is_directory(currentPath))
{
for (const std::filesystem::directory_entry& childPath : std::filesystem::directory_iterator{ currentPath })
for (const std::filesystem::directory_entry& childPath : std::filesystem::directory_iterator{ currentPath })
{
stack.push(childPath);
}
Expand All @@ -52,6 +89,13 @@ namespace IUI

logger::debug("Relative path is \"{}\"", patchRelativePath);

if (IsPatchDisabled(patchRelativePath)) {
logger::warn("Skipping disabled InfinityUI patch \"{}\"", patchRelativePath);
continue;
}

const auto patchStart = Clock::now();

std::string instancePath = GetInstanceASPath(startPath, currentPath);

std::string parentPath = GetInstanceParentASPath(instancePath);
Expand All @@ -77,29 +121,36 @@ namespace IUI
if (member.IsDisplayObject())
{
GFxDisplayObject instance = member;
ReplaceInstance(instanceName, instance, parent, patchRelativePath);
loadCount++;
if (ReplaceInstance(instanceName, instance, parent, patchRelativePath)) {
loadCount++;
}
}
else
{
AbortReplaceInstance(instanceName, member, patchRelativePath);
}
}
else
else
{
AddInstance(instanceName, parent, patchRelativePath);
loadCount++;
if (AddInstance(instanceName, parent, patchRelativePath)) {
loadCount++;
}
}
}
}
else
else
{
std::string parentName = GetInstanceASName(parentPath);

AbortReplaceInstance(parentName, member, patchRelativePath);
}
}
}

const auto patchElapsedMS = ElapsedMS(patchStart);
if (settings::patching::slowPatchWarningMS > 0 && patchElapsedMS >= settings::patching::slowPatchWarningMS) {
logger::warn("InfinityUI patch \"{}\" took {} ms", patchRelativePath, patchElapsedMS);
}
}
}

Expand All @@ -110,32 +161,69 @@ namespace IUI
return loadCount;
}

void GFxMoviePatcher::AddInstance(const std::string& a_name, GFxDisplayObject& a_parent, const std::string& a_patchRelativePath)
bool GFxMoviePatcher::AddInstance(const std::string& a_name, GFxDisplayObject& a_parent, const std::string& a_patchRelativePath)
{
GFxDisplayObject newInstance = a_parent.CreateEmptyMovieClip(a_name, a_parent.GetNextHighestDepth());

logger::trace("Relative path to patch is: {}", a_patchRelativePath);

newInstance.LoadMovie(a_patchRelativePath);
movieRoot->Advance(0);
if (!LoadInstanceMovie(newInstance, a_patchRelativePath)) {
newInstance.RemoveMovieClip();
return false;
}

logger::trace("New instance loaded!");
logger::trace("");
LogMembersOf(a_parent);
LogMembersOf(newInstance);
logger::trace("");

auto [movieDefImpl, spriteDef] = newInstance.GetDefs();

addedInstanceMovieDefs.push_back(movieDefImpl);

// Actions after loading the movieclip
API::DispatchMessage(API::PostPatchInstanceMessage{ menu, movieRoot, newInstance, movieDefImpl, spriteDef });
return true;
}

void GFxMoviePatcher::ReplaceInstance(const std::string& a_name, GFxDisplayObject& a_originalInstance,
bool GFxMoviePatcher::ReplaceInstance(const std::string& a_name, GFxDisplayObject& a_originalInstance,
GFxDisplayObject& a_parent, const std::string& a_patchRelativePath)
{
if (settings::patching::safeReplacement) {
const std::string tempName = MakeTempInstanceName(a_name);
GFxDisplayObject newInstance = a_parent.CreateEmptyMovieClip(tempName, a_parent.GetNextHighestDepth());

logger::trace("Relative path to patch is: {}", a_patchRelativePath);

if (!LoadInstanceMovie(newInstance, a_patchRelativePath, false)) {
logger::warn("Keeping original {} because replacement patch failed to load from {}", a_name, a_patchRelativePath);
newInstance.RemoveMovieClip();
AbortReplaceInstance(a_name, a_originalInstance, a_patchRelativePath);
return false;
}

// Last chance to retrieve info before removing the movieclip
API::DispatchMessage(API::PreReplaceInstanceMessage{ menu, movieRoot, a_originalInstance });

logger::trace("");
LogMembersOf(a_parent);
LogMembersOf(a_originalInstance);
logger::trace("");

a_originalInstance.SwapDepths(1);
movieRoot->Advance(0);

a_originalInstance.RemoveMovieClip();
movieRoot->Advance(0);

newInstance.SetName(a_name);
movieRoot->Advance(0);

auto [movieDefImpl, spriteDef] = newInstance.GetDefs();
API::DispatchMessage(API::PostPatchInstanceMessage{ menu, movieRoot, newInstance, movieDefImpl, spriteDef });

logger::trace("Original instance replaced!");
logger::trace("");
LogMembersOf(a_parent);
LogMembersOf(newInstance);
logger::trace("");

return true;
}

// Last chance to retrieve info before removing the movieclip
API::DispatchMessage(API::PreReplaceInstanceMessage{ menu, movieRoot, a_originalInstance });

Expand All @@ -161,15 +249,85 @@ namespace IUI
LogMembersOf(a_parent);
logger::trace("");

AddInstance(a_name, a_parent, a_patchRelativePath);
return AddInstance(a_name, a_parent, a_patchRelativePath);
}

void GFxMoviePatcher::AbortReplaceInstance(const std::string& a_name, RE::GFxValue& a_instance,
const std::string& a_patchRelativePath) const
{
logger::warn("{} exists in the movie, but it is not a DisplayObject. Aborting replacement for {}",
logger::warn("{} exists in the movie, but it is not a DisplayObject. Aborting replacement for {}",
a_name, a_patchRelativePath);

API::DispatchMessage(API::AbortPatchInstanceMessage{ menu, movieRoot, a_instance });
}

bool GFxMoviePatcher::IsPatchDisabled(const std::string& a_patchRelativePath) const
{
const char* disabledPatchFiles = settings::patching::disabledPatchFiles;
if (!disabledPatchFiles || !disabledPatchFiles[0]) {
return false;
}

const std::string patchPath = NormalizePatchPath(a_patchRelativePath);
std::stringstream disabledList{ disabledPatchFiles };
std::string disabledPath;

while (std::getline(disabledList, disabledPath, ';')) {
std::stringstream commaList{ disabledPath };
std::string token;

while (std::getline(commaList, token, ',')) {
token = NormalizePatchPath(token);
Trim(token);

if (!token.empty() && patchPath.ends_with(token)) {
return true;
}
}
}

return false;
}

bool GFxMoviePatcher::LoadInstanceMovie(GFxDisplayObject& a_instance, const std::string& a_patchRelativePath, bool a_dispatchPostPatch)
{
const auto loadStart = Clock::now();

if (!a_instance.LoadMovie(a_patchRelativePath)) {
logger::warn("loadMovie invocation failed for {}", a_patchRelativePath);
return false;
}

movieRoot->Advance(0);

const auto loadElapsedMS = ElapsedMS(loadStart);
if (settings::patching::slowPatchWarningMS > 0 && loadElapsedMS >= settings::patching::slowPatchWarningMS) {
logger::warn("loadMovie for \"{}\" took {} ms", a_patchRelativePath, loadElapsedMS);
}

auto [movieDefImpl, spriteDef] = a_instance.GetDefs();
if (!movieDefImpl || !spriteDef) {
logger::warn("Could not resolve loaded movie definitions for {}", a_patchRelativePath);
return false;
}

logger::trace("New instance loaded!");
logger::trace("");
LogMembersOf(a_instance);
logger::trace("");

addedInstanceMovieDefs.push_back(movieDefImpl);

if (a_dispatchPostPatch) {
// Actions after loading the movieclip
API::DispatchMessage(API::PostPatchInstanceMessage{ menu, movieRoot, a_instance, movieDefImpl, spriteDef });
}

return true;
}

std::string GFxMoviePatcher::MakeTempInstanceName(const std::string& a_name) const
{
return "__IUI_LoadCheck_" + a_name;
}
}
13 changes: 12 additions & 1 deletion source/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@

#include "GFxMoviePatcher.h"

#include <unordered_set>

namespace hooks
{
void PatchGFxMovie(RE::GFxMovieRoot* a_movieRoot, float a_deltaT, std::uint32_t a_frameCatchUpCount, RE::IMenu* a_menu)
{
a_movieRoot->Advance(a_deltaT, a_frameCatchUpCount);

static thread_local std::unordered_set<RE::GFxMovieRoot*> patchingMovieRoots;
const auto [it, inserted] = patchingMovieRoots.insert(a_movieRoot);
if (!inserted) {
logger::warn("Skipping recursive InfinityUI patch pass for {}", a_movieRoot->GetMovieDef()->GetFileURL());
return;
}

IUI::GFxMoviePatcher moviePatcher{ a_menu, a_movieRoot };

int loadedInstancesCount = moviePatcher.LoadInstancePatches();

patchingMovieRoots.erase(it);


const char* movieRootFileUrl = a_movieRoot->GetMovieDef()->GetFileURL();

Expand Down Expand Up @@ -42,4 +53,4 @@ namespace hooks
IUI::API::DispatchMessage(IUI::API::PostInitExtensionsMessage{ a_menu, a_movieRoot });
//}
}
}
}
Loading