GBA Savedata: Store RTC data in savegames (closes #240)

This commit is contained in:
Vicki Pfau 2022-10-06 02:57:26 -07:00
parent e2e22e2218
commit bb711d311f
8 changed files with 78 additions and 3 deletions

View File

@ -87,8 +87,9 @@ Misc:
- GBA: Automatically skip BIOS if ROM has invalid logo - GBA: Automatically skip BIOS if ROM has invalid logo
- GBA: Refine multiboot detection (fixes mgba.io/i/2192) - GBA: Refine multiboot detection (fixes mgba.io/i/2192)
- GBA Cheats: Implement "never" type codes (closes mgba.io/i/915) - GBA Cheats: Implement "never" type codes (closes mgba.io/i/915)
- GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276)
- GBA DMA: Enhanced logging (closes mgba.io/i/2454) - GBA DMA: Enhanced logging (closes mgba.io/i/2454)
- GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276)
- GBA Savedata: Store RTC data in savegames (closes mgba.io/i/240)
- GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962) - GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962)
- GBA Video: Fix highlighting for sprites with mid-frame palette changes - GBA Video: Fix highlighting for sprites with mid-frame palette changes
- mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871) - mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871)

View File

@ -42,7 +42,7 @@ enum GPIODirection {
GPIO_READ_WRITE = 1 GPIO_READ_WRITE = 1
}; };
DECL_BITFIELD(RTCControl, uint32_t); DECL_BITFIELD(RTCControl, uint8_t);
DECL_BIT(RTCControl, MinIRQ, 3); DECL_BIT(RTCControl, MinIRQ, 3);
DECL_BIT(RTCControl, Hour24, 6); DECL_BIT(RTCControl, Hour24, 6);
DECL_BIT(RTCControl, Poweroff, 7); DECL_BIT(RTCControl, Poweroff, 7);
@ -69,6 +69,8 @@ struct GBARTC {
RTCCommandData command; RTCCommandData command;
RTCControl control; RTCControl control;
uint8_t time[7]; uint8_t time[7];
time_t lastLatch;
time_t offset;
}; };
DECL_BITFIELD(GPIOPin, uint16_t); DECL_BITFIELD(GPIOPin, uint16_t);

View File

@ -72,6 +72,7 @@ struct GBASavedata {
uint8_t* data; uint8_t* data;
enum SavedataCommand command; enum SavedataCommand command;
struct VFile* vf; struct VFile* vf;
struct GBACartridgeHardware* gpio;
int mapMode; int mapMode;
bool maskWriteback; bool maskWriteback;
@ -93,6 +94,12 @@ struct GBASavedata {
enum FlashStateMachine flashState; enum FlashStateMachine flashState;
}; };
struct GBASavedataRTCBuffer {
uint8_t time[7];
uint8_t control;
uint64_t lastLatch;
};
void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataDeinit(struct GBASavedata* savedata); void GBASavedataDeinit(struct GBASavedata* savedata);
@ -116,6 +123,9 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount); void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount);
void GBASavedataRTCRead(struct GBASavedata* savedata);
void GBASavedataRTCWrite(struct GBASavedata* savedata);
struct GBASerializedState; struct GBASerializedState;
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state); void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state);
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state); void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state);

View File

@ -348,7 +348,8 @@ struct GBASerializedState {
int32_t rtcBits; int32_t rtcBits;
int32_t rtcCommandActive; int32_t rtcCommandActive;
RTCCommandData rtcCommand; RTCCommandData rtcCommand;
RTCControl rtcControl; uint8_t rtcControl;
uint8_t reserved[3];
uint8_t time[7]; uint8_t time[7];
uint8_t devices; uint8_t devices;
uint16_t gyroSample; uint16_t gyroSample;

View File

@ -100,6 +100,9 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
hw->rtc.command = 0; hw->rtc.command = 0;
hw->rtc.control = 0x40; hw->rtc.control = 0x40;
memset(hw->rtc.time, 0, sizeof(hw->rtc.time)); memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
hw->rtc.lastLatch = 0;
hw->rtc.offset = 0;
} }
void _readPins(struct GBACartridgeHardware* hw) { void _readPins(struct GBACartridgeHardware* hw) {
@ -278,6 +281,9 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
} else { } else {
t = time(0); t = time(0);
} }
hw->rtc.lastLatch = t;
t -= hw->rtc.offset;
struct tm date; struct tm date;
localtime_r(&t, &date); localtime_r(&t, &date);
hw->rtc.time[0] = _rtcBCD(date.tm_year - 100); hw->rtc.time[0] = _rtcBCD(date.tm_year - 100);

View File

@ -77,6 +77,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
gba->memory.savedata.timing = &gba->timing; gba->memory.savedata.timing = &gba->timing;
gba->memory.savedata.vf = NULL; gba->memory.savedata.vf = NULL;
gba->memory.savedata.realVf = NULL; gba->memory.savedata.realVf = NULL;
gba->memory.savedata.gpio = &gba->memory.hw;
GBASavedataInit(&gba->memory.savedata, NULL); GBASavedataInit(&gba->memory.savedata, NULL);
gba->video.p = gba; gba->video.p = gba;

View File

@ -341,6 +341,7 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
if (override->hardware & HW_RTC) { if (override->hardware & HW_RTC) {
GBAHardwareInitRTC(&gba->memory.hw); GBAHardwareInitRTC(&gba->memory.hw);
GBASavedataRTCRead(&gba->memory.savedata);
} }
if (override->hardware & HW_GYRO) { if (override->hardware & HW_GYRO) {

View File

@ -575,6 +575,7 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
if (savedata->mapMode & MAP_WRITE) { if (savedata->mapMode & MAP_WRITE) {
size_t size = GBASavedataSize(savedata); size_t size = GBASavedataSize(savedata);
if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) { if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) {
GBASavedataRTCWrite(savedata);
mLOG(GBA_SAVE, INFO, "Savedata synced"); mLOG(GBA_SAVE, INFO, "Savedata synced");
} else { } else {
mLOG(GBA_SAVE, INFO, "Savedata failed to sync!"); mLOG(GBA_SAVE, INFO, "Savedata failed to sync!");
@ -583,6 +584,58 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
} }
} }
void GBASavedataRTCWrite(struct GBASavedata* savedata) {
if (!(savedata->gpio->devices & HW_RTC)) {
return;
}
struct GBASavedataRTCBuffer buffer;
memcpy(&buffer.time, savedata->gpio->rtc.time, 7);
buffer.control = savedata->gpio->rtc.control;
STORE_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
size_t size = GBASavedataSize(savedata) & ~0xFF;
savedata->vf->seek(savedata->vf, size, SEEK_SET);
savedata->vf->write(savedata->vf, &buffer, sizeof(buffer));
}
static uint8_t _unBCD(uint8_t byte) {
return (byte >> 4) * 10 + (byte & 0xF);
}
void GBASavedataRTCRead(struct GBASavedata* savedata) {
struct GBASavedataRTCBuffer buffer;
size_t size = GBASavedataSize(savedata) & ~0xFF;
savedata->vf->seek(savedata->vf, size, SEEK_SET);
size = savedata->vf->read(savedata->vf, &buffer, sizeof(buffer));
if (size < sizeof(buffer)) {
return;
}
memcpy(savedata->gpio->rtc.time, &buffer.time, 7);
// Older FlashGBX sets this to 0x01 instead of the control flag.
// Since that bit is invalid on hardware, we can check for != 0x01
// to see if it's a valid value instead of just a filler value.
if (buffer.control != 1) {
savedata->gpio->rtc.control = buffer.control;
}
LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
struct tm date;
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1;
date.tm_mday = _unBCD(savedata->gpio->rtc.time[2]);
date.tm_hour = _unBCD(savedata->gpio->rtc.time[4]);
date.tm_min = _unBCD(savedata->gpio->rtc.time[5]);
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
date.tm_isdst = -1;
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date);
}
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) { void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
state->savedata.type = savedata->type; state->savedata.type = savedata->type;
state->savedata.command = savedata->command; state->savedata.command = savedata->command;