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 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