M include/button.h => include/button.h +13 -0
@@ 10,3 10,16 @@ public:
static constexpr const char *class_name = "BUTTON";
};
+
+class Checkbox : public Window<Checkbox>
+{
+public:
+ Checkbox(HINSTANCE instance, const std::string &name, Rect2DI rect, HWND parent);
+ virtual ~Checkbox() = default;
+
+ static constexpr const char *class_name = "BUTTON";
+
+ bool checked() const;
+ void set_checked(bool checked);
+ void toggle();
+};
M include/gamecontroller.h => include/gamecontroller.h +4 -0
@@ 56,6 56,7 @@ private: // functions
void **out_orig_funcptr);
SharedState* _shared_state() const;
+ const LDR_DATA_TABLE_ENTRY* _libafp_module();
DWORD _patched_game_tick(void *p);
void _patched_acio_hbhi_get_csb(void *p);
@@ 81,6 82,9 @@ private: // variables
SharedMemory _shared_memory;
SavedState _saved_state;
std::vector<AVSHeap> _avs_heaps;
+ bool _afp_logging_enabled;
+
+ LDR_DATA_TABLE_ENTRY *_cached_afp_module;
void *_afp_bss_ptr;
size_t _afp_bss_size;
M include/gameprocess.h => include/gameprocess.h +5 -0
@@ 24,8 24,13 @@ public:
void advance_frame() const;
void pause() const;
void resume() const;
+
void save_state() const;
void restore_state() const;
+
+ bool verbose_log_enabled() const;
+ void set_verbose_log_enabled(bool enabled);
+
size_t frame_count() const;
private:
M include/mainwindow.h => include/mainwindow.h +2 -0
@@ 25,6 25,7 @@ private:
void _handle_adv_frame_button_cmd();
void _handle_save_state_button_cmd();
void _handle_restore_state_button_cmd();
+ void _handle_verbose_log_checkbox_cmd();
void _reload_ui_state();
void _start_update_timer();
@@ 40,6 41,7 @@ private:
Button _adv_frame_button;
Button _save_state_button;
Button _restore_state_button;
+ Checkbox _verbose_log_checkbox;
std::unique_ptr<GameProcess> _game_proc;
HANDLE _update_timer;
};
M include/sharedstate.h => include/sharedstate.h +1 -0
@@ 10,5 10,6 @@ struct SharedState
bool continue_next_frame = false;
bool save_state = false;
bool restore_state = false;
+ bool verbose_logging = false;
size_t frame_count = 0;
};
M src/button.cpp => src/button.cpp +27 -0
@@ 11,3 11,30 @@ Button::Button(HINSTANCE instance, const std::string &name, Rect2DI rect, HWND p
// set default font on the button
SendMessage(_handle, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), true);
}
+
+Checkbox::Checkbox(HINSTANCE instance, const std::string &name, Rect2DI rect, HWND parent)
+ : Window<Checkbox>(instance,
+ name,
+ WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
+ 0,
+ rect,
+ parent)
+{
+ // set default font for the checkbox
+ SendMessage(_handle, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), true);
+}
+
+bool Checkbox::checked() const
+{
+ return (SendMessage(_handle, BM_GETCHECK, 0, 0) == BST_CHECKED);
+}
+
+void Checkbox::set_checked(bool checked)
+{
+ SendMessage(_handle, BM_SETCHECK, (checked ? BST_CHECKED : BST_UNCHECKED), 0);
+}
+
+void Checkbox::toggle()
+{
+ set_checked(!checked());
+}
M src/gamecontroller.cpp => src/gamecontroller.cpp +33 -2
@@ 19,6 19,7 @@ namespace
constexpr uint32_t ACIO_HBHI_GET_CSB_OFFSET = 0x00001490;
constexpr uint32_t AVS_STDBOOT_HEAP_BOOT_CALL_OFFSET = 0x000291BA;
constexpr uint32_t AVS_PBOOT_HEAP_BOOT_CALL_OFFSET = 0x0003D900;
+ constexpr uint32_t AFP_LOGGING_FLAGS_OFFSET = 0x00081148;
constexpr const char *ACIO_MODULE_NAME = "libacio.dll";
constexpr const char *ACIO_HBHI_GET_CSB_SYM_NAME = "ac_io_hbhi_get_control_status_buffer";
@@ 38,6 39,8 @@ GameController& GameController::instance()
GameController::GameController()
: _shared_memory(sizeof(SharedState), SharedMemory::Mode::OPEN, SHMEM_MAPPING_NAME),
+ _afp_logging_enabled(false),
+ _cached_afp_module(nullptr),
_afp_bss_ptr(nullptr),
_afp_bss_size(0)
{}
@@ 150,6 153,20 @@ SharedState* GameController::_shared_state() const
return (SharedState *)_shared_memory.data();
}
+const LDR_DATA_TABLE_ENTRY* GameController::_libafp_module()
+{
+ if (!_cached_afp_module) {
+ const ProcessModuleTable modtable;
+ _cached_afp_module = modtable.entry_named(AFP_MODULE_NAME);
+
+ if (!_cached_afp_module) {
+ throw std::runtime_error("Failed to load libafp module.");
+ }
+ }
+
+ return _cached_afp_module;
+}
+
DWORD GameController::_patched_game_tick(void *p)
{
DWORD result = 0;
@@ 169,6 186,21 @@ DWORD GameController::_patched_game_tick(void *p)
state->continue_next_frame = false;
}
+ if (state->verbose_logging != _afp_logging_enabled) {
+ const LDR_DATA_TABLE_ENTRY *afp_module = _libafp_module();
+ uint32_t *logging_flags = (uint32_t *)((uintptr_t)afp_module->DllBase + AFP_LOGGING_FLAGS_OFFSET);
+
+ if (state->verbose_logging) {
+ DEBUG_LOG("Enabling verbose logging.\n");
+ *logging_flags |= 0xff;
+ } else {
+ DEBUG_LOG("Disabling verbose logging.\n");
+ *logging_flags ^= 0xff;
+ }
+
+ _afp_logging_enabled = state->verbose_logging;
+ }
+
return result;
}
@@ 336,8 368,7 @@ void GameController::_save_state()
}
if (!_afp_bss_ptr) {
- const ProcessModuleTable modtable;
- const LDR_DATA_TABLE_ENTRY *afp_module = modtable.entry_named(L"libafp-win32");
+ const LDR_DATA_TABLE_ENTRY *afp_module = _libafp_module();
const void *afp_known_bss_addr = ((uint8_t *)afp_module->DllBase + 0x8334c);
const MEMORY_BASIC_INFORMATION afp_bss_region = mem_region(afp_known_bss_addr);
M src/gameprocess.cpp => src/gameprocess.cpp +10 -0
@@ 148,6 148,16 @@ void GameProcess::restore_state() const
_shared_state()->restore_state = true;
}
+bool GameProcess::verbose_log_enabled() const
+{
+ return _shared_state()->verbose_logging;
+}
+
+void GameProcess::set_verbose_log_enabled(bool enabled)
+{
+ _shared_state()->verbose_logging = enabled;
+}
+
size_t GameProcess::frame_count() const
{
return _shared_state()->frame_count;
M src/mainwindow.cpp => src/mainwindow.cpp +48 -20
@@ 7,8 7,8 @@
namespace
{
- constexpr const int BUTTONS_HMARGIN = 10;
- constexpr const int BUTTONS_VMARGIN = 10;
+ constexpr const int VIEWS_HMARGIN = 10;
+ constexpr const int VIEWS_VMARGIN = 10;
}
MainWindow::MainWindow(HINSTANCE instance)
@@ 26,6 26,7 @@ MainWindow::MainWindow(HINSTANCE instance)
_adv_frame_button(instance, "Next Frame", Rect2DI { Point2DI::zero(), Size2DI { 70, 25 } }, _handle),
_save_state_button(instance, "Save", Rect2DI { Point2DI::zero(), Size2DI { 70, 25 } }, _handle),
_restore_state_button(instance, "Restore", Rect2DI { Point2DI::zero(), Size2DI { 70, 25 } }, _handle),
+ _verbose_log_checkbox(instance, "Verbose Logging", Rect2DI { Point2DI::zero(), Size2DI { 100, 20 } }, _handle),
_update_timer(INVALID_HANDLE_VALUE)
{
_frame_counter_label.set_font((HFONT)GetStockObject(SYSTEM_FIXED_FONT));
@@ 55,6 56,8 @@ LRESULT MainWindow::proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
_handle_save_state_button_cmd();
} else if (lparam == (LPARAM)_restore_state_button.handle()) {
_handle_restore_state_button_cmd();
+ } else if (lparam == (LPARAM)_verbose_log_checkbox.handle()) {
+ _handle_verbose_log_checkbox_cmd();
}
break;
@@ 77,57 80,69 @@ void MainWindow::_layout_controls()
Rect2DI adv_frame_btn_rect = _adv_frame_button.rect();
Rect2DI save_state_btn_rect = _save_state_button.rect();
Rect2DI restore_state_btn_rect = _restore_state_button.rect();
- const int row1_btns_width = (
+ Rect2DI verbose_log_checkbox_rect = _verbose_log_checkbox.rect();
+ const int row1_wnds_width = (
start_btn_rect.size.width +
- BUTTONS_HMARGIN +
+ VIEWS_HMARGIN +
pause_btn_rect.size.width +
- BUTTONS_HMARGIN +
+ VIEWS_HMARGIN +
adv_frame_btn_rect.size.width
);
- const int btns_total_height = (
+ const int wnds_total_height = (
start_btn_rect.size.height +
- BUTTONS_VMARGIN +
- save_state_btn_rect.size.height
+ VIEWS_VMARGIN +
+ save_state_btn_rect.size.height +
+ VIEWS_VMARGIN +
+ verbose_log_checkbox_rect.size.height
);
- const int row1_btn_y = bounds.size.height / 2 - btns_total_height / 2;
- const int row2_btn_y = row1_btn_y + start_btn_rect.size.height + BUTTONS_VMARGIN;
+ const int row1_wnd_y = bounds.size.height / 2 - wnds_total_height / 2;
+ const int row2_wnd_y = row1_wnd_y + start_btn_rect.size.height + VIEWS_VMARGIN;
+ const int row3_wnd_y = row2_wnd_y + save_state_btn_rect.size.height + VIEWS_VMARGIN;
start_btn_rect.origin = Point2DI {
- bounds.size.width / 2 - row1_btns_width / 2,
- row1_btn_y
+ bounds.size.width / 2 - row1_wnds_width / 2,
+ row1_wnd_y
};
_start_button.set_rect(start_btn_rect);
pause_btn_rect.origin = Point2DI {
- start_btn_rect.max_x() + BUTTONS_HMARGIN,
- row1_btn_y
+ start_btn_rect.max_x() + VIEWS_HMARGIN,
+ row1_wnd_y
};
_pause_button.set_rect(pause_btn_rect);
adv_frame_btn_rect.origin = Point2DI {
- pause_btn_rect.max_x() + BUTTONS_HMARGIN,
- row1_btn_y
+ pause_btn_rect.max_x() + VIEWS_HMARGIN,
+ row1_wnd_y
};
_adv_frame_button.set_rect(adv_frame_btn_rect);
const int row2_btns_width = (
save_state_btn_rect.size.width +
- BUTTONS_HMARGIN +
+ VIEWS_HMARGIN +
restore_state_btn_rect.size.width
);
save_state_btn_rect.origin = Point2DI {
bounds.size.width / 2 - row2_btns_width / 2,
- row2_btn_y
+ row2_wnd_y
};
_save_state_button.set_rect(save_state_btn_rect);
restore_state_btn_rect.origin = Point2DI {
- save_state_btn_rect.max_x() + BUTTONS_HMARGIN,
- row2_btn_y
+ save_state_btn_rect.max_x() + VIEWS_HMARGIN,
+ row2_wnd_y
};
_restore_state_button.set_rect(restore_state_btn_rect);
+
+ verbose_log_checkbox_rect.origin = Point2DI {
+ bounds.size.width / 2 - verbose_log_checkbox_rect.size.width / 2,
+ row3_wnd_y
+ };
+ _verbose_log_checkbox.set_rect(verbose_log_checkbox_rect);
+
+ update();
}
void MainWindow::_handle_start_button_cmd()
@@ 186,6 201,15 @@ void MainWindow::_handle_restore_state_button_cmd()
}
}
+void MainWindow::_handle_verbose_log_checkbox_cmd()
+{
+ _verbose_log_checkbox.toggle();
+
+ if (_game_proc) {
+ _game_proc->set_verbose_log_enabled(_verbose_log_checkbox.checked());
+ }
+}
+
void MainWindow::_reload_ui_state()
{
if (_game_proc && _game_proc->is_running()) {
@@ 194,6 218,8 @@ void MainWindow::_reload_ui_state()
_adv_frame_button.set_enabled(true);
_save_state_button.set_enabled(true);
_restore_state_button.set_enabled(true);
+ _verbose_log_checkbox.set_enabled(true);
+ _verbose_log_checkbox.set_checked(_game_proc->verbose_log_enabled());
if (_game_proc->is_paused()) {
_pause_button.set_text("Resume");
@@ 209,6 235,8 @@ void MainWindow::_reload_ui_state()
_adv_frame_button.set_enabled(false);
_save_state_button.set_enabled(false);
_restore_state_button.set_enabled(false);
+ _verbose_log_checkbox.set_enabled(false);
+ _verbose_log_checkbox.set_checked(false);
_stop_update_timer();
}