rnes 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -5
- data/CHANGELOG.md +31 -0
- data/README.md +2 -2
- data/lib/rnes/cpu.rb +51 -25
- data/lib/rnes/cpu_bus.rb +6 -7
- data/lib/rnes/emulator.rb +15 -4
- data/lib/rnes/logger.rb +6 -17
- data/lib/rnes/ppu.rb +131 -91
- data/lib/rnes/ppu_bus.rb +12 -6
- data/lib/rnes/ppu_registers.rb +122 -42
- data/lib/rnes/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 768392aacc5ad611aa059d5ef7a7c23c78ca1088
|
4
|
+
data.tar.gz: 37881bb61c15ed1f35c5015174bf47d26b8fc50f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65ec79252019d26f9d6fa5aa471ecb031adcbeb067c7413a54e0c8dd58a7dbb989d9989638861694caab9ea03dce11625743c0bdcfb05919da3f7c5a9d8b11e0
|
7
|
+
data.tar.gz: 5af737101697e45640a8e211423f5dcbd858c0de846fe2b63d52d197bbd3abdf2baf53d089a3fe7c47440d6f3e6b36bd6597332945ab62a180af62ecd96987ec
|
data/.rubocop.yml
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
Layout/EmptyLineAfterGuardClause:
|
2
|
+
Enabled: false
|
3
|
+
|
1
4
|
Lint/EmptyWhen:
|
2
5
|
Enabled: false
|
3
6
|
|
@@ -31,13 +34,10 @@ Metrics/PerceivedComplexity:
|
|
31
34
|
Naming/PredicateName:
|
32
35
|
Enabled: false
|
33
36
|
|
34
|
-
|
35
|
-
Enabled: false
|
36
|
-
|
37
|
-
Layout/EmptyLineAfterGuardClause:
|
37
|
+
Naming/UncommunicativeMethodParamName:
|
38
38
|
Enabled: false
|
39
39
|
|
40
|
-
|
40
|
+
Style/Documentation:
|
41
41
|
Enabled: false
|
42
42
|
|
43
43
|
Style/EmptyMethod:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## 0.1.1 - 2018-11-12
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
|
5
|
+
- Improve logger format for compatibility with nestest.log.
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Allow break-less input on key input.
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
- Fix CPU ADC instruction.
|
14
|
+
- Fix CPU ASL instruction.
|
15
|
+
- Fix CPU JMP instruction (for the NMOS 6502 overlap bug).
|
16
|
+
- Fix CPU PHP instruction.
|
17
|
+
- Fix CPU PLP instruction
|
18
|
+
- Fix CPU RLA instruction.
|
19
|
+
- Fix CPU ROL instruction.
|
20
|
+
- Fix CPU SBC instruction.
|
21
|
+
- Fix CPU SLO instruction.
|
22
|
+
- Fix CPU stack pointer behavior on overflow (must be looped, e.g. $100 -> $1FF).
|
23
|
+
- Fix CPU indirect addressing overlap & overflow.
|
24
|
+
- Fix CPU cycle calculation on branch instruction and page cross.
|
25
|
+
- Fix PPU bus mirroring.
|
26
|
+
- Fix PPU sprit hit check.
|
27
|
+
- Fix PPU behaviors after reading $2002.
|
28
|
+
- Fix PPU buffer read on VRAM read from CPU.
|
29
|
+
- Fix PPU sprite RAM read on PPU 0x2004 from CPU.
|
30
|
+
- Fix PPU palette mirroring on $3F10, $3F14, $3F18, and $3F1C.
|
31
|
+
|
1
32
|
## 0.1.0 - 2018-11-09
|
2
33
|
|
3
34
|
### Added
|
data/README.md
CHANGED
@@ -11,7 +11,7 @@ A NES emulator written in Ruby.
|
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
14
|
-
Install rnes as a gem.
|
14
|
+
Install rnes as a gem (or clone this repository and run `exe/rnes` executable).
|
15
15
|
|
16
16
|
```sh
|
17
17
|
gem install rnes
|
@@ -22,7 +22,7 @@ gem install rnes
|
|
22
22
|
Pass ROM file path to `rnes` executable.
|
23
23
|
|
24
24
|
```sh
|
25
|
-
rnes <
|
25
|
+
rnes <FILE>
|
26
26
|
```
|
27
27
|
|
28
28
|
## Controls
|
data/lib/rnes/cpu.rb
CHANGED
@@ -16,6 +16,7 @@ module Rnes
|
|
16
16
|
def initialize(bus:, interrupt_line:)
|
17
17
|
@branched = false
|
18
18
|
@bus = bus
|
19
|
+
@crossed = false
|
19
20
|
@interrupt_line = interrupt_line
|
20
21
|
@registers = ::Rnes::CpuRegisters.new
|
21
22
|
end
|
@@ -34,7 +35,7 @@ module Rnes
|
|
34
35
|
end
|
35
36
|
|
36
37
|
# @return [Integer]
|
37
|
-
def
|
38
|
+
def step
|
38
39
|
handle_interrupts
|
39
40
|
operation = fetch_operation
|
40
41
|
operand = fetch_operand_by(operation.addressing_mode)
|
@@ -43,8 +44,10 @@ module Rnes
|
|
43
44
|
operand: operand,
|
44
45
|
operation_name: operation.name,
|
45
46
|
)
|
47
|
+
cycles_count = operation.cycle + (@branched ? 1 : 0) + (@crossed ? 1 : 0)
|
46
48
|
@branched = false
|
47
|
-
|
49
|
+
@crossed = false
|
50
|
+
cycles_count
|
48
51
|
end
|
49
52
|
|
50
53
|
private
|
@@ -203,7 +206,7 @@ module Rnes
|
|
203
206
|
if addressing_mode == :accumulator
|
204
207
|
execute_operation_rol_for_accumulator(operand)
|
205
208
|
else
|
206
|
-
|
209
|
+
execute_operation_rol_for_non_accumulator(operand)
|
207
210
|
end
|
208
211
|
when :ROR
|
209
212
|
if addressing_mode == :accumulator
|
@@ -264,7 +267,7 @@ module Rnes
|
|
264
267
|
@registers.carry = result > 0xFF
|
265
268
|
@registers.negative = result[7] == 1
|
266
269
|
@registers.overflow = (@registers.accumulator ^ operand)[7].zero? && !(@registers.accumulator ^ result)[7].zero?
|
267
|
-
@registers.zero = result.zero?
|
270
|
+
@registers.zero = (result & 0xFF).zero?
|
268
271
|
@registers.accumulator = result & 0xFF
|
269
272
|
end
|
270
273
|
|
@@ -291,7 +294,7 @@ module Rnes
|
|
291
294
|
# @param [Integer] operand
|
292
295
|
def execute_operation_asl_for_accoumulator(_operand)
|
293
296
|
value = @registers.accumulator
|
294
|
-
result = (value << 1)
|
297
|
+
result = (value << 1) & 0xFF
|
295
298
|
@registers.carry = value[7] == 1
|
296
299
|
@registers.negative = result[7] == 1
|
297
300
|
@registers.zero = result.zero?
|
@@ -301,7 +304,7 @@ module Rnes
|
|
301
304
|
# @param [Integer] operand
|
302
305
|
def execute_operation_asl_for_non_accumulator(operand)
|
303
306
|
value = read(operand)
|
304
|
-
result = (value << 1)
|
307
|
+
result = (value << 1) & 0xFF
|
305
308
|
@registers.carry = value[7] == 1
|
306
309
|
@registers.negative = result[7] == 1
|
307
310
|
@registers.zero = result.zero?
|
@@ -339,7 +342,7 @@ module Rnes
|
|
339
342
|
|
340
343
|
# @param [Integer] operand
|
341
344
|
def execute_operation_bmi(operand)
|
342
|
-
|
345
|
+
if @registers.negative?
|
343
346
|
branch(operand)
|
344
347
|
end
|
345
348
|
end
|
@@ -644,8 +647,7 @@ module Rnes
|
|
644
647
|
|
645
648
|
# @param [Integer] operand
|
646
649
|
def execute_operation_php(_operand)
|
647
|
-
@registers.
|
648
|
-
push(@registers.status)
|
650
|
+
push(@registers.status | 0x10)
|
649
651
|
end
|
650
652
|
|
651
653
|
# @param [Integer] operand
|
@@ -658,19 +660,19 @@ module Rnes
|
|
658
660
|
|
659
661
|
# @param [Integer] operand
|
660
662
|
def execute_operation_plp(_operand)
|
661
|
-
@registers.status = pop
|
663
|
+
@registers.status = pop & 0b11101111
|
662
664
|
@registers.reserved = true
|
663
665
|
end
|
664
666
|
|
665
667
|
# @param [Integer] operand
|
666
668
|
def execute_operation_rla(operand)
|
667
669
|
value = (read(operand) << 1) + @registers.carry_bit
|
668
|
-
result = (
|
670
|
+
result = (value & @registers.accumulator) & 0xFF
|
669
671
|
@registers.carry = value[8] == 1
|
670
672
|
@registers.negative = result[7] == 1
|
671
673
|
@registers.zero = result.zero?
|
672
674
|
@registers.accumulator = result
|
673
|
-
write(operand, value)
|
675
|
+
write(operand, value & 0xFF)
|
674
676
|
end
|
675
677
|
|
676
678
|
# @param [Integer] operand
|
@@ -722,6 +724,7 @@ module Rnes
|
|
722
724
|
@registers.overflow = (@registers.accumulator ^ value)[7].zero? && !(@registers.accumulator ^ result)[7].zero?
|
723
725
|
@registers.negative = result[7] == 1
|
724
726
|
@registers.zero = result.zero?
|
727
|
+
@registers.accumulator = result & 0xFF
|
725
728
|
write(operand, value)
|
726
729
|
end
|
727
730
|
|
@@ -750,7 +753,7 @@ module Rnes
|
|
750
753
|
@registers.overflow = ((@registers.accumulator ^ result) & 0x80 != 0 && ((@registers.accumulator ^ operand) & 0x80) != 0)
|
751
754
|
@registers.carry = result >= 0
|
752
755
|
@registers.negative = result[7] == 1
|
753
|
-
@registers.zero = result.zero?
|
756
|
+
@registers.zero = (result & 0xFF).zero?
|
754
757
|
@registers.accumulator = result & 0xFF
|
755
758
|
end
|
756
759
|
|
@@ -781,7 +784,7 @@ module Rnes
|
|
781
784
|
value = (read_value << 1) & 0xFF
|
782
785
|
result = value | @registers.accumulator
|
783
786
|
@registers.carry = read_value[7] == 1
|
784
|
-
@registers.negative = result == 1
|
787
|
+
@registers.negative = result[7] == 1
|
785
788
|
@registers.zero = result.zero?
|
786
789
|
@registers.accumulator = result
|
787
790
|
write(operand, value)
|
@@ -908,12 +911,16 @@ module Rnes
|
|
908
911
|
|
909
912
|
# @return [Integer]
|
910
913
|
def fetch_operand_by_absolute_x_addressing
|
911
|
-
|
914
|
+
base_address = fetch_word
|
915
|
+
@crossed = (base_address & 0xFF00) != ((base_address + @registers.index_x) & 0xFF00)
|
916
|
+
(base_address + @registers.index_x) & 0xFFFF
|
912
917
|
end
|
913
918
|
|
914
919
|
# @return [Integer]
|
915
920
|
def fetch_operand_by_absolute_y_addressing
|
916
|
-
|
921
|
+
base_address = fetch_word
|
922
|
+
@crossed = (base_address & 0xFF00) != ((base_address + @registers.index_y) & 0xFF00)
|
923
|
+
(base_address + @registers.index_y) & 0xFFFF
|
917
924
|
end
|
918
925
|
|
919
926
|
# @return [nil]
|
@@ -929,26 +936,38 @@ module Rnes
|
|
929
936
|
def fetch_operand_by_implied_addressing
|
930
937
|
end
|
931
938
|
|
939
|
+
# @note The address must not overlap a page boundary as a bug in the original 6502 prevents it from being fetched properly.
|
932
940
|
# @return [Integer]
|
933
941
|
def fetch_operand_by_indirect_absolute_addressing
|
934
|
-
|
942
|
+
address = fetch_word
|
943
|
+
low = read(address)
|
944
|
+
high = read((address & 0xFF00) | ((address + 1) & 0xFF))
|
945
|
+
low + (high << 8)
|
935
946
|
end
|
936
947
|
|
937
948
|
# @return [Integer]
|
938
949
|
def fetch_operand_by_pre_indexed_indirect_addressing
|
939
|
-
|
950
|
+
base_address = (fetch + @registers.index_x) & 0xFF
|
951
|
+
address = read_word_with_wrap_around(base_address)
|
952
|
+
@crossed = (address & 0xFF00) != (base_address & 0xFF00)
|
953
|
+
address
|
940
954
|
end
|
941
955
|
|
942
956
|
# @return [Integer]
|
943
957
|
def fetch_operand_by_post_indexed_indirect_addressing
|
944
|
-
|
958
|
+
base_address = fetch
|
959
|
+
address = (read_word_with_wrap_around(base_address) + @registers.index_y) & 0xFFFF
|
960
|
+
@crossed = (address & 0xFF00) != (base_address & 0xFF00)
|
961
|
+
address
|
945
962
|
end
|
946
963
|
|
947
964
|
# @return [Integer]
|
948
965
|
def fetch_operand_by_relative_addressing
|
949
966
|
int8 = fetch
|
950
|
-
offset = int8
|
951
|
-
@registers.program_counter + offset
|
967
|
+
offset = int8 >= 0x80 ? int8 - 256 : int8
|
968
|
+
address = @registers.program_counter + offset
|
969
|
+
@crossed = (address & 0xFF00) != (@registers.program_counter & 0xFF00)
|
970
|
+
address
|
952
971
|
end
|
953
972
|
|
954
973
|
# @return [Integer]
|
@@ -1009,10 +1028,10 @@ module Rnes
|
|
1009
1028
|
def pop
|
1010
1029
|
if @registers.stack_pointer < 0x1FF
|
1011
1030
|
@registers.stack_pointer += 1
|
1012
|
-
read(@registers.stack_pointer)
|
1013
1031
|
else
|
1014
|
-
|
1032
|
+
@registers.stack_pointer = 0x100
|
1015
1033
|
end
|
1034
|
+
read(@registers.stack_pointer)
|
1016
1035
|
end
|
1017
1036
|
|
1018
1037
|
# @return [Integer]
|
@@ -1023,11 +1042,11 @@ module Rnes
|
|
1023
1042
|
# @param [Integer] value
|
1024
1043
|
# @raise [Rnes::Errors::StackPointerOverflowError]
|
1025
1044
|
def push(value)
|
1045
|
+
write(@registers.stack_pointer, value)
|
1026
1046
|
if @registers.stack_pointer > 0x100
|
1027
|
-
write(@registers.stack_pointer, value)
|
1028
1047
|
@registers.stack_pointer -= 1
|
1029
1048
|
else
|
1030
|
-
|
1049
|
+
@registers.stack_pointer = 0x1FF
|
1031
1050
|
end
|
1032
1051
|
end
|
1033
1052
|
|
@@ -1049,6 +1068,13 @@ module Rnes
|
|
1049
1068
|
read(address) | read((address + 1) & 0xFFFF) << 8
|
1050
1069
|
end
|
1051
1070
|
|
1071
|
+
# @param [Integer] byte Unsigned integer from 0x00 to 0xFF.
|
1072
|
+
def read_word_with_wrap_around(byte)
|
1073
|
+
low = read(byte)
|
1074
|
+
high = read((byte + 1) & 0xFF)
|
1075
|
+
low + (high << 8)
|
1076
|
+
end
|
1077
|
+
|
1052
1078
|
# @param [Integer] address
|
1053
1079
|
# @param [Integer] value
|
1054
1080
|
def write(address, value)
|
data/lib/rnes/cpu_bus.rb
CHANGED
@@ -19,7 +19,6 @@ module Rnes
|
|
19
19
|
@ram = ram
|
20
20
|
end
|
21
21
|
|
22
|
-
# @todo
|
23
22
|
# @param [Integer]
|
24
23
|
# @return [Integer]
|
25
24
|
def read(address)
|
@@ -31,7 +30,7 @@ module Rnes
|
|
31
30
|
when 0x2000..0x2007
|
32
31
|
@ppu.read(address - 0x2000)
|
33
32
|
when 0x2008..0x3FFF
|
34
|
-
|
33
|
+
read(address - 0x0008)
|
35
34
|
when 0x4016
|
36
35
|
@keypad1.read
|
37
36
|
when 0x4017
|
@@ -51,7 +50,6 @@ module Rnes
|
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
54
|
-
# @todo
|
55
53
|
# @param [Integer] address
|
56
54
|
# @param [Integer] value
|
57
55
|
def write(address, value)
|
@@ -63,7 +61,7 @@ module Rnes
|
|
63
61
|
when 0x2000..0x2007
|
64
62
|
@ppu.write(address - 0x2000, value)
|
65
63
|
when 0x2008..0x3FFF
|
66
|
-
|
64
|
+
write(address - 0x0008, value)
|
67
65
|
when 0x4014
|
68
66
|
@dma_controller.request_transfer(address_hint: value)
|
69
67
|
when 0x4016
|
@@ -71,11 +69,12 @@ module Rnes
|
|
71
69
|
when 0x4017
|
72
70
|
@keypad2.write(value)
|
73
71
|
when 0x4000..0x401F
|
74
|
-
|
72
|
+
# TODO: I/O port for APU, etc
|
75
73
|
when 0x4020..0x5FFF
|
76
|
-
|
74
|
+
# TODO: extended RAM on special mappers
|
77
75
|
when 0x6000..0x7FFF
|
78
|
-
|
76
|
+
# TODO: battery-backed-up RAM
|
77
|
+
when 0x8000..0xFFFF
|
79
78
|
else
|
80
79
|
raise ::Rnes::Errors::InvalidCpuBusAddressError, address
|
81
80
|
end
|
data/lib/rnes/emulator.rb
CHANGED
@@ -28,20 +28,23 @@ module Rnes
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def run
|
31
|
+
allow_break_less_input
|
31
32
|
$stdin.noecho do
|
32
33
|
loop do
|
33
34
|
if @logger
|
34
35
|
@logger.puts
|
35
36
|
end
|
36
|
-
|
37
|
+
step
|
37
38
|
end
|
38
39
|
end
|
40
|
+
ensure
|
41
|
+
disallow_break_less_input
|
39
42
|
end
|
40
43
|
|
41
|
-
def
|
44
|
+
def step
|
42
45
|
@dma_controller.transfer_if_requested
|
43
|
-
(@cpu.
|
44
|
-
@ppu.
|
46
|
+
(@cpu.step * 3).times do
|
47
|
+
@ppu.step
|
45
48
|
end
|
46
49
|
@keypad1.check
|
47
50
|
@keypad2.check
|
@@ -49,6 +52,14 @@ module Rnes
|
|
49
52
|
|
50
53
|
private
|
51
54
|
|
55
|
+
def allow_break_less_input
|
56
|
+
`stty -icanon min 1 time 0`
|
57
|
+
end
|
58
|
+
|
59
|
+
def disallow_break_less_input
|
60
|
+
`stty icanon`
|
61
|
+
end
|
62
|
+
|
52
63
|
# @param [Rnes::Rom] from
|
53
64
|
# @param [Rnes::Ram] to
|
54
65
|
def copy(from:, to:)
|
data/lib/rnes/logger.rb
CHANGED
@@ -29,6 +29,7 @@ module Rnes
|
|
29
29
|
'',
|
30
30
|
segment_operation_code,
|
31
31
|
segment_operand,
|
32
|
+
'',
|
32
33
|
segment_operation_full_name,
|
33
34
|
segment_operand_humanized,
|
34
35
|
'',
|
@@ -36,8 +37,6 @@ module Rnes
|
|
36
37
|
segment_cpu_index_x,
|
37
38
|
segment_cpu_index_y,
|
38
39
|
segment_cpu_status,
|
39
|
-
segment_ppu_control1,
|
40
|
-
segment_ppu_control2,
|
41
40
|
segment_cpu_stack_pointer,
|
42
41
|
segment_cycle,
|
43
42
|
segment_ppu_line,
|
@@ -71,12 +70,12 @@ module Rnes
|
|
71
70
|
|
72
71
|
# @return [String]
|
73
72
|
def segment_cpu_status
|
74
|
-
format('P:%
|
73
|
+
format('P:%02X', @cpu.registers.status)
|
75
74
|
end
|
76
75
|
|
77
76
|
# @return [String]
|
78
77
|
def segment_cycle
|
79
|
-
format('CYC:%
|
78
|
+
format('CYC:%3d', @ppu.cycle)
|
80
79
|
end
|
81
80
|
|
82
81
|
# @return [String]
|
@@ -84,9 +83,9 @@ module Rnes
|
|
84
83
|
program_counter = @cpu.registers.program_counter
|
85
84
|
operation = @cpu.read_operation
|
86
85
|
case operation.addressing_mode
|
87
|
-
when :absolute, :absolute_x, :absolute_y, :indirect_absolute
|
86
|
+
when :absolute, :absolute_x, :absolute_y, :indirect_absolute
|
88
87
|
format('%02X %02X', @cpu.bus.read(program_counter + 1), @cpu.bus.read(program_counter + 2))
|
89
|
-
when :immediate, :relative, :zero_page, :zero_page_x, :zero_page_y
|
88
|
+
when :immediate, :relative, :zero_page, :zero_page_x, :zero_page_y, :pre_indexed_indirect, :post_indexed_indirect
|
90
89
|
format('%02X ', @cpu.bus.read(program_counter + 1))
|
91
90
|
else
|
92
91
|
' ' * 5
|
@@ -107,7 +106,7 @@ module Rnes
|
|
107
106
|
''
|
108
107
|
end
|
109
108
|
end
|
110
|
-
format('%-
|
109
|
+
format('%-19s', string)
|
111
110
|
end
|
112
111
|
|
113
112
|
# @return [String]
|
@@ -123,16 +122,6 @@ module Rnes
|
|
123
122
|
format('%-10s', operation.full_name)
|
124
123
|
end
|
125
124
|
|
126
|
-
# @return [String]
|
127
|
-
def segment_ppu_control1
|
128
|
-
format('CTRL1:%08b', @ppu.registers.control1)
|
129
|
-
end
|
130
|
-
|
131
|
-
# @return [String]
|
132
|
-
def segment_ppu_control2
|
133
|
-
format('CTRL2:%08b', @ppu.registers.control2)
|
134
|
-
end
|
135
|
-
|
136
125
|
# @note SL means "Scan Line".
|
137
126
|
# @return [String]
|
138
127
|
def segment_ppu_line
|
data/lib/rnes/ppu.rb
CHANGED
@@ -30,9 +30,15 @@ module Rnes
|
|
30
30
|
|
31
31
|
V_BLANK_HEIGHT = 21
|
32
32
|
|
33
|
-
|
33
|
+
WINDOW_HEIGHT = 240
|
34
34
|
|
35
|
-
|
35
|
+
WINDOW_WIDTH = 256
|
36
|
+
|
37
|
+
TILES_COUNT_IN_HORIZONTAL_LINE = WINDOW_WIDTH / TILE_WIDTH
|
38
|
+
|
39
|
+
TILES_COUNT_IN_VERTICAL_LINE = WINDOW_HEIGHT / TILE_HEIGHT
|
40
|
+
|
41
|
+
TILES_COUNT_IN_WINDOW = TILES_COUNT_IN_HORIZONTAL_LINE * TILES_COUNT_IN_VERTICAL_LINE
|
36
42
|
|
37
43
|
# @note For debug use.
|
38
44
|
# @param [Integer]
|
@@ -58,32 +64,35 @@ module Rnes
|
|
58
64
|
def initialize(bus:, interrupt_line:, renderer:)
|
59
65
|
@bus = bus
|
60
66
|
@cycle = 0
|
61
|
-
@image = ::Rnes::Image.new(height:
|
67
|
+
@image = ::Rnes::Image.new(height: WINDOW_HEIGHT, width: WINDOW_WIDTH)
|
62
68
|
@interrupt_line = interrupt_line
|
63
69
|
@line = 0
|
64
70
|
@registers = ::Rnes::PpuRegisters.new
|
65
71
|
@renderer = renderer
|
66
72
|
@sprite_ram = ::Rnes::Ram.new(bytesize: SPRITE_RAM_BYTESIZE)
|
67
|
-
@
|
68
|
-
@video_ram_address = 0x0000
|
69
|
-
@writing_to_scroll_registers = false
|
70
|
-
@writing_video_ram_address = false
|
73
|
+
@video_ram_reading_buffer = 0x00
|
71
74
|
end
|
72
75
|
|
73
76
|
# @param [Integer] address
|
74
77
|
# @return [Integer]
|
75
78
|
def read(address)
|
76
79
|
case address
|
80
|
+
when 0x0000
|
81
|
+
@registers.control
|
82
|
+
when 0x0001
|
83
|
+
@registers.mask
|
77
84
|
when 0x0002
|
78
|
-
registers.status
|
85
|
+
@registers.status
|
86
|
+
when 0x0004
|
87
|
+
read_from_sprite_ram(@registers.sprite_ram_address)
|
79
88
|
when 0x0007
|
80
|
-
|
89
|
+
read_from_video_ram_for_cpu
|
81
90
|
else
|
82
91
|
raise ::Rnes::Errors::InvalidPpuAddressError, address
|
83
92
|
end
|
84
93
|
end
|
85
94
|
|
86
|
-
def
|
95
|
+
def step
|
87
96
|
if on_visible_cycle? && x_in_tile.zero?
|
88
97
|
draw_background_8pixels
|
89
98
|
end
|
@@ -98,6 +107,7 @@ module Rnes
|
|
98
107
|
render_image
|
99
108
|
else
|
100
109
|
self.line += 1
|
110
|
+
check_sprite_hit
|
101
111
|
if on_line_to_start_v_blank?
|
102
112
|
set_v_blank
|
103
113
|
if v_blank_interrupt_enabled?
|
@@ -113,7 +123,7 @@ module Rnes
|
|
113
123
|
# @param [Integer] index
|
114
124
|
# @param [Integer] value
|
115
125
|
def transfer_sprite_data(index:, value:)
|
116
|
-
address = (@sprite_ram_address + index) % SPRITE_RAM_BYTESIZE
|
126
|
+
address = (@registers.sprite_ram_address + index) % SPRITE_RAM_BYTESIZE
|
117
127
|
@sprite_ram.write(address, value)
|
118
128
|
end
|
119
129
|
|
@@ -123,19 +133,19 @@ module Rnes
|
|
123
133
|
def write(address, value)
|
124
134
|
case address
|
125
135
|
when 0x0000
|
126
|
-
registers.
|
136
|
+
@registers.control = value
|
127
137
|
when 0x0001
|
128
|
-
registers.
|
138
|
+
@registers.mask = value
|
129
139
|
when 0x0003
|
130
|
-
|
140
|
+
@registers.sprite_ram_address = value
|
131
141
|
when 0x0004
|
132
|
-
|
142
|
+
write_to_sprite_ram_for_cpu(value)
|
133
143
|
when 0x0005
|
134
|
-
|
144
|
+
@registers.scroll = value
|
135
145
|
when 0x0006
|
136
|
-
|
146
|
+
@registers.video_ram_address = value
|
137
147
|
when 0x0007
|
138
|
-
|
148
|
+
write_to_video_ram_for_cpu(value)
|
139
149
|
else
|
140
150
|
raise ::Rnes::Errors::InvalidPpuAddressError, address
|
141
151
|
end
|
@@ -147,12 +157,41 @@ module Rnes
|
|
147
157
|
@interrupt_line.assert_nmi
|
148
158
|
end
|
149
159
|
|
160
|
+
# @return [Integer]
|
161
|
+
def base_name_table_address
|
162
|
+
ADDRESS_TO_START_NAME_TABLE + @registers.base_name_table_id * 0x400
|
163
|
+
end
|
164
|
+
|
165
|
+
# @return [Integer]
|
166
|
+
def base_background_pattern_table_address
|
167
|
+
if registers.background_pattern_table_address_banked?
|
168
|
+
0x1000
|
169
|
+
else
|
170
|
+
0x0000
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# @return [Integer]
|
175
|
+
def base_sprite_pattern_table_address
|
176
|
+
if registers.sprite_pattern_table_address_banked?
|
177
|
+
0x1000
|
178
|
+
else
|
179
|
+
0x0000
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def check_sprite_hit
|
184
|
+
if read_from_sprite_ram(0) == y && @registers.background_enabled? && @registers.sprite_enabled?
|
185
|
+
registers.sprite_hit = true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
150
189
|
def clear_sprite_hit
|
151
|
-
registers.
|
190
|
+
registers.sprite_hit = false
|
152
191
|
end
|
153
192
|
|
154
193
|
def clear_v_blank
|
155
|
-
registers.
|
194
|
+
registers.in_v_blank = false
|
156
195
|
end
|
157
196
|
|
158
197
|
def deassert_nmi
|
@@ -160,22 +199,22 @@ module Rnes
|
|
160
199
|
end
|
161
200
|
|
162
201
|
def draw_background_8pixels
|
163
|
-
|
164
|
-
character_index =
|
165
|
-
character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_tile +
|
166
|
-
character_line_low_byte =
|
167
|
-
character_line_high_byte =
|
202
|
+
base_pattern_table_address = base_background_pattern_table_address
|
203
|
+
character_index = read_character_index(tile_index)
|
204
|
+
character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_tile + base_pattern_table_address
|
205
|
+
character_line_low_byte = read_character_data(character_line_low_byte_address)
|
206
|
+
character_line_high_byte = read_character_data(character_line_low_byte_address + TILE_HEIGHT)
|
168
207
|
|
169
208
|
block_id = 0
|
170
209
|
block_id |= 0b01 if (x % BLOCK_WIDTH).odd?
|
171
210
|
block_id |= 0b10 if (y % BLOCK_HEIGHT).odd?
|
172
|
-
mini_palette_ids_byte =
|
211
|
+
mini_palette_ids_byte = read_object_attribute(tile_index)
|
173
212
|
mini_palette_id = (mini_palette_ids_byte >> (block_id * 2)) & 0b11
|
174
213
|
|
175
214
|
TILE_WIDTH.times do |x_in_character|
|
176
215
|
index_in_character_line_byte = TILE_WIDTH - 1 - x_in_character
|
177
216
|
background_palette_index = character_line_low_byte[index_in_character_line_byte] | character_line_high_byte[index_in_character_line_byte] << 1 | mini_palette_id << 2
|
178
|
-
color_id =
|
217
|
+
color_id = read_color_id(background_palette_index)
|
179
218
|
@image.write(
|
180
219
|
value: ::Rnes::Ppu::COLORS[color_id],
|
181
220
|
x: x + x_in_character,
|
@@ -198,7 +237,7 @@ module Rnes
|
|
198
237
|
# |`------- horizontal flip
|
199
238
|
# `-------- vertical flip
|
200
239
|
def draw_sprites
|
201
|
-
|
240
|
+
base_pattern_table_address = base_sprite_pattern_table_address
|
202
241
|
SPRITES_COUNT.times do |i|
|
203
242
|
base_sprite_ram_address = i * 4
|
204
243
|
y_for_sprite = (read_from_sprite_ram(base_sprite_ram_address) - TILE_HEIGHT)
|
@@ -207,18 +246,18 @@ module Rnes
|
|
207
246
|
sprite_attribute_byte = read_from_sprite_ram(base_sprite_ram_address + 2)
|
208
247
|
x_for_sprite = read_from_sprite_ram(base_sprite_ram_address + 3)
|
209
248
|
|
210
|
-
character_index =
|
249
|
+
character_index = read_character_index(name_table_index)
|
211
250
|
|
212
251
|
mini_palette_id = sprite_attribute_byte & 0b11
|
213
252
|
|
214
253
|
TILE_HEIGHT.times do |y_in_character|
|
215
|
-
character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_character +
|
216
|
-
character_line_low_byte =
|
217
|
-
character_line_high_byte =
|
254
|
+
character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_character + base_pattern_table_address
|
255
|
+
character_line_low_byte = read_character_data(character_line_low_byte_address)
|
256
|
+
character_line_high_byte = read_character_data(character_line_low_byte_address + TILE_HEIGHT)
|
218
257
|
TILE_WIDTH.times do |x_in_character|
|
219
258
|
index_in_character_line_byte = TILE_WIDTH - 1 - x_in_character
|
220
259
|
background_palette_index = character_line_low_byte[index_in_character_line_byte] | character_line_high_byte[index_in_character_line_byte] << 1 | mini_palette_id << 2
|
221
|
-
color_id =
|
260
|
+
color_id = read_color_id(background_palette_index)
|
222
261
|
@image.write(
|
223
262
|
value: ::Rnes::Ppu::COLORS[color_id],
|
224
263
|
x: x_for_sprite + x_in_character,
|
@@ -231,12 +270,12 @@ module Rnes
|
|
231
270
|
|
232
271
|
# @return [Boolean]
|
233
272
|
def on_bottom_end_line?
|
234
|
-
line ==
|
273
|
+
line == WINDOW_HEIGHT + V_BLANK_HEIGHT
|
235
274
|
end
|
236
275
|
|
237
276
|
# @return [Boolean]
|
238
277
|
def on_line_to_start_v_blank?
|
239
|
-
line ==
|
278
|
+
line == WINDOW_HEIGHT
|
240
279
|
end
|
241
280
|
|
242
281
|
# @return [Boolean]
|
@@ -246,44 +285,50 @@ module Rnes
|
|
246
285
|
|
247
286
|
# @return [Boolean]
|
248
287
|
def on_visible_cycle?
|
249
|
-
(0...
|
288
|
+
(0...WINDOW_WIDTH).cover?(x) && (0...WINDOW_HEIGHT).cover?(y)
|
250
289
|
end
|
251
290
|
|
252
291
|
# @param [Integer] index
|
253
|
-
# @return [Integer]
|
254
|
-
def
|
255
|
-
@bus.read(
|
292
|
+
# @return [Integer]
|
293
|
+
def read_character_data(index)
|
294
|
+
@bus.read(index)
|
256
295
|
end
|
257
296
|
|
258
297
|
# @param [Integer] index
|
259
298
|
# @return [Integer]
|
260
|
-
def
|
261
|
-
@bus.read(
|
299
|
+
def read_character_index(index)
|
300
|
+
@bus.read(base_name_table_address + index)
|
262
301
|
end
|
263
302
|
|
264
303
|
# @param [Integer] index
|
265
304
|
# @return [Integer]
|
266
|
-
def
|
305
|
+
def read_color_id(index)
|
267
306
|
@bus.read(ADDRESS_TO_START_BACKGROUND_PALETTE_TABLE + index)
|
268
307
|
end
|
269
308
|
|
270
|
-
# @param [Integer]
|
309
|
+
# @param [Integer] address
|
271
310
|
# @return [Integer]
|
272
|
-
def
|
273
|
-
@
|
311
|
+
def read_from_sprite_ram(address)
|
312
|
+
@sprite_ram.read(address)
|
274
313
|
end
|
275
314
|
|
276
|
-
# @
|
277
|
-
|
278
|
-
|
279
|
-
@
|
280
|
-
value
|
315
|
+
# @param [Integer] index
|
316
|
+
# @return [Integer] 4-color-palette IDs of 4 blocks, as 8 bit data.
|
317
|
+
def read_object_attribute(index)
|
318
|
+
@bus.read(ADDRESS_TO_START_ATTRIBUTE_TABLE + index)
|
281
319
|
end
|
282
320
|
|
283
|
-
# @param [Integer] address
|
284
321
|
# @return [Integer]
|
285
|
-
def
|
286
|
-
@
|
322
|
+
def read_from_video_ram_for_cpu
|
323
|
+
if (0x3F00..0x3F1F).cover?(@registers.video_ram_address % 0x4000)
|
324
|
+
value = @bus.read(@registers.video_ram_address)
|
325
|
+
@video_ram_reading_buffer = @bus.read(@registers.video_ram_address - 0x1000)
|
326
|
+
else
|
327
|
+
value = @video_ram_reading_buffer
|
328
|
+
@video_ram_reading_buffer = @bus.read(@registers.video_ram_address)
|
329
|
+
end
|
330
|
+
@registers.increment_video_ram_address(video_ram_address_offset)
|
331
|
+
value
|
287
332
|
end
|
288
333
|
|
289
334
|
def render_image
|
@@ -291,12 +336,32 @@ module Rnes
|
|
291
336
|
end
|
292
337
|
|
293
338
|
def set_v_blank
|
294
|
-
registers.
|
339
|
+
registers.in_v_blank = true
|
295
340
|
end
|
296
341
|
|
297
|
-
#
|
342
|
+
# +-----------+-----------+
|
343
|
+
# | 0(0x0000) | 1(0x0400) |
|
344
|
+
# +-----------+-----------+
|
345
|
+
# | 2(0x0800) | 3(0x0C00) |
|
346
|
+
# +-----------+-----------+
|
347
|
+
# @return [Integer] Integer from 0x0000 to 0x0FC0.
|
298
348
|
def tile_index
|
299
|
-
|
349
|
+
tile_index_in_window + tile_index_paging_offset
|
350
|
+
end
|
351
|
+
|
352
|
+
# @return [Integer] Integer from 0x0000 to 0x03C0.
|
353
|
+
def tile_index_in_window
|
354
|
+
(y_of_tile % TILES_COUNT_IN_VERTICAL_LINE) * TILES_COUNT_IN_HORIZONTAL_LINE + x_of_tile % TILES_COUNT_IN_HORIZONTAL_LINE
|
355
|
+
end
|
356
|
+
|
357
|
+
# @return [Integer] Integer from 0 to 3.
|
358
|
+
def tile_index_page
|
359
|
+
x_of_tile / TILES_COUNT_IN_HORIZONTAL_LINE + y_of_tile / TILES_COUNT_IN_VERTICAL_LINE * 2
|
360
|
+
end
|
361
|
+
|
362
|
+
# @return [Integer] 0x0000, 0x0400, 0x0800, or 0x0C00.
|
363
|
+
def tile_index_paging_offset
|
364
|
+
tile_index_page * 0x0400
|
300
365
|
end
|
301
366
|
|
302
367
|
# @return [Boolean]
|
@@ -306,48 +371,23 @@ module Rnes
|
|
306
371
|
|
307
372
|
# @return [Integer]
|
308
373
|
def video_ram_address_offset
|
309
|
-
if registers.
|
310
|
-
|
374
|
+
if registers.horizontal_increment?
|
375
|
+
TILES_COUNT_IN_HORIZONTAL_LINE
|
311
376
|
else
|
312
377
|
1
|
313
378
|
end
|
314
379
|
end
|
315
380
|
|
316
|
-
# @param [Integer] address
|
317
|
-
def write_sprite_ram_address(address)
|
318
|
-
@sprite_ram_address = address
|
319
|
-
end
|
320
|
-
|
321
381
|
# @param [Integer] value
|
322
|
-
def
|
323
|
-
|
324
|
-
|
325
|
-
else
|
326
|
-
@registers.scroll_horizontal = value
|
327
|
-
end
|
328
|
-
@writing_to_scroll_registers = !@writing_to_scroll_registers
|
329
|
-
end
|
330
|
-
|
331
|
-
# @param [Integer] value
|
332
|
-
def write_to_sprite_ram(value)
|
333
|
-
@sprite_ram.write(@sprite_ram_address, value)
|
334
|
-
@sprite_ram_address += 1
|
335
|
-
end
|
336
|
-
|
337
|
-
# @param [Integer] address
|
338
|
-
def write_video_ram_address(address)
|
339
|
-
if @writing_video_ram_address
|
340
|
-
@video_ram_address |= address
|
341
|
-
else
|
342
|
-
@video_ram_address = address << 8
|
343
|
-
end
|
344
|
-
@writing_video_ram_address = !@writing_video_ram_address
|
382
|
+
def write_to_sprite_ram_for_cpu(value)
|
383
|
+
@sprite_ram.write(@registers.sprite_ram_address, value)
|
384
|
+
@registers.sprite_ram_address += 1
|
345
385
|
end
|
346
386
|
|
347
387
|
# @param [Integer] value
|
348
|
-
def
|
349
|
-
@bus.write(@video_ram_address, value)
|
350
|
-
@
|
388
|
+
def write_to_video_ram_for_cpu(value)
|
389
|
+
@bus.write(@registers.video_ram_address, value)
|
390
|
+
@registers.increment_video_ram_address(video_ram_address_offset)
|
351
391
|
end
|
352
392
|
|
353
393
|
# @return [Integer]
|
@@ -362,7 +402,7 @@ module Rnes
|
|
362
402
|
|
363
403
|
# @return [Integer]
|
364
404
|
def x_of_tile
|
365
|
-
x / TILE_WIDTH
|
405
|
+
(x + @registers.scroll_x) / TILE_WIDTH
|
366
406
|
end
|
367
407
|
|
368
408
|
# @return [Integer]
|
@@ -377,7 +417,7 @@ module Rnes
|
|
377
417
|
|
378
418
|
# @return [Integer]
|
379
419
|
def y_of_tile
|
380
|
-
y / TILE_HEIGHT
|
420
|
+
(y + @registers.scroll_y) / TILE_HEIGHT
|
381
421
|
end
|
382
422
|
end
|
383
423
|
end
|
data/lib/rnes/ppu_bus.rb
CHANGED
@@ -25,12 +25,14 @@ module Rnes
|
|
25
25
|
read(address - 0x0800)
|
26
26
|
when 0x3000..0x3EFF
|
27
27
|
read(address - 0x1000)
|
28
|
+
when 0x3F10, 0x3F14, 0x3F18, 0x3F1C
|
29
|
+
read(address - 0x0010)
|
28
30
|
when 0x3F00..0x3F1F
|
29
31
|
@video_ram.read(address - 0x2000)
|
30
32
|
when 0x3F20..0x3FFF
|
31
|
-
read(address -
|
33
|
+
read(address - 0x0020)
|
32
34
|
when 0x4000..0xFFFF
|
33
|
-
read(address - 0x4000)
|
35
|
+
read(address - 0x4000)
|
34
36
|
else
|
35
37
|
raise ::Rnes::Errors::InvalidPpuBusAddressError, address
|
36
38
|
end
|
@@ -45,13 +47,17 @@ module Rnes
|
|
45
47
|
when 0x2000..0x27FF
|
46
48
|
@video_ram.write(address - 0x2000, value)
|
47
49
|
when 0x2800..0x2FFF
|
48
|
-
|
50
|
+
write(address - 0x0800, value)
|
49
51
|
when 0x3000..0x3EFF
|
50
|
-
|
52
|
+
write(address - 0x1000, value)
|
53
|
+
when 0x3F10, 0x3F14, 0x3F18, 0x3F1C
|
54
|
+
write(address - 0x0010, value)
|
51
55
|
when 0x3F00..0x3F1F
|
52
56
|
@video_ram.write(address - 0x2000, value)
|
53
|
-
when 0x3F00..
|
54
|
-
write(address -
|
57
|
+
when 0x3F00..0x3FFF
|
58
|
+
write(address - 0x0020, value)
|
59
|
+
when 0x4000..0xFFFF
|
60
|
+
write(address - 0x4000, value)
|
55
61
|
else
|
56
62
|
raise ::Rnes::Errors::InvalidPpuBusAddressError, address
|
57
63
|
end
|
data/lib/rnes/ppu_registers.rb
CHANGED
@@ -1,101 +1,181 @@
|
|
1
1
|
module Rnes
|
2
2
|
class PpuRegisters
|
3
3
|
STATUS_IN_V_BLANK_BIT_INDEX = 7
|
4
|
-
STATUS_SPRITE_HIT_BIT_INDEX =
|
4
|
+
STATUS_SPRITE_HIT_BIT_INDEX = 6
|
5
|
+
STATUS_OVERFLOW_BIT_INDEX = 5
|
5
6
|
|
6
7
|
# @param [Integer]
|
7
8
|
# @return [Integer]
|
8
|
-
attr_accessor :
|
9
|
+
attr_accessor :control
|
9
10
|
|
10
11
|
# @param [Integer]
|
11
12
|
# @return [Integer]
|
12
|
-
attr_accessor :
|
13
|
+
attr_accessor :mask
|
13
14
|
|
14
|
-
# @param [Integer]
|
15
15
|
# @return [Integer]
|
16
|
-
attr_accessor :
|
16
|
+
attr_accessor :sprite_ram_address
|
17
17
|
|
18
|
-
# @param [Integer]
|
19
18
|
# @return [Integer]
|
20
|
-
|
19
|
+
attr_reader :scroll_x
|
20
|
+
|
21
|
+
# @return [Integer]
|
22
|
+
attr_reader :scroll_y
|
21
23
|
|
22
|
-
# @param [Integer]
|
23
24
|
# @return [Integer]
|
24
|
-
|
25
|
+
attr_reader :video_ram_address
|
26
|
+
|
27
|
+
# @param [Integer]
|
28
|
+
attr_writer :status
|
25
29
|
|
26
30
|
def initialize
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@scroll_horizontal = 0x0
|
30
|
-
@scroll_vertical = 0x0
|
31
|
+
@control = 0x0
|
32
|
+
@mask = 0x0
|
31
33
|
@status = 0x0
|
34
|
+
|
35
|
+
@scroll_x = 0x0
|
36
|
+
@scroll_y = 0x0
|
37
|
+
|
38
|
+
@sprite_ram_address = 0x00
|
39
|
+
@video_ram_address = 0x0000
|
40
|
+
|
41
|
+
@address_latch = false
|
42
|
+
@scroll_latch = false
|
32
43
|
end
|
33
44
|
|
34
45
|
# @return [Boolean]
|
35
|
-
def
|
36
|
-
@
|
46
|
+
def background_enabled?
|
47
|
+
@mask[3] == 1
|
37
48
|
end
|
38
49
|
|
39
50
|
# @return [Boolean]
|
40
|
-
def
|
41
|
-
@
|
51
|
+
def background_pattern_table_address_banked?
|
52
|
+
@control[4] == 1
|
42
53
|
end
|
43
54
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
55
|
+
# +------------+------------|
|
56
|
+
# | 0 (0x2000) | 1 (0x2400) |
|
57
|
+
# +------------+------------|
|
58
|
+
# | 2 (0x2800) | 3 (0x2C00) |
|
59
|
+
# +------------+------------|
|
60
|
+
# @return [Integer] An integer from 0 to 3.
|
61
|
+
def base_name_table_id
|
62
|
+
@control & 0b11
|
47
63
|
end
|
48
64
|
|
49
65
|
# @return [Boolean]
|
50
|
-
def
|
51
|
-
@
|
66
|
+
def color_blue_emphasized?
|
67
|
+
@mask[7] == 1
|
52
68
|
end
|
53
69
|
|
54
70
|
# @return [Boolean]
|
55
|
-
def
|
56
|
-
@
|
71
|
+
def color_green_emphasized?
|
72
|
+
@mask[6] == 1
|
57
73
|
end
|
58
74
|
|
59
75
|
# @return [Boolean]
|
60
|
-
def
|
61
|
-
@
|
76
|
+
def color_red_emphasized?
|
77
|
+
@mask[5] == 1
|
62
78
|
end
|
63
79
|
|
64
80
|
# @return [Boolean]
|
65
|
-
def
|
66
|
-
@
|
81
|
+
def color_greyscaled?
|
82
|
+
@mask[0] == 1
|
67
83
|
end
|
68
84
|
|
69
85
|
# @return [Boolean]
|
70
86
|
def has_v_blank_irq_enabled_bit?
|
71
|
-
@
|
87
|
+
@control[7] == 1
|
72
88
|
end
|
73
89
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
# +------------+------------|
|
78
|
-
# | 2 (0x2800) | 3 (0x2C00) |
|
79
|
-
# +------------+------------|
|
80
|
-
# @return [Integer] An integer from 0 to 3.
|
81
|
-
def name_table_id
|
82
|
-
@status & 0b11
|
90
|
+
# @return [Boolean]
|
91
|
+
def horizontal_increment?
|
92
|
+
@control[2] == 1
|
83
93
|
end
|
84
94
|
|
85
|
-
|
86
|
-
|
95
|
+
# @return [Boolean]
|
96
|
+
def in_v_blank?
|
97
|
+
@status[STATUS_IN_V_BLANK_BIT_INDEX] == 1
|
87
98
|
end
|
88
99
|
|
89
100
|
# @param [Boolean] boolean
|
90
|
-
def
|
101
|
+
def in_v_blank=(boolean)
|
91
102
|
toggle_status_bit(STATUS_IN_V_BLANK_BIT_INDEX, boolean)
|
92
103
|
end
|
93
104
|
|
105
|
+
# @param [Integer] offset
|
106
|
+
def increment_video_ram_address(offset)
|
107
|
+
@video_ram_address += offset
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Boolean]
|
111
|
+
def leftmost_background_shown?
|
112
|
+
@mask[1] == 1
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Boolean]
|
116
|
+
def leftmost_sprite_shown?
|
117
|
+
@mask[2] == 1
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param [Boolean] boolean
|
121
|
+
def overflow=(boolean)
|
122
|
+
toggle_status_bit(STATUS_OVERFLOW_BIT_INDEX, boolean)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [Integer] value
|
126
|
+
def scroll=(value)
|
127
|
+
if @scroll_latch
|
128
|
+
@scroll_y = value
|
129
|
+
else
|
130
|
+
@scroll_x = value
|
131
|
+
end
|
132
|
+
@scroll_latch = !@scroll_latch
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Boolean]
|
136
|
+
def sprite_enabled?
|
137
|
+
@mask[4] == 1
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [Boolean]
|
141
|
+
def sprite_hit?
|
142
|
+
@status[STATUS_SPRITE_HIT_BIT_INDEX] == STATUS_SPRITE_HIT_BIT_INDEX
|
143
|
+
end
|
144
|
+
|
94
145
|
# @param [Boolean] boolean
|
95
|
-
def
|
146
|
+
def sprite_hit=(boolean)
|
96
147
|
toggle_status_bit(STATUS_SPRITE_HIT_BIT_INDEX, boolean)
|
97
148
|
end
|
98
149
|
|
150
|
+
# @return [Boolean]
|
151
|
+
def sprite_pattern_table_address_banked?
|
152
|
+
@control[3] == 1
|
153
|
+
end
|
154
|
+
|
155
|
+
# @return [Boolean]
|
156
|
+
def sprite_size_doubled?
|
157
|
+
@control[4] == 1
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return [Integer]
|
161
|
+
def status
|
162
|
+
value = @status
|
163
|
+
self.in_v_blank = false
|
164
|
+
@address_latch = false
|
165
|
+
@scroll_latch = false
|
166
|
+
value
|
167
|
+
end
|
168
|
+
|
169
|
+
# @param [Integer] value
|
170
|
+
def video_ram_address=(value)
|
171
|
+
if @address_latch
|
172
|
+
@video_ram_address |= value
|
173
|
+
else
|
174
|
+
@video_ram_address = value << 8
|
175
|
+
end
|
176
|
+
@address_latch = !@address_latch
|
177
|
+
end
|
178
|
+
|
99
179
|
private
|
100
180
|
|
101
181
|
# @param [Integer] index
|
data/lib/rnes/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rnes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryo Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|