psx 0.1.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 +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/exe/psx +86 -0
- data/lib/psx/bios.rb +46 -0
- data/lib/psx/cdrom.rb +202 -0
- data/lib/psx/cop0.rb +161 -0
- data/lib/psx/cpu.rb +964 -0
- data/lib/psx/disasm.rb +226 -0
- data/lib/psx/display.rb +200 -0
- data/lib/psx/dma.rb +406 -0
- data/lib/psx/gpu.rb +1116 -0
- data/lib/psx/gte.rb +775 -0
- data/lib/psx/interrupts.rb +79 -0
- data/lib/psx/memory.rb +382 -0
- data/lib/psx/ram.rb +47 -0
- data/lib/psx/sio0.rb +261 -0
- data/lib/psx/spu.rb +110 -0
- data/lib/psx/timers.rb +175 -0
- data/lib/psx/version.rb +5 -0
- data/lib/psx.rb +354 -0
- metadata +114 -0
data/lib/psx/disasm.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PSX
|
|
4
|
+
module Disasm
|
|
5
|
+
REG_NAMES = %w[
|
|
6
|
+
zero at v0 v1 a0 a1 a2 a3
|
|
7
|
+
t0 t1 t2 t3 t4 t5 t6 t7
|
|
8
|
+
s0 s1 s2 s3 s4 s5 s6 s7
|
|
9
|
+
t8 t9 k0 k1 gp sp fp ra
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
COP0_NAMES = {
|
|
13
|
+
3 => "BPC", 5 => "BDA", 6 => "JUMPDEST", 7 => "DCIC",
|
|
14
|
+
8 => "BadVAddr", 9 => "BDAM", 11 => "BPCM", 12 => "SR",
|
|
15
|
+
13 => "CAUSE", 14 => "EPC", 15 => "PRId"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def disassemble(pc, instruction)
|
|
20
|
+
return format("%08X: %08X nop", pc, instruction) if instruction == 0
|
|
21
|
+
|
|
22
|
+
opcode = (instruction >> 26) & 0x3F
|
|
23
|
+
text = decode(pc, instruction, opcode)
|
|
24
|
+
format("%08X: %08X %s", pc, instruction, text)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def decode(pc, instruction, opcode)
|
|
30
|
+
case opcode
|
|
31
|
+
when 0x00 then decode_special(instruction)
|
|
32
|
+
when 0x01 then decode_bcondz(pc, instruction)
|
|
33
|
+
when 0x02 then decode_j("j", pc, instruction)
|
|
34
|
+
when 0x03 then decode_j("jal", pc, instruction)
|
|
35
|
+
when 0x04 then decode_branch("beq", pc, instruction)
|
|
36
|
+
when 0x05 then decode_branch("bne", pc, instruction)
|
|
37
|
+
when 0x06 then decode_branch_z("blez", pc, instruction)
|
|
38
|
+
when 0x07 then decode_branch_z("bgtz", pc, instruction)
|
|
39
|
+
when 0x08 then decode_imm_arith("addi", instruction)
|
|
40
|
+
when 0x09 then decode_imm_arith("addiu", instruction)
|
|
41
|
+
when 0x0A then decode_imm_arith("slti", instruction)
|
|
42
|
+
when 0x0B then decode_imm_arith("sltiu", instruction)
|
|
43
|
+
when 0x0C then decode_imm_logic("andi", instruction)
|
|
44
|
+
when 0x0D then decode_imm_logic("ori", instruction)
|
|
45
|
+
when 0x0E then decode_imm_logic("xori", instruction)
|
|
46
|
+
when 0x0F then decode_lui(instruction)
|
|
47
|
+
when 0x10 then decode_cop0(instruction)
|
|
48
|
+
when 0x12 then decode_cop2(instruction)
|
|
49
|
+
when 0x20 then decode_load("lb", instruction)
|
|
50
|
+
when 0x21 then decode_load("lh", instruction)
|
|
51
|
+
when 0x22 then decode_load("lwl", instruction)
|
|
52
|
+
when 0x23 then decode_load("lw", instruction)
|
|
53
|
+
when 0x24 then decode_load("lbu", instruction)
|
|
54
|
+
when 0x25 then decode_load("lhu", instruction)
|
|
55
|
+
when 0x26 then decode_load("lwr", instruction)
|
|
56
|
+
when 0x28 then decode_store("sb", instruction)
|
|
57
|
+
when 0x29 then decode_store("sh", instruction)
|
|
58
|
+
when 0x2A then decode_store("swl", instruction)
|
|
59
|
+
when 0x2B then decode_store("sw", instruction)
|
|
60
|
+
when 0x2E then decode_store("swr", instruction)
|
|
61
|
+
else
|
|
62
|
+
format("??? (op=%02X)", opcode)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def decode_special(instruction)
|
|
67
|
+
funct = instruction & 0x3F
|
|
68
|
+
rs = (instruction >> 21) & 0x1F
|
|
69
|
+
rt = (instruction >> 16) & 0x1F
|
|
70
|
+
rd = (instruction >> 11) & 0x1F
|
|
71
|
+
shamt = (instruction >> 6) & 0x1F
|
|
72
|
+
|
|
73
|
+
case funct
|
|
74
|
+
when 0x00 then format("sll %s, %s, %d", reg(rd), reg(rt), shamt)
|
|
75
|
+
when 0x02 then format("srl %s, %s, %d", reg(rd), reg(rt), shamt)
|
|
76
|
+
when 0x03 then format("sra %s, %s, %d", reg(rd), reg(rt), shamt)
|
|
77
|
+
when 0x04 then format("sllv %s, %s, %s", reg(rd), reg(rt), reg(rs))
|
|
78
|
+
when 0x06 then format("srlv %s, %s, %s", reg(rd), reg(rt), reg(rs))
|
|
79
|
+
when 0x07 then format("srav %s, %s, %s", reg(rd), reg(rt), reg(rs))
|
|
80
|
+
when 0x08 then format("jr %s", reg(rs))
|
|
81
|
+
when 0x09 then format("jalr %s, %s", reg(rd), reg(rs))
|
|
82
|
+
when 0x0C then "syscall"
|
|
83
|
+
when 0x0D then "break"
|
|
84
|
+
when 0x10 then format("mfhi %s", reg(rd))
|
|
85
|
+
when 0x11 then format("mthi %s", reg(rs))
|
|
86
|
+
when 0x12 then format("mflo %s", reg(rd))
|
|
87
|
+
when 0x13 then format("mtlo %s", reg(rs))
|
|
88
|
+
when 0x18 then format("mult %s, %s", reg(rs), reg(rt))
|
|
89
|
+
when 0x19 then format("multu %s, %s", reg(rs), reg(rt))
|
|
90
|
+
when 0x1A then format("div %s, %s", reg(rs), reg(rt))
|
|
91
|
+
when 0x1B then format("divu %s, %s", reg(rs), reg(rt))
|
|
92
|
+
when 0x20 then format("add %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
93
|
+
when 0x21 then format("addu %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
94
|
+
when 0x22 then format("sub %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
95
|
+
when 0x23 then format("subu %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
96
|
+
when 0x24 then format("and %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
97
|
+
when 0x25 then format("or %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
98
|
+
when 0x26 then format("xor %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
99
|
+
when 0x27 then format("nor %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
100
|
+
when 0x2A then format("slt %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
101
|
+
when 0x2B then format("sltu %s, %s, %s", reg(rd), reg(rs), reg(rt))
|
|
102
|
+
else
|
|
103
|
+
format("??? (special funct=%02X)", funct)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def decode_bcondz(pc, instruction)
|
|
108
|
+
rs = (instruction >> 21) & 0x1F
|
|
109
|
+
rt = (instruction >> 16) & 0x1F
|
|
110
|
+
imm = instruction & 0xFFFF
|
|
111
|
+
offset = sign_extend16(imm) << 2
|
|
112
|
+
target = (pc + 4 + offset) & 0xFFFF_FFFF
|
|
113
|
+
|
|
114
|
+
link = (rt & 0x10) != 0
|
|
115
|
+
bgez = (rt & 0x01) != 0
|
|
116
|
+
|
|
117
|
+
name = if bgez
|
|
118
|
+
link ? "bgezal" : "bgez"
|
|
119
|
+
else
|
|
120
|
+
link ? "bltzal" : "bltz"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
format("%s %s, 0x%08X", name, reg(rs), target)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def decode_j(name, pc, instruction)
|
|
127
|
+
target = instruction & 0x03FF_FFFF
|
|
128
|
+
addr = ((pc + 4) & 0xF000_0000) | (target << 2)
|
|
129
|
+
format("%s 0x%08X", name, addr)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def decode_branch(name, pc, instruction)
|
|
133
|
+
rs = (instruction >> 21) & 0x1F
|
|
134
|
+
rt = (instruction >> 16) & 0x1F
|
|
135
|
+
imm = instruction & 0xFFFF
|
|
136
|
+
offset = sign_extend16(imm) << 2
|
|
137
|
+
target = (pc + 4 + offset) & 0xFFFF_FFFF
|
|
138
|
+
format("%s %s, %s, 0x%08X", name, reg(rs), reg(rt), target)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def decode_branch_z(name, pc, instruction)
|
|
142
|
+
rs = (instruction >> 21) & 0x1F
|
|
143
|
+
imm = instruction & 0xFFFF
|
|
144
|
+
offset = sign_extend16(imm) << 2
|
|
145
|
+
target = (pc + 4 + offset) & 0xFFFF_FFFF
|
|
146
|
+
format("%s %s, 0x%08X", name, reg(rs), target)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def decode_imm_arith(name, instruction)
|
|
150
|
+
rs = (instruction >> 21) & 0x1F
|
|
151
|
+
rt = (instruction >> 16) & 0x1F
|
|
152
|
+
imm = sign_extend16(instruction & 0xFFFF)
|
|
153
|
+
format("%s %s, %s, %d", name, reg(rt), reg(rs), imm)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def decode_imm_logic(name, instruction)
|
|
157
|
+
rs = (instruction >> 21) & 0x1F
|
|
158
|
+
rt = (instruction >> 16) & 0x1F
|
|
159
|
+
imm = instruction & 0xFFFF
|
|
160
|
+
format("%s %s, %s, 0x%04X", name, reg(rt), reg(rs), imm)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def decode_lui(instruction)
|
|
164
|
+
rt = (instruction >> 16) & 0x1F
|
|
165
|
+
imm = instruction & 0xFFFF
|
|
166
|
+
format("lui %s, 0x%04X", reg(rt), imm)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def decode_load(name, instruction)
|
|
170
|
+
rs = (instruction >> 21) & 0x1F
|
|
171
|
+
rt = (instruction >> 16) & 0x1F
|
|
172
|
+
imm = sign_extend16(instruction & 0xFFFF)
|
|
173
|
+
format("%s %s, %d(%s)", name, reg(rt), imm, reg(rs))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def decode_store(name, instruction)
|
|
177
|
+
rs = (instruction >> 21) & 0x1F
|
|
178
|
+
rt = (instruction >> 16) & 0x1F
|
|
179
|
+
imm = sign_extend16(instruction & 0xFFFF)
|
|
180
|
+
format("%s %s, %d(%s)", name, reg(rt), imm, reg(rs))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def decode_cop0(instruction)
|
|
184
|
+
cop_op = (instruction >> 21) & 0x1F
|
|
185
|
+
rt = (instruction >> 16) & 0x1F
|
|
186
|
+
rd = (instruction >> 11) & 0x1F
|
|
187
|
+
|
|
188
|
+
case cop_op
|
|
189
|
+
when 0x00 then format("mfc0 %s, %s", reg(rt), cop0_reg(rd))
|
|
190
|
+
when 0x04 then format("mtc0 %s, %s", reg(rt), cop0_reg(rd))
|
|
191
|
+
when 0x10 then "rfe"
|
|
192
|
+
else
|
|
193
|
+
format("cop0 (op=%02X)", cop_op)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def decode_cop2(instruction)
|
|
198
|
+
cop_op = (instruction >> 21) & 0x1F
|
|
199
|
+
rt = (instruction >> 16) & 0x1F
|
|
200
|
+
rd = (instruction >> 11) & 0x1F
|
|
201
|
+
|
|
202
|
+
case cop_op
|
|
203
|
+
when 0x00 then format("mfc2 %s, $%d", reg(rt), rd)
|
|
204
|
+
when 0x02 then format("cfc2 %s, $%d", reg(rt), rd)
|
|
205
|
+
when 0x04 then format("mtc2 %s, $%d", reg(rt), rd)
|
|
206
|
+
when 0x06 then format("ctc2 %s, $%d", reg(rt), rd)
|
|
207
|
+
else
|
|
208
|
+
cmd = instruction & 0x1FF_FFFF
|
|
209
|
+
format("cop2 cmd=0x%07X", cmd)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def reg(n)
|
|
214
|
+
"$#{REG_NAMES[n]}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def cop0_reg(n)
|
|
218
|
+
COP0_NAMES[n] || "$#{n}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def sign_extend16(val)
|
|
222
|
+
(val & 0x8000) != 0 ? (val - 0x1_0000) : val
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/psx/display.rb
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sdl2"
|
|
4
|
+
|
|
5
|
+
module PSX
|
|
6
|
+
class Display
|
|
7
|
+
WIDTH = 320
|
|
8
|
+
HEIGHT = 240
|
|
9
|
+
SCALE = 2 # 640x480 window
|
|
10
|
+
|
|
11
|
+
def initialize(title: "PSX-Ruby")
|
|
12
|
+
SDL2.init(SDL2::INIT_VIDEO)
|
|
13
|
+
|
|
14
|
+
@window = SDL2::Window.create(
|
|
15
|
+
title,
|
|
16
|
+
SDL2::Window::POS_CENTERED,
|
|
17
|
+
SDL2::Window::POS_CENTERED,
|
|
18
|
+
WIDTH * SCALE,
|
|
19
|
+
HEIGHT * SCALE,
|
|
20
|
+
0
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@renderer = @window.create_renderer(-1, SDL2::Renderer::Flags::ACCELERATED)
|
|
24
|
+
|
|
25
|
+
# Input state
|
|
26
|
+
@quit_requested = false
|
|
27
|
+
@controller_state = 0xFFFF # All buttons released (active low)
|
|
28
|
+
|
|
29
|
+
# Performance tracking
|
|
30
|
+
@frame_count = 0
|
|
31
|
+
@last_fps_time = Time.now
|
|
32
|
+
@fps = 0
|
|
33
|
+
|
|
34
|
+
# Cache texture reference
|
|
35
|
+
@texture = nil
|
|
36
|
+
|
|
37
|
+
# Pre-allocated RGBA buffer (320x240x4 = 307,200 bytes)
|
|
38
|
+
@rgba_buffer = "\x00".b * (WIDTH * HEIGHT * 4)
|
|
39
|
+
@rgba_buffer_size = WIDTH * HEIGHT * 4
|
|
40
|
+
|
|
41
|
+
# Track if we've seen real content
|
|
42
|
+
@has_rendered_content = false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update(framebuffer)
|
|
46
|
+
width = framebuffer[:width]
|
|
47
|
+
height = framebuffer[:height]
|
|
48
|
+
|
|
49
|
+
# GPU now provides pre-packed RGBA string
|
|
50
|
+
rgba = framebuffer[:rgba]
|
|
51
|
+
|
|
52
|
+
# Check if framebuffer has any non-zero content (only until first content seen)
|
|
53
|
+
if rgba && !@has_rendered_content
|
|
54
|
+
# Quick check: sample a few bytes instead of iterating all
|
|
55
|
+
has_content = rgba.getbyte(0) != 0 || rgba.getbyte(rgba.bytesize / 2) != 0
|
|
56
|
+
@has_rendered_content = true if has_content
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if rgba && @has_rendered_content
|
|
60
|
+
@rgba_buffer = rgba
|
|
61
|
+
else
|
|
62
|
+
# Use cached loading screen if size matches
|
|
63
|
+
buffer_size = width * height * 4
|
|
64
|
+
if @loading_screen_size != buffer_size
|
|
65
|
+
# Generate loading screen pattern once
|
|
66
|
+
rgba_arr = []
|
|
67
|
+
height.times do |y|
|
|
68
|
+
width.times do |x|
|
|
69
|
+
if (x % 32 < 2) || (y % 32 < 2)
|
|
70
|
+
rgba_arr.push(40, 40, 80, 255) # Grid lines
|
|
71
|
+
else
|
|
72
|
+
rgba_arr.push(0, 0, 40, 255) # Dark blue
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
@loading_screen = rgba_arr.pack("C*")
|
|
77
|
+
@loading_screen_size = buffer_size
|
|
78
|
+
end
|
|
79
|
+
@rgba_buffer = @loading_screen
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Create SDL surface from RGBA data
|
|
83
|
+
surface = SDL2::Surface.from_string(
|
|
84
|
+
@rgba_buffer,
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
32, # depth (bits per pixel)
|
|
88
|
+
width * 4, # pitch (bytes per row)
|
|
89
|
+
0x000000FF, # R mask
|
|
90
|
+
0x0000FF00, # G mask
|
|
91
|
+
0x00FF0000, # B mask
|
|
92
|
+
0xFF000000 # A mask
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Create texture from surface
|
|
96
|
+
@texture&.destroy
|
|
97
|
+
@texture = @renderer.create_texture_from(surface)
|
|
98
|
+
surface.destroy
|
|
99
|
+
|
|
100
|
+
# Render (stretch to window size)
|
|
101
|
+
@renderer.copy(@texture, nil, nil)
|
|
102
|
+
@renderer.present
|
|
103
|
+
|
|
104
|
+
# Update FPS counter
|
|
105
|
+
@frame_count += 1
|
|
106
|
+
@total_frames = (@total_frames || 0) + 1
|
|
107
|
+
now = Time.now
|
|
108
|
+
elapsed = now - @last_fps_time
|
|
109
|
+
if elapsed >= 1.0
|
|
110
|
+
@fps = (@frame_count / elapsed).round(1)
|
|
111
|
+
status = has_content ? "RENDERING" : "Loading..."
|
|
112
|
+
@window.title = "PSX-Ruby - #{@fps} FPS - Frame #{@total_frames} - #{status}"
|
|
113
|
+
@frame_count = 0
|
|
114
|
+
@last_fps_time = now
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def poll_events
|
|
119
|
+
while (event = SDL2::Event.poll)
|
|
120
|
+
case event
|
|
121
|
+
when SDL2::Event::Quit
|
|
122
|
+
@quit_requested = true
|
|
123
|
+
when SDL2::Event::KeyDown
|
|
124
|
+
handle_key(event.scancode, true)
|
|
125
|
+
when SDL2::Event::KeyUp
|
|
126
|
+
handle_key(event.scancode, false)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def quit_requested?
|
|
132
|
+
@quit_requested
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def controller_state
|
|
136
|
+
@controller_state
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def close
|
|
140
|
+
@texture&.destroy
|
|
141
|
+
@renderer.destroy
|
|
142
|
+
@window.destroy
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# PS1 controller button bits
|
|
148
|
+
BUTTON_SELECT = 0
|
|
149
|
+
BUTTON_L3 = 1
|
|
150
|
+
BUTTON_R3 = 2
|
|
151
|
+
BUTTON_START = 3
|
|
152
|
+
BUTTON_UP = 4
|
|
153
|
+
BUTTON_RIGHT = 5
|
|
154
|
+
BUTTON_DOWN = 6
|
|
155
|
+
BUTTON_LEFT = 7
|
|
156
|
+
BUTTON_L2 = 8
|
|
157
|
+
BUTTON_R2 = 9
|
|
158
|
+
BUTTON_L1 = 10
|
|
159
|
+
BUTTON_R1 = 11
|
|
160
|
+
BUTTON_TRIANGLE = 12
|
|
161
|
+
BUTTON_CIRCLE = 13
|
|
162
|
+
BUTTON_CROSS = 14
|
|
163
|
+
BUTTON_SQUARE = 15
|
|
164
|
+
|
|
165
|
+
# Keyboard to controller mapping
|
|
166
|
+
KEY_MAP = {
|
|
167
|
+
SDL2::Key::Scan::UP => BUTTON_UP,
|
|
168
|
+
SDL2::Key::Scan::DOWN => BUTTON_DOWN,
|
|
169
|
+
SDL2::Key::Scan::LEFT => BUTTON_LEFT,
|
|
170
|
+
SDL2::Key::Scan::RIGHT => BUTTON_RIGHT,
|
|
171
|
+
SDL2::Key::Scan::Z => BUTTON_CROSS,
|
|
172
|
+
SDL2::Key::Scan::X => BUTTON_CIRCLE,
|
|
173
|
+
SDL2::Key::Scan::A => BUTTON_SQUARE,
|
|
174
|
+
SDL2::Key::Scan::S => BUTTON_TRIANGLE,
|
|
175
|
+
SDL2::Key::Scan::RETURN => BUTTON_START,
|
|
176
|
+
SDL2::Key::Scan::SPACE => BUTTON_SELECT,
|
|
177
|
+
SDL2::Key::Scan::Q => BUTTON_L1,
|
|
178
|
+
SDL2::Key::Scan::W => BUTTON_R1,
|
|
179
|
+
SDL2::Key::Scan::E => BUTTON_L2,
|
|
180
|
+
SDL2::Key::Scan::R => BUTTON_R2
|
|
181
|
+
}.freeze
|
|
182
|
+
|
|
183
|
+
def handle_key(scancode, pressed)
|
|
184
|
+
# Handle quit on Escape
|
|
185
|
+
if scancode == SDL2::Key::Scan::ESCAPE && pressed
|
|
186
|
+
@quit_requested = true
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
button = KEY_MAP[scancode]
|
|
191
|
+
return unless button
|
|
192
|
+
|
|
193
|
+
if pressed
|
|
194
|
+
@controller_state &= ~(1 << button) # Active low
|
|
195
|
+
else
|
|
196
|
+
@controller_state |= (1 << button)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|