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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.vs/
build/
out/
dep/
vcpkg/
vcpkg_installed/
3 changes: 3 additions & 0 deletions lib/game/inc/1.3528.0.0/offset_haloreach.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

#define OFFSET_HALOREACH_PF_ADD_LOCAL_PLAYER 0x43DC8

#define OFFSET_HALOREACH_PF_GET_SPLITSCREEN_PLAYER_COUNT 0xC30D0
#define OFFSET_HALOREACH_PF_SPLITSCREEN_RESOLUTION_RESOURCE 0x2CA86C

#define OFFSET_HALOREACH_V_ENTRY_PLAYERS 0x3
#define OFFSET_HALOREACH_V_ENTRY_PLAYERS_ACTION 0x23
#define OFFSET_HALOREACH_V_ENTRY_SPLIT_SCREEN 0x2B
3 changes: 3 additions & 0 deletions src/input/Input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ namespace AlphaRing::Input {
bool debugKeyIsDown = (GetAsyncKeyState(debugKey) & 0x8000) != 0;
if (debugKeyIsDown && !debugKeyWasDown) {
AlphaRing::Global::Global()->show_imgui = !AlphaRing::Global::Global()->show_imgui;

AlphaRing::Global::Global()->show_imgui_mouse = AlphaRing::Global::Global()->show_imgui;

}
debugKeyWasDown = debugKeyIsDown;

Expand Down
33 changes: 17 additions & 16 deletions src/mcc/module/CModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,34 @@ static struct {
{"splitscreen_patch4", "force making splitscreen works with more than 2 players", 0x5153E, "\x83\xF8\x01\x74\x04", true},
}}, {Halo3EntrySet(), {
{"splitscreen_patch1", "", OFFSET_HALO3_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true},
{"Remove Black Bar1", "remove black bar", 0x8AE150/*0x8AD160*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
{"Remove Black Bar2", "remove black bar", 0x8AE164/*0x8AD174*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true},
{"Remove Black Bar3", "remove black bar", 0x8AE1A0/*0x8AD1B0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
{"Remove Black Bar1", "remove black bar", 0x8AE150/*0x8AD160*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar2", "remove black bar", 0x8AE164/*0x8AD174*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar3", "remove black bar", 0x8AE1A0/*0x8AD1B0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
}}, {nullptr, {
{"splitscreen_patch1", "", OFFSET_HALO4_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true},
{"splitscreen_patch2", "", OFFSET_HALO4_PF_COOP_REJOIN, "\xEB", true},
{"splitscreen_patch3", "", OFFSET_HALO4_PF_COOP_PLAYER_LIMIT, "\x90\x90\x90\x90\x90\x90", true},
{"Remove Black Bar1", "remove black bar", 0xE84E50/*0xE84E40*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false},
{"Remove Black Bar2", "remove black bar", 0xE84E64/*0xE84E54*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", false},
{"Remove Black Bar3", "remove black bar", 0xE84EA0/*0xE84E90*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false},
{"Remove Black Bar1", "remove black bar", 0xE84E50/*0xE84E40*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar2", "remove black bar", 0xE84E64/*0xE84E54*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar3", "remove black bar", 0xE84EA0/*0xE84E90*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
}}, {nullptr, {
{"splitscreen_patch1", "", OFFSET_GROUNDHOG_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true},
{"splitscreen_patch2", "", OFFSET_GROUNDHOG_PF_REJOIN, "\xEB", true},
{"Remove Black Bar1", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_1 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false},
{"Remove Black Bar2", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_2 , "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", false},
{"Remove Black Bar3", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_3 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false},
{"Remove Black Bar1", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_1 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar2", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_2 , "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar3", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_3 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
}}, {nullptr, {
{"splitscreen_patch1", "", OFFSET_HALO3ODST_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true},
{"Remove Black Bar1", "remove black bar", 0x8F1FB0/*0x8F1FC0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
{"Remove Black Bar2", "remove black bar", 0x8F1FC4/*0x8F1FD4*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true},
{"Remove Black Bar3", "remove black bar", 0x8F2000/*0x8F2010*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
}}, {nullptr, {
{"Remove Black Bar1", "remove black bar", 0x8F1FB0/*0x8F1FC0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar2", "remove black bar", 0x8F1FC4/*0x8F1FD4*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar3", "remove black bar", 0x8F2000/*0x8F2010*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
}}, {HaloReachEntrySet(), {
{"splitscreen_patch1", "", OFFSET_HALOREACH_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true},
{"splitscreen_patch2", "", OFFSET_HALOREACH_PF_COOP_REJOIN, "\xEB", true},
{"Remove Black Bar1", "remove black bar", 0xB43CE0/*0xB43D10*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
{"Remove Black Bar2", "remove black bar", 0xB43CF4/*0xB43D24*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true},
{"Remove Black Bar3", "remove black bar", 0xB43D30/*0xB43D60*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true},
{"Remove Black Bar1", "remove black bar", 0xB43CE0/*0xB43D10*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar2", "remove black bar", 0xB43CF4/*0xB43D24*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false},
{"Remove Black Bar3", "remove black bar", 0xB43D30/*0xB43D60*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false},
{"Disable Black Bar Overlay", "neuters the function that paints black-bar/divider overlays for 2p/3p/4p splitscreen (uses only slot 0's x0/x1 for pillarbox bars, spanning full height - assumes horizontal top/bottom splits only, paints over anything past slot 0's x1 for vertical/custom splits)", 0x2C6D84, "\x31\xC0\xC3\x90", false},
}}};

static std::unordered_map<std::string, CModule*> map_modules {
Expand Down
198 changes: 190 additions & 8 deletions src/mcc/module/Module.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Module.h"

#include <functional>
#include <cstring>

#include "tinyxml2.h"

Expand Down Expand Up @@ -120,25 +121,25 @@ bool MCC::Module::ReloadPatch(const char *xml_path) {
#include "global/Global.h"

namespace MCC::Module {
void ContextPatch();
void ContextDevTools();
void ContextEngine();

void ImGuiContext() {
static bool show_patch;
static bool show_devtools;
static bool show_engine;

if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("Game")) {
ImGui::MenuItem("Engine", nullptr, &show_engine);
ImGui::MenuItem("Patch", nullptr, &show_patch);
ImGui::EndMenu();
}
ImGui::MenuItem("Dev Tools", nullptr, &show_devtools);
ImGui::EndMainMenuBar();
}

if (show_patch) {
if (ImGui::Begin("Patch", &show_patch, ImGuiWindowFlags_MenuBar))
ContextPatch();
if (show_devtools) {
if (ImGui::Begin("Dev Tools", &show_devtools, ImGuiWindowFlags_MenuBar))
ContextDevTools();
ImGui::End();
}

Expand Down Expand Up @@ -196,7 +197,7 @@ namespace MCC::Module {
#pragma endregion
}

void ContextPatch() {
void ContextDevTools() {
static int counter;
auto p_print = [](CPatch* patch) {
bool enabled = patch->enabled();
Expand All @@ -209,6 +210,12 @@ namespace MCC::Module {
ImGui::SetTooltip("%s", patch->desc());
};

auto find_patch = [](CPatchSet* p_patches, const char* name) -> CPatch* {
for (auto patch : p_patches->embed_patches())
if (strcmp(patch->name(), name) == 0) return patch;
return nullptr;
};

if (ImGui::BeginMenuBar()) {
if (ImGui::MenuItem("Reload Patch")) ReloadPatch();

Expand All @@ -224,13 +231,188 @@ namespace MCC::Module {

if (ImGui::BeginTabItem(cModuleName[i])) {
ImGui::Text("Embed Patches");
for (auto patch : p_patches->embed_patches())
for (auto patch : p_patches->embed_patches()) {
// HaloReach's black-bar patches get replaced below with two
// controls scoped by which player/slot they actually affect,
// instead of three raw, easy-to-misread checkboxes.
if (i == MODULE_HALOREACH &&
(strcmp(patch->name(), "Remove Black Bar1") == 0 ||
strcmp(patch->name(), "Remove Black Bar2") == 0 ||
strcmp(patch->name(), "Remove Black Bar3") == 0))
continue;

p_print(patch);
}

if (i == MODULE_HALOREACH) {
auto p_bar1 = find_patch(p_patches, "Remove Black Bar1");
auto p_bar2 = find_patch(p_patches, "Remove Black Bar2");
auto p_bar3 = find_patch(p_patches, "Remove Black Bar3");

ImGui::Separator();
ImGui::Text("Splitscreen Display");

if (p_bar1 != nullptr && p_bar3 != nullptr) {
bool top = p_bar1->enabled() && p_bar3->enabled();
ImGui::PushID(counter++);
if (ImGui::Checkbox("Remove Black Bars - Player 1 (Top, 2 or 3 Player)", &top)) {
p_bar1->setState(top);
p_bar3->setState(top);

// The game's black-bar overlay is a single shared
// painter that only ever reads Player 1's bounds,
// so Player 2's bar physically cannot disappear
// while Player 1 still has one - turning Player 1
// back on makes that combination broken again.
if (!top && p_bar2 != nullptr && p_bar2->enabled())
p_bar2->setState(false);
}
ImGui::PopID();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Removes the black bar for whichever player occupies the top half of the screen. Applies to both 2-player and 3-player splitscreen - that slot is the same shape in either mode.");
}

if (p_bar2 != nullptr) {
bool player1_on = p_bar1 != nullptr && p_bar1->enabled();
bool bottom = p_bar2->enabled();

ImGui::BeginDisabled(!player1_on);
ImGui::PushID(counter++);
if (ImGui::Checkbox("Remove Black Bars - Player 2 (Bottom, 2 Player Only)", &bottom))
p_bar2->setState(bottom);
ImGui::PopID();
ImGui::EndDisabled();

// ImGui suppresses IsItemHovered() by default for items inside
// BeginDisabled()/EndDisabled() - AllowWhenDisabled is required
// so the explanation tooltip still shows while greyed out.
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
if (player1_on)
ImGui::SetTooltip("Removes the black bar for player 2's bottom half of the screen. 2-player only - in 3-player mode, players 2 and 3 already fill their quarter of the screen with no black bars.");
else
ImGui::SetTooltip("Requires Player 1's black bar removed too - the game's bar-painting logic is shared and only reads Player 1's bounds, so Player 2's bar can't disappear on its own.");
}
}

ImGui::Separator();
}

ImGui::Text("Patches");
for (auto patch : p_patches->patches())
p_print(patch);

if (i == MODULE_HALOREACH) {
auto hModule = GetSubModule((eModule)i)->info().hModule;

constexpr __int64 base_offset = 0xB43C40;
constexpr int entry_size = 20;
constexpr int block_count = 5;
constexpr int slot_count = 4;

if (ImGui::CollapsingHeader("Splitscreen Config Editor")) {
if (hModule == 0) {
ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "haloreach.dll not loaded");
} else {
static const char* block_labels[block_count] = {
"0: alias of 4p", "1: 1 player", "2: 2 players",
"3: 3 players", "4: 4 players"
};

auto write_bytes = [](void* dst, const void* src, size_t size) {
DWORD oldProtect;
if (VirtualProtect(dst, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
memcpy(dst, src, size);
VirtualProtect(dst, size, oldProtect, &oldProtect);
}
};

ImGui::TextDisabled("Edits write directly to live game memory. No rebuild needed;");
ImGui::TextDisabled("changes are lost on game restart unless also set as a patch.");

if (ImGui::BeginTable("splitscreen_config", 6,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Slot", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("x0", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("y0", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("x1", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("y1", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("resolution", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableHeadersRow();

for (int block = 0; block < block_count; ++block) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s", block_labels[block]);

for (int slot = 0; slot < slot_count; ++slot) {
int index = block * slot_count + slot;
auto p_entry = (unsigned char*)(hModule + base_offset + (__int64)index * entry_size);

float vals[4];
__int32 res;
memcpy(vals, p_entry, 16);
memcpy(&res, p_entry + 16, 4);

ImGui::TableNextRow();
ImGui::PushID(index);

ImGui::TableSetColumnIndex(0);
ImGui::Text(" slot %d", slot);

const char* field_labels[4] = {"##x0", "##y0", "##x1", "##y1"};
for (int f = 0; f < 4; ++f) {
ImGui::TableSetColumnIndex(1 + f);
ImGui::SetNextItemWidth(-1);
if (ImGui::InputFloat(field_labels[f], &vals[f], 0.0f, 0.0f, "%.4f"))
write_bytes(p_entry + f * 4, &vals[f], 4);
}

ImGui::TableSetColumnIndex(5);
ImGui::SetNextItemWidth(-1);
if (ImGui::InputInt("##res", &res, 0, 0))
write_bytes(p_entry + 16, &res, 4);

ImGui::PopID();
}
}
ImGui::EndTable();
}
}
}

if (ImGui::Button("Dump Splitscreen Config Table to Log")) {
if (hModule == 0) {
LOG_ERROR("Dump Splitscreen Config Table: haloreach.dll not loaded");
} else {
LOG_INFO("=== c_splitscreen_config::m_config_table @ base+0x{:X} ===", base_offset);

for (int block = 0; block < block_count; ++block) {
for (int slot = 0; slot < slot_count; ++slot) {
int index = block * slot_count + slot;
auto p_entry = (unsigned char*)(hModule + base_offset + (__int64)index * entry_size);

float f0, f1, f2, f3;
__int32 res;
memcpy(&f0, p_entry + 0, 4);
memcpy(&f1, p_entry + 4, 4);
memcpy(&f2, p_entry + 8, 4);
memcpy(&f3, p_entry + 12, 4);
memcpy(&res, p_entry + 16, 4);

LOG_INFO("block {} slot {} (entry {}, offset 0x{:X}): raw=({:.4f}, {:.4f}, {:.4f}, {:.4f}) res={}",
block, slot, index, base_offset + (__int64)index * entry_size, f0, f1, f2, f3, res);
}
}

LOG_INFO("=== end dump ===");
}
}

if (ImGui::IsItemHovered())
ImGui::SetTooltip("Reads the live, unpatched-if-not-toggled bytes directly from haloreach.dll and logs them. Run this BEFORE toggling any Remove Black Bar checkbox to capture true defaults.");
}

ImGui::EndTabItem();
}
}
Expand Down
Loading