#include "UETools.hpp" #include "Engine_classes.hpp" std::shared_ptr g_logger; SDK::APawn* GetPawnFromWorld(SDK::UWorld* world) { if (!world) world = SDK::UWorld::GetWorld(); if (!world) return nullptr; SDK::UGameInstance* gameInstance = world->OwningGameInstance; if (!gameInstance || gameInstance->LocalPlayers.Num() == 0) return nullptr; SDK::ULocalPlayer* localPlayer = gameInstance->LocalPlayers[0]; if (!localPlayer) return nullptr; SDK::APlayerController* pc = localPlayer->PlayerController; if (!pc) return nullptr; SDK::APawn* pawn = pc->AcknowledgedPawn; return pawn; } void ReactivateDevConsole(std::shared_ptr logger) { g_logger = logger; std::thread([logger]() { auto start = std::chrono::high_resolution_clock::now(); // Measure the time to renable console SDK::UEngine* Engine = nullptr; for (int i = 0; i < 100; ++i) { // gives 10 seconds to find UE Engine std::this_thread::sleep_for(std::chrono::milliseconds(100)); Engine = SDK::UEngine::GetEngine(); if (Engine && Engine->ConsoleClass && Engine->GameViewport) break; } if (!Engine || !Engine->ConsoleClass || !Engine->GameViewport) { logger->error("Console could not be found in engine."); return; } logger->info("Console found in engine"); /* Creates a new UObject of class-type specified by Engine->ConsoleClass */ SDK::UObject* NewObject = SDK::UGameplayStatics::SpawnObject(Engine->ConsoleClass, Engine->GameViewport); if (NewObject) { logger->info("Successfully spawned console object"); // Set the console viewport so that it will be displayed Engine->GameViewport->ViewportConsole = static_cast(NewObject); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; // Set the all the console shortkey to F2 for (int i = 0; i < SDK::UInputSettings::GetDefaultObj()->ConsoleKeys.Num(); i++) { SDK::UInputSettings::GetDefaultObj()->ConsoleKeys[i].KeyName = SDK::UKismetStringLibrary::Conv_StringToName(L"F2"); } logger->info("Console fully reactivated in {:.3f}s and bound to key F2", elapsed.count()); logger->info("------------------ User inputs ------------------"); g_Console_Enabled.store(true, std::memory_order_release); return; } else { logger->error("Could not spawn console object"); return; } }).detach(); } // --- HUD & UI methods --- void ApplyOffsetsSmart_Internal( SDK::UWidget* Widget, float Left, float Right, int Depth = 0, int MaxDepth = 1) { if (!Widget) return; // Apply if CanvasSlot if (Widget->Slot && Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { if (Depth <= MaxDepth) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Offsets = Slot->GetOffsets(); SDK::FAnchors Anchors = Slot->GetAnchors(); float MinX = Anchors.Minimum.X; float MaxX = Anchors.Maximum.X; #ifdef MY_VERBOSE_LOGS if (g_logger) g_logger->debug("CanvasPanelSlot: {} {} - Slot: {} {} - Offets: {} {} {} {} - Anchors: {} {} {} {}", Widget->GetName(), Widget->Class->GetName(), Slot->GetName(), Slot->Class->GetName(), Offsets.Left, Offsets.Top, Offsets.Right, Offsets.Bottom, Anchors.Minimum.X, Anchors.Minimum.Y, Anchors.Maximum.X, Anchors.Maximum.Y); #endif if (MinX == 0.f && MaxX == 1.f) return; // Ignore stretch if (MinX == 0.5f && MaxX == 0.5f) return; // Ignore centered if (MinX == 0.f && MaxX == 0.f) // Modify only pure left Offsets.Left = Left; else if (MinX == 1.f && MaxX == 1.f) // Modify only pure right Offsets.Left = -Right; Slot->SetOffsets(Offsets); } } // Stop to max depth if (Depth >= MaxDepth) return; // Browse the children if (Widget->IsA(SDK::UPanelWidget::StaticClass())) { auto* Panel = static_cast(Widget); for (int i = 0; i < Panel->GetChildrenCount(); ++i) ApplyOffsetsSmart_Internal(Panel->GetChildAt(i), Left, Right, Depth + 1, MaxDepth); } if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* UW = static_cast(Widget); if (UW->WidgetTree && UW->WidgetTree->RootWidget) ApplyOffsetsSmart_Internal(UW->WidgetTree->RootWidget, Left, Right, Depth + 1, MaxDepth); } } void ApplyOffsetsSmart(SDK::UWidget* Widget, float Left, float Right, int MaxDepth) { #ifdef MY_VERBOSE_LOGS if (g_logger) g_logger->debug("========================================================="); if (g_logger) g_logger->debug("Widget offset applying: {} {}", Widget->GetName(), Widget->Class->GetName()); if (g_logger) g_logger->debug("========================================================="); #endif ApplyOffsetsSmart_Internal(Widget, Left, Right, 0, MaxDepth); } static void ApplyOffsetsRecursive_Internal(SDK::UWidget* Widget, float Left, float Right, const std::vector& ExcludeObjects, const std::vector& ExcludeClass, int CurrentDepth, int MaxDepth) { if (!Widget || CurrentDepth > MaxDepth) return; // Apply offsets according to Slot type for (const auto& ExcludeObject : ExcludeObjects) { if (ExcludeObject == Widget) return; } std::string widgetName; std::string className; widgetName = Widget->GetName(); if (Widget->Class) className = Widget->Class->GetName(); for (const auto& pattern : ExcludeClass) { if (widgetName.find(pattern) != std::string::npos || className.find(pattern) != std::string::npos) return; } if (Widget->Slot) { if (Widget->Slot->IsA(SDK::UCanvasPanelSlot::StaticClass())) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Offsets = Slot->GetOffsets(); #ifdef MY_VERBOSE_LOGS SDK::FAnchors anchors = Slot->GetAnchors(); if (g_logger) g_logger->debug("CanvasPanelSlot: {} {} - Slot: {} {} - Offets: {} {} {} {} - Anchors: {} {} {} {}", \ Widget->GetName(), Widget->Class->GetName(), \ Slot->GetName(), Slot->Class->GetName(), \ Offsets.Left, Offsets.Top, Offsets.Right, Offsets.Bottom, \ anchors.Minimum.X, anchors.Minimum.Y, anchors.Maximum.X, anchors.Maximum.Y); #endif Offsets.Left = Left; Offsets.Right = Right; Slot->SetOffsets(Offsets); } else if (Widget->Slot->IsA(SDK::UVerticalBoxSlot::StaticClass())) { auto* Slot = static_cast(Widget->Slot); SDK::FMargin Margin = Slot->Padding; #ifdef MY_VERBOSE_LOGS if (g_logger) g_logger->debug("VerticalBox Slot: {} {} - Slot: {} {} - Margins: {} {} {} {}", \ Widget->GetName(), Widget->Class->GetName(), \ Slot->GetName(), Slot->Class->GetName(), \ Margin.Left, Margin.Top, Margin.Right, Margin.Bottom); #endif Margin.Left = Left; Margin.Right = Right; Slot->SetPadding(Margin); } } // Go deeper in children if we got a Panel if (Widget->IsA(SDK::UPanelWidget::StaticClass()) && CurrentDepth < MaxDepth) { auto* Panel = static_cast(Widget); int childrenCount = Panel->GetChildrenCount(); for (int i = 0; i < childrenCount; ++i) { ApplyOffsetsRecursive_Internal(Panel->GetChildAt(i), Left, Right, ExcludeObjects, ExcludeClass, CurrentDepth + 1, MaxDepth); } } // Go deeper if the widget is an UserWidget if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* ChildWidget = static_cast(Widget); if (ChildWidget->WidgetTree && ChildWidget->WidgetTree->RootWidget) { ApplyOffsetsRecursive_Internal(ChildWidget->WidgetTree->RootWidget, Left, Right, ExcludeObjects, ExcludeClass, CurrentDepth + 1, MaxDepth); } } } void ApplyOffsetsRecursive(SDK::UWidget* Widget, float Left, float Right, const std::vector& ExcludeObjects, const std::vector& ExcludeClass, int MaxDepth) { ApplyOffsetsRecursive_Internal(Widget, Left, Right, ExcludeObjects, ExcludeClass, 0, MaxDepth); } static void ApplyOverlayOffsetRecursive_Internal(SDK::UWidget* Widget, float left, float right, SDK::EHorizontalAlignment alignment, const std::vector& ExcludeNames, int CurrentDepth, int MaxDepth) { if (!Widget || CurrentDepth > MaxDepth) return; auto IsExcluded = [&](SDK::UWidget* widget) -> bool { for (const auto& name : ExcludeNames) { if (widget->GetName().contains(name)) return true; } return false; }; auto AdjustOverlaySlot = [&](SDK::UOverlaySlot* Slot) { if (!Slot || IsExcluded(Widget)) return; SDK::FMargin Padding = Slot->Padding; if (Slot->HorizontalAlignment == alignment) { Padding.Left = left; Padding.Right = right; } Slot->SetPadding(Padding); }; if (Widget->Slot && Widget->Slot->IsA(SDK::UOverlaySlot::StaticClass())) AdjustOverlaySlot((SDK::UOverlaySlot*)Widget->Slot); if (Widget->IsA(SDK::UPanelWidget::StaticClass()) && CurrentDepth < MaxDepth) { SDK::UPanelWidget* Panel = (SDK::UPanelWidget*)Widget; int childrenCount = Panel->GetChildrenCount(); for (int i = 0; i < childrenCount; ++i) { ApplyOverlayOffsetRecursive_Internal(Panel->GetChildAt(i), left, right, alignment, ExcludeNames, CurrentDepth + 1, MaxDepth); } } } void ApplyOverlayOffsetRecursive(SDK::UWidget* Widget, float left, float right, SDK::EHorizontalAlignment alignment, const std::vector& ExcludeNames, int MaxDepth) { ApplyOverlayOffsetRecursive_Internal(Widget, left, right, alignment, ExcludeNames, 0, MaxDepth); } void FindAndApplyCanvasRecursive(SDK::UWidget* widget, float offset, int MaxDepth, int currentDepth) { if (!widget || currentDepth > MaxDepth) return; // Si c'est un CanvasPanel, on applique directement if (widget->IsA(SDK::UCanvasPanel::StaticClass())) { ApplyOffsetsRecursive(widget, offset, offset); } // Si c'est un panel, on descend dans les enfants if (widget->IsA(SDK::UPanelWidget::StaticClass())) { auto* panel = static_cast(widget); int childrenCount = panel->GetChildrenCount(); for (int i = 0; i < childrenCount; ++i) { FindAndApplyCanvasRecursive(panel->GetChildAt(i), offset, MaxDepth, currentDepth + 1); } } } void TrackWidgetConstruct(SDK::UUserWidget* widget) { if (!widget || !widget->Class) return; std::string className = widget->Class->GetName(); auto& info = g_WidgetTracker[className]; info.ClassName = className; info.ConstructCount++; if (info.ConstructCount == 1) info.FirstSeen = std::chrono::steady_clock::now(); if (widget->IsInViewport()) info.WasInViewport = true; if (widget->Outer && widget->Outer->Class) info.OuterClass = widget->Outer->Class->GetName(); if (widget->WidgetTree && widget->WidgetTree->RootWidget) { auto* root = widget->WidgetTree->RootWidget; if (root && root->Class) info.RootWidgetClass = root->Class->GetName(); } } void TrackWidgetDestruct(SDK::UUserWidget* widget) { if (!widget || !widget->Class) return; std::string className = widget->Class->GetName(); if (g_WidgetTracker.contains(className)) g_WidgetTracker[className].DestructCount++; } static std::string ClassifyWidget(const WidgetTrackInfo& info) { bool persistent = info.ConstructCount > info.DestructCount; if (info.WasInViewport && persistent && info.OuterClass.find("PlayerController") != std::string::npos) return "HUD Candidate"; if (info.WasInViewport && (info.ClassName.find("Menu") != std::string::npos)) return "Menu UI"; if (info.WasInViewport && (info.ClassName.find("Settings") != std::string::npos)) return "Settings UI"; if (info.WasInViewport && (info.ClassName.find("Save") != std::string::npos || info.ClassName.find("Load") != std::string::npos)) return "Save/Load UI"; if (!persistent) return "Temporary Widget"; return "Unclassified"; } void DumpUIAnalysis(std::shared_ptr logger) { if (!logger) return; logger->info("========== UI ANALYSIS =========="); for (auto& [name, info] : g_WidgetTracker) { std::string type = ClassifyWidget(info); bool persistent = info.ConstructCount > info.DestructCount; logger->info("Class: {}", name); logger->info(" Type : {}", type); logger->info(" Root Type : {}", info.RootWidgetClass.empty() ? "Unknown" : info.RootWidgetClass); logger->info(" Construct : {}", info.ConstructCount); logger->info(" Destruct : {}", info.DestructCount); logger->info(" Persistent : {}", persistent ? "Yes" : "No"); logger->info(" InViewport : {}", info.WasInViewport ? "Yes" : "No"); logger->info(" Outer : {}", info.OuterClass.empty() ? "Unknown" : info.OuterClass); logger->info(" "); } logger->info("================================="); } void ClearWidgetTracking() { g_WidgetTracker.clear(); } void DumpWidgetRecursive(SDK::UWidget* Widget, int Depth, int MaxDepth) { if (!Widget || Depth > MaxDepth) return; std::string indent(Depth * 2, ' '); std::string className = Widget->Class ? Widget->Class->GetName() : "UnknownClass"; std::string widgetName = Widget->GetName(); if (g_logger) g_logger->debug("{}Widget: {} [{}]", indent, widgetName, className); // Slot info si disponible if (Widget->Slot) { std::string slotClass = Widget->Slot->Class ? Widget->Slot->Class->GetName() : "UnknownSlot"; if (g_logger) g_logger->debug("{} Slot class: {}", indent, slotClass); } // Descendre dans les enfants si c'est un panel et qu'on est encore sous MaxDepth if (Depth < MaxDepth) { if (Widget->IsA(SDK::UPanelWidget::StaticClass())) { auto* panel = static_cast(Widget); int count = panel->GetChildrenCount(); for (int i = 0; i < count; ++i) { DumpWidgetRecursive(panel->GetChildAt(i), Depth + 1, MaxDepth); } } if (Widget->IsA(SDK::UUserWidget::StaticClass())) { auto* userWidget = static_cast(Widget); if (userWidget->WidgetTree && userWidget->WidgetTree->RootWidget) { DumpWidgetRecursive(userWidget->WidgetTree->RootWidget, Depth + 1, MaxDepth); } } } }