rubyboy 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Rubyboy
6
4
  class Interrupt
7
- attr_accessor :ie, :if
5
+ INTERRUPTS = {
6
+ vblank: 0,
7
+ lcd: 1,
8
+ timer: 2,
9
+ serial: 3,
10
+ joypad: 4
11
+ }.freeze
8
12
 
9
13
  def initialize
10
14
  @ie = 0
@@ -34,7 +38,11 @@ module Rubyboy
34
38
  end
35
39
 
36
40
  def request(interrupt)
37
- @if |= interrupt
41
+ @if |= (1 << INTERRUPTS[interrupt])
42
+ end
43
+
44
+ def reset_flag(i)
45
+ @if &= (~(1 << i)) & 0xff
38
46
  end
39
47
  end
40
48
  end
@@ -29,13 +29,13 @@ module Rubyboy
29
29
  def direction_button(button)
30
30
  @direction = button | 0xf0
31
31
 
32
- @interupt.request(0b0001_0000) if button < 0b1111
32
+ @interupt.request(:joypad) if button < 0b1111
33
33
  end
34
34
 
35
35
  def action_button(button)
36
36
  @action = button | 0xf0
37
37
 
38
- @interupt.request(0b0001_0000) if button < 0b1111
38
+ @interupt.request(:joypad) if button < 0b1111
39
39
  end
40
40
  end
41
41
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Direct16
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Direct8
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class HlDec
6
+ def initialize(value = 0)
7
+ @value = value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class HlInc
6
+ def initialize(value = 0)
7
+ @value = value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Immediate16
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Immediate8
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Indirect
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Register16
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Operand
5
+ class Register8
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ class Operand
5
+ attr_reader :type, :value
6
+
7
+ def initialize(type:, value: 0)
8
+ @type = type
9
+ @value = value
10
+ end
11
+ end
12
+ end
data/lib/rubyboy/ppu.rb CHANGED
@@ -11,6 +11,33 @@ module Rubyboy
11
11
  drawing: 3
12
12
  }.freeze
13
13
 
14
+ LCDC = {
15
+ bg_window_enable: 0,
16
+ sprite_enable: 1,
17
+ sprite_size: 2,
18
+ bg_tile_map_area: 3,
19
+ bg_window_tile_data_area: 4,
20
+ window_enable: 5,
21
+ window_tile_map_area: 6,
22
+ lcd_ppu_enable: 7
23
+ }.freeze
24
+
25
+ STAT = {
26
+ ly_eq_lyc: 2,
27
+ hblank: 3,
28
+ vblank: 4,
29
+ oam_scan: 5,
30
+ lyc: 6
31
+ }.freeze
32
+
33
+ SPRITE_FLAGS = {
34
+ bank: 3,
35
+ dmg_palette: 4,
36
+ x_flip: 5,
37
+ y_flip: 6,
38
+ priority: 7
39
+ }.freeze
40
+
14
41
  LCD_WIDTH = 160
15
42
  LCD_HEIGHT = 144
16
43
 
@@ -90,8 +117,6 @@ module Rubyboy
90
117
  # ly is read only
91
118
  when 0xff45
92
119
  @lyc = value
93
- when 0xff46
94
- # dma
95
120
  when 0xff47
96
121
  @bgp = value
97
122
  when 0xff48
@@ -106,7 +131,7 @@ module Rubyboy
106
131
  end
107
132
 
108
133
  def step(cycles)
109
- return false if @lcdc[7].zero?
134
+ return false if @lcdc[LCDC[:lcd_ppu_enable]].zero?
110
135
 
111
136
  res = false
112
137
  @cycles += cycles
@@ -124,7 +149,7 @@ module Rubyboy
124
149
  render_sprites
125
150
  @cycles -= DRAWING_CYCLES
126
151
  @mode = MODE[:hblank]
127
- @interrupt.request(0b0000_0010) if @stat[3] == 1
152
+ @interrupt.request(:lcd) if @stat[STAT[:hblank]] == 1
128
153
  end
129
154
  when MODE[:hblank]
130
155
  if @cycles >= HBLANK_CYCLES
@@ -134,11 +159,11 @@ module Rubyboy
134
159
 
135
160
  if @ly == LCD_HEIGHT
136
161
  @mode = MODE[:vblank]
137
- @interrupt.request(0b0000_0001)
138
- @interrupt.request(0b0000_0010) if @stat[4] == 1
162
+ @interrupt.request(:vblank)
163
+ @interrupt.request(:lcd) if @stat[STAT[:vblank]] == 1
139
164
  else
140
165
  @mode = MODE[:oam_scan]
141
- @interrupt.request(0b0000_0010) if @stat[5] == 1
166
+ @interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
142
167
  end
143
168
  end
144
169
  when MODE[:vblank]
@@ -152,7 +177,7 @@ module Rubyboy
152
177
  @wly = 0
153
178
  handle_ly_eq_lyc
154
179
  @mode = MODE[:oam_scan]
155
- @interrupt.request(0b0000_0010) if @stat[5] == 1
180
+ @interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
156
181
  res = true
157
182
  end
158
183
  end
@@ -162,12 +187,12 @@ module Rubyboy
162
187
  end
163
188
 
164
189
  def render_bg
165
- return if @lcdc[0].zero?
190
+ return if @lcdc[LCDC[:bg_window_enable]].zero?
166
191
 
167
192
  y = (@ly + @scy) % 256
168
193
  LCD_WIDTH.times do |i|
169
194
  x = (i + @scx) % 256
170
- tile_index = get_tile_index(@lcdc[3], x, y)
195
+ tile_index = get_tile_index(@lcdc[LCDC[:bg_tile_map_area]], x, y)
171
196
  pixel = get_pixel(tile_index, x, y)
172
197
  @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
173
198
  @bg_pixels[i] = pixel
@@ -175,7 +200,7 @@ module Rubyboy
175
200
  end
176
201
 
177
202
  def render_window
178
- return if @lcdc[0].zero? || @lcdc[5].zero? || @ly < @wy
203
+ return if @lcdc[LCDC[:bg_window_enable]].zero? || @lcdc[LCDC[:window_enable]].zero? || @ly < @wy
179
204
 
180
205
  rendered = false
181
206
  y = @wly
@@ -184,7 +209,7 @@ module Rubyboy
184
209
 
185
210
  rendered = true
186
211
  x = i - (@wx - 7)
187
- tile_index = get_tile_index(@lcdc[6], x, y)
212
+ tile_index = get_tile_index(@lcdc[LCDC[:window_tile_map_area]], x, y)
188
213
  pixel = get_pixel(tile_index, x, y)
189
214
  @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
190
215
  @bg_pixels[i] = pixel
@@ -193,11 +218,12 @@ module Rubyboy
193
218
  end
194
219
 
195
220
  def render_sprites
196
- return if @lcdc[1].zero?
197
-
198
- sprite_height = @lcdc[2].zero? ? 8 : 16
221
+ return if @lcdc[LCDC[:sprite_enable]].zero?
199
222
 
200
- sprites = @oam.each_slice(4).filter_map do |sprite_attr|
223
+ sprite_height = @lcdc[LCDC[:sprite_size]].zero? ? 8 : 16
224
+ sprites = []
225
+ cnt = 0
226
+ @oam.each_slice(4).each do |sprite_attr|
201
227
  sprite = {
202
228
  y: (sprite_attr[0] - 16) % 256,
203
229
  x: (sprite_attr[1] - 8) % 256,
@@ -206,27 +232,30 @@ module Rubyboy
206
232
  }
207
233
  next if sprite[:y] > @ly || sprite[:y] + sprite_height <= @ly
208
234
 
209
- sprite
235
+ sprites << sprite
236
+ cnt += 1
237
+ break if cnt == 10
210
238
  end
211
- sprites = sprites.take(10).sort_by.with_index { |sprite, i| [-sprite[:x], -i] }
239
+ sprites = sprites.sort_by.with_index { |sprite, i| [-sprite[:x], -i] }
212
240
 
213
241
  sprites.each do |sprite|
214
- pallet = sprite[:flags][4].zero? ? @obp0 : @obp1
242
+ flags = sprite[:flags]
243
+ pallet = flags[SPRITE_FLAGS[:dmg_palette]].zero? ? @obp0 : @obp1
215
244
  tile_index = sprite[:tile_index]
216
245
  tile_index &= 0xfe if sprite_height == 16
217
246
  y = (@ly - sprite[:y]) % 256
218
- y = sprite_height - y - 1 if sprite[:flags][6] == 1
247
+ y = sprite_height - y - 1 if flags[SPRITE_FLAGS[:y_flip]] == 1
219
248
  tile_index = (tile_index + 1) % 256 if y >= 8
220
249
  y %= 8
221
250
 
222
251
  8.times do |x|
223
- x_flipped = sprite[:flags][5] == 1 ? 7 - x : x
252
+ x_flipped = flags[SPRITE_FLAGS[:x_flip]] == 1 ? 7 - x : x
224
253
 
225
254
  pixel = get_pixel(tile_index, x_flipped, y)
226
255
  i = (sprite[:x] + x) % 256
227
256
 
228
257
  next if pixel.zero? || i >= LCD_WIDTH
229
- next if sprite[:flags][7] == 1 && @bg_pixels[i] != 0
258
+ next if flags[SPRITE_FLAGS[:priority]] == 1 && @bg_pixels[i] != 0
230
259
 
231
260
  @buffer[@ly * LCD_WIDTH + i] = get_color(pallet, pixel)
232
261
  end
@@ -239,7 +268,7 @@ module Rubyboy
239
268
  tile_map_addr = tile_map_area.zero? ? 0x1800 : 0x1c00
240
269
  tile_map_index = (y / 8) * 32 + (x / 8)
241
270
  tile_index = @vram[tile_map_addr + tile_map_index]
242
- @lcdc[4].zero? ? to_signed_byte(tile_index) + 256 : tile_index
271
+ @lcdc[LCDC[:bg_window_tile_data_area]].zero? ? to_signed_byte(tile_index) + 256 : tile_index
243
272
  end
244
273
 
245
274
  def get_pixel(tile_index, x, y)
@@ -263,7 +292,7 @@ module Rubyboy
263
292
  def handle_ly_eq_lyc
264
293
  if @ly == @lyc
265
294
  @stat |= 0x04
266
- @interrupt.request(0b0000_0010) if @stat[6] == 1
295
+ @interrupt.request(:lcd) if @stat[STAT[:lyc]] == 1
267
296
  else
268
297
  @stat &= 0xfb
269
298
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Rubyboy
6
4
  class Register
7
5
  attr_reader :value
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ class Registers
5
+ attr_reader :value
6
+
7
+ def initialize
8
+ @a = 0x01
9
+ @b = 0x00
10
+ @c = 0x13
11
+ @d = 0x00
12
+ @e = 0xd8
13
+ @h = 0x01
14
+ @l = 0x4d
15
+ @f = 0xb0
16
+ end
17
+
18
+ def read8(register)
19
+ case register
20
+ when :a then @a
21
+ when :b then @b
22
+ when :c then @c
23
+ when :d then @d
24
+ when :e then @e
25
+ when :h then @h
26
+ when :l then @l
27
+ when :f then @f
28
+ end
29
+ end
30
+
31
+ def write8(register, value)
32
+ value &= 0xff
33
+ case register
34
+ when :a then @a = value
35
+ when :b then @b = value
36
+ when :c then @c = value
37
+ when :d then @d = value
38
+ when :e then @e = value
39
+ when :h then @h = value
40
+ when :l then @l = value
41
+ when :f then @f = value & 0xf0
42
+ end
43
+ end
44
+
45
+ def read16(register)
46
+ case register
47
+ when :af then (@a << 8) | @f
48
+ when :bc then (@b << 8) | @c
49
+ when :de then (@d << 8) | @e
50
+ when :hl then (@h << 8) | @l
51
+ end
52
+ end
53
+
54
+ def write16(register, value)
55
+ value &= 0xffff
56
+ case register
57
+ when :af
58
+ @a = (value >> 8) & 0xff
59
+ @f = value & 0xf0
60
+ when :bc
61
+ @b = (value >> 8) & 0xff
62
+ @c = value & 0xff
63
+ when :de
64
+ @d = (value >> 8) & 0xff
65
+ @e = value & 0xff
66
+ when :hl
67
+ @h = (value >> 8) & 0xff
68
+ @l = value & 0xff
69
+ end
70
+ end
71
+
72
+ def increment16(register)
73
+ write16(register, read16(register) + 1)
74
+ end
75
+
76
+ def decrement16(register)
77
+ write16(register, read16(register) - 1)
78
+ end
79
+ end
80
+ end
data/lib/rubyboy/rom.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rubyboy
4
4
  class Rom
5
- attr_accessor :data, :entroy_point, :logo, :title, :new_licensee_code, :sgb_flag, :cartridge_type, :rom_size, :ram_size, :destination_code, :old_licensee_code, :mask_rom_version_number, :header_checksum, :global_checksum
5
+ attr_reader :data, :entroy_point, :logo, :title, :new_licensee_code, :sgb_flag, :cartridge_type, :rom_size, :ram_size, :destination_code, :old_licensee_code, :mask_rom_version_number, :header_checksum, :global_checksum
6
6
 
7
7
  LOGO_DUMP = %w[
8
8
  CE ED 66 66 CC 0D 00 0B 03 73 00 83 00 0C 00 0D
@@ -18,49 +18,20 @@ module Rubyboy
18
18
  private
19
19
 
20
20
  def load_data
21
- # The Cartridge Header
22
- # see: https://gbdev.io/pandocs/The_Cartridge_Header.html
23
-
24
- # 0x100 - 0x103: Entry Point
25
21
  @entroy_point = @data[0x100..0x103]
26
- # p data[0x102..0x103].pack('C*').unpack('v').first
27
-
28
- # 0x104 - 0x133: Nintendo Logo
29
22
  @logo = @data[0x104..0x133]
30
23
  raise 'logo is not match' unless @logo == LOGO_DUMP
31
24
 
32
- # 0x134 - 0x143: Title
33
25
  @title = @data[0x134..0x143]
34
- # p data[0x134..0x143].pack('C*').strip
35
-
36
- # 0x144 - 0x145: New Licensee Code
37
26
  @new_licensee_code = @data[0x144..0x145]
38
-
39
- # 0x146: SGB Flag
40
27
  @sgb_flag = @data[0x146]
41
-
42
- # 0x147: Cartridge Type
43
28
  @cartridge_type = @data[0x147]
44
-
45
- # 0x148: ROM Size
46
29
  @rom_size = @data[0x148]
47
-
48
- # 0x149: RAM Size
49
30
  @ram_size = @data[0x149]
50
-
51
- # 0x14A: Destination Code
52
31
  @destination_code = @data[0x14A]
53
-
54
- # 0x14B: Old Licensee Code
55
32
  @old_licensee_code = @data[0x14B]
56
-
57
- # 0x14C: Mask ROM Version number
58
33
  @mask_rom_version_number = @data[0x14C]
59
-
60
- # 0x14D: Header Checksum
61
34
  @header_checksum = @data[0x14D]
62
-
63
- # 0x14E - 0x14F: Global Checksum
64
35
  @global_checksum = @data[0x14E..0x14F]
65
36
  end
66
37
  end
data/lib/rubyboy/timer.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Rubyboy
6
4
  class Timer
7
5
  def initialize(interrupt)
@@ -35,8 +33,8 @@ module Rubyboy
35
33
 
36
34
  return if @tima < 256
37
35
 
38
- @tima &= 0xff
39
- @interrupt.request(0b0000_0100)
36
+ @tima = @tma
37
+ @interrupt.request(:timer)
40
38
  end
41
39
 
42
40
  def read_byte(byte)
@@ -56,6 +54,7 @@ module Rubyboy
56
54
  case byte
57
55
  when 0xff04
58
56
  @div = 0
57
+ @cycles = 0
59
58
  when 0xff05
60
59
  @tima = value
61
60
  when 0xff06
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyboy
4
- VERSION = '0.4.0'
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/rubyboy.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'raylib'
4
- require 'benchmark'
5
- require_relative 'rubyboy/version'
6
4
  require_relative 'rubyboy/bus'
7
5
  require_relative 'rubyboy/cpu'
8
6
  require_relative 'rubyboy/ppu'
@@ -10,29 +8,33 @@ require_relative 'rubyboy/rom'
10
8
  require_relative 'rubyboy/timer'
11
9
  require_relative 'rubyboy/lcd'
12
10
  require_relative 'rubyboy/joypad'
11
+ require_relative 'rubyboy/interrupt'
13
12
 
14
13
  module Rubyboy
15
14
  class Console
16
15
  include Raylib
17
16
 
18
- def initialize(rom_data)
17
+ def initialize(rom_path)
19
18
  load_raylib
19
+ rom_data = File.open(rom_path, 'r') { _1.read.bytes }
20
20
  rom = Rom.new(rom_data)
21
21
  interrupt = Interrupt.new
22
22
  @ppu = Ppu.new(interrupt)
23
23
  @timer = Timer.new(interrupt)
24
24
  @joypad = Joypad.new(interrupt)
25
25
  @bus = Bus.new(@ppu, rom, @timer, interrupt, @joypad)
26
- @cpu = Cpu.new(@bus)
26
+ @cpu = Cpu.new(@bus, interrupt)
27
27
  @lcd = Lcd.new
28
28
  end
29
29
 
30
30
  def start
31
31
  until @lcd.window_should_close?
32
- key_input_check
33
32
  cycles = @cpu.exec
34
33
  @timer.step(cycles)
35
- draw if @ppu.step(cycles)
34
+ if @ppu.step(cycles)
35
+ draw
36
+ key_input_check
37
+ end
36
38
  end
37
39
  @lcd.close_window
38
40
  rescue StandardError => e
@@ -78,6 +80,3 @@ module Rubyboy
78
80
  end
79
81
  end
80
82
  end
81
-
82
- rom_data = File.open(File.expand_path('roms/bgbtest.gb', __dir__), 'r') { _1.read.bytes }
83
- Rubyboy::Console.new(rom_data).start
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyboy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sacckey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-20 00:00:00.000000000 Z
11
+ date: 2023-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raylib-bindings
@@ -26,7 +26,8 @@ dependencies:
26
26
  version: 0.5.7
27
27
  description:
28
28
  email:
29
- executables: []
29
+ executables:
30
+ - rubyboy
30
31
  extensions: []
31
32
  extra_rdoc_files: []
32
33
  files:
@@ -37,6 +38,7 @@ files:
37
38
  - LICENSE.txt
38
39
  - README.md
39
40
  - Rakefile
41
+ - exe/rubyboy
40
42
  - lib/opcodes.json
41
43
  - lib/roms/bgbtest.gb
42
44
  - lib/roms/cpu_instrs/cpu_instrs.gb
@@ -66,9 +68,20 @@ files:
66
68
  - lib/rubyboy/interrupt.rb
67
69
  - lib/rubyboy/joypad.rb
68
70
  - lib/rubyboy/lcd.rb
71
+ - lib/rubyboy/operand.rb
72
+ - lib/rubyboy/operand/direct16.rb
73
+ - lib/rubyboy/operand/direct8.rb
74
+ - lib/rubyboy/operand/hl_dec.rb
75
+ - lib/rubyboy/operand/hl_inc.rb
76
+ - lib/rubyboy/operand/immediate16.rb
77
+ - lib/rubyboy/operand/immediate8.rb
78
+ - lib/rubyboy/operand/indirect.rb
79
+ - lib/rubyboy/operand/register16.rb
80
+ - lib/rubyboy/operand/register8.rb
69
81
  - lib/rubyboy/ppu.rb
70
82
  - lib/rubyboy/ram.rb
71
83
  - lib/rubyboy/register.rb
84
+ - lib/rubyboy/registers.rb
72
85
  - lib/rubyboy/rom.rb
73
86
  - lib/rubyboy/timer.rb
74
87
  - lib/rubyboy/version.rb