rnes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ require 'rnes/errors'
2
+
3
+ module Rnes
4
+ class CpuBus
5
+ # @param [Rnes::Rom]
6
+ # @return [Rnes::Rom]
7
+ attr_accessor :program_rom
8
+
9
+ # @param [Rnes::DmaController] dma_controller
10
+ # @param [Rnes::Keypad] keypad1
11
+ # @param [Rnes::Keypad] keypad2
12
+ # @param [Rnes::Ppu] ppu
13
+ # @param [Rnes::Ram] ram
14
+ def initialize(dma_controller:, keypad1:, keypad2:, ppu:, ram:)
15
+ @dma_controller = dma_controller
16
+ @keypad1 = keypad1
17
+ @keypad2 = keypad2
18
+ @ppu = ppu
19
+ @ram = ram
20
+ end
21
+
22
+ # @todo
23
+ # @param [Integer]
24
+ # @return [Integer]
25
+ def read(address)
26
+ case address
27
+ when 0x0000..0x07FF
28
+ @ram.read(address)
29
+ when 0x0800..0x1FFF
30
+ @ram.read(address - 0x0800)
31
+ when 0x2000..0x2007
32
+ @ppu.read(address - 0x2000)
33
+ when 0x2008..0x3FFF
34
+ @ppu.read(address - 0x2008)
35
+ when 0x4016
36
+ @keypad1.read
37
+ when 0x4017
38
+ @keypad2.read
39
+ when 0x4000..0x401F
40
+ 0 # TODO: I/O port for APU, etc
41
+ when 0x4020..0x5FFF
42
+ 0 # TODO: extended RAM on special mappers
43
+ when 0x6000..0x7FFF
44
+ 0 # TODO: battery-backed-up RAM
45
+ when 0x8000..0xBFFF
46
+ try_to_read_program_rom(address - 0x8000)
47
+ when 0xC000..0xFFFF
48
+ try_to_read_program_rom(address - offset_on_reading_program_rom_higher_region)
49
+ else
50
+ raise ::Rnes::Errors::InvalidCpuBusAddressError, address
51
+ end
52
+ end
53
+
54
+ # @todo
55
+ # @param [Integer] address
56
+ # @param [Integer] value
57
+ def write(address, value)
58
+ case address
59
+ when 0x0000..0x07FF
60
+ @ram.write(address, value)
61
+ when 0x0800..0x1FFF
62
+ @ram.write(address - 0x0800, value)
63
+ when 0x2000..0x2007
64
+ @ppu.write(address - 0x2000, value)
65
+ when 0x2008..0x3FFF
66
+ @ppu.write(address - 0x2008, value)
67
+ when 0x4014
68
+ @dma_controller.request_transfer(address_hint: value)
69
+ when 0x4016
70
+ @keypad1.write(value)
71
+ when 0x4017
72
+ @keypad2.write(value)
73
+ when 0x4000..0x401F
74
+ 0 # TODO: I/O port for APU, etc
75
+ when 0x4020..0x5FFF
76
+ 0 # TODO: extended RAM on special mappers
77
+ when 0x6000..0x7FFF
78
+ 0 # TODO: battery-backed-up RAM
79
+ else
80
+ raise ::Rnes::Errors::InvalidCpuBusAddressError, address
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ # @return [Boolean]
87
+ def attatched_to_large_program_rom?
88
+ @program_rom.bytesize > 16 * 2**10
89
+ end
90
+
91
+ # @return [Integer]
92
+ def offset_on_reading_program_rom_higher_region
93
+ attatched_to_large_program_rom? ? 0x8000 : 0xC000
94
+ end
95
+
96
+ # @param [Integer] address
97
+ def try_to_read_program_rom(address)
98
+ if @program_rom
99
+ @program_rom.read(address)
100
+ else
101
+ raise ::Rnes::Errors::ProgramRomNotConnectedError
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,151 @@
1
+ module Rnes
2
+ class CpuRegisters
3
+ CARRY_BIT_INDEX = 0
4
+ ZERO_BIT_INDEX = 1
5
+ INTERRUPT_BIT_INDEX = 2
6
+ DECIMAL_BIT_INDEX = 3
7
+ BREAK_BIT_INDEX = 4
8
+ RESERVED_BIT_INDEX = 5
9
+ OVERFLOW_BIT_INDEX = 6
10
+ NEGATIVE_BIT_INDEX = 7
11
+
12
+ # @param [Integer]
13
+ # @return [Integer]
14
+ attr_accessor :accumulator
15
+
16
+ # @param [Integer]
17
+ # @return [Integer]
18
+ attr_accessor :index_x
19
+
20
+ # @param [Integer]
21
+ # @return [Integer]
22
+ attr_accessor :index_y
23
+
24
+ # @param [Integer]
25
+ # @return [Integer]
26
+ attr_accessor :program_counter
27
+
28
+ # @param [Integer]
29
+ # @return [Integer]
30
+ attr_accessor :stack_pointer
31
+
32
+ # @param [Integer]
33
+ # @return [Integer]
34
+ attr_accessor :status
35
+
36
+ def initialize
37
+ @accumulator = 0x00
38
+ @index_x = 0x00
39
+ @index_y = 0x00
40
+ @program_counter = 0x0000
41
+ @stack_pointer = 0x0000
42
+ @status = 0b00000000
43
+ end
44
+
45
+ # @return [Boolean]
46
+ def break?
47
+ @status[BREAK_BIT_INDEX] == 1
48
+ end
49
+
50
+ # @param [Boolean] boolean
51
+ def break=(boolean)
52
+ toggle_bit(BREAK_BIT_INDEX, boolean)
53
+ end
54
+
55
+ # @return [Boolean]
56
+ def carry?
57
+ @status[CARRY_BIT_INDEX] == 1
58
+ end
59
+
60
+ # @param [Boolean] boolean
61
+ def carry=(boolean)
62
+ toggle_bit(CARRY_BIT_INDEX, boolean)
63
+ end
64
+
65
+ # @return [Integer]
66
+ def carry_bit
67
+ @status[CARRY_BIT_INDEX]
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def decimal?
72
+ @status[DECIMAL_BIT_INDEX] == 1
73
+ end
74
+
75
+ # @param [Boolean] boolean
76
+ def decimal=(boolean)
77
+ toggle_bit(DECIMAL_BIT_INDEX, boolean)
78
+ end
79
+
80
+ # @return [Boolean]
81
+ def interrupt?
82
+ @status[INTERRUPT_BIT_INDEX] == 1
83
+ end
84
+
85
+ # @param [Boolean] boolean
86
+ def interrupt=(boolean)
87
+ toggle_bit(INTERRUPT_BIT_INDEX, boolean)
88
+ end
89
+
90
+ # @return [Boolean]
91
+ def negative?
92
+ @status[NEGATIVE_BIT_INDEX] == 1
93
+ end
94
+
95
+ # @param [Boolean] boolean
96
+ def negative=(boolean)
97
+ toggle_bit(NEGATIVE_BIT_INDEX, boolean)
98
+ end
99
+
100
+ # @return [Boolean]
101
+ def overflow?
102
+ @status[OVERFLOW_BIT_INDEX] == 1
103
+ end
104
+
105
+ # @return [Boolean]
106
+ def reserved?
107
+ @status[RESERVED_BIT_INDEX] == 1
108
+ end
109
+
110
+ # @param [Boolean] boolean
111
+ def reserved=(boolean)
112
+ toggle_bit(RESERVED_BIT_INDEX, boolean)
113
+ end
114
+
115
+ def reset
116
+ @accumulator = 0x00
117
+ @index_x = 0x00
118
+ @index_y = 0x00
119
+ @program_counter = 0x0000
120
+ @stack_pointer = 0x1FD
121
+ @status = 0b00110100
122
+ end
123
+
124
+ # @param [Boolean] boolean
125
+ def overflow=(boolean)
126
+ toggle_bit(OVERFLOW_BIT_INDEX, boolean)
127
+ end
128
+
129
+ # @return [Boolean]
130
+ def zero?
131
+ @status[ZERO_BIT_INDEX] == 1
132
+ end
133
+
134
+ # @param [Boolean] boolean
135
+ def zero=(boolean)
136
+ toggle_bit(ZERO_BIT_INDEX, boolean)
137
+ end
138
+
139
+ private
140
+
141
+ # @param [Integer] index
142
+ # @param [Boolean] boolean
143
+ def toggle_bit(index, boolean)
144
+ if boolean
145
+ @status |= 1 << index
146
+ else
147
+ @status &= ~(1 << index)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,35 @@
1
+ module Rnes
2
+ class DmaController
3
+ TRANSFER_BYTESIZE = 2**8
4
+
5
+ # @param [Rnes::Ppu] ppu
6
+ # @param [Rnes::Ram] working_ram
7
+ def initialize(ppu:, working_ram:)
8
+ @ppu = ppu
9
+ @requested = false
10
+ @working_ram = working_ram
11
+ end
12
+
13
+ def transfer_if_requested
14
+ if @requested
15
+ transfer
16
+ end
17
+ end
18
+
19
+ # @param [Integer] address_hint
20
+ def request_transfer(address_hint:)
21
+ @requested = true
22
+ @working_ram_address = address_hint << 8
23
+ end
24
+
25
+ private
26
+
27
+ def transfer
28
+ TRANSFER_BYTESIZE.times do |index|
29
+ value = @working_ram.read(@working_ram_address + index)
30
+ @ppu.transfer_sprite_data(index: index, value: value)
31
+ end
32
+ @requested = false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ require 'io/console'
2
+ require 'rnes/logger'
3
+ require 'rnes/parts_factory'
4
+ require 'rnes/rom_loader'
5
+
6
+ module Rnes
7
+ class Emulator
8
+ LOG_FILE_NAME = 'rnes.log'.freeze
9
+
10
+ def initialize
11
+ parts_factory = ::Rnes::PartsFactory.new
12
+ @cpu = parts_factory.cpu
13
+ @cpu_bus = parts_factory.cpu_bus
14
+ @dma_controller = parts_factory.dma_controller
15
+ @keypad1 = parts_factory.keypad1
16
+ @keypad2 = parts_factory.keypad2
17
+ @ppu = parts_factory.ppu
18
+ @ppu_bus = parts_factory.ppu_bus
19
+ @logger = ::Rnes::Logger.new(cpu: @cpu, path: LOG_FILE_NAME, ppu: @ppu)
20
+ end
21
+
22
+ # @param [Array<Integer>] rom_bytes
23
+ def load_rom(rom_bytes)
24
+ rom_loader = ::Rnes::RomLoader.new(rom_bytes)
25
+ copy(from: rom_loader.character_rom, to: @ppu_bus.character_ram)
26
+ @cpu_bus.program_rom = rom_loader.program_rom
27
+ @cpu.reset
28
+ end
29
+
30
+ def run
31
+ $stdin.noecho do
32
+ loop do
33
+ if @logger
34
+ @logger.puts
35
+ end
36
+ tick
37
+ end
38
+ end
39
+ end
40
+
41
+ def tick
42
+ @dma_controller.transfer_if_requested
43
+ (@cpu.tick * 3).times do
44
+ @ppu.tick
45
+ end
46
+ @keypad1.check
47
+ @keypad2.check
48
+ end
49
+
50
+ private
51
+
52
+ # @param [Rnes::Rom] from
53
+ # @param [Rnes::Ram] to
54
+ def copy(from:, to:)
55
+ from.bytesize.times do |address|
56
+ value = from.read(address)
57
+ to.write(address, value)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,46 @@
1
+ module Rnes
2
+ module Errors
3
+ class BaseError < ::StandardError
4
+ end
5
+
6
+ class BaseInvalidAddressError < BaseError
7
+ # @param [Integer] address
8
+ def initialize(address)
9
+ @address = address
10
+ super(to_s)
11
+ end
12
+
13
+ # @return [String]
14
+ def to_s
15
+ format('Invalid address: 0x%04X', @address)
16
+ end
17
+ end
18
+
19
+ class InvalidAddressingModeError < BaseError
20
+ end
21
+
22
+ class InvalidCpuBusAddressError < BaseInvalidAddressError
23
+ end
24
+
25
+ class InvalidInesFormatError < BaseError
26
+ end
27
+
28
+ class InvalidOperationCodeError < BaseError
29
+ end
30
+
31
+ class InvalidOperationError < BaseError
32
+ end
33
+
34
+ class InvalidPpuAddressError < BaseInvalidAddressError
35
+ end
36
+
37
+ class InvalidPpuBusAddressError < BaseInvalidAddressError
38
+ end
39
+
40
+ class ProgramRomNotConnectedError < BaseError
41
+ end
42
+
43
+ class StackPointerOverflowError < BaseError
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ module Rnes
2
+ class Image
3
+ # @return [Integer]
4
+ attr_reader :height
5
+
6
+ # @return [Integer]
7
+ attr_reader :width
8
+
9
+ # @param [Integer] height
10
+ # @param [Integer] width
11
+ def initialize(height:, width:)
12
+ @bytes = Array.new(height * width) do
13
+ [0, 0, 0]
14
+ end
15
+ @height = height
16
+ @width = width
17
+ end
18
+
19
+ # @param [Integer] x
20
+ # @param [Integer] y
21
+ def read(x:, y:)
22
+ @bytes[@width * y + x]
23
+ end
24
+
25
+ # @param [Array<Integer>] rgb
26
+ # @param [Integer] x
27
+ # @param [Integer] y
28
+ def write(value:, x:, y:)
29
+ @bytes[@width * y + x] = value
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ module Rnes
2
+ class InesHeader
3
+ BYTESIZE = 16
4
+
5
+ PREFIX_BYTES = [
6
+ 0x4E, # N
7
+ 0x45, # E
8
+ 0x53, # S
9
+ 0x1A, # end-of-file in MS-DOS
10
+ ].freeze
11
+
12
+ # @param [Array<Integer>] bytes
13
+ def initialize(bytes)
14
+ @bytes = bytes
15
+ end
16
+
17
+ # @return [Integer]
18
+ def bytesize
19
+ BYTESIZE
20
+ end
21
+
22
+ # @return [Integer]
23
+ def character_ram_bytesize
24
+ @bytes[8]
25
+ end
26
+
27
+ # @return [Integer]
28
+ def character_rom_bytesize
29
+ @bytes[5] * 8 * 2**10
30
+ end
31
+
32
+ # @return [Boolean]
33
+ def has_battery_backed_program_rom_bit?
34
+ flags1[1] == 1
35
+ end
36
+
37
+ # @return [Boolean]
38
+ def has_mirror_ignoring_bit?
39
+ flags1[3] == 1
40
+ end
41
+
42
+ # @note Trainers are 512 bytes of code which is loaded into $7000 before the game starts for hacker use.
43
+ # @return [Boolean]
44
+ def has_trainer_bit?
45
+ flags1[2] == 1
46
+ end
47
+
48
+ # @return [Boolean]
49
+ def has_vertical_mirroring_bit?
50
+ flags1[0] == 1
51
+ end
52
+
53
+ # @return [Integer]
54
+ def mapper_number
55
+ flags2 & 0b11110000 | flags1 >> 4
56
+ end
57
+
58
+ # @return [Integer]
59
+ def program_rom_bytesize
60
+ @bytes[4] * 16 * 2**10
61
+ end
62
+
63
+ # @return [Integer]
64
+ def trainer_bytesize
65
+ if has_trainer_bit?
66
+ 512
67
+ else
68
+ 0
69
+ end
70
+ end
71
+
72
+ # @return [Boolean]
73
+ def valid?
74
+ @bytes[0..3] == PREFIX_BYTES
75
+ end
76
+
77
+ private
78
+
79
+ # @return [Integer]
80
+ def flags1
81
+ @bytes[6]
82
+ end
83
+
84
+ # @return [Integer]
85
+ def flags2
86
+ @bytes[7]
87
+ end
88
+ end
89
+ end