21 size_t pos = path.find_last_of(
"/\\");
22 if (pos == std::string::npos)
return "";
23 return path.substr(0, pos);
27 std::ifstream f(path);
38 std::filesystem::path p(path);
39 return p.parent_path().string();
43 return std::filesystem::exists(path);
47 std::filesystem::create_directories(path);
58 : snes_(snes), rom_(rom) {
67 return absl::FailedPreconditionError(
"SNES or ROM not provided");
72 printf(
"[StateManager] ROM checksum: 0x%08X\n",
rom_checksum_);
75 auto state_dir_status =
77 if (state_dir_status.ok()) {
87 return absl::OkStatus();
93 if (!FileExists(path)) {
94 return absl::NotFoundError(
"State file not found: " + path);
99 if (FileExists(meta_path)) {
102 return absl::FailedPreconditionError(
103 "State incompatible with current ROM (checksum mismatch)");
111 printf(
"[StateManager] Generating state for room %d...\n", room_id);
143 metadata.
description =
"Room " + std::to_string(room_id);
151 const std::vector<std::pair<int, const char*>> baseline_rooms = {
152 {0x0012,
"Sanctuary"},
153 {0x0020,
"Hyrule Castle Entrance"},
154 {0x0028,
"Eastern Palace Entrance"},
155 {0x0004,
"Link's House"},
156 {0x0044,
"Desert Palace Entrance"},
157 {0x0075,
"Tower of Hera Entrance"},
160 int success_count = 0;
161 for (
const auto& [room_id, name] : baseline_rooms) {
162 printf(
"[StateManager] Generating %s (0x%04X)...\n", name, room_id);
167 printf(
"[StateManager] Warning: Failed to generate %s: %s\n", name,
168 std::string(status.message()).c_str());
172 printf(
"[StateManager] Generated %d/%zu baseline states\n", success_count,
173 baseline_rooms.size());
174 return absl::OkStatus();
179 return FileExists(path);
186 std::ifstream file(path, std::ios::binary);
188 return absl::NotFoundError(
"Metadata file not found");
192 file.read(
reinterpret_cast<char*
>(&metadata.
version),
sizeof(metadata.
version));
193 file.read(
reinterpret_cast<char*
>(&metadata.
rom_checksum),
195 file.read(
reinterpret_cast<char*
>(&metadata.
rom_region),
197 file.read(
reinterpret_cast<char*
>(&metadata.
room_id),
sizeof(metadata.
room_id));
198 file.read(
reinterpret_cast<char*
>(&metadata.
game_module),
202 file.read(
reinterpret_cast<char*
>(&desc_len),
sizeof(desc_len));
203 if (desc_len > 0 && desc_len < 1024) {
209 return absl::InternalError(
"Failed to read metadata");
218 std::string parent_path = GetParentPath(path);
219 if (!parent_path.empty()) {
220 CreateDirectories(parent_path);
225 if (!save_status.ok()) {
230 std::string meta_path = path +
".meta";
231 std::ofstream meta_file(meta_path, std::ios::binary);
233 return absl::InternalError(
"Failed to create metadata file");
236 meta_file.write(
reinterpret_cast<const char*
>(&metadata.
version),
238 meta_file.write(
reinterpret_cast<const char*
>(&metadata.
rom_checksum),
240 meta_file.write(
reinterpret_cast<const char*
>(&metadata.
rom_region),
242 meta_file.write(
reinterpret_cast<const char*
>(&metadata.
room_id),
244 meta_file.write(
reinterpret_cast<const char*
>(&metadata.
game_module),
248 meta_file.write(
reinterpret_cast<const char*
>(&desc_len),
sizeof(desc_len));
249 meta_file.write(metadata.
description.data(), desc_len);
252 return absl::InternalError(
"Failed while writing metadata");
254 printf(
"[StateManager] Saved state to %s\n", path.c_str());
255 return absl::OkStatus();
263 printf(
"[StateManager] Loaded state from %s\n", path.c_str());
264 return absl::OkStatus();
280 const int kMaxFrames = 2000;
281 for (
int i = 0; i < kMaxFrames; ++i) {
283 uint8_t
module = GetGameModule();
286 printf(
"[StateManager] Frame %d: module=0x%02X\n", i, module);
290 if (module == 0x01) {
291 printf(
"[StateManager] Reached File Select (module 0x01) at frame %d\n", i);
292 return absl::OkStatus();
296 if (module == 0x14) {
299 if (i % 60 == 0) printf(
"[StateManager] In Attract Mode, holding Start...\n");
307 else if (module == 0x00 && i > 300) {
310 if (i % 60 == 0) printf(
"[StateManager] In Intro, holding Start...\n");
318 printf(
"[StateManager] Boot timeout, module=0x%02X\n",
GetGameModule());
319 return absl::DeadlineExceededError(
"Failed to reach File Select");
326 const int kMaxFrames = 120;
327 for (
int i = 0; i < kMaxFrames; ++i) {
328 uint8_t
module = GetGameModule();
329 if (module == 0x01) {
330 printf(
"[StateManager] Navigated to file select (module 0x01)\n");
331 return absl::OkStatus();
335 if (module == 0x00 || module == 0x14) {
343 printf(
"[StateManager] File select timeout, module=0x%02X (continuing)\n",
345 return absl::OkStatus();
349 printf(
"[StateManager] Starting new game sequence...\n");
353 const int kFileSelectTimeout = 600;
354 for (
int i = 0; i < kFileSelectTimeout; ++i) {
356 uint8_t
module = GetGameModule();
358 if (module == 0x04) {
359 printf(
"[StateManager] Reached Name Entry (module 0x04) at frame %d\n", i);
364 if (module == 0x01) {
370 }
else if (module == 0x03) {
374 if (i == kFileSelectTimeout - 1) {
375 printf(
"[StateManager] Timeout waiting for Name Entry (current: 0x%02X)\n", module);
382 const int kNameEntryTimeout = 2000;
383 for (
int i = 0; i < kNameEntryTimeout; ++i) {
385 uint8_t
module = GetGameModule();
387 if (module == 0x07 || module == 0x09) {
388 printf(
"[StateManager] Started new game (module 0x%02X) at frame %d\n", module, i);
389 return absl::OkStatus();
393 if (module == 0x04) {
396 printf(
"[StateManager] Stuck in Name Entry, forcing Module 0x05 (Load Level)...\n");
410 }
else if (cycle >= 60 && cycle < 80) {
421 printf(
"[StateManager] Game start timeout, module=0x%02X\n",
GetGameModule());
422 return absl::DeadlineExceededError(
"Game failed to start");
429 return absl::OkStatus();
433 printf(
"[StateManager] WRAM teleport failed: %s, trying TAS fallback\n",
434 std::string(status.message()).c_str());
441 snes_->
Write(0x7E00A1, (room_id >> 8) & 0xFF);
444 bool is_dungeon = (room_id < 0x128);
445 snes_->
Write(0x7E001B, is_dungeon ? 0x01 : 0x00);
458 const int kMaxFrames = 600;
459 for (
int i = 0; i < kMaxFrames; ++i) {
464 snes_->
Write(0x7E00A1, (room_id >> 8) & 0xFF);
465 snes_->
Write(0x7E001B, (room_id < 0x128) ? 0x01 : 0x00);
468 uint8_t submodule =
ReadWram(0x7E0011);
469 printf(
"[StateManager] Teleport wait frame %d: module=0x%02X, sub=0x%02X, room=0x%04X\n",
474 printf(
"[StateManager] WRAM teleport to room 0x%04X successful\n",
476 return absl::OkStatus();
480 return absl::DeadlineExceededError(
481 "WRAM teleport failed for room " + std::to_string(room_id));
487 const int kMaxFrames = 600;
488 for (
int i = 0; i < kMaxFrames; ++i) {
493 printf(
"[StateManager] TAS: Room loaded: 0x%04X (target: 0x%04X)\n",
494 current_room, room_id);
495 if (current_room == room_id) {
496 return absl::OkStatus();
500 return absl::OkStatus();
504 return absl::DeadlineExceededError(
"TAS navigation timeout");
508 for (
int i = 0; i < frames; ++i) {
525 for (
int i = 0; i < frames; ++i) {
547 for (
int i = 0; i < max_frames; ++i) {
559 uint8_t
module = GetGameModule();
560 uint8_t submodule =
ReadWram(0x7E0011);
563 return (module == 0x07 || module == 0x09) && (submodule == 0x00 || submodule == 0x0F);
567 int context_id)
const {
568 std::string type_str;
574 type_str =
"overworld";
580 return state_directory_ +
"/" + type_str +
"_" + std::to_string(context_id) +
585 int context_id)
const {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status saveState(const std::string &path)
void SetButtonState(int player, int button, bool pressed)
uint8_t Read(uint32_t adr)
void Reset(bool hard=false)
void Write(uint32_t adr, uint8_t val)
absl::Status loadState(const std::string &path)
absl::Status GenerateRoomState(int room_id)
bool HasCachedState(StateType type, int context_id=0) const
void WaitFrames(int frames)
bool IsStateCompatible(const StateMetadata &metadata) const
uint8_t ReadWram(uint32_t addr)
uint16_t ReadWram16(uint32_t addr)
SaveStateManager(emu::Snes *snes, Rom *rom)
std::string GetStatePath(StateType type, int context_id) const
std::string state_directory_
absl::Status LoadState(StateType type, int context_id=0)
void PressButton(int button, int frames=1)
absl::Status LoadStateFromFile(const std::string &path)
absl::StatusOr< StateMetadata > GetStateMetadata(StateType type, int context_id=0) const
absl::Status NavigateToFileSelect()
bool WaitForModule(uint8_t target, int max_frames)
uint32_t CalculateRomChecksum() const
absl::Status TeleportToRoomViaWram(int room_id)
absl::Status NavigateToRoomViaTas(int room_id)
absl::Status StartNewGame()
absl::Status Initialize()
absl::Status GenerateAllBaselineStates()
std::string GetMetadataPath(StateType type, int context_id) const
absl::Status SaveStateToFile(const std::string &path, const StateMetadata &metadata)
absl::Status NavigateToRoom(int room_id)
absl::Status BootToTitleScreen()
std::string GetParentPath(const std::string &path)
void CreateDirectories(const std::string &path)
bool FileExists(const std::string &path)
constexpr uint32_t kGameModule
constexpr uint32_t kRoomId
uint32_t CalculateCRC32(const uint8_t *data, size_t size)