rubyboy 0.3.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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ class Joypad
5
+ def initialize(interupt)
6
+ @mode = 0xcf
7
+ @action = 0xff
8
+ @direction = 0xff
9
+ @interupt = interupt
10
+ end
11
+
12
+ def read_byte(addr)
13
+ raise "not implemented: write_byte #{addr}" unless addr == 0xff00
14
+
15
+ res = @mode | 0xcf
16
+ res &= @direction if @mode[4].zero?
17
+ res &= @action if @mode[5].zero?
18
+
19
+ res
20
+ end
21
+
22
+ def write_byte(addr, value)
23
+ raise "not implemented: write_byte #{addr}" unless addr == 0xff00
24
+
25
+ @mode = value & 0x30
26
+ @mode |= 0xc0
27
+ end
28
+
29
+ def direction_button(button)
30
+ @direction = button | 0xf0
31
+
32
+ @interupt.request(:joypad) if button < 0b1111
33
+ end
34
+
35
+ def action_button(button)
36
+ @action = button | 0xf0
37
+
38
+ @interupt.request(:joypad) if button < 0b1111
39
+ end
40
+ end
41
+ end
data/lib/rubyboy/lcd.rb CHANGED
@@ -11,7 +11,6 @@ module Rubyboy
11
11
  SCALE = 4
12
12
 
13
13
  def initialize
14
- load_raylib
15
14
  InitWindow(WIDTH * SCALE, HEIGHT * SCALE, 'RUBY BOY')
16
15
  image = GenImageColor(WIDTH, HEIGHT, BLACK)
17
16
  image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8
@@ -34,24 +33,5 @@ module Rubyboy
34
33
  def close_window
35
34
  CloseWindow()
36
35
  end
37
-
38
- private
39
-
40
- def load_raylib
41
- shared_lib_path = "#{Gem::Specification.find_by_name('raylib-bindings').full_gem_path}/lib/"
42
- case RUBY_PLATFORM
43
- when /mswin|msys|mingw/ # Windows
44
- Raylib.load_lib("#{shared_lib_path}libraylib.dll")
45
- when /darwin/ # macOS
46
- Raylib.load_lib("#{shared_lib_path}libraylib.dylib")
47
- when /linux/ # Ubuntu Linux (x86_64 or aarch64)
48
- arch = RUBY_PLATFORM.split('-')[0]
49
- Raylib.load_lib(shared_lib_path + "libraylib.#{arch}.so")
50
- else
51
- raise "Unknown system: #{RUBY_PLATFORM}"
52
- end
53
-
54
- SetTraceLogLevel(LOG_ERROR)
55
- end
56
36
  end
57
37
  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
 
@@ -38,6 +65,7 @@ module Rubyboy
38
65
  @cycles = 0
39
66
  @interrupt = interrupt
40
67
  @buffer = Array.new(144 * 160, 0x00)
68
+ @bg_pixels = Array.new(LCD_WIDTH, 0x00)
41
69
  end
42
70
 
43
71
  def read_byte(addr)
@@ -89,8 +117,6 @@ module Rubyboy
89
117
  # ly is read only
90
118
  when 0xff45
91
119
  @lyc = value
92
- when 0xff46
93
- # dma
94
120
  when 0xff47
95
121
  @bgp = value
96
122
  when 0xff48
@@ -105,7 +131,7 @@ module Rubyboy
105
131
  end
106
132
 
107
133
  def step(cycles)
108
- return false if @lcdc[7].zero?
134
+ return false if @lcdc[LCDC[:lcd_ppu_enable]].zero?
109
135
 
110
136
  res = false
111
137
  @cycles += cycles
@@ -123,7 +149,7 @@ module Rubyboy
123
149
  render_sprites
124
150
  @cycles -= DRAWING_CYCLES
125
151
  @mode = MODE[:hblank]
126
- @interrupt.request(0b0000_0010) if @stat[3] == 1
152
+ @interrupt.request(:lcd) if @stat[STAT[:hblank]] == 1
127
153
  end
128
154
  when MODE[:hblank]
129
155
  if @cycles >= HBLANK_CYCLES
@@ -133,11 +159,11 @@ module Rubyboy
133
159
 
134
160
  if @ly == LCD_HEIGHT
135
161
  @mode = MODE[:vblank]
136
- @interrupt.request(0b0000_0001)
137
- @interrupt.request(0b0000_0010) if @stat[4] == 1
162
+ @interrupt.request(:vblank)
163
+ @interrupt.request(:lcd) if @stat[STAT[:vblank]] == 1
138
164
  else
139
165
  @mode = MODE[:oam_scan]
140
- @interrupt.request(0b0000_0010) if @stat[5] == 1
166
+ @interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
141
167
  end
142
168
  end
143
169
  when MODE[:vblank]
@@ -151,7 +177,7 @@ module Rubyboy
151
177
  @wly = 0
152
178
  handle_ly_eq_lyc
153
179
  @mode = MODE[:oam_scan]
154
- @interrupt.request(0b0000_0010) if @stat[5] == 1
180
+ @interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
155
181
  res = true
156
182
  end
157
183
  end
@@ -161,19 +187,20 @@ module Rubyboy
161
187
  end
162
188
 
163
189
  def render_bg
164
- return if @lcdc[0].zero?
190
+ return if @lcdc[LCDC[:bg_window_enable]].zero?
165
191
 
166
192
  y = (@ly + @scy) % 256
167
193
  LCD_WIDTH.times do |i|
168
194
  x = (i + @scx) % 256
169
- tile_index = get_tile_index(@lcdc[3], x, y)
195
+ tile_index = get_tile_index(@lcdc[LCDC[:bg_tile_map_area]], x, y)
170
196
  pixel = get_pixel(tile_index, x, y)
171
197
  @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
198
+ @bg_pixels[i] = pixel
172
199
  end
173
200
  end
174
201
 
175
202
  def render_window
176
- 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
177
204
 
178
205
  rendered = false
179
206
  y = @wly
@@ -182,19 +209,21 @@ module Rubyboy
182
209
 
183
210
  rendered = true
184
211
  x = i - (@wx - 7)
185
- tile_index = get_tile_index(@lcdc[6], x, y)
212
+ tile_index = get_tile_index(@lcdc[LCDC[:window_tile_map_area]], x, y)
186
213
  pixel = get_pixel(tile_index, x, y)
187
214
  @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
215
+ @bg_pixels[i] = pixel
188
216
  end
189
217
  @wly += 1 if rendered
190
218
  end
191
219
 
192
220
  def render_sprites
193
- return if @lcdc[1].zero?
194
-
195
- sprite_height = @lcdc[2].zero? ? 8 : 16
221
+ return if @lcdc[LCDC[:sprite_enable]].zero?
196
222
 
197
- 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|
198
227
  sprite = {
199
228
  y: (sprite_attr[0] - 16) % 256,
200
229
  x: (sprite_attr[1] - 8) % 256,
@@ -203,27 +232,30 @@ module Rubyboy
203
232
  }
204
233
  next if sprite[:y] > @ly || sprite[:y] + sprite_height <= @ly
205
234
 
206
- sprite
235
+ sprites << sprite
236
+ cnt += 1
237
+ break if cnt == 10
207
238
  end
208
- 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] }
209
240
 
210
241
  sprites.each do |sprite|
211
- pallet = sprite[:flags][4].zero? ? @obp0 : @obp1
242
+ flags = sprite[:flags]
243
+ pallet = flags[SPRITE_FLAGS[:dmg_palette]].zero? ? @obp0 : @obp1
212
244
  tile_index = sprite[:tile_index]
213
245
  tile_index &= 0xfe if sprite_height == 16
214
246
  y = (@ly - sprite[:y]) % 256
215
- y = sprite_height - y - 1 if sprite[:flags][6] == 1
247
+ y = sprite_height - y - 1 if flags[SPRITE_FLAGS[:y_flip]] == 1
216
248
  tile_index = (tile_index + 1) % 256 if y >= 8
217
249
  y %= 8
218
250
 
219
251
  8.times do |x|
220
- x_flipped = sprite[:flags][5] == 1 ? 7 - x : x
252
+ x_flipped = flags[SPRITE_FLAGS[:x_flip]] == 1 ? 7 - x : x
221
253
 
222
254
  pixel = get_pixel(tile_index, x_flipped, y)
223
255
  i = (sprite[:x] + x) % 256
224
256
 
225
257
  next if pixel.zero? || i >= LCD_WIDTH
226
- next if sprite[:flags][7] == 1 && @buffer[@ly * LCD_WIDTH + i] != 0xff
258
+ next if flags[SPRITE_FLAGS[:priority]] == 1 && @bg_pixels[i] != 0
227
259
 
228
260
  @buffer[@ly * LCD_WIDTH + i] = get_color(pallet, pixel)
229
261
  end
@@ -236,7 +268,7 @@ module Rubyboy
236
268
  tile_map_addr = tile_map_area.zero? ? 0x1800 : 0x1c00
237
269
  tile_map_index = (y / 8) * 32 + (x / 8)
238
270
  tile_index = @vram[tile_map_addr + tile_map_index]
239
- @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
240
272
  end
241
273
 
242
274
  def get_pixel(tile_index, x, y)
@@ -260,7 +292,7 @@ module Rubyboy
260
292
  def handle_ly_eq_lyc
261
293
  if @ly == @lyc
262
294
  @stat |= 0x04
263
- @interrupt.request(0b0000_0010) if @stat[6] == 1
295
+ @interrupt.request(:lcd) if @stat[STAT[:lyc]] == 1
264
296
  else
265
297
  @stat &= 0xfb
266
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.3.0'
4
+ VERSION = '1.0.0'
5
5
  end