Skip to content
Merged
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
12 changes: 2 additions & 10 deletions example_community_patch_settings_en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -784,19 +784,11 @@ notify_banner_types = ""
auto_open_chat = false

# Initial side-chat width in canvas units; 0 preserves the native width. Positive values are clamped to available space and the minimum width. Requires mod 1.1.10 or newer.
default_chat_width = 0
default_chat_width = 0.0

# Change to true to remove Galaxy Chat from the game
disable_galaxy_chat = false

# Initial side-chat width in canvas units. 0 keeps the game default.
# The game clamps positive values to the available screen space and minimum width.
default_chat_width = 0.0

# Open alliance side chat once per login, after station and chat initialization.
# Waits for section completion and three continuous seconds of readiness.
auto_open_chat = false

# Change to true to remove Veil Chat from the game
disable_veil_chat = false

Expand All @@ -812,7 +804,7 @@ extend_chest_purchase_max = 160
# Subgroup: Daily Goals
# ---------------------

# Comma-separated factions (federation, klingon, romulan) for which the Daily Goals bulk-claim toggle applies; leave blank to preserve the game's default behaviour
# Comma-separated factions for Daily Goals bulk claim. Missing or blank uses the default below; "none" skips all three main-faction dailies. Other dailies remain eligible.
daily_bulk_claim_factions = "federation,klingon,romulan"

# Force the Daily Goals Claim All toggle back on the first time the screen is shown each session
Expand Down
2 changes: 1 addition & 1 deletion macos-launcher/xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ target("macOSLauncher")
add_files("src/*.cc")
add_includedirs("deps/PLzmaSDK")
add_packages("librsync", "PLzmaSDK")
add_ldflags("-lc++")
add_ldflags("-lc++", {force = true})

add_values("xcode.bundle_identifier", "com.stfcmod.startrekpatch")
add_values("xcode.codesign_identity", "-")
Expand Down
2 changes: 1 addition & 1 deletion mods/src/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,7 @@ void Config::Load()
DCU::auto_confirm_reveal_ability, write_config);
this->auto_confirm_alliance_contribution =
get_config_or_default(config, parsed, "ui", "auto_confirm_alliance_contribution",
DCU::auto_confirm_alliance_contribution, write_config);
DCU::auto_confirm_alliance_contribution, write_config);

this->extend_chest_purchase_max = get_config_or_default(config, parsed, "ui", "extend_chest_purchase_max",
DCU::extend_chest_purchase_max, write_config);
Expand Down
9 changes: 7 additions & 2 deletions mods/src/patches/native_hook_extent.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "native_hook_extent.h"
#include "patches/native_hook_extent.h"

#if __APPLE__
#include <algorithm>
Expand Down Expand Up @@ -91,7 +91,12 @@ bool native_hooks::MacHookFits(const void* method, std::size_t minimum_extent)
{
#if __APPLE__
const auto extent = FunctionExtent(method);
const bool fits = extent >= std::max(minimum_extent, std::size_t{32}) && spud::has_detour_prologue(method, extent);
bool fits = false;
if (extent >= std::max(minimum_extent, std::size_t{32})) {
const auto plan = spud::inspect_detour(method, spud::Arch::kHost, extent);
fits = plan.ok() && plan.resolved_target == reinterpret_cast<uintptr_t>(method) && plan.far_jump_size != 0
&& plan.far_required_patch_bytes >= plan.far_jump_size && plan.far_required_patch_bytes <= extent;
}
spdlog::info("[MacHookExtent] entry={} bytes={} accepted={}", method, extent, fits);
return fits;
#else
Expand Down
10 changes: 7 additions & 3 deletions mods/src/patches/parts/alliance_store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,13 @@ bool StoreHookFits(const MethodInfo* method)
#if _WIN32 && defined(_M_X64)
DWORD64 base = 0;
auto* function = RtlLookupFunctionEntry(reinterpret_cast<DWORD64>(entry), &base, nullptr);
return function && base + function->BeginAddress == reinterpret_cast<DWORD64>(entry)
&& function->EndAddress > function->BeginAddress
&& spud::has_detour_prologue(entry, function->EndAddress - function->BeginAddress);
if (!function || base + function->BeginAddress != reinterpret_cast<DWORD64>(entry)
|| function->EndAddress <= function->BeginAddress)
return false;
const size_t extent = function->EndAddress - function->BeginAddress;
const auto plan = spud::inspect_detour(entry, spud::Arch::kHost, extent);
return plan.ok() && plan.resolved_target == reinterpret_cast<uintptr_t>(entry) && plan.far_jump_size != 0
&& plan.far_required_patch_bytes >= plan.far_jump_size && plan.far_required_patch_bytes <= extent;
#elif __APPLE__
return native_hooks::MacHookFits(entry);
#else
Expand Down
168 changes: 48 additions & 120 deletions mods/src/patches/parts/chat_titles.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,122 +18,42 @@ Il2CppObject* InvokeSelf(Il2CppObject* object, const char* method_name)
{
auto* method = il2cpp_class_get_method_from_name(object ? il2cpp_object_get_class(object) : nullptr, method_name, 0);
if (!method) {
static int missing_reports = 0;
if (missing_reports++ < 8) {
spdlog::warn("[ChatTitleDisplay] Cannot resolve {} on {}", method_name,
object ? il2cpp_class_get_name(il2cpp_object_get_class(object)) : "null");
}
return nullptr;
}
Il2CppException* exception = nullptr;
auto* result = (Il2CppObject*)il2cpp_runtime_invoke(method, object, nullptr, &exception);
if (exception) {
static int exception_reports = 0;
if (exception_reports++ < 8) {
spdlog::warn("[ChatTitleDisplay] Managed invocation failed: {}", method_name);
}
return nullptr;
}
return result;
return (Il2CppObject*)il2cpp_runtime_invoke(method, object, nullptr, &exception);
}
Il2CppObject* FindChatRowTransform(void* profile_widget)
bool IsChatRow(void* profile_widget)
{
auto* mb = (Il2CppObject*)profile_widget;
if (!mb) {
return nullptr;
return false;
}

auto* transform = InvokeSelf(mb, "get_transform");
if (!transform) {
return nullptr;
return false;
}

for (int hops = 0; hops < 32 && transform; ++hops) {
auto* gameObject = InvokeSelf(transform, "get_gameObject");
if (!gameObject) {
return nullptr;
return false;
}
if (reinterpret_cast<GameObject*>(gameObject)->GetComponentFastPath2<ChatMessageWidget>()) {
return transform;
return true;
}
transform = InvokeSelf(transform, "get_parent");
}
return nullptr;
return false;
}
struct TitleLayoutElement {
static IL2CppClassHelper& get_class_helper()
{
static auto helper = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "LayoutElement");
return helper;
}
};

void HideTitleRow(void* title, Il2CppObject* row_transform)
void HideTitleRow(void* title)
{
auto* game_object = reinterpret_cast<GameObject*>(InvokeSelf(static_cast<Il2CppObject*>(title), "get_gameObject"));
if (!game_object) {
return;
}
game_object->SetActive(false);

// In the chat prefab, _container is the enclosing PlayerTitleContainer, whose
// LayoutElement reserves 24 units even when the title widget child is hidden.
// Resolve the serialized reference rather than assuming a particular parent.
static auto* container_field =
il2cpp_class_get_field_from_name(il2cpp_object_get_class(static_cast<Il2CppObject*>(title)), "_container");
GameObject* container = nullptr;
if (container_field) {
il2cpp_field_get_value(static_cast<Il2CppObject*>(title), container_field, &container);
}
if (container) {
static int container_reports = 0;
if (container_reports++ < 8) {
spdlog::info("[ChatTitleDisplay] Hiding title '{}' and container '{}' (active={})", game_object->Name(),
container->Name(), container->activeInHierarchy);
}
// The native bind path may reactivate the container after a nested title
// callback. Excluding its LayoutElement keeps that empty row out of layout.
if (auto* element = container->GetComponentFastPath2<TitleLayoutElement>()) {
static auto ignore_layout = TitleLayoutElement::get_class_helper().GetMethodInfo("set_ignoreLayout", 1);
if (ignore_layout) {
bool ignore = true;
void* args[] = {&ignore};
Il2CppException* exception = nullptr;
il2cpp_runtime_invoke(ignore_layout, element, args, &exception);
if (exception) {
spdlog::warn("[ChatTitleDisplay] Cannot exclude title container from layout");
}
}
}
container->SetActive(false);
} else {
static bool reported = false;
if (!reported) {
reported = true;
spdlog::warn("[ChatTitleDisplay] Title '{}' has no container (field found={})", game_object->Name(),
container_field != nullptr);
}
}

// Rebuild the complete message row before the virtualized list measures it;
// marking only the inner text layout leaves the enclosing row size cached.
static auto layout = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "LayoutRebuilder");
static auto rebuild = layout.GetMethodInfo("ForceRebuildLayoutImmediate", 1);
static auto mark = layout.GetMethodInfo("MarkLayoutForRebuild", 1);
for (auto* method : {rebuild, mark}) {
if (method) {
void* args[] = {row_transform};
Il2CppException* exception = nullptr;
il2cpp_runtime_invoke(method, nullptr, args, &exception);
if (exception) {
static bool reported = false;
if (!reported) {
reported = true;
spdlog::warn("[ChatTitleDisplay] Chat row layout rebuild failed");
}
}
}
}
}

void ApplyTitleDisplay(void* profile_widget)
Expand All @@ -145,26 +65,15 @@ void ApplyTitleDisplay(void* profile_widget)

const auto show = Config::Get().show_player_title;
auto* title = widget->_playerTitleWidget;
static bool reported_callback = false;
if (!reported_callback) {
reported_callback = true;
spdlog::info("[ChatTitleDisplay] Profile callback reached (show={}, title present={})", show, title != nullptr);
}
if (show || title == nullptr) {
return;
}

auto* row_transform = FindChatRowTransform(widget);
if (!row_transform) {
static bool reported = false;
if (!reported) {
reported = true;
spdlog::info("[ChatTitleDisplay] Profile callback excluded: no ChatMessageWidget ancestor");
}
if (!IsChatRow(widget)) {
return;
}

HideTitleRow(title, row_transform);
HideTitleRow(title);
}
} // namespace

Expand All @@ -176,7 +85,7 @@ void UserProfileWidget_UpdatePlayerTitle_Hook(auto original, void* _this)
} catch (...) {
}
}
void PlayerTitleWidget_SetIdentifiers_Hook(auto original, void* _this)
void PlayerTitleWidget_BindPath_Hook(auto original, void* _this)
{
original(_this);
try {
Expand Down Expand Up @@ -237,46 +146,65 @@ void InstallChatTitleDisplayHooks()
ErrorMsg::MissingMethod("UserProfileWidget", "UpdatePlayerTitle");
}

void* set_identifiers = nullptr;
void* set_widget_data = nullptr;
void* on_did_bind_context = nullptr;
if (auto title_widget = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerAvatars", "PlayerTitleWidget");
!title_widget.isValidHelper()) {
ErrorMsg::MissingHelper("Digit.Prime.PlayerAvatars", "PlayerTitleWidget");
} else if ((set_identifiers = title_widget.GetMethod("SetIdentifiers", 0)) == nullptr) {
ErrorMsg::MissingMethod("PlayerTitleWidget", "SetIdentifiers");
} else {
set_widget_data = title_widget.GetMethod("SetWidgetData", 0);
on_did_bind_context = title_widget.GetMethod("OnDidBindContext", 0);
if (!set_widget_data || !on_did_bind_context) {
ErrorMsg::MissingMethod("PlayerTitleWidget", set_widget_data ? "OnDidBindContext" : "SetWidgetData");
set_widget_data = nullptr;
on_did_bind_context = nullptr;
}
}

if (!update_player_title && !set_identifiers) {
if (!update_player_title && !set_widget_data && !on_did_bind_context) {
return;
}

if (update_player_title) {
update_player_title = ValidateDetourTarget(update_player_title, "UpdatePlayerTitle");
}
if (set_identifiers) {
set_identifiers = ValidateDetourTarget(set_identifiers, "SetIdentifiers");
if (set_widget_data) {
set_widget_data = ValidateDetourTarget(set_widget_data, "SetWidgetData");
}
if (on_did_bind_context) {
on_did_bind_context = ValidateDetourTarget(on_did_bind_context, "OnDidBindContext");
}

if (!update_player_title && !set_identifiers) {
if (!update_player_title && !set_widget_data && !on_did_bind_context) {
spdlog::warn("Skipping ChatTitleDisplayHooks: no detour target fits; no hooks installed.");
return;
}

// Both title-bind callbacks tail-call SetIdentifiers. On Windows client 272
// their shared wrapper is only 16 bytes, less than SPUD's 24-byte overwrite,
// so detouring it also corrupts the adjacent title-clear callback. Hook the
// substantive implementation once instead.
if (set_identifiers && set_identifiers == update_player_title) {
spdlog::warn("[ChatTitleDisplay] SetIdentifiers shares its target with UpdatePlayerTitle; keeping one detour");
set_identifiers = nullptr;
// The PlayerTitleWidget callbacks can resolve through thunks to one shared
// native implementation. A second detour on the same address would rebuild
// the first detour's jump into a broken trampoline, so keep one hook per
// target; the shared body funnels both method entries into the same hook.
if (set_widget_data && set_widget_data == update_player_title) {
spdlog::info("[ChatTitleDisplay] SetWidgetData shares its native implementation with UpdatePlayerTitle; "
"keeping one detour");
set_widget_data = nullptr;
}
if (on_did_bind_context
&& (on_did_bind_context == update_player_title || on_did_bind_context == set_widget_data)) {
spdlog::info("[ChatTitleDisplay] OnDidBindContext shares its native implementation with another target; "
"keeping one detour");
on_did_bind_context = nullptr;
}

if (update_player_title) {
SPUD_STATIC_DETOUR(update_player_title, UserProfileWidget_UpdatePlayerTitle_Hook);
}
if (set_identifiers) {
SPUD_STATIC_DETOUR(set_identifiers, PlayerTitleWidget_SetIdentifiers_Hook);
if (set_widget_data) {
SPUD_STATIC_DETOUR(set_widget_data, PlayerTitleWidget_BindPath_Hook);
}
if (on_did_bind_context) {
SPUD_STATIC_DETOUR(on_did_bind_context, PlayerTitleWidget_BindPath_Hook);
}

spdlog::info("Chat title display: chat player title rows {} (immediate row layout rebuild enabled)",
Config::Get().show_player_title ? "shown" : "hidden");
spdlog::info("Chat title display: chat player title rows {}", Config::Get().show_player_title ? "shown" : "hidden");
}
2 changes: 1 addition & 1 deletion mods/src/patches/parts/hotkeys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this)
auto* bar = ObjectFinder<FleetBarViewController>::Get();
auto* context = bar ? bar->CanvasContext() : nullptr;
auto* fleets = context ? context->FleetData() : nullptr;
hint_action = ShipShortcutHintAction(hint_action, fleets ? fleets->max_length : 0, [fleets](std::size_t position) {
hint_action = ShipShortcutHintAction(hint_action, fleets ? fleets->max_length : 0, [fleets](std::size_t position) {
auto* fleet = reinterpret_cast<FleetPlayerData**>(fleets->vector)[position];
return fleet ? fleet->Index : -1;
});
Expand Down
2 changes: 1 addition & 1 deletion mods/src/patches/parts/object_tracker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,6 @@ void InstallObjectTrackers()
return;
}

const auto GC_register_finalizer_inner_match = GC_register_finalizer_inner_matches.get(0);
const auto GC_register_finalizer_inner_match = GC_register_finalizer_inner_matches[0];
GC_register_finalizer_inner = (decltype(GC_register_finalizer_inner))GC_register_finalizer_inner_match.address();
}
Loading
Loading