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 | |
8 | static 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 | |
35 | void 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 | |
50 | StateManager::StateManager() |
51 | { |
52 | buffer = NULL; |
53 | tmp_state = NULL; |
54 | in_state = NULL; |
55 | init_done = false; |
56 | } |
57 | |
58 | StateManager::~StateManager() { |
59 | deallocate(); |
60 | } |
61 | |
62 | bool 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 | |
98 | int 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 | |
135 | void 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 | |
142 | void 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 | |
177 | bool 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 | |