all repos — mgba @ 5ea104844d58095e3dc002d93074d3302976cae6

mGBA Game Boy Advance Emulator

src/gba/savedata.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "savedata.h"
  7
  8#include "gba/gba.h"
  9#include "gba/serialize.h"
 10
 11#include "util/memory.h"
 12#include "util/vfs.h"
 13
 14#include <errno.h>
 15#include <fcntl.h>
 16
 17// Some testing was done here...
 18// Erase cycles can vary greatly.
 19// Some games may vary anywhere between about 2000 cycles to up to 30000 cycles. (Observed on a Macronix (09C2) chip).
 20// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip).
 21// An average estimation is as follows.
 22#define FLASH_SETTLE_CYCLES 18000
 23#define CLEANUP_THRESHOLD 15
 24
 25mLOG_DEFINE_CATEGORY(GBA_SAVE, "GBA Savedata");
 26
 27static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
 28static void _flashErase(struct GBASavedata* savedata);
 29static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
 30
 31void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
 32	savedata->type = SAVEDATA_AUTODETECT;
 33	savedata->data = 0;
 34	savedata->command = EEPROM_COMMAND_NULL;
 35	savedata->flashState = FLASH_STATE_RAW;
 36	savedata->vf = vf;
 37	savedata->realVf = vf;
 38	savedata->mapMode = MAP_WRITE;
 39	savedata->dirty = 0;
 40	savedata->dirtAge = 0;
 41}
 42
 43void GBASavedataDeinit(struct GBASavedata* savedata) {
 44	if (savedata->vf) {
 45		switch (savedata->type) {
 46		case SAVEDATA_SRAM:
 47			savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_SRAM);
 48			break;
 49		case SAVEDATA_FLASH512:
 50			savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_FLASH512);
 51			break;
 52		case SAVEDATA_FLASH1M:
 53			savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_FLASH1M);
 54			break;
 55		case SAVEDATA_EEPROM:
 56			savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_EEPROM);
 57			break;
 58		case SAVEDATA_FORCE_NONE:
 59		case SAVEDATA_AUTODETECT:
 60			break;
 61		}
 62		savedata->vf = 0;
 63	} else {
 64		switch (savedata->type) {
 65		case SAVEDATA_SRAM:
 66			mappedMemoryFree(savedata->data, SIZE_CART_SRAM);
 67			break;
 68		case SAVEDATA_FLASH512:
 69			mappedMemoryFree(savedata->data, SIZE_CART_FLASH512);
 70			break;
 71		case SAVEDATA_FLASH1M:
 72			mappedMemoryFree(savedata->data, SIZE_CART_FLASH1M);
 73			break;
 74		case SAVEDATA_EEPROM:
 75			mappedMemoryFree(savedata->data, SIZE_CART_EEPROM);
 76			break;
 77		case SAVEDATA_FORCE_NONE:
 78		case SAVEDATA_AUTODETECT:
 79			break;
 80		}
 81	}
 82	savedata->data = 0;
 83	savedata->type = SAVEDATA_AUTODETECT;
 84}
 85
 86void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) {
 87	GBASavedataDeinit(savedata);
 88	savedata->vf = vf;
 89	savedata->mapMode = MAP_READ;
 90}
 91
 92void GBASavedataUnmask(struct GBASavedata* savedata) {
 93	if (savedata->mapMode != MAP_READ) {
 94		return;
 95	}
 96	GBASavedataDeinit(savedata);
 97	savedata->vf = savedata->realVf;
 98	savedata->mapMode = MAP_WRITE;
 99}
100
101bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
102	if (savedata->data) {
103		switch (savedata->type) {
104		case SAVEDATA_SRAM:
105			return out->write(out, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
106		case SAVEDATA_FLASH512:
107			return out->write(out, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
108		case SAVEDATA_FLASH1M:
109			return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
110		case SAVEDATA_EEPROM:
111			return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
112		case SAVEDATA_AUTODETECT:
113		case SAVEDATA_FORCE_NONE:
114			return true;
115		}
116	} else if (savedata->vf) {
117		off_t read = 0;
118		uint8_t buffer[2048];
119		do {
120			read = savedata->vf->read(savedata->vf, buffer, sizeof(buffer));
121			out->write(out, buffer, read);
122		} while (read == sizeof(buffer));
123		return read >= 0;
124	}
125	return true;
126}
127
128size_t GBASavedataSize(struct GBASavedata* savedata) {
129	switch (savedata->type) {
130	case SAVEDATA_SRAM:
131		return SIZE_CART_SRAM;
132	case SAVEDATA_FLASH512:
133		return SIZE_CART_FLASH512;
134	case SAVEDATA_FLASH1M:
135		return SIZE_CART_FLASH1M;
136	case SAVEDATA_EEPROM:
137		return SIZE_CART_EEPROM;
138	case SAVEDATA_FORCE_NONE:
139		return 0;
140	case SAVEDATA_AUTODETECT:
141	default:
142		if (savedata->vf) {
143			return savedata->vf->size(savedata->vf);
144		}
145		return 0;
146	}
147}
148
149bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
150	if (savedata->vf) {
151		off_t read = 0;
152		uint8_t buffer[2048];
153		memset(buffer, 0xFF, sizeof(buffer));
154		savedata->vf->seek(savedata->vf, 0, SEEK_SET);
155		while (savedata->vf->seek(savedata->vf, 0, SEEK_CUR) < savedata->vf->size(savedata->vf)) {
156			savedata->vf->write(savedata->vf, buffer, sizeof(buffer));
157		}
158		savedata->vf->seek(savedata->vf, 0, SEEK_SET);
159		if (in) {
160			do {
161				read = in->read(in, buffer, sizeof(buffer));
162				read = savedata->vf->write(savedata->vf, buffer, read);
163			} while (read == sizeof(buffer));
164		}
165		return read >= 0;
166	} else if (savedata->data) {
167		if (!in && savedata->type != SAVEDATA_FORCE_NONE) {
168			return false;
169		}
170		switch (savedata->type) {
171		case SAVEDATA_SRAM:
172			return in->read(in, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
173		case SAVEDATA_FLASH512:
174			return in->read(in, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
175		case SAVEDATA_FLASH1M:
176			return in->read(in, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
177		case SAVEDATA_EEPROM:
178			return in->read(in, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
179		case SAVEDATA_AUTODETECT:
180		case SAVEDATA_FORCE_NONE:
181			return true;
182		}
183	}
184	return true;
185}
186
187void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
188	if (savedata->type != SAVEDATA_AUTODETECT) {
189		struct VFile* vf = savedata->vf;
190		GBASavedataDeinit(savedata);
191		GBASavedataInit(savedata, vf);
192	}
193	switch (type) {
194	case SAVEDATA_FLASH512:
195	case SAVEDATA_FLASH1M:
196		savedata->type = type;
197		GBASavedataInitFlash(savedata, realisticTiming);
198		break;
199	case SAVEDATA_EEPROM:
200		GBASavedataInitEEPROM(savedata);
201		break;
202	case SAVEDATA_SRAM:
203		GBASavedataInitSRAM(savedata);
204		break;
205	case SAVEDATA_FORCE_NONE:
206		savedata->type = SAVEDATA_FORCE_NONE;
207		break;
208	case SAVEDATA_AUTODETECT:
209		break;
210	}
211}
212
213void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) {
214	if (savedata->type == SAVEDATA_AUTODETECT) {
215		savedata->type = SAVEDATA_FLASH512;
216	}
217	if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) {
218		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
219		return;
220	}
221	int32_t flashSize = SIZE_CART_FLASH512;
222	if (savedata->type == SAVEDATA_FLASH1M) {
223		flashSize = SIZE_CART_FLASH1M;
224	}
225	off_t end;
226	if (!savedata->vf) {
227		end = 0;
228		savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M);
229	} else {
230		end = savedata->vf->size(savedata->vf);
231		if (end < flashSize) {
232			savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
233			flashSize = SIZE_CART_FLASH1M;
234		}
235		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
236	}
237
238	savedata->currentBank = savedata->data;
239	savedata->dust = 0;
240	savedata->realisticTiming = realisticTiming;
241	if (end < SIZE_CART_FLASH512) {
242		memset(&savedata->data[end], 0xFF, flashSize - end);
243	}
244}
245
246void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
247	if (savedata->type == SAVEDATA_AUTODETECT) {
248		savedata->type = SAVEDATA_EEPROM;
249	} else {
250		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
251		return;
252	}
253	off_t end;
254	if (!savedata->vf) {
255		end = 0;
256		savedata->data = anonymousMemoryMap(SIZE_CART_EEPROM);
257	} else {
258		end = savedata->vf->size(savedata->vf);
259		if (end < SIZE_CART_EEPROM) {
260			savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM);
261		}
262		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, savedata->mapMode);
263	}
264	if (end < SIZE_CART_EEPROM) {
265		memset(&savedata->data[end], 0xFF, SIZE_CART_EEPROM - end);
266	}
267}
268
269void GBASavedataInitSRAM(struct GBASavedata* savedata) {
270	if (savedata->type == SAVEDATA_AUTODETECT) {
271		savedata->type = SAVEDATA_SRAM;
272	} else {
273		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
274		return;
275	}
276	off_t end;
277	if (!savedata->vf) {
278		end = 0;
279		savedata->data = anonymousMemoryMap(SIZE_CART_SRAM);
280	} else {
281		end = savedata->vf->size(savedata->vf);
282		if (end < SIZE_CART_SRAM) {
283			savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM);
284		}
285		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, savedata->mapMode);
286	}
287
288	if (end < SIZE_CART_SRAM) {
289		memset(&savedata->data[end], 0xFF, SIZE_CART_SRAM - end);
290	}
291}
292
293uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
294	if (savedata->command == FLASH_COMMAND_ID) {
295		if (savedata->type == SAVEDATA_FLASH512) {
296			if (address < 2) {
297				return FLASH_MFG_PANASONIC >> (address * 8);
298			}
299		} else if (savedata->type == SAVEDATA_FLASH1M) {
300			if (address < 2) {
301				return FLASH_MFG_SANYO >> (address * 8);
302			}
303		}
304	}
305	if (savedata->dust > 0 && (address >> 12) == savedata->settling) {
306		// Give some overhead for waitstates and the comparison
307		// This estimation can probably be improved
308		savedata->dust -= 10;
309		return 0x5F;
310	}
311	return savedata->currentBank[address];
312}
313
314void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8_t value) {
315	switch (savedata->flashState) {
316	case FLASH_STATE_RAW:
317		switch (savedata->command) {
318		case FLASH_COMMAND_PROGRAM:
319			savedata->dirty |= SAVEDATA_DIRT_NEW;
320			savedata->currentBank[address] = value;
321			savedata->command = FLASH_COMMAND_NONE;
322			break;
323		case FLASH_COMMAND_SWITCH_BANK:
324			if (address == 0 && value < 2) {
325				_flashSwitchBank(savedata, value);
326			} else {
327				mLOG(GBA_SAVE, GAME_ERROR, "Bad flash bank switch");
328				savedata->command = FLASH_COMMAND_NONE;
329			}
330			savedata->command = FLASH_COMMAND_NONE;
331			break;
332		default:
333			if (address == FLASH_BASE_HI && value == FLASH_COMMAND_START) {
334				savedata->flashState = FLASH_STATE_START;
335			} else {
336				mLOG(GBA_SAVE, GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
337			}
338			break;
339		}
340		break;
341	case FLASH_STATE_START:
342		if (address == FLASH_BASE_LO && value == FLASH_COMMAND_CONTINUE) {
343			savedata->flashState = FLASH_STATE_CONTINUE;
344		} else {
345			mLOG(GBA_SAVE, GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
346			savedata->flashState = FLASH_STATE_RAW;
347		}
348		break;
349	case FLASH_STATE_CONTINUE:
350		savedata->flashState = FLASH_STATE_RAW;
351		if (address == FLASH_BASE_HI) {
352			switch (savedata->command) {
353			case FLASH_COMMAND_NONE:
354				switch (value) {
355				case FLASH_COMMAND_ERASE:
356				case FLASH_COMMAND_ID:
357				case FLASH_COMMAND_PROGRAM:
358				case FLASH_COMMAND_SWITCH_BANK:
359					savedata->command = value;
360					break;
361				default:
362					mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash operation: %#02x", value);
363					break;
364				}
365				break;
366			case FLASH_COMMAND_ERASE:
367				switch (value) {
368				case FLASH_COMMAND_ERASE_CHIP:
369					_flashErase(savedata);
370					break;
371				default:
372					mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
373					break;
374				}
375				savedata->command = FLASH_COMMAND_NONE;
376				break;
377			case FLASH_COMMAND_ID:
378				if (value == FLASH_COMMAND_TERMINATE) {
379					savedata->command = FLASH_COMMAND_NONE;
380				}
381				break;
382			default:
383				mLOG(GBA_SAVE, ERROR, "Flash entered bad state: %#02x", savedata->command);
384				savedata->command = FLASH_COMMAND_NONE;
385				break;
386			}
387		} else if (savedata->command == FLASH_COMMAND_ERASE) {
388			if (value == FLASH_COMMAND_ERASE_SECTOR) {
389				_flashEraseSector(savedata, address);
390				savedata->command = FLASH_COMMAND_NONE;
391			} else {
392				mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
393			}
394		}
395		break;
396	}
397}
398
399void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize) {
400	switch (savedata->command) {
401	// Read header
402	case EEPROM_COMMAND_NULL:
403	default:
404		savedata->command = value & 0x1;
405		break;
406	case EEPROM_COMMAND_PENDING:
407		savedata->command <<= 1;
408		savedata->command |= value & 0x1;
409		if (savedata->command == EEPROM_COMMAND_WRITE) {
410			savedata->writeAddress = 0;
411		} else {
412			savedata->readAddress = 0;
413		}
414		break;
415	// Do commands
416	case EEPROM_COMMAND_WRITE:
417		// Write
418		if (writeSize > 65) {
419			savedata->writeAddress <<= 1;
420			savedata->writeAddress |= (value & 0x1) << 6;
421		} else if (writeSize == 1) {
422			savedata->command = EEPROM_COMMAND_NULL;
423		} else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) {
424			uint8_t current = savedata->data[savedata->writeAddress >> 3];
425			current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
426			current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
427			savedata->dirty |= SAVEDATA_DIRT_NEW;
428			savedata->data[savedata->writeAddress >> 3] = current;
429			++savedata->writeAddress;
430		} else {
431			mLOG(GBA_SAVE, GAME_ERROR, "Writing beyond end of EEPROM: %08X", (savedata->writeAddress >> 3));
432		}
433		break;
434	case EEPROM_COMMAND_READ_PENDING:
435		// Read
436		if (writeSize > 1) {
437			savedata->readAddress <<= 1;
438			if (value & 0x1) {
439				savedata->readAddress |= 0x40;
440			}
441		} else {
442			savedata->readBitsRemaining = 68;
443			savedata->command = EEPROM_COMMAND_READ;
444		}
445		break;
446	}
447}
448
449uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
450	if (savedata->command != EEPROM_COMMAND_READ) {
451		return 1;
452	}
453	--savedata->readBitsRemaining;
454	if (savedata->readBitsRemaining < 64) {
455		int step = 63 - savedata->readBitsRemaining;
456		uint32_t address = (savedata->readAddress + step) >> 3;
457		if (address >= SIZE_CART_EEPROM) {
458			mLOG(GBA_SAVE, GAME_ERROR, "Reading beyond end of EEPROM: %08X", address);
459			return 0xFF;
460		}
461		uint8_t data = savedata->data[address] >> (0x7 - (step & 0x7));
462		if (!savedata->readBitsRemaining) {
463			savedata->command = EEPROM_COMMAND_NULL;
464		}
465		return data & 0x1;
466	}
467	return 0;
468}
469
470void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
471	if (!savedata->vf) {
472		return;
473	}
474	if (savedata->dirty & SAVEDATA_DIRT_NEW) {
475		savedata->dirty &= ~SAVEDATA_DIRT_NEW;
476		if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) {
477			savedata->dirtAge = frameCount;
478			savedata->dirty |= SAVEDATA_DIRT_SEEN;
479		}
480	} else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) {
481		size_t size;
482		switch (savedata->type) {
483		case SAVEDATA_EEPROM:
484			size = SIZE_CART_EEPROM;
485			break;
486		case SAVEDATA_SRAM:
487			size = SIZE_CART_SRAM;
488			break;
489		case SAVEDATA_FLASH512:
490			size = SIZE_CART_FLASH512;
491			break;
492		case SAVEDATA_FLASH1M:
493			size = SIZE_CART_FLASH1M;
494			break;
495		default:
496			size = 0;
497			break;
498		}
499		savedata->vf->sync(savedata->vf, savedata->data, size);
500		savedata->dirty = 0;
501		mLOG(GBA_SAVE, INFO, "Savedata synced");
502	}
503}
504
505void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
506	state->savedata.type = savedata->type;
507	state->savedata.command = savedata->command;
508	GBASerializedSavedataFlags flags = 0;
509	flags = GBASerializedSavedataFlagsSetFlashState(flags, savedata->flashState);
510	flags = GBASerializedSavedataFlagsTestFillFlashBank(flags, savedata->currentBank == &savedata->data[0x10000]);
511	state->savedata.flags = flags;
512	STORE_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
513	STORE_32(savedata->readAddress, 0, &state->savedata.readAddress);
514	STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
515	STORE_16(savedata->settling, 0, &state->savedata.settlingSector);
516	STORE_16(savedata->dust, 0, &state->savedata.settlingDust);
517}
518
519void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state) {
520	if (state->savedata.type == SAVEDATA_FORCE_NONE) {
521		return;
522	}
523	if (savedata->type != state->savedata.type) {
524		GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming);
525	}
526	savedata->command = state->savedata.command;
527	GBASerializedSavedataFlags flags = state->savedata.flags;
528	savedata->flashState = GBASerializedSavedataFlagsGetFlashState(flags);
529	LOAD_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
530	LOAD_32(savedata->readAddress, 0, &state->savedata.readAddress);
531	LOAD_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
532	LOAD_16(savedata->settling, 0, &state->savedata.settlingSector);
533	LOAD_16(savedata->dust, 0, &state->savedata.settlingDust);
534
535	if (savedata->type == SAVEDATA_FLASH1M) {
536		_flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags));
537	}
538}
539
540void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
541	mLOG(GBA_SAVE, DEBUG, "Performing flash bank switch to bank %i", bank);
542	savedata->currentBank = &savedata->data[bank << 16];
543	if (bank > 0 && savedata->type == SAVEDATA_FLASH512) {
544		savedata->type = SAVEDATA_FLASH1M;
545		if (savedata->vf) {
546			savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
547			memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512);
548		}
549	}
550}
551
552void _flashErase(struct GBASavedata* savedata) {
553	mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase");
554	savedata->dirty |= SAVEDATA_DIRT_NEW;
555	size_t size = SIZE_CART_FLASH512;
556	if (savedata->type == SAVEDATA_FLASH1M) {
557		size = SIZE_CART_FLASH1M;
558	}
559	memset(savedata->data, 0xFF, size);
560}
561
562void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
563	mLOG(GBA_SAVE, DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
564	savedata->dirty |= SAVEDATA_DIRT_NEW;
565	size_t size = 0x1000;
566	if (savedata->type == SAVEDATA_FLASH1M) {
567		mLOG(GBA_SAVE, DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
568	}
569	savedata->settling = sectorStart >> 12;
570	if (savedata->realisticTiming) {
571		savedata->dust = FLASH_SETTLE_CYCLES;
572	}
573	memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
574}