/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. * Copyright (C) 2010-2012 - Hans-Kristian Arntzen * * Some code herein may be based on code found in BSNES. * * SSNES is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with SSNES. * If not, see . */ #include "rewind.h" #include #include #include "boolean.h" #include #include #include "general.h" struct state_manager { uint64_t *buffer; size_t buf_size; size_t buf_size_mask; uint32_t *tmp_state; size_t top_ptr; size_t bottom_ptr; size_t state_size; bool first_pop; }; static inline size_t nearest_pow2_size(size_t v) { size_t orig = v; v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; #if SIZE_MAX >= UINT16_C(0xffff) v |= v >> 8; #endif #if SIZE_MAX >= UINT32_C(0xffffffff) v |= v >> 16; #endif #if SIZE_MAX >= UINT64_C(0xffffffffffffffff) v |= v >> 32; #endif v++; size_t next = v; size_t prev = v >> 1; if ((next - orig) < (orig - prev)) return next; else return prev; } state_manager_t *state_manager_new(size_t state_size, size_t buffer_size, void *init_buffer) { if (buffer_size <= state_size * 4) // Need a sufficient buffer size. return NULL; state_manager_t *state = (state_manager_t*)calloc(1, sizeof(*state)); if (!state) return NULL; // We need 4-byte aligned state_size to avoid having to enforce this with unneeded memcpy's! ssnes_assert(state_size % 4 == 0); state->top_ptr = 1; state->state_size = state_size / sizeof(uint32_t); // Works in multiple of 4. state->buf_size = nearest_pow2_size(buffer_size) / sizeof(uint64_t); // Works in multiple of 8. state->buf_size_mask = state->buf_size - 1; SSNES_LOG("Readjusted rewind buffer size to %u MiB\n", (unsigned)(sizeof(uint64_t) * (state->buf_size >> 20))); if (!(state->buffer = (uint64_t*)calloc(1, state->buf_size * sizeof(uint64_t)))) goto error; if (!(state->tmp_state = (uint32_t*)calloc(1, state->state_size * sizeof(uint32_t)))) goto error; memcpy(state->tmp_state, init_buffer, state_size); return state; error: if (state) { free(state->buffer); free(state->tmp_state); free(state); } return NULL; } void state_manager_free(state_manager_t *state) { free(state->buffer); free(state->tmp_state); free(state); } bool state_manager_pop(state_manager_t *state, void **data) { *data = state->tmp_state; if (state->first_pop) { state->first_pop = false; return true; } state->top_ptr = (state->top_ptr - 1) & state->buf_size_mask; if (state->top_ptr == state->bottom_ptr) // Our stack is completely empty... :v { state->top_ptr = (state->top_ptr + 1) & state->buf_size_mask; return false; } while (state->buffer[state->top_ptr]) { // Apply the xor patch. uint32_t addr = state->buffer[state->top_ptr] >> 32; uint32_t xor_ = state->buffer[state->top_ptr] & 0xFFFFFFFFU; state->tmp_state[addr] ^= xor_; state->top_ptr = (state->top_ptr - 1) & state->buf_size_mask; } if (state->top_ptr == state->bottom_ptr) // Our stack is completely empty... :v { state->top_ptr = (state->top_ptr + 1) & state->buf_size_mask; return true; } return true; } static void reassign_bottom(state_manager_t *state) { state->bottom_ptr = (state->top_ptr + 1) & state->buf_size_mask; while (state->buffer[state->bottom_ptr]) // Skip ahead until we find the first 0 (boundary for state delta). state->bottom_ptr = (state->bottom_ptr + 1) & state->buf_size_mask; } static void generate_delta(state_manager_t *state, const void *data) { bool crossed = false; const uint32_t *old_state = state->tmp_state; const uint32_t *new_state = (const uint32_t*)data; state->buffer[state->top_ptr++] = 0; // For each separate delta, we have a 0 value sentinel in between. state->top_ptr &= state->buf_size_mask; // Check if top_ptr and bottom_ptr crossed each other, which means we need to delete old cruft. if (state->top_ptr == state->bottom_ptr) crossed = true; for (uint64_t i = 0; i < state->state_size; i++) { uint64_t xor_ = old_state[i] ^ new_state[i]; // If the data differs (xor != 0), we push that xor on the stack with index and xor. // This can be reversed by reapplying the xor. // This, if states don't really differ much, we'll save lots of space :) // Hopefully this will work really well with save states. if (xor_) { state->buffer[state->top_ptr] = (i << 32) | xor_; state->top_ptr = (state->top_ptr + 1) & state->buf_size_mask; if (state->top_ptr == state->bottom_ptr) crossed = true; } } if (crossed) reassign_bottom(state); } bool state_manager_push(state_manager_t *state, const void *data) { generate_delta(state, data); memcpy(state->tmp_state, data, state->state_size * sizeof(uint32_t)); state->first_pop = true; return true; }