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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +17 -0
- data/lib/roms/bgbtest.gb +0 -0
- data/lib/roms/dmg-acid2.gb +0 -0
- data/lib/rubyboy/bus.rb +29 -74
- data/lib/rubyboy/cartridge/factory.rb +2 -0
- data/lib/rubyboy/cartridge/mbc1.rb +0 -10
- data/lib/rubyboy/cartridge/nombc.rb +6 -1
- data/lib/rubyboy/cpu.rb +2 -2
- data/lib/rubyboy/interrupt.rb +18 -0
- data/lib/rubyboy/joypad.rb +41 -0
- data/lib/rubyboy/lcd.rb +37 -0
- data/lib/rubyboy/ppu.rb +237 -41
- data/lib/rubyboy/version.rb +1 -1
- data/lib/rubyboy.rb +49 -25
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a90f9cbc5a0ac4a9f9b971f0c4454421dc83ae9686222c275679f173e7503232
|
4
|
+
data.tar.gz: 52833516da6531982602c373acb11a74d292db6221b8ad34d4420a94be7fe017
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef45830027a11b9827e8660bef2fbb45a5a0fc867486aef58ece8a930acae959eb66ac5709d425e8562d1a546608cbb4cce040efc9e17940b627dfc9095c4a7b
|
7
|
+
data.tar.gz: d8564cc7f5c66dbce015fead7ff770527736943e9c1de3858ed813c1cc012c1e9c5b9248010d16ee39a5fa668e720eb9cda93443b2097260b3b24688b9acdd42
|
data/.rubocop.yml
CHANGED
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
|
data/lib/roms/bgbtest.gb
ADDED
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.
|
31
|
-
when 0xa000..
|
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
|
-
@
|
41
|
+
@ppu.read_byte(addr)
|
37
42
|
when 0xfea0..0xfeff
|
38
43
|
# unused
|
39
44
|
0xff
|
40
45
|
when 0xff00
|
41
|
-
|
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.
|
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.
|
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.
|
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.
|
116
|
-
when 0xa000..
|
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
|
-
@
|
101
|
+
@ppu.write_byte(addr, value)
|
122
102
|
when 0xfea0..0xfeff
|
123
103
|
# unused
|
124
104
|
when 0xff00
|
125
|
-
|
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.
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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.
|
143
|
+
@interrupt.write_byte(addr, value)
|
189
144
|
end
|
190
145
|
end
|
191
146
|
|
@@ -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
|
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
|
data/lib/rubyboy/interrupt.rb
CHANGED
@@ -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
|
data/lib/rubyboy/lcd.rb
ADDED
@@ -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
|
-
|
5
|
+
attr_reader :buffer
|
6
6
|
|
7
|
-
|
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
|
-
@
|
14
|
-
@
|
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
|
-
@
|
23
|
-
|
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
|
27
|
-
|
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
|
-
|
180
|
+
rendered = false
|
181
|
+
y = @wly
|
182
|
+
LCD_WIDTH.times do |i|
|
183
|
+
next if i < @wx - 7
|
31
184
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
195
|
+
def render_sprites
|
196
|
+
return if @lcdc[1].zero?
|
39
197
|
|
40
|
-
|
41
|
-
pixelsa = tile[i * 2]
|
42
|
-
pixelsb = tile[i * 2 + 1]
|
198
|
+
sprite_height = @lcdc[2].zero? ? 8 : 16
|
43
199
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
data/lib/rubyboy/version.rb
CHANGED
data/lib/rubyboy.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
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
|
14
|
-
|
15
|
+
class Console
|
16
|
+
include Raylib
|
15
17
|
|
16
18
|
def initialize(rom_data)
|
17
|
-
|
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
|
-
@
|
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
|
31
|
-
|
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
|
-
@
|
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.
|
45
|
-
@
|
46
|
-
|
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
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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/
|
59
|
-
Rubyboy::Console.new(rom_data).
|
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.
|
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-
|
11
|
+
date: 2023-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: raylib-bindings
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
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:
|
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
|