SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
Core/rewind.c
1 #include "gb.h"
2 #include <stdint.h>
3 #include <stddef.h>
4 #include <stdlib.h>
5 #include <math.h>
6 #include <assert.h>
7 #include <string.h>
8
9 static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size)
10 {
11 size_t malloc_size = 0x1000;
12 uint8_t *compressed = malloc(malloc_size);
13 size_t counter_pos = 0;
14 size_t data_pos = sizeof(uint16_t);
15 bool prev_mode = true;
16 *(uint16_t *)compressed = 0;
17 #define COUNTER (*(uint16_t *)&compressed[counter_pos])
18 #define DATA (compressed[data_pos])
19
20 while (uncompressed_size) {
21 if (prev_mode) {
22 if (*data == *prev && COUNTER != 0xFFFF) {
23 COUNTER++;
24 data++;
25 prev++;
26 uncompressed_size--;
27 }
28 else {
29 prev_mode = false;
30 counter_pos += sizeof(uint16_t);
31 data_pos = counter_pos + sizeof(uint16_t);
32 if (data_pos >= malloc_size) {
33 malloc_size *= 2;
34 compressed = realloc(compressed, malloc_size);
35 }
36 COUNTER = 0;
37 }
38 }
39 else {
40 if (*data != *prev && COUNTER != 0xFFFF) {
41 COUNTER++;
42 DATA = *data;
43 data_pos++;
44 data++;
45 prev++;
46 uncompressed_size--;
47 if (data_pos >= malloc_size) {
48 malloc_size *= 2;
49 compressed = realloc(compressed, malloc_size);
50 }
51 }
52 else {
53 prev_mode = true;
54 counter_pos = data_pos;
55 data_pos = counter_pos + sizeof(uint16_t);
56 if (counter_pos >= malloc_size - 1) {
57 malloc_size *= 2;
58 compressed = realloc(compressed, malloc_size);
59 }
60 COUNTER = 0;
61 }
62 }
63 }
64
65 return realloc(compressed, data_pos);
66 #undef DATA
67 #undef COUNTER
68 }
69
70
71 static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size)
72 {
73 size_t counter_pos = 0;
74 size_t data_pos = sizeof(uint16_t);
75 bool prev_mode = true;
76 #define COUNTER (*(uint16_t *)&data[counter_pos])
77 #define DATA (data[data_pos])
78
79 while (uncompressed_size) {
80 if (prev_mode) {
81 if (COUNTER) {
82 COUNTER--;
83 *(dest++) = *(prev++);
84 uncompressed_size--;
85 }
86 else {
87 prev_mode = false;
88 counter_pos += sizeof(uint16_t);
89 data_pos = counter_pos + sizeof(uint16_t);
90 }
91 }
92 else {
93 if (COUNTER) {
94 COUNTER--;
95 *(dest++) = DATA;
96 data_pos++;
97 prev++;
98 uncompressed_size--;
99 }
100 else {
101 prev_mode = true;
102 counter_pos = data_pos;
103 data_pos += sizeof(uint16_t);
104 }
105 }
106 }
107 #undef DATA
108 #undef COUNTER
109 }
110
111 void GB_rewind_push(GB_gameboy_t *gb)
112 {
113 const size_t save_size = GB_get_save_state_size_no_bess(gb);
114 if (gb->rewind_state_size != save_size) {
115 GB_rewind_reset(gb);
116 gb->rewind_state_size = save_size;
117 }
118 if (!gb->rewind_sequences) {
119 if (gb->rewind_buffer_length) {
120 gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
121 memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
122 gb->rewind_pos = 0;
123 }
124 else {
125 return;
126 }
127 }
128
129 if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) {
130 gb->rewind_pos++;
131 if (gb->rewind_pos == gb->rewind_buffer_length) {
132 gb->rewind_pos = 0;
133 }
134 if (gb->rewind_sequences[gb->rewind_pos].key_state) {
135 free(gb->rewind_sequences[gb->rewind_pos].key_state);
136 gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
137 }
138 for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) {
139 if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) {
140 free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]);
141 gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = NULL;
142 }
143 }
144 gb->rewind_sequences[gb->rewind_pos].pos = 0;
145 }
146
147 if (!gb->rewind_sequences[gb->rewind_pos].key_state) {
148 gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size);
149 gb->rewind_sequences[gb->rewind_pos].instruction_count[0] = 0;
150 GB_save_state_to_buffer_no_bess(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
151 }
152 else {
153 uint8_t *save_state = malloc(save_size);
154 assert(gb->rewind_sequences[gb->rewind_pos].key_state);
155 GB_save_state_to_buffer_no_bess(gb, save_state);
156 gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] =
157 state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size);
158 gb->rewind_sequences[gb->rewind_pos].instruction_count[gb->rewind_sequences[gb->rewind_pos].pos] = 0;
159 free(save_state);
160 }
161
162 }
163
164 bool GB_rewind_pop(GB_gameboy_t *gb)
165 {
166 GB_ASSERT_NOT_RUNNING(gb)
167
168 if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) {
169 return false;
170 }
171
172 const size_t save_size = GB_get_save_state_size_no_bess(gb);
173 if (gb->rewind_sequences[gb->rewind_pos].pos == 0) {
174 gb->rewind_disable_invalidation = true;
175 GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size);
176 gb->rewind_disable_invalidation = false;
177 free(gb->rewind_sequences[gb->rewind_pos].key_state);
178 gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
179 gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1;
180 return true;
181 }
182
183 uint8_t *save_state = malloc(save_size);
184 state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state,
185 gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos],
186 save_state,
187 save_size);
188 free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]);
189 gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL;
190 gb->rewind_disable_invalidation = true;
191 GB_load_state_from_buffer(gb, save_state, save_size);
192 gb->rewind_disable_invalidation = false;
193 free(save_state);
194 return true;
195 }
196
197 void GB_rewind_reset(GB_gameboy_t *gb)
198 {
199 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
200
201 if (!gb->rewind_sequences) return;
202 for (unsigned i = 0; i < gb->rewind_buffer_length; i++) {
203 if (gb->rewind_sequences[i].key_state) {
204 free(gb->rewind_sequences[i].key_state);
205 }
206 for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) {
207 if (gb->rewind_sequences[i].compressed_states[j]) {
208 free(gb->rewind_sequences[i].compressed_states[j]);
209 }
210 }
211 }
212 free(gb->rewind_sequences);
213 gb->rewind_sequences = NULL;
214 }
215
216 void GB_set_rewind_length(GB_gameboy_t *gb, double seconds)
217 {
218 GB_rewind_reset(gb);
219 if (seconds == 0) {
220 gb->rewind_buffer_length = 0;
221 }
222 else {
223 gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY);
224 }
225 }
226
227 void GB_rewind_invalidate_for_backstepping(GB_gameboy_t *gb)
228 {
229 if (gb->rewind_disable_invalidation) return;
230 if (gb->rewind_sequences && gb->rewind_sequences[gb->rewind_pos].key_state) {
231 typeof(gb->rewind_sequences[0]) *sequence = &gb->rewind_sequences[gb->rewind_pos];
232 sequence->instruction_count[sequence->pos] |= 0x80000000;
233 }
234 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.