rubyboy 0.2.0 → 0.4.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.
- 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
|