Skip to content
Draft
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
16 changes: 16 additions & 0 deletions combo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,24 @@ target_link_libraries(comboui PRIVATE libultraship)
add_dependencies(comboui libultraship)

# Source files
# core/: launcher-only modules. combo/gui/ builds into comboui.dll and combo/rando/ is header-only
# (shared with comborando and both game DLLs) — neither belongs here.
set(COMBO_SOURCES
ComboShip.cpp
core/ComboPlatform.cpp
core/ComboDllApi.cpp
core/ComboContainer.cpp
core/ComboSeedFile.cpp
core/ComboBootstrap.cpp
core/ComboAnchorNet.cpp
core/ComboCrossItems.cpp
core/ComboGoal.cpp
core/ComboHintReveal.cpp
core/ComboSeedState.cpp
core/ComboGeneration.cpp
core/ComboReload.cpp
core/ComboTransition.cpp
core/ComboSlotBind.cpp
)

# Create executable
Expand Down
72 changes: 30 additions & 42 deletions combo/ComboRandoHeadless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
#include "rando/ComboPlaythrough.h"
#include "rando/CrossForeign.h" // BuildForeignArray (foreign enrichment parity with RunComboFill)
#include "rando/CrossHints.h"
#include "core/ComboSeedMath.h"
#include "core/ComboFillDriver.h"

namespace {

Expand All @@ -51,22 +53,6 @@ typedef const char* (*FnOracleGetChecks)(void);
typedef void (*FnOraclePlaceItem)(const char*, const char*);
typedef uint8_t (*FnOracleGetPortalOpen)(void); // OOT->MM portal gate (see CrossWorldRando.h)

// Must match ComboShip.cpp's ComboHash (FNV-1a 32-bit) so headless seeds match in-game seeds.
uint32_t ComboHash(const std::string& s) {
uint32_t h = 2166136261u;
for (unsigned char c : s)
h = (h ^ c) * 16777619u;
return h;
}

// #135: resolve the starting-game config (0 = OOT, 1 = MM, 2 = Random) for one attempt. Must stay
// byte-identical to ComboShip.cpp's ResolveStartingGameMM, derivation string included.
bool ResolveStartingGameMM(int cfg, uint32_t masterSeed) {
if (cfg == 2)
return (ComboHash("startingGame:" + std::to_string(masterSeed)) & 1u) != 0;
return cfg == 1;
}

template <typename T> T Sym(HMODULE m, const char* name) {
return reinterpret_cast<T>(GetProcAddress(m, name));
}
Expand Down Expand Up @@ -498,6 +484,18 @@ int main(int argc, char** argv) {
: "")
<< "\n";
int failures = 0;
// Same hook table the launcher builds from its export pointers - this is the point of the driver.
CwFillHooks hooks;
hooks.SetSeedOot = SOH_SetSeed;
hooks.SetSeedMm = MM_SetSeed;
hooks.SetGoalOot = SOH_SetComboGoal;
hooks.SetGoalMm = MM_SetComboGoal;
hooks.SetStartingGame = SOH_SetComboStartingGame;
hooks.DumpOot = SOH_Dump;
hooks.DumpMm = MM_Dump;
hooks.GetForced = SOH_GetForced;
hooks.ShuffleEntrances = SOH_ShuffleEntrances;

auto t0 = std::chrono::steady_clock::now();
for (int i = 0; i < count; ++i) {
const uint32_t base = (haveMasterSeed ? masterSeedArg : ComboHash(seed)) + static_cast<uint32_t>(i);
Expand All @@ -513,38 +511,28 @@ int main(int argc, char** argv) {
masterSeed = base + attempt * 0x9E3779B9u;
const bool mmStart = !pinStartOot && !(startCfg == 2 && attempt + 1 == kFillAttempts) &&
ResolveStartingGameMM(startCfg, masterSeed);
if (SOH_SetSeed)
SOH_SetSeed(masterSeed);
if (MM_SetSeed)
MM_SetSeed(masterSeed);
// Before the dumps: the goal shapes OOT's wincon placement and MM's hunt pool.
if (SOH_SetComboGoal)
SOH_SetComboGoal(goal.hunt ? 1 : 0, goal.required, ComboRando::CwOotPieces(goal.total));
if (MM_SetComboGoal)
MM_SetComboGoal(goal.hunt ? 1 : 0, goal.required, ComboRando::CwMmPieces(goal.total));
// Same reason (#135): an MM start forces OOT's age/forest/exclusions.
if (SOH_SetComboStartingGame)
SOH_SetComboStartingGame(mmStart ? 1 : 0);
sohDump = SOH_Dump();
mmDump = MM_Dump();
if (goal.hunt) {
const int pieces = ComboRando::CountPoolTriforcePieces(sohDump, mmDump);
if (pieces < goal.required) {
std::cerr << "[comborando] Triforce Hunt needs " << goal.required << " pieces but only " << pieces
<< " are in the combined pool — raise --triforce-total / the Combo pool total\n";
return 2;
}
CwPrologueOut pro;
const CwPrologue prologue = ComboFillPrologue(hooks, masterSeed, mmStart, goal, pro);
sohDump = pro.sohDump;
mmDump = pro.mmDump;
if (prologue == CwPrologue::EmptyDump) {
std::cerr << "[comborando] empty static-data dump — cannot generate\n";
return 2;
}
if (prologue == CwPrologue::TriforceShort) {
std::cerr << "[comborando] Triforce Hunt needs " << goal.required << " pieces but only "
<< pro.poolPieces
<< " are in the combined pool — raise --triforce-total / the Combo pool total\n";
return 2;
}
std::string forced = SOH_GetForced ? SOH_GetForced(masterSeed) : "";
// Same placement as RunComboFill: after the dump/forced read, before the fill, so logic
// validates the shuffled world. No-op when the entrance options are off.
if (SOH_ShuffleEntrances && !SOH_ShuffleEntrances(masterSeed)) {
if (prologue == CwPrologue::ShuffleFailed) {
std::cerr << "[comborando] seed " << masterSeed << ": entrance shuffle found no valid layout\n";
if (mmStart && startCfg == 2)
pinStartOot = true;
continue;
}
ComboRando::OotAccess ootAccess = ComboRando::OotAccessFromDump(sohDump);
const std::string& forced = pro.forcedOot;
const ComboRando::OotAccess ootAccess = pro.ootAccess;
r = ComboRando::CrossWorldCombinedFill(sohDump, mmDump, masterSeed, oot, mmO, nullptr, forced, ootAccess,
goal, mmStart ? ComboRando::GAME_MM : ComboRando::GAME_OOT);
if (r.success) {
Expand Down
Loading
Loading