src/gba/gba-memory.c (view raw)
1#include "gba-memory.h"
2
3#include "gba-io.h"
4
5#include <limits.h>
6#include <string.h>
7#include <sys/mman.h>
8
9static const char* GBA_CANNOT_MMAP = "Could not map memory";
10
11static void GBASetActiveRegion(struct ARMMemory* memory, uint32_t region);
12
13static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 };
14static const char GBA_BASE_WAITSTATES_SEQ[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 };
15static const char GBA_ROM_WAITSTATES[] = { 4, 3, 2, 8 };
16static const char GBA_ROM_WAITSTATES_SEQ[] = { 2, 1, 4, 1, 8, 1 };
17static const int DMA_OFFSET[] = { 1, -1, 0, 1 };
18
19void GBAMemoryInit(struct GBAMemory* memory) {
20 memory->d.load32 = GBALoad32;
21 memory->d.load16 = GBALoad16;
22 memory->d.loadU16 = GBALoadU16;
23 memory->d.load8 = GBALoad8;
24 memory->d.loadU8 = GBALoadU8;
25 memory->d.store32 = GBAStore32;
26 memory->d.store16 = GBAStore16;
27 memory->d.store8 = GBAStore8;
28
29 memory->bios = 0;
30 memory->wram = mmap(0, SIZE_WORKING_RAM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
31 memory->iwram = mmap(0, SIZE_WORKING_IRAM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
32 memory->rom = 0;
33 memset(memory->io, 0, sizeof(memory->io));
34 memset(memory->dma, 0, sizeof(memory->dma));
35
36 if (!memory->wram || !memory->iwram) {
37 GBAMemoryDeinit(memory);
38 memory->p->errno = GBA_OUT_OF_MEMORY;
39 memory->p->errstr = GBA_CANNOT_MMAP;
40 }
41
42 int i;
43 for (i = 0; i < 16; ++i) {
44 memory->waitstates16[i] = GBA_BASE_WAITSTATES[i];
45 memory->waitstatesSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i];
46 memory->waitstates32[i] = GBA_BASE_WAITSTATES[i] + GBA_BASE_WAITSTATES_SEQ[i] + 1;
47 memory->waitstatesSeq32[i] = GBA_BASE_WAITSTATES_SEQ[i] + GBA_BASE_WAITSTATES_SEQ[i] + 1;
48 }
49 for (; i < 256; ++i) {
50 memory->waitstates16[i] = 0;
51 memory->waitstatesSeq16[i] = 0;
52 memory->waitstates32[i] = 0;
53 memory->waitstatesSeq32[i] = 0;
54 }
55
56 memory->activeRegion = 0;
57 memory->d.activeRegion = 0;
58 memory->d.activeMask = 0;
59 memory->d.setActiveRegion = GBASetActiveRegion;
60 memory->d.activePrefetchCycles32 = 0;
61 memory->d.activePrefetchCycles16 = 0;
62}
63
64void GBAMemoryDeinit(struct GBAMemory* memory) {
65 munmap(memory->wram, SIZE_WORKING_RAM);
66 munmap(memory->iwram, SIZE_WORKING_IRAM);
67}
68
69static void GBASetActiveRegion(struct ARMMemory* memory, uint32_t address) {
70 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
71
72 memory->activePrefetchCycles32 = gbaMemory->waitstates32[address >> BASE_OFFSET];
73 memory->activePrefetchCycles16 = gbaMemory->waitstates16[address >> BASE_OFFSET];
74 gbaMemory->activeRegion = address >> BASE_OFFSET;
75 switch (address & ~OFFSET_MASK) {
76 case BASE_BIOS:
77 memory->activeRegion = gbaMemory->bios;
78 memory->activeMask = 0;
79 break;
80 case BASE_WORKING_RAM:
81 memory->activeRegion = gbaMemory->wram;
82 memory->activeMask = SIZE_WORKING_RAM - 1;
83 break;
84 case BASE_WORKING_IRAM:
85 memory->activeRegion = gbaMemory->iwram;
86 memory->activeMask = SIZE_WORKING_IRAM - 1;
87 break;
88 case BASE_CART0:
89 case BASE_CART0_EX:
90 case BASE_CART1:
91 case BASE_CART1_EX:
92 case BASE_CART2:
93 case BASE_CART2_EX:
94 memory->activeRegion = gbaMemory->rom;
95 memory->activeMask = SIZE_CART0 - 1;
96 break;
97 default:
98 memory->activeRegion = 0;
99 memory->activeMask = 0;
100 break;
101 }
102}
103
104int32_t GBALoad32(struct ARMMemory* memory, uint32_t address) {
105 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
106
107 switch (address & ~OFFSET_MASK) {
108 case BASE_BIOS:
109 break;
110 case BASE_WORKING_RAM:
111 return gbaMemory->wram[(address & (SIZE_WORKING_RAM - 1)) >> 2];
112 case BASE_WORKING_IRAM:
113 return gbaMemory->iwram[(address & (SIZE_WORKING_IRAM - 1)) >> 2];
114 case BASE_IO:
115 break;
116 case BASE_PALETTE_RAM:
117 break;
118 case BASE_VRAM:
119 break;
120 case BASE_OAM:
121 break;
122 case BASE_CART0:
123 case BASE_CART0_EX:
124 case BASE_CART1:
125 case BASE_CART1_EX:
126 case BASE_CART2:
127 case BASE_CART2_EX:
128 return gbaMemory->rom[(address & (SIZE_CART0 - 1)) >> 2];
129 case BASE_CART_SRAM:
130 break;
131 default:
132 break;
133 }
134
135 return 0;
136}
137
138int16_t GBALoad16(struct ARMMemory* memory, uint32_t address) {
139 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
140
141 switch (address & ~OFFSET_MASK) {
142 case BASE_BIOS:
143 break;
144 case BASE_WORKING_RAM:
145 return ((int16_t*) gbaMemory->wram)[(address & (SIZE_WORKING_RAM - 1)) >> 1];
146 case BASE_WORKING_IRAM:
147 return ((int16_t*) gbaMemory->iwram)[(address & (SIZE_WORKING_IRAM - 1)) >> 1];
148 case BASE_IO:
149 return GBAIORead(gbaMemory->p, address & (SIZE_IO - 1));
150 case BASE_PALETTE_RAM:
151 break;
152 case BASE_VRAM:
153 break;
154 case BASE_OAM:
155 break;
156 case BASE_CART0:
157 case BASE_CART0_EX:
158 case BASE_CART1:
159 case BASE_CART1_EX:
160 case BASE_CART2:
161 case BASE_CART2_EX:
162 return ((int16_t*) gbaMemory->rom)[(address & (SIZE_CART0 - 1)) >> 1];
163 case BASE_CART_SRAM:
164 break;
165 default:
166 break;
167 }
168
169 return 0;
170}
171
172uint16_t GBALoadU16(struct ARMMemory* memory, uint32_t address) {
173 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
174
175 switch (address & ~OFFSET_MASK) {
176 case BASE_BIOS:
177 break;
178 case BASE_WORKING_RAM:
179 return ((uint16_t*) gbaMemory->wram)[(address & (SIZE_WORKING_RAM - 1)) >> 1];
180 case BASE_WORKING_IRAM:
181 return ((uint16_t*) gbaMemory->iwram)[(address & (SIZE_WORKING_IRAM - 1)) >> 1];
182 case BASE_IO:
183 return GBAIORead(gbaMemory->p, address & (SIZE_IO - 1));
184 case BASE_PALETTE_RAM:
185 break;
186 case BASE_VRAM:
187 break;
188 case BASE_OAM:
189 break;
190 case BASE_CART0:
191 case BASE_CART0_EX:
192 case BASE_CART1:
193 case BASE_CART1_EX:
194 case BASE_CART2:
195 case BASE_CART2_EX:
196 return ((uint16_t*) gbaMemory->rom)[(address & (SIZE_CART0 - 1)) >> 1];
197 case BASE_CART_SRAM:
198 break;
199 default:
200 break;
201 }
202
203 return 0;
204}
205
206int8_t GBALoad8(struct ARMMemory* memory, uint32_t address) {
207 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
208
209 switch (address & ~OFFSET_MASK) {
210 case BASE_BIOS:
211 break;
212 case BASE_WORKING_RAM:
213 return ((int8_t*) gbaMemory->wram)[address & (SIZE_WORKING_RAM - 1)];
214 case BASE_WORKING_IRAM:
215 return ((int8_t*) gbaMemory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
216 case BASE_IO:
217 break;
218 case BASE_PALETTE_RAM:
219 break;
220 case BASE_VRAM:
221 break;
222 case BASE_OAM:
223 break;
224 case BASE_CART0:
225 case BASE_CART0_EX:
226 case BASE_CART1:
227 case BASE_CART1_EX:
228 case BASE_CART2:
229 case BASE_CART2_EX:
230 return ((int8_t*) gbaMemory->rom)[address & (SIZE_CART0 - 1)];
231 case BASE_CART_SRAM:
232 break;
233 default:
234 break;
235 }
236
237 return 0;
238}
239
240uint8_t GBALoadU8(struct ARMMemory* memory, uint32_t address) {
241 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
242
243 switch (address & ~OFFSET_MASK) {
244 case BASE_BIOS:
245 break;
246 case BASE_WORKING_RAM:
247 return ((uint8_t*) gbaMemory->wram)[address & (SIZE_WORKING_RAM - 1)];
248 break;
249 case BASE_WORKING_IRAM:
250 return ((uint8_t*) gbaMemory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
251 break;
252 case BASE_IO:
253 break;
254 case BASE_PALETTE_RAM:
255 break;
256 case BASE_VRAM:
257 break;
258 case BASE_OAM:
259 break;
260 case BASE_CART0:
261 case BASE_CART0_EX:
262 case BASE_CART1:
263 case BASE_CART1_EX:
264 case BASE_CART2:
265 case BASE_CART2_EX:
266 return ((uint8_t*) gbaMemory->rom)[address & (SIZE_CART0 - 1)];
267 case BASE_CART_SRAM:
268 break;
269 default:
270 break;
271 }
272
273 return 0;
274}
275
276void GBAStore32(struct ARMMemory* memory, uint32_t address, int32_t value) {
277 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
278
279 switch (address & ~OFFSET_MASK) {
280 case BASE_WORKING_RAM:
281 gbaMemory->wram[(address & (SIZE_WORKING_RAM - 1)) >> 2] = value;
282 break;
283 case BASE_WORKING_IRAM:
284 gbaMemory->iwram[(address & (SIZE_WORKING_IRAM - 1)) >> 2] = value;
285 break;
286 case BASE_IO:
287 GBAIOWrite32(gbaMemory->p, address & (SIZE_IO - 1), value);
288 break;
289 case BASE_PALETTE_RAM:
290 break;
291 case BASE_VRAM:
292 break;
293 case BASE_OAM:
294 break;
295 case BASE_CART0:
296 break;
297 case BASE_CART2_EX:
298 break;
299 case BASE_CART_SRAM:
300 break;
301 default:
302 break;
303 }
304}
305
306void GBAStore16(struct ARMMemory* memory, uint32_t address, int16_t value) {
307 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
308
309 switch (address & ~OFFSET_MASK) {
310 case BASE_WORKING_RAM:
311 ((int16_t*) gbaMemory->wram)[(address & (SIZE_WORKING_RAM - 1)) >> 1] = value;
312 break;
313 case BASE_WORKING_IRAM:
314 ((int16_t*) gbaMemory->iwram)[(address & (SIZE_WORKING_IRAM - 1)) >> 1] = value;
315 break;
316 case BASE_IO:
317 GBAIOWrite(gbaMemory->p, address & (SIZE_IO - 1), value);
318 break;
319 case BASE_PALETTE_RAM:
320 break;
321 case BASE_VRAM:
322 break;
323 case BASE_OAM:
324 break;
325 case BASE_CART0:
326 break;
327 case BASE_CART2_EX:
328 break;
329 case BASE_CART_SRAM:
330 break;
331 default:
332 break;
333 }
334}
335
336void GBAStore8(struct ARMMemory* memory, uint32_t address, int8_t value) {
337 struct GBAMemory* gbaMemory = (struct GBAMemory*) memory;
338
339 switch (address & ~OFFSET_MASK) {
340 case BASE_WORKING_RAM:
341 ((int8_t*) gbaMemory->wram)[address & (SIZE_WORKING_RAM - 1)] = value;
342 break;
343 case BASE_WORKING_IRAM:
344 ((int8_t*) gbaMemory->iwram)[address & (SIZE_WORKING_IRAM - 1)] = value;
345 break;
346 case BASE_IO:
347 break;
348 case BASE_PALETTE_RAM:
349 break;
350 case BASE_VRAM:
351 break;
352 case BASE_OAM:
353 break;
354 case BASE_CART0:
355 break;
356 case BASE_CART2_EX:
357 break;
358 case BASE_CART_SRAM:
359 break;
360 default:
361 break;
362 }
363}
364
365void GBAAdjustWaitstates(struct GBAMemory* memory, uint16_t parameters) {
366 int sram = parameters & 0x0003;
367 int ws0 = (parameters & 0x000C) >> 2;
368 int ws0seq = (parameters & 0x0010) >> 4;
369 int ws1 = (parameters & 0x0060) >> 5;
370 int ws1seq = (parameters & 0x0080) >> 7;
371 int ws2 = (parameters & 0x0300) >> 8;
372 int ws2seq = (parameters & 0x0400) >> 10;
373 int prefetch = parameters & 0x4000;
374
375 memory->waitstates16[REGION_CART_SRAM] = GBA_ROM_WAITSTATES[sram];
376 memory->waitstatesSeq16[REGION_CART_SRAM] = GBA_ROM_WAITSTATES[sram];
377 memory->waitstates32[REGION_CART_SRAM] = 2 * GBA_ROM_WAITSTATES[sram] + 1;
378 memory->waitstatesSeq32[REGION_CART_SRAM] = 2 * GBA_ROM_WAITSTATES[sram] + 1;
379
380 memory->waitstates16[REGION_CART0] = memory->waitstates16[REGION_CART0_EX] = GBA_ROM_WAITSTATES[ws0];
381 memory->waitstates16[REGION_CART1] = memory->waitstates16[REGION_CART1_EX] = GBA_ROM_WAITSTATES[ws1];
382 memory->waitstates16[REGION_CART2] = memory->waitstates16[REGION_CART2_EX] = GBA_ROM_WAITSTATES[ws2];
383
384 memory->waitstatesSeq16[REGION_CART0] = memory->waitstatesSeq16[REGION_CART0_EX] = GBA_ROM_WAITSTATES_SEQ[ws0seq];
385 memory->waitstatesSeq16[REGION_CART1] = memory->waitstatesSeq16[REGION_CART1_EX] = GBA_ROM_WAITSTATES_SEQ[ws1seq + 2];
386 memory->waitstatesSeq16[REGION_CART2] = memory->waitstatesSeq16[REGION_CART2_EX] = GBA_ROM_WAITSTATES_SEQ[ws2seq + 4];
387
388 memory->waitstates32[REGION_CART0] = memory->waitstates32[REGION_CART0_EX] = memory->waitstates16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0];
389 memory->waitstates32[REGION_CART1] = memory->waitstates32[REGION_CART1_EX] = memory->waitstates16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1];
390 memory->waitstates32[REGION_CART2] = memory->waitstates32[REGION_CART2_EX] = memory->waitstates16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2];
391
392 memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0 + 1] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1;
393 memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1 + 1] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1;
394 memory->waitstatesSeq32[REGION_CART2] = memory->waitstatesSeq32[REGION_CART2 + 1] = 2 * memory->waitstatesSeq16[REGION_CART2] + 1;
395
396 memory->d.activePrefetchCycles32 = memory->waitstates32[memory->activeRegion];
397 memory->d.activePrefetchCycles16 = memory->waitstates16[memory->activeRegion];
398}
399
400int32_t GBAMemoryProcessEvents(struct GBAMemory* memory, int32_t cycles) {
401 struct GBADMA* dma;
402 int32_t test = INT_MAX;
403
404 dma = &memory->dma[0];
405 dma->nextIRQ -= cycles;
406 if (dma->enable && dma->doIrq && dma->nextIRQ) {
407 if (dma->nextIRQ <= 0) {
408 dma->nextIRQ = INT_MAX;
409 GBARaiseIRQ(memory->p, IRQ_DMA0);
410 } else if (dma->nextIRQ < test) {
411 test = dma->nextIRQ;
412 }
413 }
414
415 dma = &memory->dma[1];
416 dma->nextIRQ -= cycles;
417 if (dma->enable && dma->doIrq && dma->nextIRQ) {
418 if (dma->nextIRQ <= 0) {
419 dma->nextIRQ = INT_MAX;
420 GBARaiseIRQ(memory->p, IRQ_DMA1);
421 } else if (dma->nextIRQ < test) {
422 test = dma->nextIRQ;
423 }
424 }
425
426 dma = &memory->dma[2];
427 dma->nextIRQ -= cycles;
428 if (dma->enable && dma->doIrq && dma->nextIRQ) {
429 if (dma->nextIRQ <= 0) {
430 dma->nextIRQ = INT_MAX;
431 GBARaiseIRQ(memory->p, IRQ_DMA2);
432 } else if (dma->nextIRQ < test) {
433 test = dma->nextIRQ;
434 }
435 }
436
437 dma = &memory->dma[3];
438 dma->nextIRQ -= cycles;
439 if (dma->enable && dma->doIrq && dma->nextIRQ) {
440 if (dma->nextIRQ <= 0) {
441 dma->nextIRQ = INT_MAX;
442 GBARaiseIRQ(memory->p, IRQ_DMA3);
443 } else if (dma->nextIRQ < test) {
444 test = dma->nextIRQ;
445 }
446 }
447
448 return test;
449}
450
451void GBAMemoryWriteDMASAD(struct GBAMemory* memory, int dma, uint32_t address) {
452 memory->dma[dma].source = address & 0xFFFFFFFE;
453}
454
455void GBAMemoryWriteDMADAD(struct GBAMemory* memory, int dma, uint32_t address) {
456 memory->dma[dma].dest = address & 0xFFFFFFFE;
457}
458
459void GBAMemoryWriteDMACNT_LO(struct GBAMemory* memory, int dma, uint16_t count) {
460 memory->dma[dma].count = count ? count : (dma == 3 ? 0x10000 : 0x4000);
461}
462
463void GBAMemoryWriteDMACNT_HI(struct GBAMemory* memory, int dma, uint16_t control) {
464 struct GBADMA* currentDma = &memory->dma[dma];
465 int wasEnabled = currentDma->enable;
466 currentDma->packed = control;
467 currentDma->nextIRQ = 0;
468
469 if (currentDma->drq) {
470 GBALog(GBA_LOG_STUB, "DRQ not implemented");
471 }
472
473 if (!wasEnabled && currentDma->enable) {
474 currentDma->nextSource = currentDma->source;
475 currentDma->nextDest = currentDma->dest;
476 currentDma->nextCount = currentDma->count;
477 GBAMemoryScheduleDMA(memory, dma, currentDma);
478 }
479};
480
481void GBAMemoryScheduleDMA(struct GBAMemory* memory, int number, struct GBADMA* info) {
482 switch (info->timing) {
483 case DMA_TIMING_NOW:
484 GBAMemoryServiceDMA(memory, number, info);
485 break;
486 case DMA_TIMING_HBLANK:
487 // Handled implicitly
488 break;
489 case DMA_TIMING_VBLANK:
490 // Handled implicitly
491 break;
492 case DMA_TIMING_CUSTOM:
493 switch (number) {
494 case 0:
495 GBALog(GBA_LOG_WARN, "Discarding invalid DMA0 scheduling");
496 break;
497 case 1:
498 case 2:
499 //this.cpu.irq.audio.scheduleFIFODma(number, info);
500 break;
501 case 3:
502 //this.cpu.irq.video.scheduleVCaptureDma(dma, info);
503 break;
504 }
505 }
506}
507
508void GBAMemoryRunHblankDMAs(struct GBAMemory* memory) {
509 struct GBADMA* dma;
510 int i;
511 for (i = 0; i < 4; ++i) {
512 dma = &memory->dma[i];
513 if (dma->enable && dma->timing == DMA_TIMING_HBLANK) {
514 GBAMemoryServiceDMA(memory, i, dma);
515 }
516 }
517}
518
519void GBAMemoryRunVblankDMAs(struct GBAMemory* memory) {
520 struct GBADMA* dma;
521 int i;
522 for (i = 0; i < 4; ++i) {
523 dma = &memory->dma[i];
524 if (dma->enable && dma->timing == DMA_TIMING_VBLANK) {
525 GBAMemoryServiceDMA(memory, i, dma);
526 }
527 }
528}
529
530void GBAMemoryServiceDMA(struct GBAMemory* memory, int number, struct GBADMA* info) {
531 if (!info->enable) {
532 // There was a DMA scheduled that got canceled
533 return;
534 }
535
536 uint32_t width = info->width ? 4 : 2;
537 int sourceOffset = DMA_OFFSET[info->srcControl] * width;
538 int destOffset = DMA_OFFSET[info->dstControl] * width;
539 int32_t wordsRemaining = info->nextCount;
540 uint32_t source = info->nextSource;
541 uint32_t dest = info->nextDest;
542 uint32_t sourceRegion = source >> BASE_OFFSET;
543 uint32_t destRegion = dest >> BASE_OFFSET;
544
545 if (width == 4) {
546 int32_t word;
547 source &= 0xFFFFFFFC;
548 dest &= 0xFFFFFFFC;
549 while (wordsRemaining--) {
550 word = GBALoad32(&memory->d, source);
551 GBAStore32(&memory->d, dest, word);
552 source += sourceOffset;
553 dest += destOffset;
554 }
555 } else {
556 uint16_t word;
557 while (wordsRemaining--) {
558 word = GBALoadU16(&memory->d, source);
559 GBAStore16(&memory->d, dest, word);
560 source += sourceOffset;
561 dest += destOffset;
562 }
563 }
564
565 if (info->doIrq) {
566 info->nextIRQ = memory->p->cpu.cycles + 2;
567 info->nextIRQ += (width == 4 ? memory->waitstates32[sourceRegion] + memory->waitstates32[destRegion]
568 : memory->waitstates16[sourceRegion] + memory->waitstates16[destRegion]);
569 info->nextIRQ += (info->count - 1) * (width == 4 ? memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion]
570 : memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]);
571 }
572
573 info->nextSource = source;
574 info->nextDest = dest;
575 info->nextCount = wordsRemaining;
576
577 if (!info->repeat) {
578 info->enable = 0;
579
580 // Clear the enable bit in memory
581 memory->io[(REG_DMA0CNT_HI + number * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0;
582 } else {
583 info->nextCount = info->count;
584 if (info->dstControl == DMA_INCREMENT_RELOAD) {
585 info->nextDest = info->dest;
586 }
587 GBAMemoryScheduleDMA(memory, number, info);
588 }
589}