src/gb/core.c (view raw)
1/* Copyright (c) 2013-2016 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 <mgba/gb/core.h>
7
8#include <mgba/core/core.h>
9#include <mgba/internal/gb/cheats.h>
10#include <mgba/internal/gb/extra/cli.h>
11#include <mgba/internal/gb/gb.h>
12#include <mgba/internal/gb/mbc.h>
13#include <mgba/internal/gb/overrides.h>
14#include <mgba/internal/gb/renderers/software.h>
15#include <mgba/internal/gb/serialize.h>
16#include <mgba/internal/lr35902/lr35902.h>
17#include <mgba/internal/lr35902/debugger/debugger.h>
18#include <mgba-util/crc32.h>
19#include <mgba-util/memory.h>
20#include <mgba-util/patch.h>
21#include <mgba-util/vfs.h>
22
23struct GBCore {
24 struct mCore d;
25 struct GBVideoSoftwareRenderer renderer;
26 uint8_t keys;
27 struct mCPUComponent* components[CPU_COMPONENT_MAX];
28 const struct Configuration* overrides;
29 struct mDebuggerPlatform* debuggerPlatform;
30 struct mCheatDevice* cheatDevice;
31};
32
33static bool _GBCoreInit(struct mCore* core) {
34 struct GBCore* gbcore = (struct GBCore*) core;
35
36 struct LR35902Core* cpu = anonymousMemoryMap(sizeof(struct LR35902Core));
37 struct GB* gb = anonymousMemoryMap(sizeof(struct GB));
38 if (!cpu || !gb) {
39 free(cpu);
40 free(gb);
41 return false;
42 }
43 core->cpu = cpu;
44 core->board = gb;
45 gbcore->overrides = NULL;
46 gbcore->debuggerPlatform = NULL;
47 gbcore->cheatDevice = NULL;
48
49 GBCreate(gb);
50 memset(gbcore->components, 0, sizeof(gbcore->components));
51 LR35902SetComponents(cpu, &gb->d, CPU_COMPONENT_MAX, gbcore->components);
52 LR35902Init(cpu);
53
54 GBVideoSoftwareRendererCreate(&gbcore->renderer);
55 gbcore->renderer.outputBuffer = NULL;
56
57 gbcore->keys = 0;
58 gb->keySource = &gbcore->keys;
59
60#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
61 mDirectorySetInit(&core->dirs);
62#endif
63
64 return true;
65}
66
67static void _GBCoreDeinit(struct mCore* core) {
68 LR35902Deinit(core->cpu);
69 GBDestroy(core->board);
70 mappedMemoryFree(core->cpu, sizeof(struct LR35902Core));
71 mappedMemoryFree(core->board, sizeof(struct GB));
72#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
73 mDirectorySetDeinit(&core->dirs);
74#endif
75
76 struct GBCore* gbcore = (struct GBCore*) core;
77 free(gbcore->debuggerPlatform);
78 if (gbcore->cheatDevice) {
79 mCheatDeviceDestroy(gbcore->cheatDevice);
80 }
81 free(gbcore->cheatDevice);
82 mCoreConfigFreeOpts(&core->opts);
83 free(core);
84}
85
86static enum mPlatform _GBCorePlatform(const struct mCore* core) {
87 UNUSED(core);
88 return PLATFORM_GB;
89}
90
91static void _GBCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
92 struct GB* gb = core->board;
93 gb->sync = sync;
94}
95
96static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* config) {
97 UNUSED(config);
98
99 struct GB* gb = core->board;
100 if (core->opts.mute) {
101 gb->audio.masterVolume = 0;
102 } else {
103 gb->audio.masterVolume = core->opts.volume;
104 }
105 gb->video.frameskip = core->opts.frameskip;
106
107#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
108 struct GBCore* gbcore = (struct GBCore*) core;
109 gbcore->overrides = mCoreConfigGetOverridesConst(config);
110#endif
111}
112
113static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
114 UNUSED(core);
115 *width = GB_VIDEO_HORIZONTAL_PIXELS;
116 *height = GB_VIDEO_VERTICAL_PIXELS;
117}
118
119static void _GBCoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {
120 struct GBCore* gbcore = (struct GBCore*) core;
121 gbcore->renderer.outputBuffer = buffer;
122 gbcore->renderer.outputBufferStride = stride;
123}
124
125static void _GBCoreGetPixels(struct mCore* core, const void** buffer, size_t* stride) {
126 struct GBCore* gbcore = (struct GBCore*) core;
127 gbcore->renderer.d.getPixels(&gbcore->renderer.d, stride, buffer);
128}
129
130static void _GBCorePutPixels(struct mCore* core, const void* buffer, size_t stride) {
131 struct GBCore* gbcore = (struct GBCore*) core;
132 gbcore->renderer.d.putPixels(&gbcore->renderer.d, stride, buffer);
133}
134
135static struct blip_t* _GBCoreGetAudioChannel(struct mCore* core, int ch) {
136 struct GB* gb = core->board;
137 switch (ch) {
138 case 0:
139 return gb->audio.left;
140 case 1:
141 return gb->audio.right;
142 default:
143 return NULL;
144 }
145}
146
147static void _GBCoreSetAudioBufferSize(struct mCore* core, size_t samples) {
148 struct GB* gb = core->board;
149 GBAudioResizeBuffer(&gb->audio, samples);
150}
151
152static size_t _GBCoreGetAudioBufferSize(struct mCore* core) {
153 struct GB* gb = core->board;
154 return gb->audio.samples;
155}
156
157static void _GBCoreSetCoreCallbacks(struct mCore* core, struct mCoreCallbacks* coreCallbacks) {
158 struct GB* gb = core->board;
159 gb->coreCallbacks = coreCallbacks;
160}
161
162static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
163 struct GB* gb = core->board;
164 gb->stream = stream;
165 if (stream && stream->videoDimensionsChanged) {
166 stream->videoDimensionsChanged(stream, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
167 }
168}
169
170static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {
171 return GBLoadROM(core->board, vf);
172}
173
174static bool _GBCoreLoadBIOS(struct mCore* core, struct VFile* vf, int type) {
175 UNUSED(type);
176 GBLoadBIOS(core->board, vf);
177 return true;
178}
179
180static bool _GBCoreLoadSave(struct mCore* core, struct VFile* vf) {
181 return GBLoadSave(core->board, vf);
182}
183
184static bool _GBCoreLoadTemporarySave(struct mCore* core, struct VFile* vf) {
185 struct GB* gb = core->board;
186 GBSavedataMask(gb, vf, false);
187 return true; // TODO: Return a real value
188}
189
190static bool _GBCoreLoadPatch(struct mCore* core, struct VFile* vf) {
191 if (!vf) {
192 return false;
193 }
194 struct Patch patch;
195 if (!loadPatch(vf, &patch)) {
196 return false;
197 }
198 GBApplyPatch(core->board, &patch);
199 return true;
200}
201
202static void _GBCoreUnloadROM(struct mCore* core) {
203 struct GBCore* gbcore = (struct GBCore*) core;
204 struct LR35902Core* cpu = core->cpu;
205 if (gbcore->cheatDevice) {
206 LR35902HotplugDetach(cpu, CPU_COMPONENT_CHEAT_DEVICE);
207 cpu->components[CPU_COMPONENT_CHEAT_DEVICE] = NULL;
208 mCheatDeviceDestroy(gbcore->cheatDevice);
209 gbcore->cheatDevice = NULL;
210 }
211 return GBUnloadROM(core->board);
212}
213
214static void _GBCoreChecksum(const struct mCore* core, void* data, enum mCoreChecksumType type) {
215 struct GB* gb = (struct GB*) core->board;
216 switch (type) {
217 case CHECKSUM_CRC32:
218 memcpy(data, &gb->romCrc32, sizeof(gb->romCrc32));
219 break;
220 }
221 return;
222}
223
224static void _GBCoreReset(struct mCore* core) {
225 struct GBCore* gbcore = (struct GBCore*) core;
226 struct GB* gb = (struct GB*) core->board;
227 if (gbcore->renderer.outputBuffer) {
228 GBVideoAssociateRenderer(&gb->video, &gbcore->renderer.d);
229 }
230
231 if (gb->memory.rom) {
232 struct GBCartridgeOverride override;
233 const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
234 override.headerCrc32 = doCrc32(cart, sizeof(*cart));
235 if (GBOverrideFind(gbcore->overrides, &override)) {
236 GBOverrideApply(gb, &override);
237 }
238 }
239
240#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
241 struct VFile* bios = NULL;
242 if (core->opts.useBios) {
243 bool found = false;
244 if (core->opts.bios) {
245 bios = VFileOpen(core->opts.bios, O_RDONLY);
246 if (bios && GBIsBIOS(bios)) {
247 found = true;
248 } else if (bios) {
249 bios->close(bios);
250 bios = NULL;
251 }
252 }
253 if (!found) {
254 char path[PATH_MAX];
255 GBDetectModel(gb);
256 mCoreConfigDirectory(path, PATH_MAX);
257 switch (gb->model) {
258 case GB_MODEL_DMG:
259 case GB_MODEL_SGB: // TODO
260 strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path));
261 break;
262 case GB_MODEL_CGB:
263 case GB_MODEL_AGB:
264 strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path));
265 break;
266 default:
267 break;
268 };
269 bios = VFileOpen(path, O_RDONLY);
270 }
271 }
272 if (bios) {
273 GBLoadBIOS(gb, bios);
274 }
275#endif
276
277 LR35902Reset(core->cpu);
278}
279
280static void _GBCoreRunFrame(struct mCore* core) {
281 struct GB* gb = core->board;
282 int32_t frameCounter = gb->video.frameCounter;
283 while (gb->video.frameCounter == frameCounter) {
284 LR35902Run(core->cpu);
285 }
286}
287
288static void _GBCoreRunLoop(struct mCore* core) {
289 LR35902Run(core->cpu);
290}
291
292static void _GBCoreStep(struct mCore* core) {
293 struct LR35902Core* cpu = core->cpu;
294 do {
295 LR35902Tick(cpu);
296 } while (cpu->executionState != LR35902_CORE_FETCH);
297}
298
299static size_t _GBCoreStateSize(struct mCore* core) {
300 UNUSED(core);
301 return sizeof(struct GBSerializedState);
302}
303
304static bool _GBCoreLoadState(struct mCore* core, const void* state) {
305 return GBDeserialize(core->board, state);
306}
307
308static bool _GBCoreSaveState(struct mCore* core, void* state) {
309 struct LR35902Core* cpu = core->cpu;
310 while (cpu->executionState != LR35902_CORE_FETCH) {
311 LR35902Tick(cpu);
312 }
313 GBSerialize(core->board, state);
314 return true;
315}
316
317static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
318 struct GBCore* gbcore = (struct GBCore*) core;
319 gbcore->keys = keys;
320}
321
322static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) {
323 struct GBCore* gbcore = (struct GBCore*) core;
324 gbcore->keys |= keys;
325}
326
327static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) {
328 struct GBCore* gbcore = (struct GBCore*) core;
329 gbcore->keys &= ~keys;
330}
331
332static int32_t _GBCoreFrameCounter(const struct mCore* core) {
333 const struct GB* gb = core->board;
334 return gb->video.frameCounter;
335}
336
337static int32_t _GBCoreFrameCycles(const struct mCore* core) {
338 UNUSED(core);
339 return GB_VIDEO_TOTAL_LENGTH;
340}
341
342static int32_t _GBCoreFrequency(const struct mCore* core) {
343 UNUSED(core);
344 // TODO: GB differences
345 return DMG_LR35902_FREQUENCY;
346}
347
348static void _GBCoreGetGameTitle(const struct mCore* core, char* title) {
349 GBGetGameTitle(core->board, title);
350}
351
352static void _GBCoreGetGameCode(const struct mCore* core, char* title) {
353 GBGetGameCode(core->board, title);
354}
355
356static void _GBCoreSetRTC(struct mCore* core, struct mRTCSource* rtc) {
357 struct GB* gb = core->board;
358 gb->memory.rtc = rtc;
359}
360
361static void _GBCoreSetRotation(struct mCore* core, struct mRotationSource* rotation) {
362 struct GB* gb = core->board;
363 gb->memory.rotation = rotation;
364}
365
366static void _GBCoreSetRumble(struct mCore* core, struct mRumble* rumble) {
367 struct GB* gb = core->board;
368 gb->memory.rumble = rumble;
369}
370
371static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) {
372 struct LR35902Core* cpu = core->cpu;
373 return cpu->memory.load8(cpu, address);
374}
375
376static uint32_t _GBCoreBusRead16(struct mCore* core, uint32_t address) {
377 struct LR35902Core* cpu = core->cpu;
378 return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8);
379}
380
381static uint32_t _GBCoreBusRead32(struct mCore* core, uint32_t address) {
382 struct LR35902Core* cpu = core->cpu;
383 return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8) |
384 (cpu->memory.load8(cpu, address + 2) << 16) | (cpu->memory.load8(cpu, address + 3) << 24);
385}
386
387static void _GBCoreBusWrite8(struct mCore* core, uint32_t address, uint8_t value) {
388 struct LR35902Core* cpu = core->cpu;
389 cpu->memory.store8(cpu, address, value);
390}
391
392static void _GBCoreBusWrite16(struct mCore* core, uint32_t address, uint16_t value) {
393 struct LR35902Core* cpu = core->cpu;
394 cpu->memory.store8(cpu, address, value);
395 cpu->memory.store8(cpu, address + 1, value >> 8);
396}
397
398static void _GBCoreBusWrite32(struct mCore* core, uint32_t address, uint32_t value) {
399 struct LR35902Core* cpu = core->cpu;
400 cpu->memory.store8(cpu, address, value);
401 cpu->memory.store8(cpu, address + 1, value >> 8);
402 cpu->memory.store8(cpu, address + 2, value >> 16);
403 cpu->memory.store8(cpu, address + 3, value >> 24);
404}
405
406static uint32_t _GBCoreRawRead8(struct mCore* core, uint32_t address, int segment) {
407 struct LR35902Core* cpu = core->cpu;
408 return GBView8(cpu, address, segment);
409}
410
411static uint32_t _GBCoreRawRead16(struct mCore* core, uint32_t address, int segment) {
412 struct LR35902Core* cpu = core->cpu;
413 return GBView8(cpu, address, segment) | (GBView8(cpu, address + 1, segment) << 8);
414}
415
416static uint32_t _GBCoreRawRead32(struct mCore* core, uint32_t address, int segment) {
417 struct LR35902Core* cpu = core->cpu;
418 return GBView8(cpu, address, segment) | (GBView8(cpu, address + 1, segment) << 8) |
419 (GBView8(cpu, address + 2, segment) << 16) | (GBView8(cpu, address + 3, segment) << 24);
420}
421
422static void _GBCoreRawWrite8(struct mCore* core, uint32_t address, int segment, uint8_t value) {
423 struct LR35902Core* cpu = core->cpu;
424 GBPatch8(cpu, address, value, NULL, segment);
425}
426
427static void _GBCoreRawWrite16(struct mCore* core, uint32_t address, int segment, uint16_t value) {
428 struct LR35902Core* cpu = core->cpu;
429 GBPatch8(cpu, address, value, NULL, segment);
430 GBPatch8(cpu, address + 1, value >> 8, NULL, segment);
431}
432
433static void _GBCoreRawWrite32(struct mCore* core, uint32_t address, int segment, uint32_t value) {
434 struct LR35902Core* cpu = core->cpu;
435 GBPatch8(cpu, address, value, NULL, segment);
436 GBPatch8(cpu, address + 1, value >> 8, NULL, segment);
437 GBPatch8(cpu, address + 2, value >> 16, NULL, segment);
438 GBPatch8(cpu, address + 3, value >> 24, NULL, segment);
439}
440
441#ifdef USE_DEBUGGERS
442static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
443 UNUSED(core);
444 switch (type) {
445 case DEBUGGER_CLI:
446 return true;
447 default:
448 return false;
449 }
450}
451
452static struct mDebuggerPlatform* _GBCoreDebuggerPlatform(struct mCore* core) {
453 struct GBCore* gbcore = (struct GBCore*) core;
454 if (!gbcore->debuggerPlatform) {
455 gbcore->debuggerPlatform = LR35902DebuggerPlatformCreate();
456 }
457 return gbcore->debuggerPlatform;
458}
459
460static struct CLIDebuggerSystem* _GBCoreCliDebuggerSystem(struct mCore* core) {
461 return GBCLIDebuggerCreate(core);
462}
463
464static void _GBCoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) {
465 struct LR35902Core* cpu = core->cpu;
466 if (core->debugger) {
467 LR35902HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
468 }
469 cpu->components[CPU_COMPONENT_DEBUGGER] = &debugger->d;
470 LR35902HotplugAttach(cpu, CPU_COMPONENT_DEBUGGER);
471 core->debugger = debugger;
472}
473
474static void _GBCoreDetachDebugger(struct mCore* core) {
475 struct LR35902Core* cpu = core->cpu;
476 if (core->debugger) {
477 LR35902HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
478 }
479 cpu->components[CPU_COMPONENT_DEBUGGER] = NULL;
480 core->debugger = NULL;
481}
482#endif
483
484static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {
485 struct GBCore* gbcore = (struct GBCore*) core;
486 if (!gbcore->cheatDevice) {
487 gbcore->cheatDevice = GBCheatDeviceCreate();
488 ((struct LR35902Core*) core->cpu)->components[CPU_COMPONENT_CHEAT_DEVICE] = &gbcore->cheatDevice->d;
489 LR35902HotplugAttach(core->cpu, CPU_COMPONENT_CHEAT_DEVICE);
490 gbcore->cheatDevice->p = core;
491 }
492 return gbcore->cheatDevice;
493}
494
495static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) {
496 struct GB* gb = core->board;
497 struct VFile* vf = gb->sramVf;
498 if (vf) {
499 *sram = malloc(vf->size(vf));
500 vf->seek(vf, 0, SEEK_SET);
501 return vf->read(vf, *sram, vf->size(vf));
502 }
503 *sram = malloc(gb->sramSize);
504 memcpy(*sram, gb->memory.sram, gb->sramSize);
505 return gb->sramSize;
506}
507
508static bool _GBCoreSavedataRestore(struct mCore* core, const void* sram, size_t size, bool writeback) {
509 struct GB* gb = core->board;
510 if (!writeback) {
511 struct VFile* vf = VFileMemChunk(sram, size);
512 GBSavedataMask(gb, vf, true);
513 return true;
514 }
515 struct VFile* vf = gb->sramVf;
516 if (vf) {
517 vf->seek(vf, 0, SEEK_SET);
518 return vf->write(vf, sram, size) > 0;
519 }
520 if (size > 0x20000) {
521 size = 0x20000;
522 }
523 GBResizeSram(gb, size);
524 memcpy(gb->memory.sram, sram, size);
525 return true;
526}
527
528struct mCore* GBCoreCreate(void) {
529 struct GBCore* gbcore = malloc(sizeof(*gbcore));
530 struct mCore* core = &gbcore->d;
531 memset(&core->opts, 0, sizeof(core->opts));
532 core->cpu = NULL;
533 core->board = NULL;
534 core->debugger = NULL;
535 core->init = _GBCoreInit;
536 core->deinit = _GBCoreDeinit;
537 core->platform = _GBCorePlatform;
538 core->setSync = _GBCoreSetSync;
539 core->loadConfig = _GBCoreLoadConfig;
540 core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
541 core->setVideoBuffer = _GBCoreSetVideoBuffer;
542 core->getPixels = _GBCoreGetPixels;
543 core->putPixels = _GBCorePutPixels;
544 core->getAudioChannel = _GBCoreGetAudioChannel;
545 core->setAudioBufferSize = _GBCoreSetAudioBufferSize;
546 core->getAudioBufferSize = _GBCoreGetAudioBufferSize;
547 core->setAVStream = _GBCoreSetAVStream;
548 core->setCoreCallbacks = _GBCoreSetCoreCallbacks;
549 core->isROM = GBIsROM;
550 core->loadROM = _GBCoreLoadROM;
551 core->loadBIOS = _GBCoreLoadBIOS;
552 core->loadSave = _GBCoreLoadSave;
553 core->loadTemporarySave = _GBCoreLoadTemporarySave;
554 core->loadPatch = _GBCoreLoadPatch;
555 core->unloadROM = _GBCoreUnloadROM;
556 core->checksum = _GBCoreChecksum;
557 core->reset = _GBCoreReset;
558 core->runFrame = _GBCoreRunFrame;
559 core->runLoop = _GBCoreRunLoop;
560 core->step = _GBCoreStep;
561 core->stateSize = _GBCoreStateSize;
562 core->loadState = _GBCoreLoadState;
563 core->saveState = _GBCoreSaveState;
564 core->setKeys = _GBCoreSetKeys;
565 core->addKeys = _GBCoreAddKeys;
566 core->clearKeys = _GBCoreClearKeys;
567 core->frameCounter = _GBCoreFrameCounter;
568 core->frameCycles = _GBCoreFrameCycles;
569 core->frequency = _GBCoreFrequency;
570 core->getGameTitle = _GBCoreGetGameTitle;
571 core->getGameCode = _GBCoreGetGameCode;
572 core->setRTC = _GBCoreSetRTC;
573 core->setRotation = _GBCoreSetRotation;
574 core->setRumble = _GBCoreSetRumble;
575 core->busRead8 = _GBCoreBusRead8;
576 core->busRead16 = _GBCoreBusRead16;
577 core->busRead32 = _GBCoreBusRead32;
578 core->busWrite8 = _GBCoreBusWrite8;
579 core->busWrite16 = _GBCoreBusWrite16;
580 core->busWrite32 = _GBCoreBusWrite32;
581 core->rawRead8 = _GBCoreRawRead8;
582 core->rawRead16 = _GBCoreRawRead16;
583 core->rawRead32 = _GBCoreRawRead32;
584 core->rawWrite8 = _GBCoreRawWrite8;
585 core->rawWrite16 = _GBCoreRawWrite16;
586 core->rawWrite32 = _GBCoreRawWrite32;
587#ifdef USE_DEBUGGERS
588 core->supportsDebuggerType = _GBCoreSupportsDebuggerType;
589 core->debuggerPlatform = _GBCoreDebuggerPlatform;
590 core->cliDebuggerSystem = _GBCoreCliDebuggerSystem;
591 core->attachDebugger = _GBCoreAttachDebugger;
592 core->detachDebugger = _GBCoreDetachDebugger;
593#endif
594 core->cheatDevice = _GBCoreCheatDevice;
595 core->savedataClone = _GBCoreSavedataClone;
596 core->savedataRestore = _GBCoreSavedataRestore;
597 return core;
598}