Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* DoritoCPU, a DCPU-16 emulator
- by NovaSquirrel, 2013
- http://dcpu.com/dcpu-16/
- */
- #include "SDL/SDL.h"
- #include <stdio.h>
- #include <string.h>
- #include <stdarg.h>
- #include <stdlib.h>
- #include <time.h>
- #include <ctype.h>
- #include <malloc.h>
- #define MASK_16(x) ((x)&0xffff)
- const int SCREEN_WIDTH = 32*4*2;
- const int SCREEN_HEIGHT = 8*12*2;
- const int SCREEN_BPP = 32;
- const int MONITOR_WIDTH = 32;
- const int MONITOR_HEIGHT = 12;
- SDL_Surface *screen = NULL;
- SDL_Surface *MonitorScreen = NULL;
- #define DELAY_TIME 17
- #define CYCLES_PER_DELAY 1666
- int Retraces = 0, quit = 0;
- typedef unsigned short u16;
- typedef unsigned long u32;
- void inline rectfillwh(SDL_Surface *Bmp, int X1, int Y1, int W, int H, int Color) {
- SDL_Rect A = {X1, Y1, W, H};
- SDL_FillRect(Bmp, &A, Color);
- }
- void inline blit(SDL_Surface* SrcBmp, SDL_Surface* DstBmp, int SourceX, int SourceY, int DestX, int DestY, int Width, int Height) {
- SDL_Rect A = {SourceX, SourceY, Width, Height};
- SDL_Rect B = {DestX, DestY};
- SDL_BlitSurface(SrcBmp, &A, DstBmp, &B);
- }
- void inline clear_to_color(SDL_Surface *Bmp, int Color) {
- SDL_FillRect(Bmp, NULL, Color);
- }
- enum DataRegister {
- REG_A, REG_B, REG_C, REG_X, REG_Y, REG_Z, REG_I, REG_J
- };
- typedef struct DCPU {
- u16 DataRegister[8]; // A, B, C, X, Y, Z, I or J
- u16 PC, SP, EX, IA, IAQ;
- u32 Cycles;
- u16 RAM[0x10000];
- u16 WriteConst; // where writes to constants go
- int HardwareCount;
- void *Hardware[8];
- } DCPU;
- void DCPU_IRQ(DCPU *VM, u16 Message);
- typedef struct Hardware {
- u32 Id;
- u16 Version;
- u32 Manufacturer;
- int (*Update)(DCPU *VM, struct Hardware *Hard);
- int (*Interrupt)(DCPU *VM, struct Hardware *Hard);
- int (*WriteRAM)(DCPU *VM, u16 Location, u16 Value);
- int (*Create)(DCPU *VM);
- int (*Destroy)(DCPU *VM);
- void *Storage;
- } Hardware;
- // until I put in multiple screen support or multiple keyboard support this will do
- u16 ClockRate = 0;
- u16 ClockRealticks = 0;
- u16 ClockTicks = 0;
- u16 ClockInterrupt = 0;
- u16 MonitorTilemap = 0;
- u16 MonitorFont = 0;
- u16 MonitorPalette = 0;
- u16 MonitorBorder = 0;
- //u16 Keyboard
- const u16 DefaultFont[] = {
- 0xff9e, 0xff8e, 0x722c, 0xfff4, 0xffbb, 0xff8f, 0xfff9, 0xb158, 0x242e, 0x2400, 0x082a, 0x0800,
- 0x0008, 0x0000, 0x0808, 0x0808, 0xffff, 0x0000, 0xfff8, 0x0808, 0xfff8, 0x0000, 0x080f, 0x0000,
- 0x000f, 0x0808, 0xffff, 0x0808, 0xfff8, 0x0808, 0xffff, 0x0000, 0x080f, 0x0808, 0xffff, 0x0808,
- 0x6633, 0xffcc, 0x9933, 0xffcc, 0xfff8, 0xff80, 0x7f1f, 0x0701, 0x0107, 0x1f7f, 0xffe0, 0xfffe,
- 0x5500, 0xaa00, 0xffaa, 0xffaa, 0xffaa, 0xff55, 0x0f0f, 0x0f0f, 0xfff0, 0xfff0, 0x0000, 0xffff,
- 0xffff, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x005f, 0x0000, 0x0300, 0x0300, 0x3e14, 0x3e00,
- 0x266b, 0x3200, 0x611c, 0x4300, 0x3629, 0x7650, 0x0002, 0x0100, 0x1c22, 0x4100, 0x4122, 0x1c00,
- 0x1408, 0x1400, 0x081c, 0x0800, 0x4020, 0x0000, 0x0808, 0x0800, 0x0040, 0x0000, 0x601c, 0x0300,
- 0x3e49, 0x3e00, 0x427f, 0x4000, 0x6259, 0x4600, 0x2249, 0x3600, 0x0f08, 0x7f00, 0x2745, 0x3900,
- 0x3e49, 0x3200, 0x6119, 0x0700, 0x3649, 0x3600, 0x2649, 0x3e00, 0x0024, 0x0000, 0x4024, 0x0000,
- 0x0814, 0x2200, 0x1414, 0x1400, 0x2214, 0x0800, 0x0259, 0x0600, 0x3e59, 0x5e00, 0x7e09, 0x7e00,
- 0x7f49, 0x3600, 0x3e41, 0x2200, 0x7f41, 0x3e00, 0x7f49, 0x4100, 0x7f09, 0x0100, 0x3e41, 0x7a00,
- 0x7f08, 0x7f00, 0x417f, 0x4100, 0x2040, 0x3f00, 0x7f08, 0x7700, 0x7f40, 0x4000, 0x7f06, 0x7f00,
- 0x7f01, 0x7e00, 0x3e41, 0x3e00, 0x7f09, 0x0600, 0x3e61, 0x7e00, 0x7f09, 0x7600, 0x2649, 0x3200,
- 0x017f, 0x0100, 0x3f40, 0x7f00, 0x1f60, 0x1f00, 0x7f30, 0x7f00, 0x7708, 0x7700, 0x0778, 0x0700,
- 0x7149, 0x4700, 0x007f, 0x4100, 0x031c, 0x6000, 0x417f, 0x0000, 0x0201, 0x0200, 0xff80, 0x8000,
- 0x0001, 0x0200, 0x2454, 0x7800, 0x7f44, 0x3800, 0x3844, 0x2800, 0x3844, 0x7f00, 0x3854, 0x5800,
- 0x087e, 0x0900, 0x4854, 0x3c00, 0x7f04, 0x7800, 0x047d, 0x0000, 0x2040, 0x3d00, 0x7f10, 0x6c00,
- 0x017f, 0x0000, 0x7c18, 0x7c00, 0x7c04, 0x7800, 0x3844, 0x3800, 0x7c14, 0x0800, 0x0814, 0x7c00,
- 0x7c04, 0x0800, 0x4854, 0x2400, 0x043e, 0x4400, 0x3c40, 0x7c00, 0x1c60, 0x1c00, 0x7c30, 0x7c00,
- 0x6c10, 0x6c00, 0x4c50, 0x3c00, 0x6454, 0x4c00, 0x0836, 0x4100, 0x0077, 0x0000, 0x4136, 0x0800,
- 0x0201, 0x0201, 0x0205, 0x0200,
- };
- const u16 DefaultPalette[] = {
- 0x0000, 0x000a, 0x00a0, 0x00aa, 0x0a00, 0x0a0a, 0x0a50, 0x0aaa,
- 0x0555, 0x055f, 0x05f5, 0x05ff, 0x0f55, 0x0f5f, 0x0ff5, 0x0fff,
- };
- int TextMonitorUpdate(DCPU *VM, Hardware *Hard) {
- int cx, cy, px, py, i;
- int Colors[16];
- if(!MonitorTilemap)
- return 0;
- const u16 *Tilemap = &VM->RAM[MonitorTilemap];
- const u16 *Palette = DefaultPalette;
- if(MonitorPalette)
- Palette = &VM->RAM[MonitorPalette];
- for(i=0;i<16;i++) // prerender palette
- Colors[i] = SDL_MapRGB(screen->format, ((Palette[i]>>8)&15)<<4, ((Palette[i]>>4)&15)<<4, (Palette[i]&15)<<4);
- const u16 *Font = DefaultFont;
- if(MonitorFont)
- Font = &VM->RAM[MonitorFont];
- for(cy=0;cy<12;cy++)
- for(cx=0;cx<32;cx++) {
- u16 Char = Tilemap[cy*32+cx];
- u16 FG = (Char >> 12) & 15; // seems to be backwards when I don't do this?
- u16 BG = (Char >> 8) & 15;
- u16 T = Char & 127;
- const u16 *Ptr = Font+T*2;
- char Col[4] = {Ptr[0]>>8, Ptr[0]&255, Ptr[1]>>8, Ptr[1]&255};
- for(px=0;px<4;px++)
- for(py=0;py<8;py++)
- rectfillwh(screen, (cx*4+px)*2, (cy*8+py)*2, 2, 2, ((Col[px]<<(7-py))&0x80)?Colors[FG]:Colors[BG]);
- }
- return 1;
- }
- int TextMonitorIRQ(DCPU *VM, Hardware *Hard) {
- int i;
- switch(VM->DataRegister[REG_A]) {
- case 0:
- MonitorTilemap = VM->DataRegister[REG_B];
- break;
- case 1:
- MonitorFont = VM->DataRegister[REG_B];
- break;
- case 2:
- MonitorPalette = VM->DataRegister[REG_B];
- break;
- case 3:
- MonitorBorder = VM->DataRegister[REG_B];
- break;
- case 4:
- VM->Cycles+=256;
- for(i=0;i<256;i++)
- VM->RAM[(i+VM->DataRegister[REG_B]) & 0xffff] = DefaultFont[i];
- break;
- case 5:
- VM->Cycles+=16;
- for(i=0;i<16;i++)
- VM->RAM[(i+VM->DataRegister[REG_B]) & 0xffff] = DefaultPalette[i];
- break;
- }
- return 1;
- }
- int GenericKeyboardIRQ(DCPU *VM, Hardware *Hard) {
- switch(VM->DataRegister[REG_A]) {
- }
- return 1;
- }
- int GenericClockUpdate(DCPU *VM, Hardware *Hard) {
- if(!ClockRate)
- return 0;
- ClockRealticks++;
- if(ClockRate == ClockRealticks) {
- ClockRealticks = 0;
- ClockTicks = 0;
- if(ClockInterrupt)
- DCPU_IRQ(VM, ClockInterrupt);
- }
- return 1;
- }
- int GenericClockIRQ(DCPU *VM, Hardware *Hard) {
- switch(VM->DataRegister[REG_A]) {
- case 0:
- ClockRate = VM->DataRegister[REG_B];
- ClockTicks = 0;
- ClockRealticks = 0;
- break;
- case 1:
- VM->DataRegister[REG_C] = ClockTicks;
- break;
- case 2:
- ClockInterrupt = VM->DataRegister[REG_B];
- break;
- }
- return 1;
- }
- Hardware TextMonitor = {
- 0x7349f615, 0x1802, 0x1c6c8b36,
- TextMonitorUpdate,
- TextMonitorIRQ,
- NULL,
- NULL, NULL,
- };
- Hardware GenericKeyboard = {
- 0x30cf7406, 0x0001, 0x00000000,
- NULL,
- GenericKeyboardIRQ,
- NULL,
- NULL, NULL,
- };
- Hardware GenericClock = {
- 0x12d0b402, 0x0001, 0x00000000,
- GenericClockUpdate,
- GenericClockIRQ,
- NULL,
- NULL, NULL,
- };
- struct DCPU VM = {
- {0,0,0,0,0,0,0,0}, // registers
- 0, 0xffff, 0, 0, 0, // more registers
- 0, // cycles
- {0}, // ram
- 0, // constant
- 3, // hardware
- {&GenericKeyboard,&TextMonitor,&GenericClock,NULL}
- };
- u16 DCPU_NextWord(DCPU *VM) {
- return VM->RAM[++VM->PC];
- }
- u16 DCPU_ReadWord(DCPU *VM, u16 Address) {
- return VM->RAM[Address&0xffff];
- }
- u16 DCPU_ValParam(DCPU *VM, u16 Param) {
- switch(Param) {
- case 0x00 ... 0x07:
- return VM->DataRegister[Param&7];
- case 0x08 ... 0x0f:
- return VM->RAM[MASK_16(VM->DataRegister[Param&7])];
- case 0x10 ... 0x17: {
- VM->Cycles++;
- return VM->RAM[MASK_16(DCPU_NextWord(VM)+VM->DataRegister[Param&7])];
- }
- case 0x18: // pop
- return VM->RAM[MASK_16(VM->SP++)];
- case 0x19:
- return VM->RAM[MASK_16(VM->SP)];
- case 0x1a: {
- VM->Cycles++;
- return VM->RAM[MASK_16(VM->SP+DCPU_NextWord(VM))];
- }
- case 0x1b:
- return VM->SP;
- case 0x1c:
- return VM->PC;
- case 0x1d:
- return VM->EX;
- case 0x1e: {
- VM->Cycles++;
- return VM->RAM[MASK_16(DCPU_NextWord(VM))];
- }
- case 0x1f: {
- VM->Cycles++;
- return DCPU_NextWord(VM);
- }
- default:
- return Param-32-1;
- }
- }
- u16 *DCPU_PtrParam(DCPU *VM, u16 Param) {
- switch(Param) {
- case 0x00 ... 0x07:
- return &VM->DataRegister[Param&7];
- case 0x08 ... 0x0f:
- return &VM->RAM[MASK_16(VM->DataRegister[Param&7])];
- case 0x10 ... 0x17: {
- VM->Cycles++;
- return &VM->RAM[MASK_16(DCPU_NextWord(VM)+VM->DataRegister[Param&7])];
- }
- case 0x18: // push
- return &VM->RAM[MASK_16(--VM->SP)];
- case 0x19:
- return &VM->RAM[MASK_16(VM->SP)];
- case 0x1a: {
- VM->Cycles++;
- return &VM->RAM[MASK_16(VM->SP+DCPU_NextWord(VM))];
- }
- case 0x1b:
- return &VM->SP;
- case 0x1c:
- return &VM->PC;
- case 0x1d:
- return &VM->EX;
- case 0x1e: {
- VM->Cycles++;
- return &VM->RAM[MASK_16(DCPU_NextWord(VM))];
- }
- case 0x1f: {
- VM->Cycles++;
- VM->WriteConst = DCPU_NextWord(VM);
- return &VM->WriteConst;
- }
- default: // should not happen
- return NULL;
- }
- }
- void DCPU_IRQ(DCPU *VM, u16 Message) {
- VM->RAM[--VM->SP] = VM->PC + 1;
- VM->RAM[--VM->SP] = VM->DataRegister[REG_A];
- VM->PC = VM->IA - 1;
- VM->DataRegister[REG_A] = Message;
- };
- int DCPU_OpSize(u16 Opcode) {
- int Size = 1;
- u16 Func = Opcode & 31;
- u16 ParA = (Opcode>>10) & 63;
- u16 ParB = (Opcode>>5) & 31;
- const char Increase[] = {0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,1,0,0,0,1,1};
- if((ParA < 0x20) && Increase[ParA]) Size++;
- if(Func)
- if((ParB < 0x20) && Increase[ParB]) Size++;
- // printf("size %i\n", Size);
- return Size;
- }
- void DCPU_Step(DCPU *VM) {
- // aaaaaabbbbbooooo
- u16 Op = VM->RAM[VM->PC];
- u16 Func = Op & 31;
- u16 ParA, ParB;
- ParA = (Op>>10) & 63;
- ParB = (Op>>5) & 31;
- // both the source and destination can add another word, so order matters
- // printf("PC %.3i, AC %.4x - %.2x: %.2x %.2x \n", VM->PC, VM->DataRegister[REG_A], Func, ParA, ParB);
- u16 Val, *Dst;
- if(Func) {
- Val = DCPU_ValParam(VM, ParA);
- Dst = DCPU_PtrParam(VM, ParB);
- } else {
- Val = 0;
- Dst = DCPU_PtrParam(VM, ParA);
- }
- Hardware *Hard;
- signed short TempUnsigned;
- signed short TempSigned, TempSigned2;
- int Skipped = 0;
- switch(Func) {
- case 0x00: // extended opcode
- switch(ParB) {
- case 0x00:
- puts("Invalid opcode");
- exit(0);
- default:
- printf("Unsupported extended opcode 0x%.2x", ParB);
- exit(0);
- case 0x01: // jsr
- VM->Cycles+=3;
- VM->RAM[--VM->SP] = VM->PC + 1;
- VM->PC = (*Dst) - 1;
- break;
- case 0x08: // int, software interrupt with message a
- VM->Cycles+=4;
- DCPU_IRQ(VM, *Dst);
- break;
- case 0x09: // iag
- VM->Cycles++;
- *Dst = VM->IA;
- break;
- case 0x0a: // ias
- VM->Cycles++;
- VM->IA = *Dst;
- break;
- case 0x0b: // rfi
- VM->Cycles+=3;
- VM->DataRegister[REG_A] = VM->RAM[VM->SP++];
- VM->PC = VM->RAM[VM->SP++];
- VM->IAQ = 0;
- break;
- case 0x0c: // iaq
- VM->Cycles+=2;
- VM->IAQ = 1;
- break;
- case 0x10: // hwn
- VM->Cycles+=2;
- TempUnsigned = 0;
- *Dst = VM->HardwareCount;
- break;
- case 0x11: // hwq
- VM->Cycles+=4;
- Hard = (Hardware*)VM->Hardware[*Dst];
- VM->DataRegister[REG_A] = Hard->Id&0xffff;
- VM->DataRegister[REG_B] = Hard->Id>>16;
- VM->DataRegister[REG_C] = Hard->Version;
- VM->DataRegister[REG_X] = Hard->Manufacturer&0xffff;
- VM->DataRegister[REG_Y] = Hard->Manufacturer>>16;
- break;
- case 0x12: // hwi
- VM->Cycles+=4;
- Hard = (Hardware*)VM->Hardware[*Dst];
- Hard->Interrupt(VM, Hard);
- break;
- }
- break;
- case 0x01: // set b,a
- VM->Cycles++;
- *Dst = Val;
- if(Dst == &VM->PC) (*Dst)--;
- break;
- case 0x02: // add b,a
- VM->Cycles+=2;
- *Dst += Val;
- break;
- case 0x03: // sub b,a
- VM->Cycles+=2;
- *Dst -= Val;
- break;
- case 0x04: // mul b,a
- VM->Cycles+=2;
- *Dst *= Val;
- break;
- case 0x05: // mli b,a <--- signed
- VM->Cycles+=2;
- TempSigned = (signed)*Dst;
- TempSigned *= (signed)Val;
- *Dst = (unsigned)TempSigned;
- break;
- case 0x06: // div b,a
- VM->Cycles+=3;
- *Dst /= (signed)Val;
- break;
- case 0x07: // dvi b,a <--- signed
- VM->Cycles+=3;
- TempSigned = (signed)*Dst;
- TempSigned /= (signed)Val;
- *Dst = (unsigned)TempSigned;
- break;
- case 0x08: // mod b,a
- VM->Cycles+=3;
- *Dst %= (signed)Val;
- break;
- case 0x09: // mdi b,a <--- signed
- VM->Cycles+=3;
- TempSigned = (signed)*Dst;
- TempSigned %= (signed)Val;
- *Dst = (unsigned)TempSigned;
- break;
- case 0x0a: // and b,a
- VM->Cycles++;
- *Dst &= Val;
- break;
- case 0x0b: // bor b,a
- VM->Cycles++;
- *Dst |= Val;
- break;
- case 0x0c: // xor b,a
- VM->Cycles++;
- *Dst ^= Val;
- break;
- case 0x0d: // shr b,a
- VM->Cycles++;
- *Dst >>= Val;
- break;
- case 0x0e: // asr b,a
- VM->Cycles++;
- TempUnsigned = *Dst;
- *Dst >>= Val;
- *Dst |= TempUnsigned & 0x8000;
- break;
- case 0x0f: // shl b,a
- VM->Cycles++;
- *Dst <<= Val;
- break;
- case 0x10: // ifb b,a
- VM->Cycles+=2;
- Skipped = !((*Dst&Val)!=0);
- break;
- case 0x11: // ifc b,a
- VM->Cycles+=2;
- Skipped = !((*Dst&Val)==0);
- break;
- case 0x12: // ife b,a
- VM->Cycles+=2;
- Skipped = (*Dst!=Val);
- break;
- case 0x13: // ifn b,a
- VM->Cycles+=2;
- Skipped = (*Dst==Val);
- break;
- case 0x14: // ifg b,a
- VM->Cycles+=2;
- Skipped = (*Dst<=Val);
- break;
- case 0x15: // ifa b,a <--- signed
- VM->Cycles+=2;
- TempSigned = *Dst;
- TempSigned2 = Val;
- Skipped = (TempSigned<=TempSigned2);
- break;
- case 0x16: // ifl b,a
- VM->Cycles+=2;
- Skipped = (*Dst>=Val);
- break;
- case 0x17: // ifu b,a <--- signed
- VM->Cycles+=2;
- TempSigned = *Dst;
- TempSigned2 = Val;
- Skipped = (TempSigned>=TempSigned2);
- break;
- case 0x1a: // adx b,a
- VM->Cycles+=3;
- *Dst = *Dst + Val + VM->EX;
- break;
- case 0x1b: // sbx b,a
- VM->Cycles+=3;
- *Dst = *Dst - Val + VM->EX;
- break;
- case 0x1e: // sti b,a
- VM->DataRegister[REG_I]++;
- VM->DataRegister[REG_J]++;
- VM->Cycles+=2;
- *Dst = Val;
- break;
- case 0x1f: // std b,a
- VM->DataRegister[REG_I]--;
- VM->DataRegister[REG_J]--;
- VM->Cycles+=2;
- *Dst = Val;
- break;
- default:
- break;
- }
- if(Skipped) {
- VM->PC++;
- VM->Cycles++;
- // skip if "if" opcodes found
- // printf("skipped onto %.4x \n", VM->RAM[VM->PC]);
- while(((VM->RAM[VM->PC]&31) >= 0x10) && ((VM->RAM[VM->PC]&31) <= 0x17)) {
- VM->Cycles++;
- VM->PC+= DCPU_OpSize(VM->RAM[VM->PC]);
- // printf("leaving off on %.4x \n", VM->RAM[VM->PC]);
- }
- VM->PC+= DCPU_OpSize(VM->RAM[VM->PC]); // skip the next opcode, after IFs or not
- } else {
- VM->PC++;
- }
- }
- void EndFrame() {
- SDL_Event event;
- while(SDL_PollEvent(&event)) {
- if(event.type == SDL_QUIT)
- quit=1;
- }
- SDL_Flip(screen);
- SDL_Delay(DELAY_TIME);
- Retraces++;
- }
- int main( int argc, char* args[] ) {
- char TestFile[512] = "hello.bin";
- if(argc>=2)
- strcpy(TestFile, args[1]);
- if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
- puts("failed to init");
- return -1;
- }
- freopen("CON", "w", stdout);
- freopen("CON", "w", stderr);
- screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
- MonitorScreen = SDL_CreateRGBSurface(SDL_SWSURFACE, 128,96, SCREEN_BPP, 0,0,0,0);
- SDL_WM_SetCaption("Doritos16", NULL);
- FILE *ProgramFile = fopen(TestFile,"rb");
- int Fill = 0;
- if(ProgramFile == NULL) {
- puts("Can't open the file");
- return 0;
- }
- while(!feof(ProgramFile)) {
- unsigned char k = fgetc(ProgramFile);
- VM.RAM[Fill++] = (k<<8) | fgetc(ProgramFile);
- //printf("%.4x\n", VM.RAM[Fill-1]);
- }
- fclose(ProgramFile);
- int i;
- int OpsRun = 0;
- while(!quit) {
- while(VM.Cycles < CYCLES_PER_DELAY) { // ends up running about 99960 cycles/second instead of 100khz
- DCPU_Step(&VM);
- OpsRun++;
- // if(OpsRun == 30)
- // return 0;
- }
- EndFrame();
- VM.Cycles -= CYCLES_PER_DELAY;
- for(i=0;i<VM.HardwareCount;i++) {
- Hardware *Hard = (Hardware*)VM.Hardware[i];
- if(Hard->Update)
- Hard->Update(&VM, Hard);
- // printf("%i\n", VM.PC);
- }
- }
- SDL_Quit();
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement