-
Notifications
You must be signed in to change notification settings - Fork 0
/
Gameboy.js
3936 lines (3708 loc) · 133 KB
/
Gameboy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function Gameboy() {
// 1 Machine Cycle = 4 Clock Cycles
var ths = this;
// Graphics
// Use Tiles and Sprites - Tiles are 8x8 pixels
// The screen can display 160x144 but the real resolution of the screen is
// 256x256 (32x32 tiles) to allow for scrolling in and out of the screen
// There is also a window between background (tiles) and sprites. This is
// a fixed panel that doesn't scroll with the background and can be used to
// display stuff like health or scores
this.SCREEN_HEIGHT = 256;
this.SCREEN_WIDTH = 256;
this.VISIBLE_WIDTH = 160;
this.VISIBLE_HEIGHT = 144;
this.screenData = [];
// Flag for if STOP occurred - halt CPU and LCD display
this.cpuStopped = false;
// Flag for halted CPU
this.halted = false;
// Flag Bits in Register F
this.ZERO_BIT = 7;
this.SUBTRACT_BIT = 6;
this.HALF_CARRY_BIT = 5;
this.CARRY_BIT = 4;
// Time Stuff
this.timerCounter = 1024; // initial value, frequency 4096 (4194304/4096)
this.dividerCounter = 0;
this.isClockEnabled = true;
// Timer Memory Address Constants
this.DIVIDER_REGISTER_ADDR = 0xFF04 // The divider register is located here
this.TIMER_ADDR = 0xFF05; // The time is located here and counts up at a set interval
this.TIMER_MODULATOR_ADDR = 0xFF06; // Timer modulator that timer resets to on overflow is here
// Timer Controller is 3-bit that controls timer and specifies frequency.
// The 1st 2 bits describe frequency. Here is the mapping:
// 00: 4096 Hz
// 01: 262144 Hz
// 10: 65536 Hz
// 11: 16384 Hz
//
// The third bit specifies if the timer is enabled (1) or disabled (0)
// This is the memory address that the controller is stored at
this.TIMER_CONTROLLER_ADDR = 0xFF07;
// There are 4 types of interrupts that can occur and the following are the bits
// that are set in the enabled register and request register when they occur
// Note: the lower the bit, the higher priority of the interrupt
// Bit 0: V-Blank Interupt
// Bit 1: LCD Interupt
// Bit 2: Timer Interupt
// Bit 4: Joypad Interupt
//
// Interrupt Register Address Constants
this.INTERRUPT_ENABLED_ADDR = 0xFFFF;
this.INTERRUPT_REQUEST_ADDR = 0xFF0F;
// Interrupt enabled switch, if this is off, then no interrupts are serviced
this.interruptsEnabled = true;
// Used by instruction DI to determine if interrupts will be disabled
this.toDisableInterrupts = -1;
// User by instruction EI to determine if interrupts will be enabled
this.toEnableInterrupts = -1;
// counter to know when a scanline has finished drawing and it is time to move
// onto the next scanline. It takes 456 clock cycles to draw a scanline
this.scanlineCounter = 456;
// Scanline Constants
this.CURRENT_SCANLINE_ADDR = 0xFF44;
this.LCD_STATUS_ADDR = 0xFF41;
// The LCD Control Register is VERY IMPORTANT. Here is what each bit represents
// Bit 7 - LCD Display Enable (0=Off, 1=On)
// Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
// Bit 5 - Window Display Enable (0=Off, 1=On)
// Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF)
// Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
// Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16)
// Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On)
// Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
//
// THis is The address of the register
this.LCD_CONTROL_ADDR = 0xFF40;
// Memory Management Unit
this.mmu = new MMU();
this.registers = {
// 8-bit Registers (Can be 0 - 255)
A: 0,
B: 0,
C: 0,
D: 0,
E: 0,
F: 0,
H: 0,
L: 0,
// 16-bit Registers
PC: 0, // Program Counter
SP: 0, // Stack Pointer
};
this.initialize = function() {
ths.halted = false;
// Set init values of PC and SP to these specified values from GB Docs
ths.registers.PC = 0x100;
ths.registers.SP = 0xFFFE;
// Initial values of registers from Docs - Word Pairs should look like the following:
// AF=0x01B0;
// BC=0x0013;
// DE=0x00D8;
// HL=0x014D;
ths.registers.A = 0x01;
ths.registers.F = 0xB0;
ths.registers.B = 0x00;
ths.registers.C = 0x13;
ths.registers.D = 0x00;
ths.registers.E = 0xD8;
ths.registers.H = 0x01;
ths.registers.L = 0x4D;
// Initialize Graphics
for (var i = 0; i < ths.VISIBLE_WIDTH; i++) {
ths.screenData[i] = new Array();
for (var j = 0; j < ths.VISIBLE_HEIGHT; j++) {
ths.screenData[i][j] = new Array(3);
ths.screenData[i][j][0] = 0;
ths.screenData[i][j][1] = 0;
ths.screenData[i][j][2] = 0;
}
}
// Initialize memory
ths.mmu.initialize();
};
this.loadProgram = function(data) {
ths.mmu.setCartridgeData(data);
};
this.debug = 0;
this.executeOpcode = function() {
var cycles = 0;
if (!ths.halted) {
// Fetch the next operation that the program counter points too.
var nextOp = ths.mmu.read(ths.registers.PC);
if (ths.debug < 300) {
// console.log(nextOp.toString(16) + '\n');
}
// Execute the operation
cycles = ths.executeOperation(nextOp);
// Increment the program counter
ths.registers.PC++;
ths.debug++;
} else {
cycles = 4;
}
// Enable or disable interrupts
if (ths.toDisableInterrupts >= 0) {
ths.toDisableInterrupts += 1;
if (ths.toDisableInterrupts === 2) {
ths.interruptsEnabled = false;
ths.toDisableInterrupts = -1;
}
}
if (ths.toEnableInterrupts >= 0) {
ths.toEnableInterrupts += 1;
if (ths.toEnableInterrupts === 2) {
ths.interruptsEnabled = true;
ths.toEnableInterrupts = -1;
}
}
return cycles;
};
ths.pushToStack = function(data) {
ths.registers.SP--;
ths.mmu.write(ths.registers.SP, data);
}
ths.popFromStack = function() {
var data = ths.mmu.read(ths.registers.SP);
ths.registers.SP++;
return data;
}
this.requestInterrupt = function(bit) {
// bit = 0: V-Blank Interrupt
// bit = 1: LCD Interrupt
// bit = 2: Timer Interrupt
// bit = 4: Joypad Interrupt
// Make sure we only flip the one requested bit without messing up the others
var currentRegisterVal = ths.mmu.read(ths.INTERRUPT_REQUEST_ADDR);
switch (bit) {
case 0:
currentRegisterVal |= 1;
break;
case 1:
currentRegisterVal |= 2;
break;
case 2:
currentRegisterVal |= 4;
break;
case 4:
currentRegisterVal |= 16;
break;
}
ths.mmu.write(ths.INTERRUPT_REQUEST_ADDR, currentRegisterVal);
};
this.doInterrupts = function() {
// Check to see if interrupts are enabled
// Check if interrupts are requested in order of priority
// Check if interrupt that is request is enabled and if so it runs it
if (ths.interruptsEnabled) {
var requestedInterrupts = ths.mmu.read(ths.INTERRUPT_REQUEST_ADDR);
var enabledInterrupts = ths.mmu.read(ths.INTERRUPT_ENABLED_ADDR);
if (requestedInterrupts > 0) {
// Only need to check bits if we know any of them are set
for (var i = 0; i < 5; i++) {
if (ths.checkInterruptBitSet(i, requestedInterrupts)) {
if (ths.checkInterruptBitSet(i, enabledInterrupts)) {
ths.runInterrupt(i);
}
}
}
}
}
};
this.checkInterruptBitSet = function(bit, val) {
switch (bit) {
case 0:
return 1 & val;
break;
case 1:
return 2 & val;
break;
case 2:
return 4 & val;
break;
case 4:
return 16 & val;
break;
}
};
this.runInterrupt = function(interrupt) {
// The requested interrupt bit is performed
// Interrupt operations are found in the following locations in game memory
// V-Blank: 0x40
// LCD: 0x48
// TIMER: 0x50
// JOYPAD: 0x60
// Interrupt happened, un-halt CPU
ths.halted = false;
// We need to flip the master interrupt switch off and then turn off the
// bit in the interrupt request register for the interrupt we are running
ths.interruptsEnabled = false;
var requestedValue = ths.mmu.read(ths.INTERRUPT_REQUEST_ADDR);
// XOR will turn off the bits because we know it is set in the register
// It will leave the other ones intact as they are XOR-ing with 0
switch (interrupt) {
case 0:
requestedValue ^= 1;
break;
case 1:
requestedValue ^= 2;
break;
case 2:
requestedValue ^= 4;
break;
case 4:
requestedValue ^= 16;
break;
}
ths.mmu.write(ths.INTERRUPT_REQUEST_ADDR, requestedValue);
// We now need to save the current PC by pushing it on the stack
// Then set the PC to the address of the requested interrupt
ths.pushToStack(ths.registers.PC >> 8);
ths.pushToStack(ths.registers.PC & 0xFF);
switch (interrupt) {
case 0:
ths.registers.PC = 0x40;
break;
case 1:
ths.registers.PC = 0x48;
break;
case 2:
ths.registers.PC = 0x50;
break;
case 4:
ths.registers.PC = 0x60;
break;
}
};
this.updateTimers = function(cycles) {
// We should set the clock frequency right here in case it was just changed
// by the game (something wrote to address 0xFF07)
ths.setClockFrequency();
// The Divider Register counts up continuously from 0 to 255
// Overflow causes it to reset to 0
// It can't be paused by isClockEnabled and counts up at frequency of 16382 hz
// which is every 256 clock cycles
ths.incrementDividerRegister(cycles);
// The clock can be disabled so make sure it is enabled before updating anything
if (ths.isClockEnabled()) {
// Update based on how many cycles passed
// The timer increments when this hits 0 as that is based on the
// frequency in which the timer should increment
ths.timerCounter -= cycles;
if (ths.timerCounter <= 0) {
// We need to reset the counter value so timer can increment again at the
// correct frequenct
ths.setClockFrequency();
// Need to account for overflow - if overflow then we can write the value
// that is held in the modulator addr and request Timer Interrupt which is
// bit 2 of the interrupt register in memory
// Otherwise we can just increment the timer
var currentTimerValue = ths.mmu.read(ths.TIMER_ADDR);
if (ths.mmu.read(ths.TIMER_ADDR) == 255) {
ths.mmu.write(ths.TIMER_ADDR, ths.mmu.read(ths.TIMER_MODULATOR_ADDR));
ths.requestInterrupt(2);
} else {
ths.mmu.write(ths.TIMER_ADDR, currentTimerValue + 1);
}
}
}
};
this.incrementDividerRegister = function(cycles) {
ths.dividerCounter += cycles;
if (ths.dividerCounter >= 255) {
ths.dividerCounter = 0;
var currentDividerValue = ths.mmu.read(ths.DIVIDER_REGISTER_ADDR);
if (currentDividerValue === 255) {
ths.mmu.memory[ths.DIVIDER_REGISTER_ADDR] = 0;
} else {
ths.mmu.memory[ths.DIVIDER_REGISTER_ADDR] = currentDividerValue + 1;
}
}
};
this.isClockEnabled = function() {
var timerController = ths.mmu.read(ths.TIMER_CONTROLLER_ADDR);
// this is the second bit of the controller, which specifies enabled or disabled
return timerController & 4;
};
this.getClockFrequency = function() {
// We only care about the first 2 bits to find out the frequency
return ths.mmu.read(ths.TIMER_CONTROLLER_ADDR) & 3;
};
this.setClockFrequency = function() {
// ths.timerCounter will be equal to clockspeed(4194304)/frequency
var frequency = ths.getClockFrequency();
switch (frequency) {
case 0:
// frequency 4096
ths.timerCounter = 1024;
break;
case 1:
// frequency 262144
ths.timerCounter = 16;
break;
case 2:
// frequency 65536
ths.timerCounter = 64;
break;
case 3:
// frequency 16382
ths.timerCounter = 256;
break;
}
};
this.updateGraphics = function(cycles) {
// Deal with setting LCD status
ths.setLcdStatus();
// If LCD Display is enabled, decerement counter by number of cycles
// Otherwise do nothing
if (ths.isLcdDisplayEnabled()) {
ths.scanlineCounter -= cycles;
} else {
return;
}
// If scanline counter hit 0, we need to move onto the next scanline
// Current scanline is found in memory in 0xFF44
// We can't write to this memory location using write functionas doing so
// should cause the value here to be set to 0 so access the memory directly
// Scanline 0 - 143 (144 in total) need to be rendered onto the screen
// Scanline 144 - 153 is the Vertical Blank Period and we need to
// request the Vertical Blank Interrupt
// If Scanline is greater than 153, reset to 0
if (ths.scanlineCounter <= 0) {
var scanline = ths.mmu.read(ths.CURRENT_SCANLINE_ADDR);
scanline++;
ths.mmu.memory[ths.CURRENT_SCANLINE_ADDR] = scanline;
// Reset scanline counter
ths.scanlineCounter = 456;
if (scanline <= 143) {
ths.drawToScreen();
} else if (scanline == 144) {
// We only need to request this interrupt as we enter Vertical
// Blank period, not for every value in V-Blank so only check
// for first V-Blank scanline
ths.requestInterrupt(0); // 0 is the bit for V-Blank interrupt
} else if (scanline > 153) {
scanline = 0;
ths.mmu.memory[ths.CURRENT_SCANLINE_ADDR] = scanline;
}
}
};
this.isLcdDisplayEnabled = function() {
// Bit 7 of LCD Control Register tell us if the LCD is enabled or disabled
return ths.mmu.read(ths.LCD_CONTROL_ADDR) & parseInt("10000000", 2);
};
this.setLcdStatus = function() {
// LCD status is stored in memory address 0xFF41
// The first 2 bits represent the mode of the LCD and are as follows:
// 00 (0): Horizontal-Blank
// 01 (1): Vertical-Blank
// 10 (2): Searching Sprites Atts
// 11 (3): Transfering Data to LCD Driver
var currentLcdStatus = ths.mmu.read(ths.LCD_STATUS_ADDR);
var currentScanline = ths.mmu.read(ths.CURRENT_SCANLINE_ADDR);
// IMPORTANT: If LCD is disabled, then the LCD mode must be set to 1 (V-Blank)
// When doing this, make sure to reset the scanline counter and current scanline
if (!ths.isLcdDisplayEnabled()) {
// Set the status bits to 1 and write
currentLcdStatus &= parseInt("11111100", 2);
currentLcdStatus ^= parseInt("00000001", 2);
ths.mmu.write(ths.LCD_STATUS_ADDR, currentLcdStatus);
ths.scanlineCounter = 456;
ths.mmu.memory[ths.CURRENT_SCANLINE_ADDR] = 0;
return;
}
// Each scanline takes 456 clock cycles and this is further split up
// If within the first 80 cycles of the 456, we should be in mode 2
// If within the next 172 cycles of the 456, we should be in mode 3
// Past this point up to the end of the 456, we should be in mode 0
// If within V-Blank (scanline 144 - 153) we should be in mode 1
var mode = currentLcdStatus & 3; // This will give us the value of the first 2 bits
var newMode = mode;
// Interrupts are only enabled during mode change if the following bits are
// enabled in the status register when a mode is enabled
// Bit 3: Mode 0 Interupt Enabled
// Bit 4: Mode 1 Interupt Enabled
// Bit 5: Mode 2 Interupt Enabled
// Have a boolean to see if an interrupt should be enabled
var interruptEnabled = 0;
if (currentScanline >= 144) {
// Set mode to 1
currentLcdStatus &= parseInt("11111100", 2);
currentLcdStatus ^= parseInt("00000001", 2);
newMode = 1;
// Interrupt enabled if bit 4 set
interruptEnabled = currentLcdStatus & parseInt("00010000", 2);
} else {
if (ths.scanlineCounter >= (456 - 80) && ths.scanlineCounter <= 456) {
// Set mode to 2
currentLcdStatus &= parseInt("11111100", 2);
currentLcdStatus ^= parseInt("00000010", 2);
newMode = 2;
// Interrupt enabled if bit 5 set
interruptEnabled = currentLcdStatus & parseInt("00100000", 2);
} else if (ths.scanlineCounter >= (456 - 80 - 172) && ths.scanlineCounter < (456 - 80)) {
// Set mode to 3
currentLcdStatus &= parseInt("11111100", 2);
currentLcdStatus ^= parseInt("00000011", 2);
newMode = 3;
} else {
// Set mode to 0
currentLcdStatus &= parseInt("11111100", 2);
currentLcdStatus ^= parseInt("00000000", 2);
newMode = 0;
// Interrupt enabled if bit 3 set
interruptEnabled = currentLcdStatus & parseInt("00001000", 2);
}
}
// If the mode has changed to 0, 1, or 2 and the appropriate interrupt bit
// was set (interruptEnabled > 0), then we need to request LCD Interrupt
if (interruptEnabled > 0 && newMode !== 3 && newMode !== mode) {
ths.requestInterrupt(1);
}
// Bit 2 of Status register is Coincedence Flag
// This should be set to true if current scanline (0xFF44) is equal to
// value in register 0xFF45. Otherwise turn it off.
// If bit 6 is set in the Status register and the coincedence flag is turned
// on, then request an LCD Interrupt
if (currentScanline == ths.mmu.read(0xFF45)) {
currentLcdStatus |= parseInt("00000100", 2);
if (currentLcdStatus & parseInt("01000000", 2)) {
ths.requestInterrupt(1);
}
} else {
// currentLcdStatus &= parseInt("11111011", 2);
currentLcdStatus &= ~(1 << 2);
}
ths.mmu.write(ths.LCD_STATUS_ADDR, currentLcdStatus);
};
this.drawToScreen = function() {
// Recall from above, bits of LCD Control Register
// Bit 7 - LCD Display Enable (0=Off, 1=On)
// Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
// Bit 5 - Window Display Enable (0=Off, 1=On)
// Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF)
// Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
// Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16)
// Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On)
// Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
var lcdControlValue = ths.mmu.read(ths.LCD_CONTROL_ADDR);
// If bit 0 is set, then we draw the background tiles
if (lcdControlValue & parseInt("00000001", 2)) {
ths.drawTiles(lcdControlValue);
}
// If bit 1 is set, then we draw the sprites
if (lcdControlValue & parseInt("00000010", 2)) {
ths.drawSprites(lcdControlValue);
}
};
this.drawTiles = function(lcdControlValue) {
// Important Memory addresses for drawing background and window which
// are necessary to know because the background (256x256) is bigger than
// the viewing area (160x144)
// ScrollY (0xFF42): The Y Position of the BACKGROUND where to start
// drawing the viewing area from
// ScrollX (0xFF43): The X Position of the BACKGROUND to start drawing
// the viewing area from
// WindowY (0xFF4A): The Y Position of the VIEWING AREA to start drawing
// the window from
// WindowX (0xFF4B): The X Positions -7 of the VIEWING AREA to start drawing
// the window from
var scrollY = ths.mmu.read(0xFF42);
var scrollX = ths.mmu.read(0xFF43);
var windowY = ths.mmu.read(0xFF4A);
var windowX = ths.mmu.read(0xFF4B) - 7;
// Each tile is 8x8 pixels which is represented by 16 bytes of in memory
// because each line of the tile is 2 bytes
var sizeOfTileInBytes = 16;
// We need Tile Identification numbers that are used to lookup tile data
// These can be in memory ranges 9800-9BFF or 9C00-9FFF for both the background
// and the window. Check bit 3 of lcd control register to see what region to use
// for the background and bit 6 for the window. Value of 0 for the bit
// means 9800--9BFF and 1 indicates 9C00-9FFF
var backgroundIdentificationRegion = lcdControlValue & parseInt("00001000", 2);
var windowIdentificationRegion = lcdControlValue & parseInt("01000000", 2);
var currentScanline = ths.mmu.read(ths.CURRENT_SCANLINE_ADDR);
// We only draw the window if it's enabled (bit 5)
var isWindowEnabled = lcdControlValue & parseInt("00100000", 2);
var drawWindow = false;
if (isWindowEnabled) {
// We need to see if the current scanline we are drawing is even
// within the Y position of the window. Otherwise we have no window
// to draw
if (windowY <= currentScanline) {
drawWindow = true;
}
}
// Tile data is in one of two regions based on bit 4. We need to figure this out
// If the region is 8800-97FF then the tile identification number is SIGNED and
// the value is between -127 and 127
// If the region is 8000-8FFF then the tile identification number is UNSIGNED and
// the value is between 0 and 255
var bitFourValue = lcdControlValue & parseInt("00010000", 2);
var tileDataRegionStart = null;
var unsigned = true;
if (bitFourValue) {
tileDataRegionStart = 0x8000;
} else {
tileDataRegionStart = 0x8800;
unsigned = false;
}
// We need to find out which region the tile identification number is in
// Remember that which region is dependent on the variables above and if we
// are drawing the window or not. If we draw the window, we don't have to draw
// the background behind it
var tileIdentificationRegionStart = null;
if (drawWindow) {
if (windowIdentificationRegion) {
tileIdentificationRegionStart = 0x9C00;
} else {
tileIdentificationRegionStart = 0x9800;
}
} else {
if (backgroundIdentificationRegion) {
tileIdentificationRegionStart = 0x9C00;
} else {
tileIdentificationRegionStart = 0x9800;
}
}
// We need to know what tile we are actually drawing. Use the
// scrollY or windowY and the current scanline to find this
var yPosition = null;
if (drawWindow) {
yPosition = currentScanline - windowY;
} else {
yPosition = scrollY + currentScanline
}
// We also need to know where in the tile we are drawing
var rowOfTile = ((yPosition / 8) * 32) & 0xFFFF;
// Now we can draw the scanline (160 pixels horizontal)
for (var px = 0; px < 160; px++) {
// Determine proper x-position based on window or background
var xPosition = px + scrollX;
if (drawWindow) {
if (px >= windowX) {
xPosition = px - windowX ;
}
}
// determine which tile we are on (horizontally)
var tileColumn = xPosition / 8;
// Now we need to get the tileIdentificationNumber
var tileIdentificationAddress = tileIdentificationRegionStart + rowOfTile + tileColumn;
var tileIdentifier = ths.mmu.read(tileIdentificationAddress);
// If The value has to be signed, offset it by 128
if (!unsigned) {
if (tileIdentifier > 127) tileIdentifier = -(128 - (tileIdentifier - 128));
}
// Now we have the tile identifier, we can find the region of memory where
// the tile data itself is
var tileDataAddress = tileDataRegionStart;
if (unsigned) {
tileDataAddress += (tileIdentifier * 16) & 0xFFFF;
} else {
tileDataAddress += ((tileIdentifier + 128) * 16) & 0xFFFF;
}
// We need to find the correct vertical line we are on of the tile
var line = yPosition % 8;
// Every line takes up 2 bytes, not 1 so multiply 2 to get correct line
line = line * 2;
// Get the two bytes from memory
var firstTileByte = ths.mmu.read(tileDataAddress + line);
var secondTileByte = ths.mmu.read(tileDataAddress + line + 1);
// An 8-bit line of pixels has colour determined like this example
// pixel# = 1 2 3 4 5 6 7 8
// data 2 = 1 0 1 0 1 1 1 0
// data 1 = 0 0 1 1 0 1 0 1
// Pixel 1 colour id: 10
// Pixel 2 colour id: 00
// Pixel 3 colour id: 11
// Pixel 4 colour id: 01
// Pixel 5 colour id: 10
// Pixel 6 colour id: 11
// Pixel 7 colour id: 10
// Pixel 8 colour id: 01
// Determine what pixel we are currently colouring
var colourBit = xPosition % 8;
colourBit -= 7;
colourBit *= -1;
// Now we need to combine the tile bytes and determine the colour ID
// using the colour bit
var colourId = (secondTileByte >> colourBit) & parseInt("00000001", 2);
colourId <<= 1;
colourId |= ((firstTileByte >> colourBit) & parseInt("00000001", 2));
// Get colour as a string, the colour palette is in memory 0xFF47
var colour = ths.getColour(colourId, 0xFF47);
var red = 0;
var blue = 0;
var green = 0;
switch(colour) {
case "white":
red = 255; green = 255; blue = 255;
break;
case "light_gray":
red = 0xCC; green = 0xCC; blue = 0xCC;
break;
case "dark_gray":
red = 0x77; green = 0x77; blue = 0x77;
break;
}
var finaly = ths.mmu.read(ths.CURRENT_SCANLINE_ADDR);
// safety check to make sure what im about
// to set is int the 160x144 bounds
if (finaly < 0 || finaly > 143 || px < 0 || px > 159) {
continue;
}
// console.log(red, green, blue);
ths.screenData[px][finaly][0] = red;
ths.screenData[px][finaly][1] = green;
ths.screenData[px][finaly][2] = blue;
}
};
this.drawSprites = function(lcdControlValue) {
// Sprite data is located at 0x8000-0x8FFF
// Sprite attributes are located at 0xFE00-0xFE9F and in this region
// each sprite has 4 bytes of attributes. These are what are in each byte
// of sprite attributes
// 0: Sprite Y Position: Position of the sprite on the Y axis of the
// viewing display minus 16
// 1: Sprite X Position: Position of the sprite on the X axis of the
// viewing display minus 8
// 2: Pattern number: This is the sprite identifier used for looking up
// the sprite data in memory region 0x8000-0x8FFF
// 3: Attributes: These are the attributes of the sprite
// Start by determine the size of the sprite from bit 2 of lcdControl
var is8x16 = lcdControlValue & parseInt("00000100", 2);
// There are 40 sprite tiles. Loop through all of them and if they are
// visible and intercepting with the current scanline, then we can draw
// them
for (var sprite = 0; sprite < 40; sprite++) {
// get Index offset of sprite attributes. Remember there are 4 bytes
// of attributes per sprite
var idxOffset = sprite * 4;
var yPosition = ths.mmu.read(0xFE00 + idxOffset) - 16;
var xPosition = ths.mmu.read(0xFE00 + idxOffset + 1) - 8;
var patternNum = ths.mmu.read(0xFE00 + idxOffset + 2);
var attributes = ths.mmu.read(0xFE00 + idxOffset + 3);
// The following are what the bits represent in the attributes
// Bit7: Sprite to Background Priority
// Bit6: Y flip
// Bit5: X flip
// Bit4: Palette number. 0 then it gets it palette from 0xFF48 otherwise 0xFF49
// Bit3: Not used in standard gameboy
// Bit2-0: Not used in standard gameboy
var yFlip = attributes & parseInt("01000000", 2);
var xFlip = attributes & parseInt("00100000", 2);
var spriteHeight = is8x16 ? 16 : 8;
var currentScanline = ths.mmu.read(ths.CURRENT_SCANLINE_ADDR);
// determine if the sprite intercepts with the scanline
if ((currentScanline >= yPosition) && (currentScanline < (yPosition + spriteHeight))) {
var line = currentScanline - yPosition;
// If we are flipping the sprite vertically (yFlip) read the sprite
// in backwards
if (yFlip) {
line -= spriteHeight;
line *= -1;
}
// Similar process as for tiles
line *= 2;
var tileDataAddress = (0x8000 + (patternNum * 16)) + line;
var firstTileByte = ths.mmu.read(tileDataAddress);
var secondTileByte = ths.mmu.read(tileDataAddress + 1);
// its easier to read in from right to left as pixel 0 is
// bit 7 in the colour data, pixel 1 is bit 6 etc...
for (var tilePixel = 7; tilePixel >= 0; tilePixel--) {
var colourBit = tilePixel;
// If we are flipping the sprite horizontally (xFlip) read the
// sprite in backwards
if (xFlip) {
colourbit -= 7;
colourbit *= -1;
}
var colourId = (secondTileByte >> colourBit) & parseInt("00000001", 2);
colourId <<= 1;
colourId |= ((firstTileByte >> colourBit) & parseInt("00000001", 2));
var paletteAddrBit = attributes & parseInt("00010000", 2);
var paletteAddr = 0xFF48;
if (paletteAddrBit) paletteAddr = 0xFF49;
var colour = getColour(colourId, paletteAddr);
// White spirtes are transparent so don't draw it
if (colour == "white") continue;
var red = 0;
var green = 0;
var blue = 0;
switch(colour) {
case "white": red = 255; green = 255; blue = 255; break;
case "light_gray": red = 0xCC; green = 0xCC; blue = 0xCC; break;
case "dark_gray": red = 0x77; green = 0x77; blue = 0x77; break;
}
var xPix = 0 - tilePixel;
xPix += 7;
var pixel = xPosition + xPix;
// sanity check
if (scanline < 0 || scanline > 143 || pixel < 0 || pixel>159) {
continue;
}
ths.screenData[pixel][currentScanline][0] = red;
ths.screenData[pixel][currentScanline][1] = green;
ths.screenData[pixel][currentScanline][2] = blue;
}
}
}
};
this.getColour = function(colourNum, paletteAddr) {
var result = "white";
var palette = ths.mmu.read(paletteAddr);
var hi = 0;
var lo = 0;
// which bits of the colour palette does the colour id map to?
switch (colourNum) {
case 0: hi = 1; lo = 0; break;
case 1: hi = 3; lo = 2; break;
case 2: hi = 5; lo = 4; break;
case 3: hi = 7; lo = 6; break;
}
// use the palette to get the colour
var colour = 0;
colour = ((palette >> hi) & parseInt("00000001", 2)) << 1;
colour |= ((palette >> lo) & parseInt("00000001", 2));
// convert the game colour to emulator colour
switch (colour) {
case 0: result = "white"; break;
case 1: result = "light_gray"; break;
case 2: result = "dark_gray"; break;
case 3: result = "black"; break;
}
return result;
};
this.keyPressed = function(keyBit) {
// We will represent keys pressed as 8 bits
// Map this way (Gameboy = Bit)
// Right = 0
// Left = 1
// Up = 2
// Down = 3
// A = 4
// B = 5
// SELECT = 6
// START = 7
// Internal Gameboy memory denotes a key pressed if a bit value is 0
// Joypad memory is in 0xFF00 and looks like this:
// Bit 7 - Not used
// Bit 6 - Not used
// Bit 5 - P15 Select Button Keys (0=Select)
// Bit 4 - P14 Select Direction Keys (0=Select)
// Bit 3 - P13 Input Down or Start (0=Pressed) (Read Only)
// Bit 2 - P12 Input Up or Select (0=Pressed) (Read Only)
// Bit 1 - P11 Input Left or Button B (0=Pressed) (Read Only)
// Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only)
// Resume CPU if stopped
ths.cpuStopped = false;
ths.debug = 0;
// Check if the current key requested was not pressed, if it wasn't pressed
// already, we might need an interrupt
var alreadyPressed = !((ths.mmu.JOYPAD >> keyBit) & 1);
// Set joypad state by toggling specific bit off
ths.mmu.JOYPAD &= ~(1 << keyBit);
// We only need to request an interrupt if the button requested was one
// that the game cared about. Basically, if bit 5 is set, the game cares
// about buttons (a, b, select, start) and if bit 4 is set, the game cares
// about directional keys
var joypadMemValue = ths.mmu.read(0xFF00);
var bitFourSet = joypadMemValue & parseInt("00010000", 2);
var bitFiveSet = joypadMemValue & parseInt("00100000", 2);
var shouldRequestInterrupt = false;
if (bitFourSet && !bitFiveSet && keyBit <= 3) {
shouldRequestInterrupt = true;
}
if (!bitFourSet && bitFiveSet && keyBit > 3) {
shouldRequestInterrupt = true;
}
if (shouldRequestInterrupt && !alreadyPressed) {
ths.requestInterrupt(4);
}
};
this.keyReleased = function(keyBit) {
// Flip the bit on (key released) in our Joypad representation
ths.mmu.JOYPAD |= (1 << keyBit);
};
this.setFlagBit = function(bit, isSet) {
if (isSet) ths.registers.F |= (1 << bit);
else ths.registers.F &= ~(1 << bit);
};
this.inc8Bit = function(register) {
// Increment register, if overflow past 0xFF roll back to 0
// set zero bit if zero occurs, half carry bit if carry from lower nibble to upper
// nibble, and subtract bit to 0
var curVal = register;
// We are about to roll into the second nibble (lower nibble is 0xF),
// half-carry is true
if ((curVal & 0xF) === 0xF) ths.setFlagBit(ths.HALF_CARRY_BIT, true);
curVal++;
if (curVal > 0xFF) {
curVal = 0;
ths.setFlagBit(ths.ZERO_BIT, true);
}
ths.setFlagBit(ths.SUBTRACT_BIT, false);
return curVal;
};
this.inc16Bit = function(registerHi, registerLo) {
// Increment register pair, If overflow past 0xFFFF roll back to 0
var curVal = (registerHi << 8) ^ registerLo;
curVal++;
if (curVal > 0xFFFF) curVal = 0;
registerHi = curVal >> 8;
registerLo = curVal & 0xFF;
return {hi: registerHi, lo: registerLo};
};
this.dec8Bit = function(register) {