bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { GBALog(0, GBA_LOG_ERROR, "Could not initialize SDL sound system"); return false; } context->desiredSpec.freq = 44100; context->desiredSpec.format = AUDIO_S16SYS; context->desiredSpec.channels = 2; context->desiredSpec.samples = context->samples; context->desiredSpec.callback = _GBASDLAudioCallback; context->desiredSpec.userdata = context; #if RESAMPLE_LIBRARY == RESAMPLE_NN context->drift = 0.f; #endif if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) { GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; } context->thread = threadContext; context->samples = context->obtainedSpec.samples; float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); threadContext->audioBuffers = context->samples / ratio; if (context->samples > threadContext->audioBuffers) { threadContext->audioBuffers = context->samples * 2; } SDL_PauseAudio(0); return true; }
bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { GBALog(0, GBA_LOG_ERROR, "Could not initialize SDL sound system: %s", SDL_GetError()); return false; } context->desiredSpec.freq = 44100; context->desiredSpec.format = AUDIO_S16SYS; context->desiredSpec.channels = 2; context->desiredSpec.samples = context->samples; context->desiredSpec.callback = _GBASDLAudioCallback; context->desiredSpec.userdata = context; #if RESAMPLE_LIBRARY == RESAMPLE_NN context->drift = 0.f; #endif #if SDL_VERSION_ATLEAST(2, 0, 0) context->deviceId = SDL_OpenAudioDevice(0, 0, &context->desiredSpec, &context->obtainedSpec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (context->deviceId == 0) { #else if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) { #endif GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; } context->thread = threadContext; context->samples = context->obtainedSpec.samples; float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); threadContext->audioBuffers = context->samples / ratio; if (context->samples > threadContext->audioBuffers) { threadContext->audioBuffers = context->samples * 2; } #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_PauseAudioDevice(context->deviceId, 0); #else SDL_PauseAudio(0); #endif return true; } void GBASDLDeinitAudio(struct GBASDLAudio* context) { UNUSED(context); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_PauseAudioDevice(context->deviceId, 1); SDL_CloseAudioDevice(context->deviceId); #else SDL_PauseAudio(1); SDL_CloseAudio(); #endif SDL_QuitSubSystem(SDL_INIT_AUDIO); }
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; if (address == REG_SIOCNT) { GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value); if (value & 0x0080) { if (!node->id) { GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Transfer initiated", node->id); MutexLock(&node->p->mutex); node->p->transferActive = true; node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1]; node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; MutexUnlock(&node->p->mutex); } else {
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) { struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true); if (!vf) { return false; } bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot); vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot); } else { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot); } return success; }
bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) { struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false); if (!vf) { return false; } threadContext->rewindBufferSize = 0; bool success = GBALoadStateNamed(threadContext->gba, vf); vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot); } else { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to load", slot); } return success; }
uint16_t GBARRQueryInput(struct GBARRContext* rr) { if (!GBARRIsPlaying(rr)) { return 0; } if (rr->peekedTag == TAG_INPUT) { _readTag(rr, rr->movieStream); } rr->inputThisFrame = true; if (rr->currentInput == INVALID_INPUT) { GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); } GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput); return rr->currentInput; }
uint16_t GBAMGMQueryInput(struct GBARRContext* rr) { if (!rr->isPlaying(rr)) { return 0; } struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; if (mgm->peekedTag == TAG_INPUT) { _readTag(mgm, mgm->movieStream); } mgm->inputThisFrame = true; if (mgm->currentInput == INVALID_INPUT) { GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); } GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", mgm->currentInput); return mgm->currentInput; }
bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) { if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) { return false; } mgm->movieStream = 0; mgm->streamId = streamId; mgm->currentInput = INVALID_INPUT; char buffer[14]; snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); if (mgm->d.isRecording(&mgm->d)) { int flags = O_CREAT | O_RDWR; if (streamId > mgm->maxStreamId) { flags |= O_TRUNC; } mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags); } else if (mgm->d.isPlaying(&mgm->d)) { mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY); mgm->peekedTag = TAG_INVALID; if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) { mgm->d.stopPlaying(&mgm->d); } } GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); mgm->d.frames = 0; mgm->d.lagFrames = 0; return true; }
bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) { if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) { return false; } rr->movieStream = 0; rr->streamId = streamId; rr->currentInput = INVALID_INPUT; char buffer[14]; snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); if (GBARRIsRecording(rr)) { int flags = O_CREAT | O_RDWR; if (streamId > rr->maxStreamId) { flags |= O_TRUNC; } rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags); } else if (GBARRIsPlaying(rr)) { rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY); rr->peekedTag = TAG_INVALID; if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) { GBARRStopPlaying(rr); } } GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); rr->frames = 0; rr->lagFrames = 0; return true; }
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; node->nextEvent = LOCKSTEP_INCREMENT; node->d.p->multiplayerControl.slave = node->id > 0; GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Node init", node->id); return true; }
void _rtcProcessByte(struct GBACartridgeHardware* hw) { --hw->rtc.bytesRemaining; if (!hw->rtc.commandActive) { union RTCCommandData command; command.packed = hw->rtc.bits; if (command.magic == 0x06) { hw->rtc.command = command; hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command]; hw->rtc.commandActive = hw->rtc.bytesRemaining > 0; switch (command.command) { case RTC_RESET: hw->rtc.control.packed = 0; break; case RTC_DATETIME: case RTC_TIME: _rtcUpdateClock(hw); break; case RTC_FORCE_IRQ: case RTC_CONTROL: break; } } else { GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits); } } else { switch (hw->rtc.command.command) { case RTC_CONTROL: hw->rtc.control.packed = hw->rtc.bits; break; case RTC_FORCE_IRQ: GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command); break; case RTC_RESET: case RTC_DATETIME: case RTC_TIME: break; } } hw->rtc.bits = 0; hw->rtc.bitsRead = 0; if (!hw->rtc.bytesRemaining) { hw->rtc.commandActive = 0; hw->rtc.command.reading = 0; } }
void GBATakeScreenshot(struct GBA* gba, struct VDir* dir) { #ifdef USE_PNG unsigned stride; const void* pixels = 0; struct VFile* vf = VDirOptionalOpenIncrementFile(dir, gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY); bool success = false; if (vf) { gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); PNGWriteClose(png, info); vf->close(vf); } if (success) { GBALog(gba, GBA_LOG_STATUS, "Screenshot saved"); return; } #endif GBALog(gba, GBA_LOG_STATUS, "Failed to take screenshot"); }
void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) { if (!GBARRIsRecording(rr)) { return; } if (keys != rr->currentInput) { _emitTag(rr, rr->movieStream, TAG_INPUT); rr->movieStream->write(rr->movieStream, &keys, sizeof(keys)); rr->currentInput = keys; } GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput); rr->inputThisFrame = true; }
void GBAMGMNextFrame(struct GBARRContext* rr) { if (!rr->isRecording(rr) && !rr->isPlaying(rr)) { return; } struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; if (rr->isPlaying(rr)) { while (mgm->peekedTag == TAG_INPUT) { _readTag(mgm, mgm->movieStream); GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!"); } if (mgm->peekedTag == TAG_LAG) { GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream"); if (mgm->inputThisFrame) { GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie"); } } } ++mgm->d.frames; GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", mgm->d.frames); if (!mgm->inputThisFrame) { ++mgm->d.lagFrames; GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", mgm->d.lagFrames); } if (rr->isRecording(rr)) { if (!mgm->inputThisFrame) { _emitTag(mgm, mgm->movieStream, TAG_LAG); } _emitTag(mgm, mgm->movieStream, TAG_FRAME); mgm->inputThisFrame = false; } else { if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) { _streamEndReached(mgm); } } }
void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) { if (!rr->isRecording(rr)) { return; } struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; if (keys != mgm->currentInput) { _emitTag(mgm, mgm->movieStream, TAG_INPUT); mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys)); mgm->currentInput = keys; } GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", mgm->currentInput); mgm->inputThisFrame = true; }
bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) { uint32_t newStreamId = mgm->maxStreamId + 1; uint32_t oldStreamId = mgm->streamId; if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) { if (!_markStreamNext(mgm, newStreamId, recursive)) { return false; } } if (!_loadStream(mgm, newStreamId)) { return false; } GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); _emitMagic(mgm, mgm->movieStream); mgm->maxStreamId = newStreamId; _emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY); mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId)); _emitTag(mgm, mgm->movieStream, TAG_BEGIN); mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET); mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); mgm->previously = oldStreamId; return true; }
bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) { uint32_t newStreamId = rr->maxStreamId + 1; uint32_t oldStreamId = rr->streamId; if (GBARRIsRecording(rr) && rr->movieStream) { if (!_markStreamNext(rr, newStreamId, recursive)) { return false; } } if (!GBARRLoadStream(rr, newStreamId)) { return false; } GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); _emitMagic(rr, rr->movieStream); rr->maxStreamId = newStreamId; _emitTag(rr, rr->movieStream, TAG_PREVIOUSLY); rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId)); _emitTag(rr, rr->movieStream, TAG_BEGIN); rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET); rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); rr->previously = oldStreamId; return true; }
void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) { switch (address) { case GPIO_REG_DATA: hw->pinState &= ~hw->direction; hw->pinState |= value; _readPins(hw); break; case GPIO_REG_DIRECTION: hw->direction = value; break; case GPIO_REG_CONTROL: hw->readWrite = value; break; default: GBALog(hw->p, GBA_LOG_WARN, "Invalid GPIO address"); } if (hw->readWrite) { uint16_t old = hw->gpioBase[0]; old &= ~hw->direction; hw->gpioBase[0] = old | hw->pinState; } else { hw->gpioBase[0] = 0; } }
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { bool error = false; if (state->versionMagic != GBA_SAVESTATE_MAGIC) { GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate"); error = true; } if (state->biosChecksum != gba->biosChecksum) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS"); if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) { error = true; } } if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game"); error = true; } else if (!gba->memory.rom && state->id != 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded"); error = true; } if (state->romCrc32 != gba->romCrc32) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game"); } if (state->cpu.cycles < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); error = true; } if (state->cpu.cycles >= (int32_t) GBA_ARM7TDMI_FREQUENCY) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are too high"); error = true; } if (state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); error = true; } int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET); if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM"); error = true; } if (error) { return false; } memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs)); gba->cpu->cpsr = state->cpu.cpsr; gba->cpu->spsr = state->cpu.spsr; gba->cpu->cycles = state->cpu.cycles; gba->cpu->nextEvent = state->cpu.nextEvent; memcpy(gba->cpu->bankedRegisters, state->cpu.bankedRegisters, 6 * 7 * sizeof(int32_t)); memcpy(gba->cpu->bankedSPSRs, state->cpu.bankedSPSRs, 6 * sizeof(int32_t)); gba->cpu->privilegeMode = gba->cpu->cpsr.priv; gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); if (state->biosPrefetch) { gba->memory.biosPrefetch = state->biosPrefetch; } if (gba->cpu->cpsr.t) { gba->cpu->executionMode = MODE_THUMB; if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) { gba->cpu->prefetch[0] = state->cpuPrefetch[0] & 0xFFFF; gba->cpu->prefetch[1] = state->cpuPrefetch[1] & 0xFFFF; } else { // Maintain backwards compat LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); LOAD_16(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); } } else { gba->cpu->executionMode = MODE_ARM; if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) { gba->cpu->prefetch[0] = state->cpuPrefetch[0]; gba->cpu->prefetch[1] = state->cpuPrefetch[1]; } else { // Maintain backwards compat LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); LOAD_32(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); } } GBAMemoryDeserialize(&gba->memory, state); GBAIODeserialize(gba, state); GBAVideoDeserialize(&gba->video, state); GBAAudioDeserialize(&gba->audio, state); GBASavedataDeserialize(&gba->memory.savedata, state, false); if (gba->rr) { gba->rr->stateLoaded(gba->rr, state); } return true; }
bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { char line[14] = "XXXXXXXX XXXX"; snprintf(line, sizeof(line), "%08X %04X", op1, op2); GBACheatRegisterLine(cheats, line); enum GBACodeBreakerType type = op1 >> 28; struct GBACheat* cheat = 0; if (cheats->incompleteCheat) { cheats->incompleteCheat->repeat = op1 & 0xFFFF; cheats->incompleteCheat->addressOffset = op2; cheats->incompleteCheat->operandOffset = 0; cheats->incompleteCheat = 0; return true; } switch (type) { case CB_GAME_ID: // TODO: Run checksum return true; case CB_HOOK: if (cheats->hook) { return false; } cheats->hook = malloc(sizeof(*cheats->hook)); cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); cheats->hook->mode = MODE_THUMB; cheats->hook->refs = 1; cheats->hook->reentries = 0; return true; case CB_OR_2: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_OR; cheat->width = 2; break; case CB_ASSIGN_1: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_ASSIGN; cheat->width = 1; break; case CB_FILL: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_ASSIGN; cheat->width = 2; cheats->incompleteCheat = cheat; break; case CB_FILL_8: GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); return false; case CB_AND_2: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_AND; cheat->width = 2; break; case CB_IF_EQ: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_EQ; cheat->width = 2; break; case CB_ASSIGN_2: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_ASSIGN; cheat->width = 2; break; case CB_ENCRYPT: GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); return false; case CB_IF_NE: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_NE; cheat->width = 2; break; case CB_IF_GT: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_GT; cheat->width = 2; break; case CB_IF_LT: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_LT; cheat->width = 2; break; case CB_IF_SPECIAL: switch (op1 & 0x0FFFFFFF) { case 0x20: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_AND; cheat->width = 2; cheat->address = BASE_IO | REG_JOYSTAT; cheat->operand = op2; cheat->repeat = 1; return true; default: GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); return false; } case CB_ADD_2: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_ADD; cheat->width = 2; break; case CB_IF_AND: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_AND; cheat->width = 2; break; } cheat->address = op1 & 0x0FFFFFFF; cheat->operand = op2; cheat->repeat = 1; cheat->negativeRepeat = 0; return true; }
void _rtcReadPins(struct GBACartridgeHardware* hw) { // Transfer sequence: // P: 0 | 1 | 2 | 3 // == Initiate // > HI | - | LO | - // > HI | - | HI | - // == Transfer bit (x8) // > LO | x | HI | - // > HI | - | HI | - // < ?? | x | ?? | - // == Terminate // > - | - | LO | - switch (hw->rtc.transferStep) { case 0: if ((hw->pinState & 5) == 1) { hw->rtc.transferStep = 1; } break; case 1: if ((hw->pinState & 5) == 5) { hw->rtc.transferStep = 2; } break; case 2: if (!hw->p0) { hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead; } else { if (hw->p2) { // GPIO direction should always != reading if (hw->dir1) { if (hw->rtc.command.reading) { GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode"); } ++hw->rtc.bitsRead; if (hw->rtc.bitsRead == 8) { _rtcProcessByte(hw); } } else { _outputPins(hw, 5 | (_rtcOutput(hw) << 1)); ++hw->rtc.bitsRead; if (hw->rtc.bitsRead == 8) { --hw->rtc.bytesRemaining; if (hw->rtc.bytesRemaining <= 0) { hw->rtc.commandActive = 0; hw->rtc.command.reading = 0; } hw->rtc.bitsRead = 0; } } } else { hw->rtc.bitsRead = 0; hw->rtc.bytesRemaining = 0; hw->rtc.commandActive = 0; hw->rtc.command.reading = 0; hw->rtc.transferStep = 0; } } break; } }