Blaster Master + other fixes
[fceu.git] / x6502.c
1 /* FCE Ultra - NES/Famicom Emulator
2  *
3  * Copyright notice for this file:
4  *  Copyright (C) 2002 Ben Parnell
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <string.h>
22
23 #include "types.h"
24 #include "x6502.h"
25 #include "fce.h"
26 #include "sound.h"
27 #include "cart.h"
28
29 #ifdef DEBUG_ASM_6502
30 #include <stdio.h>
31 #include <stdlib.h>
32 extern uint32 PC_prev, OP_prev;
33 extern uint8  dreads[4];
34 extern uint32 dwrites_c[2];
35 extern int dread_count_c, dwrite_count_c;
36 extern int mapirq_cyc_c;
37 extern void (*MapIRQHook)(int a);
38 #define DummyRdMem(...)
39 #else
40 #define DummyRdMem RdMem
41 void FP_FASTAPASS(1) (*MapIRQHook)(int a);
42 #endif
43
44 X6502 X;
45 uint32 timestamp;
46
47 #define _PC              X.PC
48 #define _A               X.A
49 #define _X               X.X
50 #define _Y               X.Y
51 #define _S               X.S
52 #define _P               X.P
53 #define _PI              X.mooPI
54 //#define _PZ              X.PZ         // unused?
55 #define _DB              X.DB
56 #define _count           X.count
57 #define _tcount          X.tcount
58 #define _IRQlow          X.IRQlow
59 #define _jammed          X.jammed
60
61
62 static INLINE uint8 RdMem(unsigned int A)
63 {
64  // notaz: try to avoid lookup of every address at least for ROM and RAM areas
65  // I've verified that if ARead[0xfff0] points to CartBR, it is always normal ROM read.
66 #if 0
67  if ((A&0x8000)/* && ARead[0xfff0] == CartBR*/) {
68   return (_DB=Page[A>>11][A]);
69  }
70 #endif
71 #if 0 // enabling this causes 4fps slowdown. Why?
72  if ((A&0xe000) == 0) { // RAM area (always 0-0x1fff)
73   return (_DB=RAM[A&0x7FF]);
74  }
75 #endif
76  _DB=ARead[A](A);
77 #ifdef DEBUG_ASM_6502
78  //printf("a == %x, pc == %x\n", A, _PC);
79  if (A >= 0x2000 && A != _PC && A != _PC - 1 && A != _PC + 1) {
80   dreads[dread_count_c++] = _DB;
81   if (dread_count_c > 4) { printf("dread_count out of range\n"); exit(1); }
82  }
83 #endif
84  return _DB;
85 }
86
87 static INLINE void WrMem(unsigned int A, uint8 V)
88 {
89  if ((A&0xe000) == 0) { // RAM area (always 0-0x1fff)
90   RAM[A&0x7FF] = V;
91   return;
92  }
93  BWrite[A](A,V);
94 #ifdef DEBUG_ASM_6502
95  dwrites_c[dwrite_count_c++] = (A<<8)|V;
96  if (dwrite_count_c > 2) { printf("dwrite_count_c out of range\n"); exit(1); }
97 #endif
98 }
99
100 static INLINE uint8 RdRAM(unsigned int A)
101 {
102  return((_DB=RAM[A]));
103 }
104
105 static INLINE void WrRAM(unsigned int A, uint8 V)
106 {
107  RAM[A]=V;
108 }
109
110 static INLINE void ADDCYC(int x)
111 {
112  _tcount+=x;
113  _count-=x*48;
114  timestamp+=x;
115 }
116
117 void FASTAPASS(1) X6502_AddCycles_c(int x)
118 {
119  ADDCYC(x);
120 }
121
122 static INLINE void PUSH(uint8 V)
123 {
124  WrRAM(0x100+_S,V);
125  _S--;
126 }
127
128 static INLINE uint8 POP(void)
129 {
130  _S++;
131  return(RdRAM(0x100+_S));
132 }
133
134 #if 0
135 static uint8 ZNTable[256] = {
136         Z_FLAG,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
137         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
138         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
139         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
140         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
141         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
142         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
143         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
144         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
145         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
146         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
147         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
148         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
149         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
150         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
151         N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG
152 };
153 #endif
154 /* Some of these operations will only make sense if you know what the flag
155    constants are. */
156 //#define X_ZN(zort)         _P&=~(Z_FLAG|N_FLAG);_P|=ZNTable[zort]
157 //#define X_ZNT(zort)   _P|=ZNTable[zort]
158 #define X_ZN(zort)         _P&=~(Z_FLAG|N_FLAG);if(!zort) _P|=Z_FLAG;else _P|=zort&N_FLAG
159 #define X_ZNT(zort)     if(!zort) _P|=Z_FLAG;else _P|=(zort&N_FLAG)
160
161 /* Care must be taken if you want to turn this into a macro.  Use { and }. */
162 #define JR();   \
163 {               \
164  uint32 tmp;    \
165  int8 disp;     \
166  disp=RdMem(_PC++);     \
167  ADDCYC(1);     \
168  tmp=_PC;       \
169  _PC+=disp;     \
170  if((tmp^_PC)&0x100)    \
171   ADDCYC(1);    \
172 }
173
174 #define LDA        _A=x;X_ZN(_A)
175 #define LDX        _X=x;X_ZN(_X)
176 #define LDY        _Y=x;X_ZN(_Y)
177
178 /*  All of the freaky arithmetic operations. */
179 #define AND        _A&=x;X_ZN(_A)
180 //#define BIT        _P&=~(Z_FLAG|V_FLAG|N_FLAG);_P|=ZNTable[x&_A]&Z_FLAG;_P|=x&(V_FLAG|N_FLAG)
181 #define BIT        _P&=~(Z_FLAG|V_FLAG|N_FLAG);if(!(x&_A)) _P|=Z_FLAG;_P|=x&(V_FLAG|N_FLAG)
182 #define EOR        _A^=x;X_ZN(_A)
183 #define ORA        _A|=x;X_ZN(_A)
184
185 #define ADC  {  \
186               uint32 l=_A+x+(_P&1);     \
187               _P&=~(Z_FLAG|C_FLAG|N_FLAG|V_FLAG);       \
188               _P|=((((_A^x)&0x80)^0x80) & ((_A^l)&0x80))>>1;    \
189               _P|=(l>>8)&C_FLAG;        \
190               _A=l;     \
191               X_ZNT(_A);        \
192              }
193 #define SBC  {  \
194               uint32 l=_A-x-((_P&1)^1); \
195               _P&=~(Z_FLAG|C_FLAG|N_FLAG|V_FLAG);       \
196               _P|=((_A^l)&(_A^x)&0x80)>>1;      \
197               _P|=((l>>8)&C_FLAG)^C_FLAG;       \
198               _A=l;     \
199               X_ZNT(_A);        \
200              }
201
202 #define CMPL(a1,a2) {   \
203                      uint32 t=a1-a2;    \
204                      X_ZN(t&0xFF);      \
205                      _P&=~C_FLAG;       \
206                      _P|=((t>>8)&C_FLAG)^C_FLAG;        \
207                     }
208
209 /* Special undocumented operation.  Very similar to CMP. */
210 #define AXS         {   \
211                      uint32 t=(_A&_X)-x;    \
212                      X_ZN(t&0xFF);      \
213                      _P&=~C_FLAG;       \
214                      _P|=((t>>8)&C_FLAG)^C_FLAG;        \
215                      _X=t;      \
216                     }
217
218 #define CMP             CMPL(_A,x)
219 #define CPX             CMPL(_X,x)
220 #define CPY             CMPL(_Y,x)
221
222 /* The following operations modify the byte being worked on. */
223 #define DEC             x--;X_ZN(x)
224 #define INC             x++;X_ZN(x)
225
226 #define ASL        _P&=~C_FLAG;_P|=x>>7;x<<=1;X_ZN(x)
227 #define LSR     _P&=~(C_FLAG|N_FLAG|Z_FLAG);_P|=x&1;x>>=1;X_ZNT(x)
228
229 /* For undocumented instructions, maybe for other things later... */
230 #define LSRA    _P&=~(C_FLAG|N_FLAG|Z_FLAG);_P|=_A&1;_A>>=1;X_ZNT(_A)
231
232 #define ROL     {       \
233                  uint8 l=x>>7;  \
234                  x<<=1; \
235                  x|=_P&C_FLAG;  \
236                  _P&=~(Z_FLAG|N_FLAG|C_FLAG);   \
237                  _P|=l; \
238                  X_ZNT(x);      \
239                 }
240 #define ROR     {       \
241                  uint8 l=x&1;   \
242                  x>>=1; \
243                  x|=(_P&C_FLAG)<<7;     \
244                  _P&=~(Z_FLAG|N_FLAG|C_FLAG);   \
245                  _P|=l; \
246                  X_ZNT(x);      \
247                 }
248
249 /* Icky icky thing for some undocumented instructions.  Can easily be
250    broken if names of local variables are changed.
251 */
252
253 /* Absolute */
254 #define GetAB(target)   \
255 {       \
256  target=RdMem(_PC++);   \
257  target|=RdMem(_PC++)<<8;       \
258 }
259
260 /* Absolute Indexed(for reads) */
261 #define GetABIRD(target, i)     \
262 {       \
263  unsigned int tmp;      \
264  GetAB(tmp);    \
265  target=tmp;    \
266  target+=i;     \
267  if((target^tmp)&0x100) \
268  {      \
269   target&=0xFFFF;       \
270   DummyRdMem(target^0x100);     \
271   ADDCYC(1);    \
272  }      \
273 }
274
275 /* Absolute Indexed(for writes and rmws) */
276 #define GetABIWR(target, i)     \
277 {       \
278  unsigned int rt;       \
279  GetAB(rt);     \
280  target=rt;     \
281  target+=i;     \
282  target&=0xFFFF;        \
283  DummyRdMem((target&0x00FF)|(rt&0xFF00));       \
284 }
285
286 /* Zero Page */
287 #define GetZP(target)   \
288 {       \
289  target=RdMem(_PC++);   \
290 }
291
292 /* Zero Page Indexed */
293 #define GetZPI(target,i)        \
294 {       \
295  target=i+RdMem(_PC++); \
296 }
297
298 /* Indexed Indirect */
299 #define GetIX(target)   \
300 {       \
301  uint8 tmp;     \
302  tmp=RdMem(_PC++);      \
303  tmp+=_X;       \
304  target=RdRAM(tmp++);   \
305  target|=RdRAM(tmp)<<8; \
306 }
307
308 /* Indirect Indexed(for reads) */
309 #define GetIYRD(target) \
310 {       \
311  unsigned int rt;       \
312  uint8 tmp;     \
313  tmp=RdMem(_PC++);      \
314  rt=RdRAM(tmp++);       \
315  rt|=RdRAM(tmp)<<8;     \
316  target=rt;     \
317  target+=_Y;    \
318  if((target^rt)&0x100)  \
319  {      \
320   target&=0xFFFF;       \
321   DummyRdMem(target^0x100);     \
322   ADDCYC(1);    \
323  }      \
324 }
325
326 /* Indirect Indexed(for writes and rmws) */
327 #define GetIYWR(target) \
328 {       \
329  unsigned int rt;       \
330  uint8 tmp;     \
331  tmp=RdMem(_PC++);      \
332  rt=RdRAM(tmp++);       \
333  rt|=RdRAM(tmp)<<8;     \
334  target=rt;     \
335  target+=_Y;    \
336  DummyRdMem((target&0x00FF)|(rt&0xFF00));       \
337 }
338
339 /* Now come the macros to wrap up all of the above stuff addressing mode functions
340    and operation macros.  Note that operation macros will always operate(redundant
341    redundant) on the variable "x".
342 */
343
344 #define RMW_A(op) {uint8 x=_A; op; _A=x; break; } /* Meh... */
345 #define RMW_AB(op) {unsigned int A; uint8 x; GetAB(A); x=RdMem(A); WrMem(A,x); op; WrMem(A,x); break; }
346 #define RMW_ABI(reg,op) {unsigned int A; uint8 x; GetABIWR(A,reg); x=RdMem(A); WrMem(A,x); op; WrMem(A,x); break; }
347 #define RMW_ABX(op)     RMW_ABI(_X,op)
348 #define RMW_ABY(op)     RMW_ABI(_Y,op)
349 #define RMW_IX(op)  {unsigned int A; uint8 x; GetIX(A); x=RdMem(A); WrMem(A,x); op; WrMem(A,x); break; }
350 #define RMW_IY(op)  {unsigned int A; uint8 x; GetIYWR(A); x=RdMem(A); WrMem(A,x); op; WrMem(A,x); break; }
351 #define RMW_ZP(op)  {uint8 A; uint8 x; GetZP(A); x=RdRAM(A); op; WrRAM(A,x); break; }
352 #define RMW_ZPX(op) {uint8 A; uint8 x; GetZPI(A,_X); x=RdRAM(A); op; WrRAM(A,x); break;}
353
354 #define LD_IM(op)       {uint8 x; x=RdMem(_PC++); op; break;}
355 #define LD_ZP(op)       {uint8 A; uint8 x; GetZP(A); x=RdRAM(A); op; break;}
356 #define LD_ZPX(op)  {uint8 A; uint8 x; GetZPI(A,_X); x=RdRAM(A); op; break;}
357 #define LD_ZPY(op)  {uint8 A; uint8 x; GetZPI(A,_Y); x=RdRAM(A); op; break;}
358 #define LD_AB(op)       {unsigned int A; uint8 x; GetAB(A); x=RdMem(A); op; break; }
359 #define LD_ABI(reg,op)  {unsigned int A; uint8 x; GetABIRD(A,reg); x=RdMem(A); op; break;}
360 #define LD_ABX(op)      LD_ABI(_X,op)
361 #define LD_ABY(op)      LD_ABI(_Y,op)
362 #define LD_IX(op)       {unsigned int A; uint8 x; GetIX(A); x=RdMem(A); op; break;}
363 #define LD_IY(op)       {unsigned int A; uint8 x; GetIYRD(A); x=RdMem(A); op; break;}
364
365 #define ST_ZP(r)        {uint8 A; GetZP(A); WrRAM(A,r); break;}
366 #define ST_ZPX(r)       {uint8 A; GetZPI(A,_X); WrRAM(A,r); break;}
367 #define ST_ZPY(r)       {uint8 A; GetZPI(A,_Y); WrRAM(A,r); break;}
368 #define ST_AB(r)        {unsigned int A; GetAB(A); WrMem(A,r); break;}
369 #define ST_ABI(reg,r)   {unsigned int A; GetABIWR(A,reg); WrMem(A,r); break; }
370 #define ST_ABX(r)       ST_ABI(_X,r)
371 #define ST_ABY(r)       ST_ABI(_Y,r)
372 #define ST_IX(r)        {unsigned int A; GetIX(A); WrMem(A,r); break; }
373 #define ST_IY(r)        {unsigned int A; GetIYWR(A); WrMem(A,r); break; }
374
375 static uint8 CycTable[256] =
376 {
377 /*0x00*/ 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,
378 /*0x10*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
379 /*0x20*/ 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,
380 /*0x30*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
381 /*0x40*/ 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,
382 /*0x50*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
383 /*0x60*/ 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,
384 /*0x70*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
385 /*0x80*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
386 /*0x90*/ 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,
387 /*0xA0*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
388 /*0xB0*/ 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,
389 /*0xC0*/ 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
390 /*0xD0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
391 /*0xE0*/ 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,
392 /*0xF0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
393 };
394
395 void FASTAPASS(1) X6502_IRQBegin_c(int w)
396 {
397  _IRQlow|=w;
398 }
399
400 void FASTAPASS(1) X6502_IRQEnd_c(int w)
401 {
402  _IRQlow&=~w;
403 }
404
405 void TriggerIRQ_c(void) /* This function should probably be phased out. */
406 {
407  _IRQlow|=FCEU_IQTEMP;
408 }
409
410 void TriggerNMINSF_c(void)
411 {
412  ADDCYC(7);
413  PUSH(_PC>>8);
414  PUSH(_PC);
415  PUSH((_P&~B_FLAG)|(U_FLAG));
416  _PC=0x3800;
417 }
418
419 void TriggerNMI_c(void)
420 {
421  _IRQlow|=FCEU_IQNMI;
422 }
423
424 static void TriggerNMIReal(void)
425 {
426  if(!_jammed)
427  {
428   ADDCYC(7);
429   PUSH(_PC>>8);
430   PUSH(_PC);
431   _P&=~B_FLAG;
432   PUSH(_P|U_FLAG);
433   _PC=RdMem(0xFFFA);
434   _PC|=RdMem(0xFFFB)<<8;
435 #ifdef DEBUG_ASM_6502
436   PC_prev = _PC;
437   OP_prev = 0x100;
438 #endif
439  }
440 }
441
442 void TriggerIRQReal(void)
443 {
444  if(!(_PI&I_FLAG) && !_jammed)
445  {
446   ADDCYC(7);
447   PUSH(_PC>>8);
448   PUSH(_PC);
449   _P&=~B_FLAG;
450   PUSH(_P|U_FLAG);
451   _P|=I_FLAG;
452   _PC=RdMem(0xFFFE);
453   _PC|=RdMem(0xFFFF)<<8;
454 #ifdef DEBUG_ASM_6502
455   PC_prev = _PC;
456   OP_prev = 0x101;
457 #endif
458  }
459 }
460
461 void X6502_Reset_c(void)
462 {
463   _PC=RdMem(0xFFFC);
464   _PC|=RdMem(0xFFFD)<<8;
465   if(FCEUGameInfo.type==GIT_NSF) _PC=0x3830;
466   _jammed=0;
467   _PI=_P=I_FLAG;
468 }
469
470 void X6502_Power_c(void)
471 {
472  memset((void *)&X,0,sizeof(X));
473  timestamp=0;
474  X6502_Reset_c();
475 }
476
477
478 //int asdc = 0;
479 void X6502_Run_c(void/*int32 cycles*/)
480 {
481 /*
482         if(PAL)
483          cycles*=15;          // 15*4=60
484         else
485          cycles*=16;          // 16*4=64
486
487         _count+=cycles;
488 */
489 //      if (_count <= 0) asdc++;
490
491         while(_count>0)
492         {
493          int32 temp;
494          uint8 b1;
495
496          if(_IRQlow)
497          {
498           if(_IRQlow&FCEU_IQNMI)
499            TriggerNMIReal();
500           else
501            TriggerIRQReal();
502
503           _IRQlow&=~(FCEU_IQTEMP|FCEU_IQNMI);
504           if(_count<=0)
505           {
506 #ifdef DEBUG_ASM_6502
507            if(MapIRQHook) mapirq_cyc_c = _tcount;
508            _tcount=0;
509 #endif
510            _PI=_P;
511            return; /* Should increase accuracy without a major speed hit. */
512           }
513          }
514          _PI=_P;
515          b1=RdMem(_PC);
516          ADDCYC(CycTable[b1]);
517          temp=_tcount;
518
519          temp*=48;
520
521          fhcnt-=temp;
522          if(fhcnt<=0)
523          {
524           FrameSoundUpdate();
525           fhcnt+=fhinc;
526          }
527
528          if(PCMIRQCount>0)
529          {
530           PCMIRQCount-=temp;
531           if(PCMIRQCount<=0)
532           {
533            vdis=1;
534            if((PSG[0x10]&0x80) && !(PSG[0x10]&0x40))
535            {
536             extern uint8 SIRQStat;
537             SIRQStat|=0x80;
538             X6502_IRQBegin_c(FCEU_IQDPCM);
539            }
540           }
541          }
542
543 #ifdef DEBUG_ASM_6502
544          PC_prev = _PC;
545          OP_prev = b1;
546 #endif
547           //printf("$%04x:$%02x\n",_PC,b1);
548          //_PC++;
549          //printf("$%02x\n",b1);
550          _PC++;
551          switch(b1)
552          {
553           #include "ops.h"
554          }
555
556          temp=_tcount; /* Gradius II (J) glitches if _tcount is not used */
557          _tcount=0;
558
559          if(MapIRQHook) {
560 #ifdef DEBUG_ASM_6502
561           mapirq_cyc_c = temp;
562 #endif
563           MapIRQHook(temp);
564          }
565
566 #ifdef DEBUG_ASM_6502
567          _PI=_P;
568 #endif
569         }
570 }
571
572