2012-03-28 00:18:03 +02:00
|
|
|
#include "statemanager.h"
|
|
|
|
#include "snapshot.h"
|
|
|
|
|
2012-03-29 15:23:04 +02:00
|
|
|
/* State Manager Class that records snapshot data for rewinding
|
2012-03-28 00:18:03 +02:00
|
|
|
mostly based on SSNES's rewind code by Themaister
|
|
|
|
*/
|
|
|
|
|
|
|
|
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 >= 0xffff
|
|
|
|
v |= v >> 8;
|
|
|
|
#endif
|
|
|
|
#if SIZE_MAX >= 0xffffffff
|
|
|
|
v |= v >> 16;
|
|
|
|
#endif
|
|
|
|
#if SIZE_MAX >= 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StateManager::deallocate() {
|
|
|
|
if(buffer) {
|
|
|
|
delete [] buffer;
|
|
|
|
buffer = NULL;
|
|
|
|
}
|
|
|
|
if(tmp_state) {
|
|
|
|
delete [] tmp_state;
|
|
|
|
tmp_state = NULL;
|
|
|
|
}
|
|
|
|
if(in_state) {
|
|
|
|
delete [] in_state;
|
|
|
|
in_state = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StateManager::StateManager()
|
|
|
|
{
|
|
|
|
buffer = NULL;
|
|
|
|
tmp_state = NULL;
|
|
|
|
in_state = NULL;
|
|
|
|
init_done = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
StateManager::~StateManager() {
|
|
|
|
deallocate();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StateManager::init(size_t buffer_size) {
|
|
|
|
|
|
|
|
init_done = false;
|
|
|
|
|
|
|
|
deallocate();
|
|
|
|
|
|
|
|
real_state_size = S9xFreezeSize();
|
|
|
|
state_size = real_state_size / sizeof(uint32_t); // Works in multiple of 4.
|
|
|
|
|
|
|
|
// We need 4-byte aligned state_size to avoid having to enforce this with unneeded memcpy's!
|
|
|
|
if(real_state_size % sizeof(uint32_t)) state_size ++;
|
|
|
|
|
|
|
|
if (buffer_size <= real_state_size) // Need a sufficient buffer size.
|
|
|
|
return false;
|
|
|
|
|
|
|
|
top_ptr = 1;
|
|
|
|
|
|
|
|
|
|
|
|
buf_size = nearest_pow2_size(buffer_size) / sizeof(uint64_t); // Works in multiple of 8.
|
|
|
|
buf_size_mask = buf_size - 1;
|
|
|
|
|
|
|
|
if (!(buffer = new uint64_t[buf_size]))
|
|
|
|
return false;
|
|
|
|
if (!(tmp_state = new uint32_t[state_size]))
|
|
|
|
return false;
|
|
|
|
if (!(in_state = new uint32_t[state_size]))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
memset(tmp_state,0,state_size * sizeof(uint32_t));
|
|
|
|
memset(in_state,0,state_size * sizeof(uint32_t));
|
|
|
|
|
|
|
|
init_done = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int StateManager::pop()
|
|
|
|
{
|
|
|
|
if(!init_done)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (first_pop)
|
|
|
|
{
|
|
|
|
first_pop = false;
|
|
|
|
return S9xUnfreezeGameMem((uint8 *)tmp_state,real_state_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
top_ptr = (top_ptr - 1) & buf_size_mask;
|
|
|
|
|
|
|
|
if (top_ptr == bottom_ptr) // Our stack is completely empty... :v
|
|
|
|
{
|
|
|
|
top_ptr = (top_ptr + 1) & buf_size_mask;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (buffer[top_ptr])
|
|
|
|
{
|
|
|
|
// Apply the xor patch.
|
|
|
|
uint32_t addr = buffer[top_ptr] >> 32;
|
|
|
|
uint32_t xor_ = buffer[top_ptr] & 0xFFFFFFFFU;
|
|
|
|
tmp_state[addr] ^= xor_;
|
|
|
|
|
|
|
|
top_ptr = (top_ptr - 1) & buf_size_mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (top_ptr == bottom_ptr) // Our stack is completely empty... :v
|
|
|
|
{
|
|
|
|
top_ptr = (top_ptr + 1) & buf_size_mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
return S9xUnfreezeGameMem((uint8 *)tmp_state,real_state_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StateManager::reassign_bottom()
|
|
|
|
{
|
|
|
|
bottom_ptr = (top_ptr + 1) & buf_size_mask;
|
|
|
|
while (buffer[bottom_ptr]) // Skip ahead until we find the first 0 (boundary for state delta).
|
|
|
|
bottom_ptr = (bottom_ptr + 1) & buf_size_mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StateManager::generate_delta(const void *data)
|
|
|
|
{
|
|
|
|
bool crossed = false;
|
|
|
|
const uint32_t *old_state = tmp_state;
|
|
|
|
const uint32_t *new_state = (const uint32_t*)data;
|
|
|
|
|
|
|
|
buffer[top_ptr++] = 0; // For each separate delta, we have a 0 value sentinel in between.
|
|
|
|
top_ptr &= buf_size_mask;
|
|
|
|
|
|
|
|
// Check if top_ptr and bottom_ptr crossed each other, which means we need to delete old cruft.
|
|
|
|
if (top_ptr == bottom_ptr)
|
|
|
|
crossed = true;
|
|
|
|
|
|
|
|
for (uint64_t i = 0; i < 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_)
|
|
|
|
{
|
|
|
|
buffer[top_ptr] = (i << 32) | xor_;
|
|
|
|
top_ptr = (top_ptr + 1) & buf_size_mask;
|
|
|
|
|
|
|
|
if (top_ptr == bottom_ptr)
|
|
|
|
crossed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (crossed)
|
|
|
|
reassign_bottom();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StateManager::push()
|
|
|
|
{
|
|
|
|
if(!init_done)
|
|
|
|
return false;
|
|
|
|
if(!S9xFreezeGameMem((uint8 *)in_state,real_state_size))
|
|
|
|
return false;
|
|
|
|
generate_delta(in_state);
|
|
|
|
uint32 *tmp = tmp_state;
|
|
|
|
tmp_state = in_state;
|
|
|
|
in_state = tmp;
|
|
|
|
|
|
|
|
first_pop = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|