rubyboy 0.2.0 → 0.4.0

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
  SHA256:
3
- metadata.gz: 1c4b114437f8fdfe135e5dbb47c8ce18de914bf3bff25376a28d54a4550d3bfc
4
- data.tar.gz: f6f8714c97f42513c68d2a371742490a3a7ed295e056965e2bf47a50495fad0f
3
+ metadata.gz: a90f9cbc5a0ac4a9f9b971f0c4454421dc83ae9686222c275679f173e7503232
4
+ data.tar.gz: 52833516da6531982602c373acb11a74d292db6221b8ad34d4420a94be7fe017
5
5
  SHA512:
6
- metadata.gz: 810c319bb3b7766b277ba715c82d959bf97e5f16ad35994f3a84b7bd2726f24adac056427e067313b73c2f093c957d1a8f060b41b979083a34ceca5a9b20d956
7
- data.tar.gz: 70b411a71e7604f00b3f16257d8ccceec77795d0156e79f04c85b95093853a755fa75d949045f752c325f095e3427049fd71127436e50f77aa3f90be401009a2
6
+ metadata.gz: ef45830027a11b9827e8660bef2fbb45a5a0fc867486aef58ece8a930acae959eb66ac5709d425e8562d1a546608cbb4cce040efc9e17940b627dfc9095c4a7b
7
+ data.tar.gz: d8564cc7f5c66dbce015fead7ff770527736943e9c1de3858ed813c1cc012c1e9c5b9248010d16ee39a5fa668e720eb9cda93443b2097260b3b24688b9acdd42
data/.rubocop.yml CHANGED
@@ -14,6 +14,9 @@ Layout/LineLength:
14
14
  Metrics/AbcSize:
15
15
  Enabled: false
16
16
 
17
+ Metrics/BlockNesting:
18
+ Enabled: false
19
+
17
20
  Metrics/CyclomaticComplexity:
18
21
  Enabled: false
19
22
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2023-11-20
4
+
5
+ - Fix interrupt
6
+ - Fix sprite priority
7
+ - Implement joypad
8
+ - Pass bgbtest
9
+
10
+ ## [0.3.0] - 2023-11-19
11
+
12
+ - Fix bg rendering
13
+ - Add render_window
14
+ - Add render_sprites
15
+ - Add interrupt in ppu
16
+ - Add oam_dma_transfer
17
+ - Use raylib for rendering
18
+ - Pass dmg-acid2 test
19
+
3
20
  ## [0.2.0] - 2023-11-14
4
21
 
5
22
  - Add MBC1
Binary file
Binary file
data/lib/rubyboy/bus.rb CHANGED
@@ -8,11 +8,12 @@ module Rubyboy
8
8
  class Bus
9
9
  attr_accessor :ppu, :rom, :interrupt
10
10
 
11
- def initialize(ppu, rom, timer, interrupt)
11
+ def initialize(ppu, rom, timer, interrupt, joypad)
12
12
  @ppu = ppu
13
13
  @rom = rom
14
14
  @ram = Ram.new
15
15
  @mbc = Cartridge::Factory.create(rom, @ram)
16
+ @joypad = joypad
16
17
 
17
18
  @interrupt = interrupt
18
19
  @timer = timer
@@ -27,62 +28,37 @@ module Rubyboy
27
28
  when 0x0000..0x7fff
28
29
  @mbc.read_byte(addr)
29
30
  when 0x8000..0x9fff
30
- @ppu.vram[addr - 0x8000]
31
- when 0xa000..0xdfff
31
+ @ppu.read_byte(addr)
32
+ when 0xa000..0xbfff
32
33
  @mbc.read_byte(addr)
34
+ when 0xc000..0xcfff
35
+ @ram.wram1[addr - 0xc000]
36
+ when 0xd000..0xdfff
37
+ @ram.wram2[addr - 0xd000]
33
38
  when 0xe000..0xfdff
34
39
  # echo ram
35
40
  when 0xfe00..0xfe9f
36
- @oam[addr - 0xfe00]
41
+ @ppu.read_byte(addr)
37
42
  when 0xfea0..0xfeff
38
43
  # unused
39
44
  0xff
40
45
  when 0xff00
41
- # joypad
42
- @tmp[addr] ||= 0
46
+ @joypad.read_byte(addr)
43
47
  when 0xff01..0xff02
44
48
  # serial
45
49
  @tmp[addr] ||= 0
46
50
  when 0xff04..0xff07
47
51
  @timer.read_byte(addr)
48
52
  when 0xff0f
49
- @interrupt.if
53
+ @interrupt.read_byte(addr)
50
54
  when 0xff10..0xff26
51
55
  # sound
52
56
  @tmp[addr] ||= 0
53
57
  when 0xff30..0xff3f
54
58
  # wave pattern ram
55
59
  @tmp[addr] ||= 0
56
- when 0xff40
57
- @ppu.lcdc
58
- when 0xff41
59
- # stat
60
- @tmp[addr] ||= 0
61
- when 0xff42
62
- @ppu.scy
63
- when 0xff43
64
- @ppu.scx
65
- when 0xff44
66
- @ppu.ly
67
- when 0xff45
68
- @ppu.lyc
69
- when 0xff46
70
- # dma
71
- @tmp[addr] ||= 0
72
- when 0xff47
73
- @ppu.bgp
74
- when 0xff48
75
- # obp0
76
- @tmp[addr] ||= 0
77
- when 0xff49
78
- # obp1
79
- @tmp[addr] ||= 0
80
- when 0xff4a
81
- # wy
82
- @tmp[addr] ||= 0
83
- when 0xff4b
84
- # wx
85
- @tmp[addr] ||= 0
60
+ when 0xff40..0xff4b
61
+ @ppu.read_byte(addr)
86
62
  when 0xff4f
87
63
  # vbk
88
64
  @tmp[addr] ||= 0
@@ -101,7 +77,7 @@ module Rubyboy
101
77
  when 0xff80..0xfffe
102
78
  @ram.hram[addr - 0xff80]
103
79
  when 0xffff
104
- @interrupt.ie
80
+ @interrupt.read_byte(addr)
105
81
  else
106
82
  0xff
107
83
  end
@@ -112,61 +88,40 @@ module Rubyboy
112
88
  when 0x0000..0x7fff
113
89
  @mbc.write_byte(addr, value)
114
90
  when 0x8000..0x9fff
115
- @ppu.vram[addr - 0x8000] = value
116
- when 0xa000..0xdfff
91
+ @ppu.write_byte(addr, value)
92
+ when 0xa000..0xbfff
117
93
  @mbc.write_byte(addr, value)
94
+ when 0xc000..0xcfff
95
+ @ram.wram1[addr - 0xc000] = value
96
+ when 0xd000..0xdfff
97
+ @ram.wram2[addr - 0xd000] = value
118
98
  when 0xe000..0xfdff
119
99
  # echo ram
120
100
  when 0xfe00..0xfe9f
121
- @oam[addr - 0xfe00] = value
101
+ @ppu.write_byte(addr, value)
122
102
  when 0xfea0..0xfeff
123
103
  # unused
124
104
  when 0xff00
125
- # joypad
126
- @tmp[addr] = value
105
+ @joypad.write_byte(addr, value)
127
106
  when 0xff01..0xff02
128
107
  # serial
129
108
  @tmp[addr] = value
130
109
  when 0xff04..0xff07
131
110
  @timer.write_byte(addr, value)
132
111
  when 0xff0f
133
- @interrupt.if = value
112
+ @interrupt.write_byte(addr, value)
134
113
  when 0xff10..0xff26
135
114
  # sound
136
115
  @tmp[addr] = value
137
116
  when 0xff30..0xff3f
138
117
  # wave pattern ram
139
118
  @tmp[addr] = value
140
- when 0xff40
141
- @ppu.lcdc = value
142
- when 0xff41
143
- # stat
144
- @tmp[addr] = value
145
- when 0xff42
146
- @ppu.scy = value
147
- when 0xff43
148
- @ppu.scx = value
149
- when 0xff44
150
- @ppu.ly = value
151
- when 0xff45
152
- @ppu.lyc = value
153
119
  when 0xff46
154
- # dma
155
- @tmp[addr] = value
156
- when 0xff47
157
- @ppu.bgp = value
158
- when 0xff48
159
- # obp0
160
- @tmp[addr] = value
161
- when 0xff49
162
- # obp1
163
- @tmp[addr] = value
164
- when 0xff4a
165
- # wy
166
- @tmp[addr] = value
167
- when 0xff4b
168
- # wx
169
- @tmp[addr] = value
120
+ 0xa0.times do |i|
121
+ write_byte(0xfe00 + i, read_byte((value << 8) + i))
122
+ end
123
+ when 0xff40..0xff4b
124
+ @ppu.write_byte(addr, value)
170
125
  when 0xff4f
171
126
  # vbk
172
127
  @tmp[addr] = value
@@ -185,7 +140,7 @@ module Rubyboy
185
140
  when 0xff80..0xfffe
186
141
  @ram.hram[addr - 0xff80] = value
187
142
  when 0xffff
188
- @interrupt.ie = value
143
+ @interrupt.write_byte(addr, value)
189
144
  end
190
145
  end
191
146
 
@@ -12,6 +12,8 @@ module Rubyboy
12
12
  Nombc.new(rom)
13
13
  when 0x01..0x03
14
14
  Mbc1.new(rom, ram)
15
+ when 0x08..0x09
16
+ Nombc.new(rom)
15
17
  else
16
18
  raise "Unsupported cartridge type: #{cartridge_type}"
17
19
  end
@@ -30,10 +30,6 @@ module Rubyboy
30
30
  else
31
31
  0xff
32
32
  end
33
- when 0xc000..0xcfff
34
- @ram.wram1[addr - 0xc000]
35
- when 0xd000..0xdfff
36
- @ram.wram2[addr - 0xd000]
37
33
  else
38
34
  raise "not implemented: read_byte #{addr}"
39
35
  end
@@ -43,11 +39,9 @@ module Rubyboy
43
39
  case addr
44
40
  when 0x0000..0x1fff
45
41
  @ram_enable = value & 0x0f == 0x0a
46
- @rom.data[addr] = value
47
42
  when 0x2000..0x3fff
48
43
  @rom_bank = value & 0x1f
49
44
  @rom_bank = 1 if @rom_bank.zero?
50
- @rom.data[addr] = value
51
45
  when 0x4000..0x5fff
52
46
  @ram_bank = value & 0x03
53
47
  when 0x6000..0x7fff
@@ -60,10 +54,6 @@ module Rubyboy
60
54
  @ram.eram[addr - 0xa000] = value
61
55
  end
62
56
  end
63
- when 0xc000..0xcfff
64
- @ram.wram1[addr - 0xc000] = value
65
- when 0xd000..0xdfff
66
- @ram.wram2[addr - 0xd000] = value
67
57
  end
68
58
  end
69
59
  end
@@ -8,7 +8,12 @@ module Rubyboy
8
8
  end
9
9
 
10
10
  def read_byte(addr)
11
- @rom.data[addr]
11
+ case addr
12
+ when 0x0000..0x7fff
13
+ @rom.data[addr]
14
+ else
15
+ raise "not implemented: read_byte #{addr}"
16
+ end
12
17
  end
13
18
 
14
19
  def write_byte(_addr, _value)
data/lib/rubyboy/cpu.rb CHANGED
@@ -85,12 +85,12 @@ module Rubyboy
85
85
 
86
86
  @bus.interrupt.if &= (~(1 << i)) & 0xff
87
87
  @ime = false
88
- @bus.write_word(@sp - 2, @pc)
89
88
  @sp -= 2
89
+ @bus.write_word(@sp, @pc)
90
90
  @pc = pcs[i]
91
- opcode = read_byte(@pc)
92
91
  break
93
92
  end
93
+ return 20
94
94
  end
95
95
 
96
96
  if @ime_delay
@@ -11,6 +11,24 @@ module Rubyboy
11
11
  @if = 0
12
12
  end
13
13
 
14
+ def read_byte(addr)
15
+ case addr
16
+ when 0xff0f
17
+ @if
18
+ when 0xffff
19
+ @ie
20
+ end
21
+ end
22
+
23
+ def write_byte(addr, value)
24
+ case addr
25
+ when 0xff0f
26
+ @if = value
27
+ when 0xffff
28
+ @ie = value
29
+ end
30
+ end
31
+
14
32
  def interrupts
15
33
  @if & @ie & 0x1f
16
34
  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(0b0001_0000) if button < 0b1111
33
+ end
34
+
35
+ def action_button(button)
36
+ @action = button | 0xf0
37
+
38
+ @interupt.request(0b0001_0000) if button < 0b1111
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'raylib'
4
+
5
+ module Rubyboy
6
+ class Lcd
7
+ include Raylib
8
+
9
+ WIDTH = 160
10
+ HEIGHT = 144
11
+ SCALE = 4
12
+
13
+ def initialize
14
+ InitWindow(WIDTH * SCALE, HEIGHT * SCALE, 'RUBY BOY')
15
+ image = GenImageColor(WIDTH, HEIGHT, BLACK)
16
+ image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8
17
+ @texture = LoadTextureFromImage(image)
18
+ end
19
+
20
+ def draw(pixel_data)
21
+ UpdateTexture(@texture, pixel_data)
22
+
23
+ BeginDrawing()
24
+ ClearBackground(BLACK)
25
+ DrawTextureEx(@texture, Vector2.create(0, 0), 0.0, SCALE, WHITE)
26
+ EndDrawing()
27
+ end
28
+
29
+ def window_should_close?
30
+ WindowShouldClose()
31
+ end
32
+
33
+ def close_window
34
+ CloseWindow()
35
+ end
36
+ end
37
+ end
data/lib/rubyboy/ppu.rb CHANGED
@@ -2,74 +2,270 @@
2
2
 
3
3
  module Rubyboy
4
4
  class Ppu
5
- attr_accessor :lcdc, :scy, :scx, :ly, :lyc, :bgp, :vram, :cycles
5
+ attr_reader :buffer
6
6
 
7
- def initialize
7
+ MODE = {
8
+ hblank: 0,
9
+ vblank: 1,
10
+ oam_scan: 2,
11
+ drawing: 3
12
+ }.freeze
13
+
14
+ LCD_WIDTH = 160
15
+ LCD_HEIGHT = 144
16
+
17
+ OAM_SCAN_CYCLES = 80
18
+ DRAWING_CYCLES = 172
19
+ HBLANK_CYCLES = 204
20
+ ONE_LINE_CYCLES = OAM_SCAN_CYCLES + DRAWING_CYCLES + HBLANK_CYCLES
21
+
22
+ def initialize(interrupt)
23
+ @mode = MODE[:oam_scan]
8
24
  @lcdc = 0x91
25
+ @stat = 0x00
9
26
  @scy = 0x00
10
27
  @scx = 0x00
11
28
  @ly = 0x00
12
29
  @lyc = 0x00
13
- @bgp = 0xfc
14
- @vram = Array.new(8192, 0x00)
30
+ @obp0 = 0x00
31
+ @obp1 = 0x00
32
+ @wy = 0x00
33
+ @wx = 0x00
34
+ @bgp = 0x00
35
+ @vram = Array.new(0x2000, 0x00)
36
+ @oam = Array.new(0xa0, 0x00)
37
+ @wly = 0x00
15
38
  @cycles = 0
39
+ @interrupt = interrupt
40
+ @buffer = Array.new(144 * 160, 0x00)
41
+ @bg_pixels = Array.new(LCD_WIDTH, 0x00)
42
+ end
43
+
44
+ def read_byte(addr)
45
+ case addr
46
+ when 0x8000..0x9fff
47
+ @mode == MODE[:drawing] ? 0xff : @vram[addr - 0x8000]
48
+ when 0xfe00..0xfe9f
49
+ @mode == MODE[:oam_scan] || @mode == MODE[:drawing] ? 0xff : @oam[addr - 0xfe00]
50
+ when 0xff40
51
+ @lcdc
52
+ when 0xff41
53
+ @stat | 0x80 | @mode
54
+ when 0xff42
55
+ @scy
56
+ when 0xff43
57
+ @scx
58
+ when 0xff44
59
+ @ly
60
+ when 0xff45
61
+ @lyc
62
+ when 0xff47
63
+ @bgp
64
+ when 0xff48
65
+ @obp0
66
+ when 0xff49
67
+ @obp1
68
+ when 0xff4a
69
+ @wy
70
+ when 0xff4b
71
+ @wx
72
+ end
73
+ end
74
+
75
+ def write_byte(addr, value)
76
+ case addr
77
+ when 0x8000..0x9fff
78
+ @vram[addr - 0x8000] = value if @mode != MODE[:drawing]
79
+ when 0xfe00..0xfe9f
80
+ @oam[addr - 0xfe00] = value if @mode != MODE[:oam_scan] && @mode != MODE[:drawing]
81
+ when 0xff40
82
+ @lcdc = value
83
+ when 0xff41
84
+ @stat = value & 0x78
85
+ when 0xff42
86
+ @scy = value
87
+ when 0xff43
88
+ @scx = value
89
+ when 0xff44
90
+ # ly is read only
91
+ when 0xff45
92
+ @lyc = value
93
+ when 0xff46
94
+ # dma
95
+ when 0xff47
96
+ @bgp = value
97
+ when 0xff48
98
+ @obp0 = value
99
+ when 0xff49
100
+ @obp1 = value
101
+ when 0xff4a
102
+ @wy = value
103
+ when 0xff4b
104
+ @wx = value
105
+ end
16
106
  end
17
107
 
18
108
  def step(cycles)
109
+ return false if @lcdc[7].zero?
110
+
111
+ res = false
19
112
  @cycles += cycles
20
- return if @cycles < 456
21
113
 
22
- @cycles -= 456
23
- @ly = (@ly + 1) % 154
114
+ case @mode
115
+ when MODE[:oam_scan]
116
+ if @cycles >= OAM_SCAN_CYCLES
117
+ @cycles -= OAM_SCAN_CYCLES
118
+ @mode = MODE[:drawing]
119
+ end
120
+ when MODE[:drawing]
121
+ if @cycles >= DRAWING_CYCLES
122
+ render_bg
123
+ render_window
124
+ render_sprites
125
+ @cycles -= DRAWING_CYCLES
126
+ @mode = MODE[:hblank]
127
+ @interrupt.request(0b0000_0010) if @stat[3] == 1
128
+ end
129
+ when MODE[:hblank]
130
+ if @cycles >= HBLANK_CYCLES
131
+ @cycles -= HBLANK_CYCLES
132
+ @ly += 1
133
+ handle_ly_eq_lyc
134
+
135
+ if @ly == LCD_HEIGHT
136
+ @mode = MODE[:vblank]
137
+ @interrupt.request(0b0000_0001)
138
+ @interrupt.request(0b0000_0010) if @stat[4] == 1
139
+ else
140
+ @mode = MODE[:oam_scan]
141
+ @interrupt.request(0b0000_0010) if @stat[5] == 1
142
+ end
143
+ end
144
+ when MODE[:vblank]
145
+ if @cycles >= ONE_LINE_CYCLES
146
+ @cycles -= ONE_LINE_CYCLES
147
+ @ly += 1
148
+ handle_ly_eq_lyc
149
+
150
+ if @ly == 154
151
+ @ly = 0
152
+ @wly = 0
153
+ handle_ly_eq_lyc
154
+ @mode = MODE[:oam_scan]
155
+ @interrupt.request(0b0000_0010) if @stat[5] == 1
156
+ res = true
157
+ end
158
+ end
159
+ end
160
+
161
+ res
162
+ end
163
+
164
+ def render_bg
165
+ return if @lcdc[0].zero?
166
+
167
+ y = (@ly + @scy) % 256
168
+ LCD_WIDTH.times do |i|
169
+ x = (i + @scx) % 256
170
+ tile_index = get_tile_index(@lcdc[3], x, y)
171
+ pixel = get_pixel(tile_index, x, y)
172
+ @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
173
+ @bg_pixels[i] = pixel
174
+ end
24
175
  end
25
176
 
26
- def draw_bg
27
- tile_map_addr = @lcdc[3].zero? ? 0x9800 : 0x9c00
28
- tile_data_addr = @lcdc[4].zero? ? 0x9000 : 0x8000
177
+ def render_window
178
+ return if @lcdc[0].zero? || @lcdc[5].zero? || @ly < @wy
29
179
 
30
- bg = Array.new(256) { Array.new(256, 0x00) }
180
+ rendered = false
181
+ y = @wly
182
+ LCD_WIDTH.times do |i|
183
+ next if i < @wx - 7
31
184
 
32
- 32.times do |y|
33
- 32.times do |x|
34
- tile_map_index = (y * 32 + x)
35
- tile_index = @vram[tile_map_addr - 0x8000 + tile_map_index]
36
- # TODO: if @lcdc[4] == 0 then the addr becomes signed
185
+ rendered = true
186
+ x = i - (@wx - 7)
187
+ tile_index = get_tile_index(@lcdc[6], x, y)
188
+ pixel = get_pixel(tile_index, x, y)
189
+ @buffer[@ly * LCD_WIDTH + i] = get_color(@bgp, pixel)
190
+ @bg_pixels[i] = pixel
191
+ end
192
+ @wly += 1 if rendered
193
+ end
37
194
 
38
- tile = @vram[tile_data_addr - 0x8000 + tile_index * 16, 16]
195
+ def render_sprites
196
+ return if @lcdc[1].zero?
39
197
 
40
- 8.times do |i|
41
- pixelsa = tile[i * 2]
42
- pixelsb = tile[i * 2 + 1]
198
+ sprite_height = @lcdc[2].zero? ? 8 : 16
43
199
 
44
- 8.times do |j|
45
- c = (pixelsb[7 - j] << 1) + pixelsa[7 - j]
46
- bg[y * 8 + i][x * 8 + j] = get_color(c)
47
- end
48
- end
49
- end
200
+ sprites = @oam.each_slice(4).filter_map do |sprite_attr|
201
+ sprite = {
202
+ y: (sprite_attr[0] - 16) % 256,
203
+ x: (sprite_attr[1] - 8) % 256,
204
+ tile_index: sprite_attr[2],
205
+ flags: sprite_attr[3]
206
+ }
207
+ next if sprite[:y] > @ly || sprite[:y] + sprite_height <= @ly
208
+
209
+ sprite
50
210
  end
51
- view_port = Array.new(144) { Array.new(160, 0x00) }
211
+ sprites = sprites.take(10).sort_by.with_index { |sprite, i| [-sprite[:x], -i] }
212
+
213
+ sprites.each do |sprite|
214
+ pallet = sprite[:flags][4].zero? ? @obp0 : @obp1
215
+ tile_index = sprite[:tile_index]
216
+ tile_index &= 0xfe if sprite_height == 16
217
+ y = (@ly - sprite[:y]) % 256
218
+ y = sprite_height - y - 1 if sprite[:flags][6] == 1
219
+ tile_index = (tile_index + 1) % 256 if y >= 8
220
+ y %= 8
221
+
222
+ 8.times do |x|
223
+ x_flipped = sprite[:flags][5] == 1 ? 7 - x : x
224
+
225
+ pixel = get_pixel(tile_index, x_flipped, y)
226
+ i = (sprite[:x] + x) % 256
52
227
 
53
- 144.times do |y|
54
- 160.times do |x|
55
- view_port[y][x] = bg[(y + @scy) % 144][(x + @scx) % 160]
228
+ next if pixel.zero? || i >= LCD_WIDTH
229
+ next if sprite[:flags][7] == 1 && @bg_pixels[i] != 0
230
+
231
+ @buffer[@ly * LCD_WIDTH + i] = get_color(pallet, pixel)
56
232
  end
57
233
  end
58
- view_port
59
234
  end
60
235
 
61
236
  private
62
237
 
63
- def get_color(color_num)
64
- case color_num
65
- when 0
66
- [0xff, 0xff, 0xff, 0xff]
67
- when 1
68
- [0xcc, 0xcc, 0xcc, 0xff]
69
- when 2
70
- [0x77, 0x77, 0x77, 0xff]
71
- when 3
72
- [0x00, 0x00, 0x00, 0xff]
238
+ def get_tile_index(tile_map_area, x, y)
239
+ tile_map_addr = tile_map_area.zero? ? 0x1800 : 0x1c00
240
+ tile_map_index = (y / 8) * 32 + (x / 8)
241
+ tile_index = @vram[tile_map_addr + tile_map_index]
242
+ @lcdc[4].zero? ? to_signed_byte(tile_index) + 256 : tile_index
243
+ end
244
+
245
+ def get_pixel(tile_index, x, y)
246
+ @vram[tile_index * 16 + (y % 8) * 2][7 - (x % 8)] + (@vram[tile_index * 16 + (y % 8) * 2 + 1][7 - (x % 8)] << 1)
247
+ end
248
+
249
+ def get_color(pallet, pixel)
250
+ case (pallet >> (pixel * 2)) & 0b11
251
+ when 0 then 0xff
252
+ when 1 then 0xaa
253
+ when 2 then 0x55
254
+ when 3 then 0x00
255
+ end
256
+ end
257
+
258
+ def to_signed_byte(byte)
259
+ byte &= 0xff
260
+ byte > 127 ? byte - 256 : byte
261
+ end
262
+
263
+ def handle_ly_eq_lyc
264
+ if @ly == @lyc
265
+ @stat |= 0x04
266
+ @interrupt.request(0b0000_0010) if @stat[6] == 1
267
+ else
268
+ @stat &= 0xfb
73
269
  end
74
270
  end
75
271
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyboy
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/rubyboy.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gosu'
3
+ require 'raylib'
4
4
  require 'benchmark'
5
5
  require_relative 'rubyboy/version'
6
6
  require_relative 'rubyboy/bus'
@@ -8,52 +8,76 @@ require_relative 'rubyboy/cpu'
8
8
  require_relative 'rubyboy/ppu'
9
9
  require_relative 'rubyboy/rom'
10
10
  require_relative 'rubyboy/timer'
11
+ require_relative 'rubyboy/lcd'
12
+ require_relative 'rubyboy/joypad'
11
13
 
12
14
  module Rubyboy
13
- class Console < Gosu::Window
14
- SCALE = 4
15
+ class Console
16
+ include Raylib
15
17
 
16
18
  def initialize(rom_data)
17
- super(160 * SCALE, 144 * SCALE, false)
18
- # TODO: Display title
19
- self.caption = 'RUBY BOY'
20
- @total_cycles = 0
21
-
19
+ load_raylib
22
20
  rom = Rom.new(rom_data)
23
21
  interrupt = Interrupt.new
24
- @ppu = Ppu.new
22
+ @ppu = Ppu.new(interrupt)
25
23
  @timer = Timer.new(interrupt)
26
- @bus = Bus.new(@ppu, rom, @timer, interrupt)
24
+ @joypad = Joypad.new(interrupt)
25
+ @bus = Bus.new(@ppu, rom, @timer, interrupt, @joypad)
27
26
  @cpu = Cpu.new(@bus)
27
+ @lcd = Lcd.new
28
28
  end
29
29
 
30
- def update
31
- while @total_cycles < 70224
30
+ def start
31
+ until @lcd.window_should_close?
32
+ key_input_check
32
33
  cycles = @cpu.exec
33
- @total_cycles += cycles
34
34
  @timer.step(cycles)
35
- @ppu.step(cycles)
35
+ draw if @ppu.step(cycles)
36
36
  end
37
- @total_cycles -= 70224
37
+ @lcd.close_window
38
38
  rescue StandardError => e
39
39
  p e.to_s[0, 100]
40
40
  raise e
41
41
  end
42
42
 
43
+ private
44
+
43
45
  def draw
44
- pixel_data = @ppu.draw_bg.flatten.pack('C*')
45
- @image = Gosu::Image.from_blob(160, 144, pixel_data)
46
- @image.draw(0, 0, 0, SCALE, SCALE)
46
+ pixel_data = buffer_to_pixel_data(@ppu.buffer)
47
+ @lcd.draw(pixel_data)
48
+ end
49
+
50
+ def buffer_to_pixel_data(buffer)
51
+ buffer.map do |row|
52
+ [row, row, row]
53
+ end.flatten.pack('C*')
47
54
  end
48
55
 
49
- def draw_pixel(x, y, color)
50
- x *= SCALE
51
- y *= SCALE
52
- c = Gosu::Color.new(255, color[0], color[1], color[2])
53
- draw_quad(x, y, c, x + SCALE, y, c, x, y + SCALE, c, x + SCALE, y + SCALE, c)
56
+ def key_input_check
57
+ direction = (IsKeyUp(KEY_D) && 1 || 0) | ((IsKeyUp(KEY_A) && 1 || 0) << 1) | ((IsKeyUp(KEY_W) && 1 || 0) << 2) | ((IsKeyUp(KEY_S) && 1 || 0) << 3)
58
+ action = (IsKeyUp(KEY_K) && 1 || 0) | ((IsKeyUp(KEY_J) && 1 || 0) << 1) | ((IsKeyUp(KEY_U) && 1 || 0) << 2) | ((IsKeyUp(KEY_I) && 1 || 0) << 3)
59
+ @joypad.direction_button(direction)
60
+ @joypad.action_button(action)
61
+ end
62
+
63
+ def load_raylib
64
+ shared_lib_path = "#{Gem::Specification.find_by_name('raylib-bindings').full_gem_path}/lib/"
65
+ case RUBY_PLATFORM
66
+ when /mswin|msys|mingw/ # Windows
67
+ Raylib.load_lib("#{shared_lib_path}libraylib.dll")
68
+ when /darwin/ # macOS
69
+ Raylib.load_lib("#{shared_lib_path}libraylib.dylib")
70
+ when /linux/ # Ubuntu Linux (x86_64 or aarch64)
71
+ arch = RUBY_PLATFORM.split('-')[0]
72
+ Raylib.load_lib(shared_lib_path + "libraylib.#{arch}.so")
73
+ else
74
+ raise "Unknown system: #{RUBY_PLATFORM}"
75
+ end
76
+
77
+ SetTraceLogLevel(LOG_ERROR)
54
78
  end
55
79
  end
56
80
  end
57
81
 
58
- rom_data = File.open(File.expand_path('roms/cpu_instrs/cpu_instrs.gb', __dir__), 'r') { _1.read.bytes }
59
- Rubyboy::Console.new(rom_data).show
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,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyboy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.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-14 00:00:00.000000000 Z
11
+ date: 2023-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: gosu
14
+ name: raylib-bindings
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.4'
19
+ version: 0.5.7
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.4'
26
+ version: 0.5.7
27
27
  description:
28
28
  email:
29
29
  executables: []
@@ -38,6 +38,7 @@ files:
38
38
  - README.md
39
39
  - Rakefile
40
40
  - lib/opcodes.json
41
+ - lib/roms/bgbtest.gb
41
42
  - lib/roms/cpu_instrs/cpu_instrs.gb
42
43
  - lib/roms/cpu_instrs/individual/01-special.gb
43
44
  - lib/roms/cpu_instrs/individual/02-interrupts.gb
@@ -51,6 +52,7 @@ files:
51
52
  - lib/roms/cpu_instrs/individual/10-bit ops.gb
52
53
  - lib/roms/cpu_instrs/individual/11-op a,(hl).gb
53
54
  - lib/roms/cpu_instrs/readme.txt
55
+ - lib/roms/dmg-acid2.gb
54
56
  - lib/roms/hello-world.gb
55
57
  - lib/roms/instr_timing/instr_timing.gb
56
58
  - lib/roms/instr_timing/readme.txt
@@ -62,6 +64,8 @@ files:
62
64
  - lib/rubyboy/cartridge/nombc.rb
63
65
  - lib/rubyboy/cpu.rb
64
66
  - lib/rubyboy/interrupt.rb
67
+ - lib/rubyboy/joypad.rb
68
+ - lib/rubyboy/lcd.rb
65
69
  - lib/rubyboy/ppu.rb
66
70
  - lib/rubyboy/ram.rb
67
71
  - lib/rubyboy/register.rb