bsnes/Core/rewind.c

209 lines
6.9 KiB
C

#include "gb.h"
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <math.h>
static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size)
{
size_t malloc_size = 0x1000;
uint8_t *compressed = malloc(malloc_size);
size_t counter_pos = 0;
size_t data_pos = sizeof(uint16_t);
bool prev_mode = true;
*(uint16_t *)compressed = 0;
#define COUNTER (*(uint16_t *)&compressed[counter_pos])
#define DATA (compressed[data_pos])
while (uncompressed_size) {
if (prev_mode) {
if (*data == *prev && COUNTER != 0xffff) {
COUNTER++;
data++;
prev++;
uncompressed_size--;
}
else {
prev_mode = false;
counter_pos += sizeof(uint16_t);
data_pos = counter_pos + sizeof(uint16_t);
if (data_pos >= malloc_size) {
malloc_size *= 2;
compressed = realloc(compressed, malloc_size);
}
COUNTER = 0;
}
}
else {
if (*data != *prev && COUNTER != 0xffff) {
COUNTER++;
DATA = *data;
data_pos++;
data++;
prev++;
uncompressed_size--;
if (data_pos >= malloc_size) {
malloc_size *= 2;
compressed = realloc(compressed, malloc_size);
}
}
else {
prev_mode = true;
counter_pos = data_pos;
data_pos = counter_pos + sizeof(uint16_t);
if (counter_pos >= malloc_size - 1) {
malloc_size *= 2;
compressed = realloc(compressed, malloc_size);
}
COUNTER = 0;
}
}
}
return realloc(compressed, data_pos);
#undef DATA
#undef COUNTER
}
static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size)
{
size_t counter_pos = 0;
size_t data_pos = sizeof(uint16_t);
bool prev_mode = true;
#define COUNTER (*(uint16_t *)&data[counter_pos])
#define DATA (data[data_pos])
while (uncompressed_size) {
if (prev_mode) {
if (COUNTER) {
COUNTER--;
*(dest++) = *(prev++);
uncompressed_size--;
}
else {
prev_mode = false;
counter_pos += sizeof(uint16_t);
data_pos = counter_pos + sizeof(uint16_t);
}
}
else {
if (COUNTER) {
COUNTER--;
*(dest++) = DATA;
data_pos++;
prev++;
uncompressed_size--;
}
else {
prev_mode = true;
counter_pos = data_pos;
data_pos += sizeof(uint16_t);
}
}
}
#undef DATA
#undef COUNTER
}
void GB_rewind_push(GB_gameboy_t *gb)
{
const size_t save_size = GB_get_save_state_size(gb);
if (!gb->rewind_sequences) {
if (gb->rewind_buffer_length) {
gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
gb->rewind_pos = 0;
}
else {
return;
}
}
if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) {
gb->rewind_pos++;
if (gb->rewind_pos == gb->rewind_buffer_length) {
gb->rewind_pos = 0;
}
if (gb->rewind_sequences[gb->rewind_pos].key_state) {
free(gb->rewind_sequences[gb->rewind_pos].key_state);
gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
}
for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) {
if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) {
free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]);
gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0;
}
}
gb->rewind_sequences[gb->rewind_pos].pos = 0;
}
if (!gb->rewind_sequences[gb->rewind_pos].key_state) {
gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size);
GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
}
else {
uint8_t *save_state = malloc(save_size);
GB_save_state_to_buffer(gb, save_state);
gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] =
state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size);
free(save_state);
}
}
bool GB_rewind_pop(GB_gameboy_t *gb)
{
if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) {
return false;
}
const size_t save_size = GB_get_save_state_size(gb);
if (gb->rewind_sequences[gb->rewind_pos].pos == 0) {
GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size);
free(gb->rewind_sequences[gb->rewind_pos].key_state);
gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1;
return true;
}
uint8_t *save_state = malloc(save_size);
state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state,
gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos],
save_state,
save_size);
free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]);
gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL;
GB_load_state_from_buffer(gb, save_state, save_size);
free(save_state);
return true;
}
void GB_rewind_free(GB_gameboy_t *gb)
{
if (!gb->rewind_sequences) return;
for (unsigned i = 0; i < gb->rewind_buffer_length; i++) {
if (gb->rewind_sequences[i].key_state) {
free(gb->rewind_sequences[i].key_state);
}
for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) {
if (gb->rewind_sequences[i].compressed_states[j]) {
free(gb->rewind_sequences[i].compressed_states[j]);
}
}
}
free(gb->rewind_sequences);
gb->rewind_sequences = NULL;
}
void GB_set_rewind_length(GB_gameboy_t *gb, double seconds)
{
GB_rewind_free(gb);
if (seconds == 0) {
gb->rewind_buffer_length = 0;
}
else {
gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY);
}
}