1#include "statemanager.h"
2#include "snapshot.h"
3
4/* State Manager Class that records snapshot data for rewinding
5 mostly based on SSNES's rewind code by Themaister
6*/
7
8static inline size_t nearest_pow2_size(size_t v)
9{
10 size_t orig = v;
11 v--;
12 v |= v >> 1;
13 v |= v >> 2;
14 v |= v >> 4;
15#if SIZE_MAX >= 0xffff
16 v |= v >> 8;
17#endif
18#if SIZE_MAX >= 0xffffffff
19 v |= v >> 16;
20#endif
21#if SIZE_MAX >= 0xffffffffffffffff
22 v |= v >> 32;
23#endif
24 v++;
25
26 size_t next = v;
27 size_t prev = v >> 1;
28
29 if ((next - orig) < (orig - prev))
30 return next;
31 else
32 return prev;
33}
34
35void StateManager::deallocate() {
36 if(buffer) {
37 delete [] buffer;
38 buffer = NULL;
39 }
40 if(tmp_state) {
41 delete [] tmp_state;
42 tmp_state = NULL;
43 }
44 if(in_state) {
45 delete [] in_state;
46 in_state = NULL;
47 }
48}
49
50StateManager::StateManager()
51{
52 buffer = NULL;
53 tmp_state = NULL;
54 in_state = NULL;
55 init_done = false;
56}
57
58StateManager::~StateManager() {
59 deallocate();
60}
61
62bool StateManager::init(size_t buffer_size) {
63
64 init_done = false;
65
66 deallocate();
67
68 real_state_size = S9xFreezeSize();
69 state_size = real_state_size / sizeof(uint32_t); // Works in multiple of 4.
70
71 // We need 4-byte aligned state_size to avoid having to enforce this with unneeded memcpy's!
72 if(real_state_size % sizeof(uint32_t)) state_size ++;
73
74 if (buffer_size <= real_state_size) // Need a sufficient buffer size.
75 return false;
76
77 top_ptr = 1;
78
79
80 buf_size = nearest_pow2_size(buffer_size) / sizeof(uint64_t); // Works in multiple of 8.
81 buf_size_mask = buf_size - 1;
82
83 if (!(buffer = new uint64_t[buf_size]))
84 return false;
85 if (!(tmp_state = new uint32_t[state_size]))
86 return false;
87 if (!(in_state = new uint32_t[state_size]))
88 return false;
89
90 memset(tmp_state,0,state_size * sizeof(uint32_t));
91 memset(in_state,0,state_size * sizeof(uint32_t));
92
93 init_done = true;
94
95 return true;
96}
97
98int StateManager::pop()
99{
100 if(!init_done)
101 return 0;
102
103 if (first_pop)
104 {
105 first_pop = false;
106 return S9xUnfreezeGameMem((uint8 *)tmp_state,real_state_size);
107 }
108
109 top_ptr = (top_ptr - 1) & buf_size_mask;
110
111 if (top_ptr == bottom_ptr) // Our stack is completely empty... :v
112 {
113 top_ptr = (top_ptr + 1) & buf_size_mask;
114 return 0;
115 }
116
117 while (buffer[top_ptr])
118 {
119 // Apply the xor patch.
120 uint32_t addr = buffer[top_ptr] >> 32;
121 uint32_t xor_ = buffer[top_ptr] & 0xFFFFFFFFU;
122 tmp_state[addr] ^= xor_;
123
124 top_ptr = (top_ptr - 1) & buf_size_mask;
125 }
126
127 if (top_ptr == bottom_ptr) // Our stack is completely empty... :v
128 {
129 top_ptr = (top_ptr + 1) & buf_size_mask;
130 }
131
132 return S9xUnfreezeGameMem((uint8 *)tmp_state,real_state_size);
133}
134
135void StateManager::reassign_bottom()
136{
137 bottom_ptr = (top_ptr + 1) & buf_size_mask;
138 while (buffer[bottom_ptr]) // Skip ahead until we find the first 0 (boundary for state delta).
139 bottom_ptr = (bottom_ptr + 1) & buf_size_mask;
140}
141
142void StateManager::generate_delta(const void *data)
143{
144 bool crossed = false;
145 const uint32_t *old_state = tmp_state;
146 const uint32_t *new_state = (const uint32_t*)data;
147
148 buffer[top_ptr++] = 0; // For each separate delta, we have a 0 value sentinel in between.
149 top_ptr &= buf_size_mask;
150
151 // Check if top_ptr and bottom_ptr crossed each other, which means we need to delete old cruft.
152 if (top_ptr == bottom_ptr)
153 crossed = true;
154
155 for (uint64_t i = 0; i < state_size; i++)
156 {
157 uint64_t xor_ = old_state[i] ^ new_state[i];
158
159 // If the data differs (xor != 0), we push that xor on the stack with index and xor.
160 // This can be reversed by reapplying the xor.
161 // This, if states don't really differ much, we'll save lots of space :)
162 // Hopefully this will work really well with save states.
163 if (xor_)
164 {
165 buffer[top_ptr] = (i << 32) | xor_;
166 top_ptr = (top_ptr + 1) & buf_size_mask;
167
168 if (top_ptr == bottom_ptr)
169 crossed = true;
170 }
171 }
172
173 if (crossed)
174 reassign_bottom();
175}
176
177bool StateManager::push()
178{
179 if(!init_done)
180 return false;
181 if(!S9xFreezeGameMem((uint8 *)in_state,real_state_size))
182 return false;
183 generate_delta(in_state);
184 uint32 *tmp = tmp_state;
185 tmp_state = in_state;
186 in_state = tmp;
187
188 first_pop = true;
189
190 return true;
191}
192