Alchemy
Loading...
Searching...
No Matches

save.c

Go to the documentation of this file.
1/// @file
2/// Saving: flash sectors, records and the save workspace.
3/// Part of runtime.c, the runtime object, compiled only through it.
4
6
7// Saving. The flash chip holds 16 sectors of 4 KB; each holds one record: a
8// 16-byte header (signature, record id, checksum of the payload, sequence
9// number) and its payload. A save file is records n and n + 3 (n = 0 to 2).
10// Writing a record goes to a free sector before the old copy is invalidated,
11// and the highest sequence number of a record id is its current copy.
12
13enum {
21};
22
23/// Allocates the save workspace (heap slot HEAP_SLOT_SAVE), installs the flash
24/// library's timer 2 handler, identifies the flash chip (up to eight tries,
25/// a frame apart) and scans all 16 sectors into the workspace's occupied,
26/// recordId and sequence tables: a sector counts as taken when its signature
27/// and checksum are good, and of two copies of a record only the newer stays
28/// taken. Returns 1 when no flash chip answers, 0 otherwise; either way the
29/// caller ends with SaveState_ReleaseWorkspace. Called by the save screens
30/// (window/battle_command/save.c) before every save, load, copy or delete.
32 struct SaveWorkspace *work;
33 struct SaveSlotHeader header;
34 struct SaveSlotHeader *slot;
35 u32 index;
36
37 work = Runtime_AllocateBlock(HEAP_SLOT_SAVE, sizeof(*work));
38 Dma_Fill32(0, work, sizeof(*work));
40
41 // Yield after every failed probe, including the last one, before
42 // reporting that no flash chip answered.
43 for (index = 0; index < SAVE_FLASH_IDENTIFY_ATTEMPTS; index++) {
44 if ((u16)IdentifyFlash() == 0)
45 break;
46 WaitFrames(1);
47 }
49 return 1;
50
51 for (index = 0; index < SAVE_SECTOR_COUNT; index++) {
52 u32 status;
53 u32 record;
54 u8 id;
55
56 work->occupied[index] = 0;
57 work->recordId[index] = SAVE_RECORD_NONE;
58 work->sequence[index] = 0;
60 START_DMA(&work->slot, &header, DMA_COPY_WORDS(4));
61 WAIT_DMA();
62 slot = &header;
63
64 // Not a save sector, or one for no record, or a damaged one: it stays
65 // free. A sector with the signature still keeps its sequence number.
67 continue;
68 work->sequence[index] = slot->sequence;
69 record = slot->recordId;
70 id = record;
71 if (id >= SAVE_RECORD_NONE)
72 continue;
73 if (status != 0)
74 continue;
75
76 work->occupied[index] = 1;
77 work->recordId[index] = record;
78 // An interrupted replacement can leave both copies valid. Retain the
79 // larger sequence, preferring the earlier sector when they tie; status
80 // is the successful checksum result (0), reused as the scan cursor.
81 if (status < index) {
82 do {
83 if (work->recordId[status] == (u8)record) {
84 if (work->sequence[status] < slot->sequence)
85 work->occupied[status] = 0;
86 else
87 work->occupied[index] = 0;
88 }
89 status++;
90 } while (status < index);
91 }
92 }
93 return 0;
94}
95
96/// Picks the free sector to write record `mode` to, at random. The last free
97/// sector only goes to a record that already has a copy, so replacing a save
98/// always has room. Called by SaveState_WriteRecord. Returns 16 when there is
99/// no sector to use.
101 u32 empty[SAVE_SECTOR_COUNT];
102 s32 count;
103 u32 index;
104 u8 *slot;
105 u8 value;
106
107 slot = gSaveWorkspace->occupied;
108 count = 0;
109 index = 0;
110 do {
111 value = *slot++;
112 if (value == 0) {
113 empty[count++] = index;
114 }
115 index += 1;
116 } while (index < SAVE_SECTOR_COUNT);
117 index = SAVE_SECTOR_COUNT;
118 if (count != 0) {
119 if (count == 1) {
120 index = empty[0];
122 index = SAVE_SECTOR_COUNT;
123 }
124 } else {
125 index = Random16() % count;
126 index = empty[index];
127 }
128 }
129 return index;
130}
131
132/// Programs the workspace's sector buffer into flash sector `sector` and
133/// verifies it, through the flash library's routine for this chip. Called by
134/// SaveState_WriteRecord and SaveState_InvalidateSlot. Returns 0 on success
135/// and 1 on failure.
137 s32 *param = &gFlash_Handler0;
138 s32 result;
139 struct SaveWorkspace *work;
140 s32 value;
141
142 work = gSaveWorkspace;
143 value = (u16)sector;
144 if ((_call_via_r3(value, (s32)&work->slot, (s32)param, *param) << 0x10) !=
145 0) {
146 return 1U;
147 }
148 result = Flash_VerifySector(value, (s32)&work->slot);
149 return (u32)((0 - result) | result) >> 0x1F;
150}
151
152/// Reads flash sector `index` into the workspace's sector buffer. Called by
153/// SaveState_InitializeWorkspace and SaveState_ReadRecordPayload. Returns 0
154/// when the payload matches the header's checksum.
156 struct SaveWorkspace *work;
157 struct SaveSlotHeader header;
159
160 work = gSaveWorkspace;
161 ReadFlash(index, 0, work->slot.bytes, sizeof(work->slot));
162 Dma_Set(&work->slot, &header, DMA_COPY_WORDS(4), REG_DMA3);
163 WAIT_DMA();
165 return (u16)checksum - header.checksum;
166}
167
169
170/// Erases flash sector `sector` with the flash library's routine for this chip.
171/// Nothing calls it.
173 return ((EraseSectorProc)gEraseFlashSector)(sector);
174}
175
176/// Saves 0xff0 bytes from `source` as record `recordId`: writes the new copy
177/// with the next sequence number, then invalidates the old one, updating the
178/// workspace's sector tables. When the sequence passes 65000 it restarts at 1,
179/// written back over the old sector. Called by the save screens. Returns 0 on
180/// success and 1 on failure.
182 struct SaveWorkspace *work;
183 struct SaveSlotHeader header;
184 u32 current;
185 u32 slot;
186
187 work = gSaveWorkspace;
188 Dma_Fill32(0, &work->slot, sizeof(work->slot));
189 WAIT_DMA();
192 if (slot >= SAVE_SECTOR_COUNT)
193 return 1;
194
195 // Build the new sector before changing which old sector counts as current.
196 START_DMA(source, work->slot.record.payload, DMA_COPY_WORDS(0x3fc));
197 WAIT_DMA();
199 WAIT_DMA();
200 header.recordId = recordId;
203 START_DMA(&header, &work->slot.record.header, DMA_COPY_WORDS(4));
204 WAIT_DMA();
205
206 if (SaveState_WriteWorkspaceSlot(slot) != 0)
207 return 1;
208 // A power loss before invalidation leaves two valid copies; the next
209 // scan chooses the new one by its higher sequence.
210 if (current < SAVE_SECTOR_COUNT && SaveState_InvalidateSlot(current) != 0)
211 return 1;
212
213 if (header.sequence > SAVE_SEQUENCE_LIMIT) {
214 // Sequence order is ordinary, not modular. Keep the high-sequence copy
215 // valid until the old sector has been rewritten and verified as 1, then
216 // invalidate the high copy so it cannot win the next scan.
217 header.sequence = 1;
218 START_DMA(&header, &work->slot.record.header, DMA_COPY_WORDS(4));
219 WAIT_DMA();
220 if (SaveState_WriteWorkspaceSlot(current) != 0)
221 return 1;
222 if (SaveState_InvalidateSlot(slot) != 0)
223 return 1;
224 slot = current;
225 }
226
227 work->occupied[slot] = 1;
228 work->recordId[slot] = recordId;
229 work->sequence[slot] = header.sequence;
230 return 0;
231}
232
233/// Loads the current copy of record `recordId` into `destination`. Called by
234/// the save screens to load or copy a file. Returns 1 when there is none and
235/// 0 otherwise.
237 struct SaveWorkspace *work;
238 u32 index;
239
240 work = gSaveWorkspace;
242 if (index >= SAVE_SECTOR_COUNT)
243 return 1;
245 START_DMA(work->slot.record.payload, destination, DMA_COPY_WORDS(0x3fc));
246 WAIT_DMA();
247 return 0;
248}
249
250/// Invalidates the current copy of record `recordId`. Called by the save
251/// screen's delete. Returns 0 on success and 1 when there is none or the
252/// write failed.
254 s32 index;
255 s32 deletionResult;
256
258 if ((u32)index >= SAVE_SECTOR_COUNT) {
259 return 1U;
260 }
261 deletionResult = SaveState_InvalidateSlot(index);
262 return (u32)((0 - deletionResult) | deletionResult) >> 0x1F;
263}
264
265/// Sums the first 0xfe8 payload bytes of the workspace's sector buffer; the
266/// final eight payload bytes are excluded. Called when a sector is read and
267/// before one is written.
269 struct SaveWorkspace *work;
270 u32 limit;
271 u32 offset;
272 s32 sum;
273
274 work = gSaveWorkspace;
275 limit = SAVE_CHECKSUM_BYTES - 1;
276 sum = 0;
277 offset = 0;
278 do {
279 sum += work->slot.record.payload[offset + 0];
280 sum += work->slot.record.payload[offset + 1];
281 sum += work->slot.record.payload[offset + 2];
282 sum += work->slot.record.payload[offset + 3];
283 sum += work->slot.record.payload[offset + 4];
284 sum += work->slot.record.payload[offset + 5];
285 sum += work->slot.record.payload[offset + 6];
286 sum += work->slot.record.payload[offset + 7];
287 offset += 8;
288 } while (offset <= limit);
289 return sum;
290}
291
292/// Returns the occupied sector holding the newest copy of record `recordId`,
293/// or 16. Called by every record operation in this file.
295 struct SaveWorkspace *work;
296 u32 latestSequence;
297 u32 latestSlot;
298 u32 i;
299
300 work = gSaveWorkspace;
301 latestSlot = SAVE_SECTOR_COUNT;
302 latestSequence = 0;
303 for (i = 0; i < SAVE_SECTOR_COUNT; i++) {
304 if (work->occupied[i] != 0 && recordId == work->recordId[i]) {
305 if (latestSequence < work->sequence[i]) {
306 latestSequence = work->sequence[i];
307 latestSlot = i;
308 }
309 }
310 }
311 return latestSlot;
312}
313
314/// Overwrites sector `index` with a header for no record (id 16) and clears
315/// its occupied entry. Called by SaveState_WriteRecord and
316/// SaveState_DeleteRecord. Returns 0 on success and 1 on failure.
318 struct SaveWorkspace *work;
319 struct SaveSlotHeader header;
320
321 work = gSaveWorkspace;
322 Dma_Fill32(0, &header, sizeof(header));
323 WAIT_DMA();
325 WAIT_DMA();
326 header.recordId = SAVE_RECORD_NONE;
327 header.sequence = 0;
328 START_DMA(&header, &work->slot.record.header, DMA_COPY_WORDS(4));
329 WAIT_DMA();
330 if (SaveState_WriteWorkspaceSlot(index) != 0)
331 return 1;
332 work->occupied[index] = 0;
333 work->recordId[index] = SAVE_RECORD_NONE;
334 work->sequence[index] = 0;
335 return 0;
336}
337
338/// Compares `count` bytes, as memcmp does. Called by
339/// SaveState_InitializeWorkspace to check each sector's signature.
340s32 SaveState_CompareBytes(u8 *left, u8 *right, s32 count) {
341 s32 difference = 0;
342
343 while (count != 0) {
344 difference = *left - *right;
345 if (difference != 0)
346 break;
347 count--;
348 left++;
349 right++;
350 }
351 return difference;
352}
353
354/// Returns the highest sequence number among the copies of record `recordId`.
355/// Called by SaveState_WriteRecord to number the new copy.
357 struct SaveWorkspace *work;
358 u32 latestSequence;
359 u32 i;
360
361 work = gSaveWorkspace;
362 latestSequence = 0;
363 for (i = 0; i < SAVE_SECTOR_COUNT; i++) {
364 if (work->occupied[i] != 0 && recordId == work->recordId[i]) {
365 if (latestSequence < work->sequence[i])
366 latestSequence = work->sequence[i];
367 }
368 }
369 return latestSequence;
370}
371
372/// Reads the summary at the start of each of the three save files into
373/// `summary` for the file select screen, and four bytes from offset 0x110 of
374/// each file's second record (zero when it has none). Called by the save
375/// screens. Returns how many save files exist.
377 struct SaveWorkspace *work;
378 struct SaveSummary *summary;
379 u32 group;
380 s32 count;
381
382 work = gSaveWorkspace;
383 summary = work->summary;
384 count = 0;
385 group = 0;
386 do {
387 u32 index;
388
389 Dma_Fill32(0, summary, sizeof(*summary));
390 index = SaveState_FindLatestSlot(group);
391 if (index < SAVE_SECTOR_COUNT) {
392 ReadFlash(index, 0, (u8 *)summary, sizeof(*summary));
393 count++;
394 }
396 if (index < SAVE_SECTOR_COUNT)
397 ReadFlash(index, 0x110, summary->unk38, sizeof(summary->unk38));
398 else
399 *(u32 *)summary->unk38 = 0;
400 group++;
401 summary++;
402 } while (group < SAVE_FILE_COUNT);
403 return count;
404}
405
406/// Removes the flash timer handler and frees the save workspace's heap slot.
407/// Called by the save screens after every save operation. Declared as
408/// returning a value, it returns none (pop {r1}; bx r1).
412 // UB: nothing is returned, and no caller reads the result
413#ifdef UBFIX
414 return 0;
415#endif
416}
static __inline__ void Dma_Set(const volatile void *source, volatile void *destination, u32 control, volatile u32 *channel)
Starts a DMA transfer: stores the source, destination and control words into channel's registers with...
Definition dma.h:62
#define Dma_Fill32(value, destination, size)
Fills size bytes at destination with the word value by DMA3.
Definition dma.h:30
s32 SetFlashTimerIntr(u8 timerIndex, void(**callback)(void))
Selects a hardware timer and returns its timeout interrupt callback.
Definition flash.c:106
u32 gEraseFlashSector
void ReadFlash(u16 sector, u32 offset, u8 *destination, s32 size)
Copies the RAM reader and reads a range using the standard sector geometry.
Definition flash.c:214
s32 Flash_VerifySector(u16 sector, s32 source)
Copies the verifier to RAM and compares a standard flash sector with source data.
Definition flash.c:259
s32 IdentifyFlash(void)
Selects matching chip handlers, falling back to the final table entry when unknown.
Definition flash.c:61
u32 gFlash_Handler0
void Runtime_SetIrqHandler(u32 irq, s32 vcount, InterruptHandler handler)
Installs handler for interrupt irq and enables that interrupt, or with a null handler disables it and...
Definition frame.c:37
void WaitFrames(s32 frames)
The game's frame loop: runs frames frames and returns.
Definition frame.c:97
void * Runtime_AllocateBlock(s32 kind, s32 size)
As Runtime_AllocateHeapBlock, but tries EWRAM first, for blocks that need room more than speed: the b...
Definition heap.c:80
@ HEAP_SLOT_SAVE
Definition heap_state.h:50
#define REG_DMA3
Definition io_reg.h:153
#define DMA_COPY_WORDS(n)
Definition io_reg.h:226
InterruptHandler gIrqHandlers[IRQ_COUNT]
The handler the IWRAM dispatcher calls for each interrupt source.
@ IRQ_TIMER2
Definition irq.h:18
u32 Random16(void)
Advances the game's random number generator (the C library's classic linear congruential one),...
Definition math.c:10
void Runtime_ReleaseHeapBlock(s32 slot)
u32 SaveState_ReadRecordPayload(s32 recordId, void *destination)
Loads the current copy of record recordId into destination.
Definition save.c:236
u32 SaveState_FindLatestSlot(s32 recordId)
Returns the occupied sector holding the newest copy of record recordId, or 16.
Definition save.c:294
u16(*) EraseSectorProc(u16)
Definition save.c:168
@ SAVE_FLASH_IDENTIFY_ATTEMPTS
Definition save.c:15
@ SAVE_FLASH_TIMER
Definition save.c:14
@ SAVE_CHECKSUM_BYTES
Definition save.c:20
@ SAVE_RECORD_NONE
Definition save.c:17
@ SAVE_SEQUENCE_LIMIT
Definition save.c:19
@ SAVE_SECTOR_COUNT
Definition save.c:16
s32 SaveState_LoadSummaryRecords(void)
Reads the summary at the start of each of the three save files into summary for the file select scree...
Definition save.c:376
u32 SaveState_ReleaseWorkspace(void)
Removes the flash timer handler and frees the save workspace's heap slot.
Definition save.c:409
u32 SaveState_DeleteRecord(s32 recordId)
Invalidates the current copy of record recordId.
Definition save.c:253
u32 SaveState_GetLatestSequence(s32 recordId)
Returns the highest sequence number among the copies of record recordId.
Definition save.c:356
u32 SaveState_SelectWriteSlot(s32 mode)
Picks the free sector to write record mode to, at random.
Definition save.c:100
s32 SaveState_WriteRecord(s32 recordId, void *source)
Saves 0xff0 bytes from source as record recordId: writes the new copy with the next sequence number,...
Definition save.c:181
u32 SaveState_WriteWorkspaceSlot(s32 sector)
Programs the workspace's sector buffer into flash sector sector and verifies it, through the flash li...
Definition save.c:136
s32 SaveState_ReadSlotAndCheckChecksum(s32 index)
Reads flash sector index into the workspace's sector buffer.
Definition save.c:155
s32 SaveState_InvalidateSlot(s32 index)
Overwrites sector index with a header for no record (id 16) and clears its occupied entry.
Definition save.c:317
s32 SaveState_CompareBytes(u8 *left, u8 *right, s32 count)
Compares count bytes, as memcmp does.
Definition save.c:340
UNUSED u16 SaveState_EraseSlotSector(u16 sector)
Erases flash sector sector with the flash library's routine for this chip.
Definition save.c:172
s32 SaveState_ChecksumWorkspace(void)
Sums the first 0xfe8 payload bytes of the workspace's sector buffer; the final eight payload bytes ar...
Definition save.c:268
s32 SaveState_InitializeWorkspace(void)
Allocates the save workspace (heap slot HEAP_SLOT_SAVE), installs the flash library's timer 2 handler...
Definition save.c:31
What every part of the runtime object shares: its includes and the library call two parts make.
struct SaveWorkspace * gSaveWorkspace
#define START_DMA(source_, destination_, control_)
Definition save_state.h:93
#define WAIT_DMA()
Definition save_state.h:101
const u8 gSave_Signature[]
The signature that marks a save record.
Definition savedata.c:5
const u8 gSave_HeaderTemplate[8]
The record header's first eight bytes, with no terminator.
Definition savedata.c:7
s32 _call_via_r3(s32, s32, s32, s32)
#define NULL
Definition bilibin.h:6
The header of a save sector in flash: its signature, the record it holds, its checksum and its sequen...
Definition save_state.h:18
u8 signature[7]
CAMELOT, unterminated
Definition save_state.h:19
u16 checksum
low half of the payload's checksum
Definition save_state.h:22
u16 sequence
the highest is the current copy; restarts after 65000
Definition save_state.h:23
u8 recordId
File n's records are n and n + 3 (n from 0 to 2); 16 marks a free sector.
Definition save_state.h:21
A save file's summary for the file screen: the leader's name, level and class, the area,...
Definition save_state.h:29
u8 unk38[4]
Filled on reading: the play time from the file's second record.
Definition save_state.h:54
The save work: which of the 16 flash sectors hold a record, their record ids and sequence numbers,...
Definition save_state.h:61
struct SaveSummary summary[3]
each file's, read from its first record
Definition save_state.h:72
u8 recordId[16]
each sector's record; 16 for none
Definition save_state.h:63
u8 occupied[16]
1 for a sector holding the current copy of a record
Definition save_state.h:62
u16 sequence[16]
each sector's sequence number
Definition save_state.h:64
union SaveWorkspace::@331263107057156204162071305274032234046256201132 slot
one sector, read from or written to flash
signed int s32
Definition types.h:14
unsigned int u32
Definition types.h:15
unsigned short u16
Definition types.h:13
#define UNUSED
Definition types.h:22
unsigned char u8
Definition types.h:11
#define SAVE_FILE_COUNT
Definition save.c:10