rubyboy 0.4.0 → 1.0.0

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