@@ -79,6 +79,82 @@ std::unique_ptr<FunctionHook> g_wwise_listener_update_hook{};
7979std::optional<regenny::via::Size> g_previous_size{};
8080#endif
8181
82+ bool is_apollo_justice_exe () {
83+ static const bool value = utility::get_module_path (utility::get_executable ())->find (" GS456.exe" ) != std::string::npos;
84+ return value;
85+ }
86+
87+ enum class ApolloEpisodeFamily {
88+ Unknown,
89+ GS4 ,
90+ GS5 ,
91+ GS6 ,
92+ };
93+
94+ ApolloEpisodeFamily get_apollo_visible_episode_family () {
95+ if (!is_apollo_justice_exe () || !g_framework->is_ready ()) {
96+ return ApolloEpisodeFamily::Unknown;
97+ }
98+
99+ const auto gui_manager = sdk::get_managed_singleton<REManagedObject>(" app.GUIManager" );
100+
101+ if (gui_manager == nullptr ) {
102+ return ApolloEpisodeFamily::Unknown;
103+ }
104+
105+ const auto all_gui = sdk::get_object_field<sdk::SystemArray*>(gui_manager, " _AllGUI" );
106+
107+ if (all_gui == nullptr || *all_gui == nullptr ) {
108+ return ApolloEpisodeFamily::Unknown;
109+ }
110+
111+ int gs4_visible{};
112+ int gs5_visible{};
113+ int gs6_visible{};
114+
115+ for (size_t i = 0 ; i < (*all_gui)->get_size (); ++i) {
116+ const auto gui = (REManagedObject*)(*all_gui)->get_element ((int32_t )i);
117+
118+ if (gui == nullptr || !sdk::call_object_func_easy<bool >(gui, " get_IsVisible" )) {
119+ continue ;
120+ }
121+
122+ const auto type_definition = utility::re_managed_object::get_type_definition (gui);
123+
124+ if (type_definition == nullptr ) {
125+ continue ;
126+ }
127+
128+ const auto full_name = type_definition->get_full_name ();
129+
130+ if (full_name.rfind (" app.gs4." , 0 ) == 0 ) {
131+ ++gs4_visible;
132+ } else if (full_name.rfind (" app.gs5." , 0 ) == 0 ) {
133+ ++gs5_visible;
134+ } else if (full_name.rfind (" app.gs6." , 0 ) == 0 ) {
135+ if (full_name == " app.gs6.uThumbnailGUI" ) {
136+ continue ;
137+ }
138+
139+ ++gs6_visible;
140+ }
141+ }
142+
143+ if (gs4_visible >= gs5_visible && gs4_visible >= gs6_visible && gs4_visible > 0 ) {
144+ return ApolloEpisodeFamily::GS4 ;
145+ }
146+
147+ if (gs5_visible >= gs6_visible && gs5_visible > 0 ) {
148+ return ApolloEpisodeFamily::GS5 ;
149+ }
150+
151+ if (gs6_visible > 0 ) {
152+ return ApolloEpisodeFamily::GS6 ;
153+ }
154+
155+ return ApolloEpisodeFamily::Unknown;
156+ }
157+
82158// Purpose: spoof the render target size to the size of the HMD displays
83159void VR::on_view_get_size (REManagedObject* scene_view, float * result) {
84160 // There are some very dumb optimizations that cause set_DisplayType
@@ -2386,6 +2462,7 @@ struct GUIRestoreData {
23862462thread_local std::vector<std::unique_ptr<GUIRestoreData>> g_elements_to_reset{};
23872463thread_local std::vector<GUIRestoreData::ApolloNitroRootRestore> g_apollo_nitro_roots_to_reset{};
23882464thread_local bool g_apollo_nitro_roots_applied{};
2465+ thread_local bool g_apollo_thumbnail_rebound_this_frame{};
23892466
23902467static void apply_apollo_nitro_root_sync (const Matrix4x4f& gui_matrix) {
23912468 if (g_apollo_nitro_roots_applied) {
@@ -2541,6 +2618,94 @@ static void apply_apollo_cell_animation_rtt_workaround() {
25412618 }
25422619}
25432620
2621+ static void apply_apollo_thumbnail_texture_workaround () {
2622+ static auto thumbnail_manager_t = sdk::find_type_definition (" app.ThumbnailTextureManager" );
2623+ static auto thumbnail_gui_t = sdk::find_type_definition (" app.gs6.uThumbnailGUI" );
2624+
2625+ static auto manager_thumbnail_gui_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" mpThumbnailGUI" ) : nullptr ;
2626+ static auto evidence_gs4_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _EvidenceTextureListGS4" ) : nullptr ;
2627+ static auto evidence_gs5_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _EvidenceTextureListGS5" ) : nullptr ;
2628+ static auto evidence_gs6_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _EvidenceTextureListGS6" ) : nullptr ;
2629+ static auto profile_gs4_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _ProfileTextureListGS4" ) : nullptr ;
2630+ static auto profile_gs5_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _ProfileTextureListGS5" ) : nullptr ;
2631+ static auto profile_gs6_field = thumbnail_manager_t != nullptr ? thumbnail_manager_t ->get_field (" _ProfileTextureListGS6" ) : nullptr ;
2632+
2633+ static auto thumbnail_texture_field = thumbnail_gui_t != nullptr ? thumbnail_gui_t ->get_field (" _TextureImage" ) : nullptr ;
2634+ static auto thumbnail_type_field = thumbnail_gui_t != nullptr ? thumbnail_gui_t ->get_field (" mType" ) : nullptr ;
2635+ static auto thumbnail_item_id_field = thumbnail_gui_t != nullptr ? thumbnail_gui_t ->get_field (" mItemID" ) : nullptr ;
2636+
2637+ if (manager_thumbnail_gui_field == nullptr || evidence_gs4_field == nullptr || evidence_gs5_field == nullptr || evidence_gs6_field == nullptr ||
2638+ profile_gs4_field == nullptr || profile_gs5_field == nullptr || profile_gs6_field == nullptr || thumbnail_texture_field == nullptr ||
2639+ thumbnail_type_field == nullptr || thumbnail_item_id_field == nullptr ) {
2640+ return ;
2641+ }
2642+
2643+ const auto thumbnail_manager = sdk::get_managed_singleton<REManagedObject>(" app.ThumbnailTextureManager" );
2644+
2645+ if (thumbnail_manager == nullptr ) {
2646+ return ;
2647+ }
2648+
2649+ const auto thumbnail_gui = manager_thumbnail_gui_field->get_data <REManagedObject*>(thumbnail_manager);
2650+
2651+ if (thumbnail_gui == nullptr || !sdk::call_object_func_easy<bool >(thumbnail_gui, " get_IsVisible" )) {
2652+ return ;
2653+ }
2654+
2655+ const auto thumbnail_texture = thumbnail_texture_field->get_data <REManagedObject*>(thumbnail_gui);
2656+
2657+ if (thumbnail_texture == nullptr ) {
2658+ return ;
2659+ }
2660+
2661+ const auto episode_family = get_apollo_visible_episode_family ();
2662+
2663+ if (episode_family == ApolloEpisodeFamily::Unknown) {
2664+ return ;
2665+ }
2666+
2667+ const auto thumbnail_type = thumbnail_type_field->get_data <int32_t >(thumbnail_gui);
2668+ const auto thumbnail_item_id = thumbnail_item_id_field->get_data <uint32_t >(thumbnail_gui);
2669+ const auto texture_list_field = [&]() -> sdk::REField* {
2670+ const auto is_evidence_thumbnail = thumbnail_type == 0 ;
2671+
2672+ switch (episode_family) {
2673+ case ApolloEpisodeFamily::GS4 :
2674+ return is_evidence_thumbnail ? evidence_gs4_field : profile_gs4_field;
2675+ case ApolloEpisodeFamily::GS5 :
2676+ return is_evidence_thumbnail ? evidence_gs5_field : profile_gs5_field;
2677+ case ApolloEpisodeFamily::GS6 :
2678+ return is_evidence_thumbnail ? evidence_gs6_field : profile_gs6_field;
2679+ default :
2680+ return nullptr ;
2681+ }
2682+ }();
2683+
2684+ if (texture_list_field == nullptr ) {
2685+ return ;
2686+ }
2687+
2688+ const auto texture_list = texture_list_field->get_data <sdk::SystemArray*>(thumbnail_manager);
2689+
2690+ if (texture_list == nullptr || thumbnail_item_id >= texture_list->get_size ()) {
2691+ return ;
2692+ }
2693+
2694+ const auto target_texture_holder = (REManagedObject*)texture_list->get_element ((int32_t )thumbnail_item_id);
2695+
2696+ if (target_texture_holder == nullptr ) {
2697+ return ;
2698+ }
2699+
2700+ const auto current_texture_holder = sdk::call_object_func_easy<REManagedObject*>(thumbnail_texture, " getTexture" );
2701+
2702+ if (current_texture_holder == target_texture_holder) {
2703+ return ;
2704+ }
2705+
2706+ sdk::call_object_func<void *>(thumbnail_texture, " setTexture" , sdk::get_thread_context (), thumbnail_texture, target_texture_holder);
2707+ }
2708+
25442709static void restore_pending_gui_elements () {
25452710 if (g_elements_to_reset.empty () && g_apollo_nitro_roots_to_reset.empty ()) {
25462711 inside_gui_draw = false ;
@@ -2602,6 +2767,11 @@ bool VR::on_pre_gui_draw_element(REComponent* gui_element, void* primitive_conte
26022767 if (game_object != nullptr && game_object_transform != nullptr ) {
26032768 auto context = sdk::get_thread_context ();
26042769
2770+ if (is_apollo_justice_exe () && !g_apollo_thumbnail_rebound_this_frame) {
2771+ apply_apollo_thumbnail_texture_workaround ();
2772+ g_apollo_thumbnail_rebound_this_frame = true ;
2773+ }
2774+
26052775 const auto name = utility::re_game_object::get_name (game_object);
26062776 const auto name_hash = utility::hash (name);
26072777
@@ -3203,7 +3373,9 @@ void VR::on_pre_begin_rendering(void* entry) {
32033373 static const auto is_apollo_justice = utility::get_module_path (utility::get_executable ())->find (" GS456.exe" ) != std::string::npos;
32043374
32053375 if (is_apollo_justice) {
3376+ g_apollo_thumbnail_rebound_this_frame = false ;
32063377 apply_apollo_cell_animation_rtt_workaround ();
3378+ apply_apollo_thumbnail_texture_workaround ();
32073379 }
32083380
32093381 // Apollo's GS4 GUI can render additional portrait/evidence layers after the
0 commit comments