rnes 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bbefb5781cd92b0dfc9dc54c37bd93927ba138b6
4
- data.tar.gz: e7a40524558104975f1fa6c6f4398150e6a6e841
3
+ metadata.gz: 768392aacc5ad611aa059d5ef7a7c23c78ca1088
4
+ data.tar.gz: 37881bb61c15ed1f35c5015174bf47d26b8fc50f
5
5
  SHA512:
6
- metadata.gz: 12966fa652b5fae1f0815ac80f76f1db5610535c3833f9c760a1bcc8d94d5ff8ded03ab2468a7116e701a05f179d93c1e9184f65e249daad61ffd3591790eff1
7
- data.tar.gz: 48143512a1c3206de0dfe00feced98e097a95ed6b4675f8f57fef9fdbf0c7be7dc1462d1eac222a1aeb24a316aa0f8020b6d157889e3840a6335494a4a8749c0
6
+ metadata.gz: 65ec79252019d26f9d6fa5aa471ecb031adcbeb067c7413a54e0c8dd58a7dbb989d9989638861694caab9ea03dce11625743c0bdcfb05919da3f7c5a9d8b11e0
7
+ data.tar.gz: 5af737101697e45640a8e211423f5dcbd858c0de846fe2b63d52d197bbd3abdf2baf53d089a3fe7c47440d6f3e6b36bd6597332945ab62a180af62ecd96987ec
@@ -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
- Style/Documentation:
35
- Enabled: false
36
-
37
- Layout/EmptyLineAfterGuardClause:
37
+ Naming/UncommunicativeMethodParamName:
38
38
  Enabled: false
39
39
 
40
- Naming/UncommunicativeMethodParamName:
40
+ Style/Documentation:
41
41
  Enabled: false
42
42
 
43
43
  Style/EmptyMethod:
@@ -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 <path-to-rom-file>
25
+ rnes <FILE>
26
26
  ```
27
27
 
28
28
  ## Controls
@@ -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 tick
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
- operation.cycle
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
- execute_operation_rol_for_non_accumulator_(operand)
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) && 0xFF
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) && 0xFF
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
- unless @registers.negative?
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.break = true
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 = (result & @registers.accumulator) & 0xFF
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
- (fetch_word + @registers.index_x) & 0xFFFF
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
- (fetch_word + @registers.index_y) & 0xFFFF
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
- read_word(fetch_word)
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
- read_word((fetch + @registers.index_x) & 0xFF)
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
- (read_word(fetch) + @registers.index_y) & 0xFF
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[7] == 1 ? int8 - 256 : 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
- raise ::Rnes::Errors::StackPointerOverflowError
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
- raise ::Rnes::Errors::StackPointerOverflowError
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)
@@ -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
- @ppu.read(address - 0x2008)
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
- @ppu.write(address - 0x2008, value)
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
- 0 # TODO: I/O port for APU, etc
72
+ # TODO: I/O port for APU, etc
75
73
  when 0x4020..0x5FFF
76
- 0 # TODO: extended RAM on special mappers
74
+ # TODO: extended RAM on special mappers
77
75
  when 0x6000..0x7FFF
78
- 0 # TODO: battery-backed-up RAM
76
+ # TODO: battery-backed-up RAM
77
+ when 0x8000..0xFFFF
79
78
  else
80
79
  raise ::Rnes::Errors::InvalidCpuBusAddressError, address
81
80
  end
@@ -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
- tick
37
+ step
37
38
  end
38
39
  end
40
+ ensure
41
+ disallow_break_less_input
39
42
  end
40
43
 
41
- def tick
44
+ def step
42
45
  @dma_controller.transfer_if_requested
43
- (@cpu.tick * 3).times do
44
- @ppu.tick
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:)
@@ -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:%08b', @cpu.registers.status)
73
+ format('P:%02X', @cpu.registers.status)
75
74
  end
76
75
 
77
76
  # @return [String]
78
77
  def segment_cycle
79
- format('CYC:%03d', @ppu.cycle)
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, :pre_indexed_absolute, :post_indexed_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('%-5s', string)
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
@@ -30,9 +30,15 @@ module Rnes
30
30
 
31
31
  V_BLANK_HEIGHT = 21
32
32
 
33
- VISIBLE_WINDOW_HEIGHT = 240
33
+ WINDOW_HEIGHT = 240
34
34
 
35
- VISIBLE_WINDOW_WIDTH = 256
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: VISIBLE_WINDOW_HEIGHT, width: VISIBLE_WINDOW_WIDTH)
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
- @sprite_ram_address = 0x00
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
- read_from_video_ram
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 tick
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.control1 = value
136
+ @registers.control = value
127
137
  when 0x0001
128
- registers.control2 = value
138
+ @registers.mask = value
129
139
  when 0x0003
130
- write_sprite_ram_address(value)
140
+ @registers.sprite_ram_address = value
131
141
  when 0x0004
132
- write_to_sprite_ram(value)
142
+ write_to_sprite_ram_for_cpu(value)
133
143
  when 0x0005
134
- write_to_scroll_registers(value)
144
+ @registers.scroll = value
135
145
  when 0x0006
136
- write_video_ram_address(value)
146
+ @registers.video_ram_address = value
137
147
  when 0x0007
138
- write_to_video_ram(value)
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.toggle_sprite_hit_bit(false)
190
+ registers.sprite_hit = false
152
191
  end
153
192
 
154
193
  def clear_v_blank
155
- registers.toggle_in_v_blank_bit(false)
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
- character_address_offset = registers.has_background_bank_bit? ? 0x1000 : 0
164
- character_index = read_from_name_table(tile_index)
165
- character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_tile + character_address_offset
166
- character_line_low_byte = read_from_character_rom(character_line_low_byte_address)
167
- character_line_high_byte = read_from_character_rom(character_line_low_byte_address + 8)
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 = read_from_attribute_table(tile_index)
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 = read_from_background_palette_table(background_palette_index)
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
- character_address_offset = registers.has_sprite_bank_bit? ? 0x1000 : 0
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 = read_from_name_table(name_table_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 + character_address_offset
216
- character_line_low_byte = read_from_character_rom(character_line_low_byte_address)
217
- character_line_high_byte = read_from_character_rom(character_line_low_byte_address + 8)
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 = read_from_background_palette_table(background_palette_index)
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 == VISIBLE_WINDOW_HEIGHT + V_BLANK_HEIGHT
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 == VISIBLE_WINDOW_HEIGHT
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...VISIBLE_WINDOW_WIDTH).cover?(x) && (0...VISIBLE_WINDOW_HEIGHT).cover?(y)
288
+ (0...WINDOW_WIDTH).cover?(x) && (0...WINDOW_HEIGHT).cover?(y)
250
289
  end
251
290
 
252
291
  # @param [Integer] index
253
- # @return [Integer] 4-color-palette IDs of 4 blocks, as 8 bit data.
254
- def read_from_attribute_table(index)
255
- @bus.read(ADDRESS_TO_START_ATTRIBUTE_TABLE + index)
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 read_from_name_table(index)
261
- @bus.read(ADDRESS_TO_START_NAME_TABLE + index)
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 read_from_background_palette_table(index)
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] index
309
+ # @param [Integer] address
271
310
  # @return [Integer]
272
- def read_from_character_rom(index)
273
- @bus.read(index)
311
+ def read_from_sprite_ram(address)
312
+ @sprite_ram.read(address)
274
313
  end
275
314
 
276
- # @return [Integer]
277
- def read_from_video_ram
278
- value = @bus.read(@video_ram_address)
279
- @video_ram_address += video_ram_address_offset
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 read_from_sprite_ram(address)
286
- @sprite_ram.read(address)
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.set_in_v_blank_bit
339
+ registers.in_v_blank = true
295
340
  end
296
341
 
297
- # @return [Integer]
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
- y_of_tile * (VISIBLE_WINDOW_WIDTH / TILE_WIDTH) + x_of_tile
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.has_large_video_ram_address_offset_bit?
310
- 32
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 write_to_scroll_registers(value)
323
- if @writing_to_scroll_registers
324
- @registers.scroll_vertical = value
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 write_to_video_ram(value)
349
- @bus.write(@video_ram_address, value)
350
- @video_ram_address += video_ram_address_offset
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
@@ -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 - 0x20) # mirror to 0x3F00..0x3F1F
33
+ read(address - 0x0020)
32
34
  when 0x4000..0xFFFF
33
- read(address - 0x4000) # mirror to 0x0000..0x3FFF
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
- @video_ram.write(address - 0x0800, value)
50
+ write(address - 0x0800, value)
49
51
  when 0x3000..0x3EFF
50
- @video_ram.write(address - 0x1000, value)
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..0xFFFF
54
- write(address - 0x1000, value)
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
@@ -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 = 5
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 :control1
9
+ attr_accessor :control
9
10
 
10
11
  # @param [Integer]
11
12
  # @return [Integer]
12
- attr_accessor :control2
13
+ attr_accessor :mask
13
14
 
14
- # @param [Integer]
15
15
  # @return [Integer]
16
- attr_accessor :scroll_horizontal
16
+ attr_accessor :sprite_ram_address
17
17
 
18
- # @param [Integer]
19
18
  # @return [Integer]
20
- attr_accessor :scroll_vertical
19
+ attr_reader :scroll_x
20
+
21
+ # @return [Integer]
22
+ attr_reader :scroll_y
21
23
 
22
- # @param [Integer]
23
24
  # @return [Integer]
24
- attr_accessor :status
25
+ attr_reader :video_ram_address
26
+
27
+ # @param [Integer]
28
+ attr_writer :status
25
29
 
26
30
  def initialize
27
- @control1 = 0x0
28
- @control2 = 0x0
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 has_background_bank_bit?
36
- @control1[4] == 1
46
+ def background_enabled?
47
+ @mask[3] == 1
37
48
  end
38
49
 
39
50
  # @return [Boolean]
40
- def has_background_enabled_bit?
41
- @control2[3] == 1
51
+ def background_pattern_table_address_banked?
52
+ @control[4] == 1
42
53
  end
43
54
 
44
- # @return [Boolean]
45
- def has_in_v_blank_bit?
46
- @status[STATUS_IN_V_BLANK_BIT_INDEX] == 1
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 has_large_video_ram_address_offset_bit?
51
- @control1[4] == 1
66
+ def color_blue_emphasized?
67
+ @mask[7] == 1
52
68
  end
53
69
 
54
70
  # @return [Boolean]
55
- def has_sprite_enabled_bit?
56
- @control2[4] == 1
71
+ def color_green_emphasized?
72
+ @mask[6] == 1
57
73
  end
58
74
 
59
75
  # @return [Boolean]
60
- def has_sprite_hit_bit?
61
- @status[STATUS_SPRITE_HIT_BIT_INDEX] == 1
76
+ def color_red_emphasized?
77
+ @mask[5] == 1
62
78
  end
63
79
 
64
80
  # @return [Boolean]
65
- def has_sprite_bank_bit?
66
- @control1[3] == 1
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
- @control1[7] == 1
87
+ @control[7] == 1
72
88
  end
73
89
 
74
- # Name table id (address)
75
- # +------------+------------|
76
- # | 0 (0x2000) | 1 (0x2400) |
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
- def set_in_v_blank_bit
86
- @status |= (1 << STATUS_IN_V_BLANK_BIT_INDEX)
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 toggle_in_v_blank_bit(boolean)
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 toggle_sprite_hit_bit(boolean)
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
@@ -1,3 +1,3 @@
1
1
  module Rnes
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.1.1'.freeze
3
3
  end
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.0
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-08 00:00:00.000000000 Z
11
+ date: 2018-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler