mirror of https://github.com/mgba-emu/mgba.git
GBA Savedata: Store RTC data in savegames (closes #240)
This commit is contained in:
parent
e2e22e2218
commit
bb711d311f
3
CHANGES
3
CHANGES
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue