amaterasu 0.6.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/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +54 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +267 -0
- data/LICENSE +21 -0
- data/README.md +115 -0
- data/Steepfile +7 -0
- data/exe/amaterasu +23 -0
- data/lib/amaterasu/cartridge/mbc1.rb +56 -0
- data/lib/amaterasu/cartridge/rom.rb +118 -0
- data/lib/amaterasu/cartridge.rb +68 -0
- data/lib/amaterasu/cli.rb +60 -0
- data/lib/amaterasu/emulator.rb +121 -0
- data/lib/amaterasu/game_boy/apu.rb +12 -0
- data/lib/amaterasu/game_boy/bus.rb +161 -0
- data/lib/amaterasu/game_boy/cpu/instructions/adc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add16.rb +73 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add8.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/and.rb +62 -0
- data/lib/amaterasu/game_boy/cpu/instructions/base.rb +38 -0
- data/lib/amaterasu/game_boy/cpu/instructions/call.rb +48 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb +52 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_res.rb +49 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_set.rb +51 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb +71 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb +67 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cp.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/daa.rb +59 -0
- data/lib/amaterasu/game_boy/cpu/instructions/dec.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/di.rb +21 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ei.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/halt.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/inc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jp.rb +54 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jr.rb +45 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld16.rb +79 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld8.rb +210 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ldh.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/misc.rb +53 -0
- data/lib/amaterasu/game_boy/cpu/instructions/nop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/or.rb +56 -0
- data/lib/amaterasu/game_boy/cpu/instructions/pop.rb +39 -0
- data/lib/amaterasu/game_boy/cpu/instructions/push.rb +43 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ret.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rotate.rb +120 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rst.rb +33 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sbc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/stop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sub.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/xor.rb +60 -0
- data/lib/amaterasu/game_boy/cpu/instructions.rb +600 -0
- data/lib/amaterasu/game_boy/cpu/registers.rb +264 -0
- data/lib/amaterasu/game_boy/cpu.rb +232 -0
- data/lib/amaterasu/game_boy/dma.rb +114 -0
- data/lib/amaterasu/game_boy/interrupts.rb +108 -0
- data/lib/amaterasu/game_boy/joypad.rb +127 -0
- data/lib/amaterasu/game_boy/oam/sprite.rb +106 -0
- data/lib/amaterasu/game_boy/oam.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/disabled.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/h_blank.rb +45 -0
- data/lib/amaterasu/game_boy/ppu/modes/oam_scan.rb +93 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb +204 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb +83 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb +70 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb +140 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering.rb +108 -0
- data/lib/amaterasu/game_boy/ppu/modes/v_blank.rb +43 -0
- data/lib/amaterasu/game_boy/ppu/modes.rb +22 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_control.rb +57 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_status.rb +88 -0
- data/lib/amaterasu/game_boy/ppu/registers.rb +131 -0
- data/lib/amaterasu/game_boy/ppu.rb +207 -0
- data/lib/amaterasu/game_boy/ram.rb +70 -0
- data/lib/amaterasu/game_boy/serial.rb +91 -0
- data/lib/amaterasu/game_boy/timer.rb +230 -0
- data/lib/amaterasu/game_boy/vram/tile.rb +68 -0
- data/lib/amaterasu/game_boy/vram/tile_data.rb +52 -0
- data/lib/amaterasu/game_boy/vram/tile_map.rb +71 -0
- data/lib/amaterasu/game_boy/vram.rb +51 -0
- data/lib/amaterasu/hal/console.rb +23 -0
- data/lib/amaterasu/hal/sdl2/bindings.rb +59 -0
- data/lib/amaterasu/hal/sdl2.rb +127 -0
- data/lib/amaterasu/utils/bit_ops.rb +22 -0
- data/lib/amaterasu.rb +13 -0
- data/sig/akane/cartridge/rom.rbs +29 -0
- data/sig/akane/cartridge.rbs +12 -0
- data/sig/akane/cli.rbs +16 -0
- data/sig/akane/emulator.rbs +19 -0
- data/sig/akane/game_boy/apu.rbs +7 -0
- data/sig/akane/game_boy/bus.rbs +25 -0
- data/sig/akane/game_boy/cpu/instructions/adc.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/add16.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/add8.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/and.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/base.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/call.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/cb_bit.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_res.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rlc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rr.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rrc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_set.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sla.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sra.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_srl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_swap.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/daa.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/dec.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/di.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/ei.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/halt.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/inc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/jp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/jr.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld16.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld8.rbs +31 -0
- data/sig/akane/game_boy/cpu/instructions/ldh.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/misc.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/nop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/or.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/pop.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/push.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ret.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/rotate.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/rst.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/sbc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/stop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/sub.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/xor.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions.rbs +12 -0
- data/sig/akane/game_boy/cpu/registers.rbs +56 -0
- data/sig/akane/game_boy/cpu.rbs +39 -0
- data/sig/akane/game_boy/interrupts.rbs +28 -0
- data/sig/akane/game_boy/joypad.rbs +25 -0
- data/sig/akane/game_boy/oam/sprite.rbs +30 -0
- data/sig/akane/game_boy/ppu/modes/disabled.rbs +17 -0
- data/sig/akane/game_boy/ppu/modes/h_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes/oam_scan.rbs +28 -0
- data/sig/akane/game_boy/ppu/modes/rendering.rbs +26 -0
- data/sig/akane/game_boy/ppu/modes/v_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes.rbs +13 -0
- data/sig/akane/game_boy/ppu.rbs +59 -0
- data/sig/akane/game_boy/ram.rbs +16 -0
- data/sig/akane/game_boy/serial.rbs +21 -0
- data/sig/akane/game_boy/timer.rbs +30 -0
- data/sig/akane/utils/bit_ops.rbs +11 -0
- data/sig/akane.rbs +3 -0
- metadata +226 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
# Models the built-in clock timer inside the Game Boy.
|
|
6
|
+
#
|
|
7
|
+
# As of now it is implemented using M-cycle accuracy,
|
|
8
|
+
# I might change it afterwards to T-cycle accuracy, but
|
|
9
|
+
# as of now all acceptance tests are passing, so no need.
|
|
10
|
+
class Timer
|
|
11
|
+
# Each tick advances 4 T-cycles / 1 M-cycle
|
|
12
|
+
T_CYCLES = 4
|
|
13
|
+
|
|
14
|
+
# Master clock defined by the hardware specs (in T-cycles).
|
|
15
|
+
MASTER_CLOCK_FREQUENCY = 4_194_304
|
|
16
|
+
|
|
17
|
+
# Frequency in which TIMA increments once for each TAC clock select.
|
|
18
|
+
TIMA_INCREMENT_FREQUENCIES = [
|
|
19
|
+
4_096,
|
|
20
|
+
262_144,
|
|
21
|
+
65_536,
|
|
22
|
+
16_384
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
# How many T-cycles are needed to increment TIMA once for each clock select.
|
|
26
|
+
TIMA_INCREMENT_CYCLES = [
|
|
27
|
+
MASTER_CLOCK_FREQUENCY / TIMA_INCREMENT_FREQUENCIES[0b00],
|
|
28
|
+
MASTER_CLOCK_FREQUENCY / TIMA_INCREMENT_FREQUENCIES[0b01],
|
|
29
|
+
MASTER_CLOCK_FREQUENCY / TIMA_INCREMENT_FREQUENCIES[0b10],
|
|
30
|
+
MASTER_CLOCK_FREQUENCY / TIMA_INCREMENT_FREQUENCIES[0b11]
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# TIMA only increments if there is a falling edge.
|
|
34
|
+
# The value needs to be divided by 2 to achieve the correct value.
|
|
35
|
+
# The given bit needs to flip twice to reach a falling edge (0 -> 1 and 1 -> 0).
|
|
36
|
+
COUNTER_FALLING_EDGE_CYCLES = [
|
|
37
|
+
TIMA_INCREMENT_CYCLES[0b00] / 2,
|
|
38
|
+
TIMA_INCREMENT_CYCLES[0b01] / 2,
|
|
39
|
+
TIMA_INCREMENT_CYCLES[0b10] / 2,
|
|
40
|
+
TIMA_INCREMENT_CYCLES[0b11] / 2
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
# In binary a given Bit N always flips its value after 2^N ticks.
|
|
44
|
+
# Based on the number of cycles derived above you can find the correct bit to watch.
|
|
45
|
+
#
|
|
46
|
+
# Example for clock select 0b00:
|
|
47
|
+
# - 1024 T-cycles to increment TIMA.
|
|
48
|
+
# - So we need to find a Bit N that flips (1024 / 2) times to achieve a falling edge.
|
|
49
|
+
# - 2^N = 512 => N = log2(512) => N = 9 (Watch Bit 9 from the system counter).
|
|
50
|
+
COUNTER_BITS_TO_WATCH = [
|
|
51
|
+
Math.log2(COUNTER_FALLING_EDGE_CYCLES[0b00]).round,
|
|
52
|
+
Math.log2(COUNTER_FALLING_EDGE_CYCLES[0b01]).round,
|
|
53
|
+
Math.log2(COUNTER_FALLING_EDGE_CYCLES[0b10]).round,
|
|
54
|
+
Math.log2(COUNTER_FALLING_EDGE_CYCLES[0b11]).round
|
|
55
|
+
].freeze
|
|
56
|
+
|
|
57
|
+
# Returns the 8-bit value stored in the TIMA (Timer Counter) register.
|
|
58
|
+
attr_reader :tima
|
|
59
|
+
|
|
60
|
+
# Returns the 8-bit value stored in the TMA (Timer Modulo) register.
|
|
61
|
+
attr_reader :tma
|
|
62
|
+
|
|
63
|
+
# Returns the 8-bit value stored in the TAC (Timer Control) register.
|
|
64
|
+
attr_reader :tac
|
|
65
|
+
|
|
66
|
+
# Creates an instance of the timer.
|
|
67
|
+
#
|
|
68
|
+
# - Needs the interrupts instance to request a timer interrupt.
|
|
69
|
+
# - Has an internal-only 16-bit counter that increments each T-cycle.
|
|
70
|
+
def initialize(interrupts, skip_boot_rom: true, trace_timer: false)
|
|
71
|
+
@interrupts = interrupts
|
|
72
|
+
@trace_timer = trace_timer
|
|
73
|
+
|
|
74
|
+
@counter = skip_boot_rom ? 0xABCC : 0x0000
|
|
75
|
+
@tima = 0x00
|
|
76
|
+
@tma = 0x00
|
|
77
|
+
@tac = skip_boot_rom ? 0xF8 : 0x00
|
|
78
|
+
|
|
79
|
+
@tac_enable_bit = @tac[2]
|
|
80
|
+
@tac_clock_select_bits = @tac & 0b11
|
|
81
|
+
@counter_watched_bit_pos = COUNTER_BITS_TO_WATCH[@tac_clock_select_bits]
|
|
82
|
+
|
|
83
|
+
@state = :running
|
|
84
|
+
@tima_overflow = false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Reading DIV only exposes the upper byte of the system counter.
|
|
88
|
+
#
|
|
89
|
+
# @return [Integer]
|
|
90
|
+
def div
|
|
91
|
+
(@counter >> 8) & 0xFF
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Writing to DIV register always resets the system counter.
|
|
95
|
+
#
|
|
96
|
+
# When the value is reset to 0x0000, if the current bit being "watched"
|
|
97
|
+
# goes from 1 -> 0 it can trigger a TIMA increment due to a falling edge
|
|
98
|
+
# in the joint signal.
|
|
99
|
+
#
|
|
100
|
+
# @param value [Integer] Value is ignored and resets the whole @counter.
|
|
101
|
+
def div=(_value)
|
|
102
|
+
old_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
103
|
+
@counter = 0x0000
|
|
104
|
+
new_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
105
|
+
|
|
106
|
+
increment_tima if falling_edge?(old_signal, new_signal)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Sets a 8-bit value into the TIMA register.
|
|
110
|
+
#
|
|
111
|
+
# Obscure behaviors:
|
|
112
|
+
# - If the CPU tries to write to TIMA the same cycle it was already reloaded
|
|
113
|
+
# with TMA, the write is completely ignored and TIMA keeps the TMA value.
|
|
114
|
+
# - If the CPU tries to write to TIMA the cycle immediately after it overflows,
|
|
115
|
+
# but hasn't been reloaded with TMA yet, the write succeeds, TIMA keeps the
|
|
116
|
+
# value written by the CPU and the reload is cancelled as if the overflow never happened.
|
|
117
|
+
#
|
|
118
|
+
# @param value [Integer] 8-bit value to store in the TIMA register.
|
|
119
|
+
def tima=(value)
|
|
120
|
+
return if @state == :tima_reloaded
|
|
121
|
+
|
|
122
|
+
@state = :tima_reloaded if @state == :tima_reload_pending
|
|
123
|
+
@tima = value & 0xFF
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Sets a 8-bit value into the TMA register.
|
|
127
|
+
#
|
|
128
|
+
# Obscure behavior:
|
|
129
|
+
# - If the CPU writes to TMA the cycle after TIMA was already reloaded,
|
|
130
|
+
# TIMA is reloaded a second time with the new value because the TMA latch
|
|
131
|
+
# remains open for 2 M-cycles (the original reload + the next).
|
|
132
|
+
#
|
|
133
|
+
# @param value [Integer] 8-bit value to store in the TMA register.
|
|
134
|
+
def tma=(value)
|
|
135
|
+
@tma = value & 0xFF
|
|
136
|
+
@tima = @tma if @state == :tima_reloaded
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Sets a 8-bit value into the TAC register.
|
|
140
|
+
#
|
|
141
|
+
# There are 2 distinct cases in which writing to TAC can cause
|
|
142
|
+
# a sporadic TIMA increment:
|
|
143
|
+
# - TAC Enable (Bit 2) going from 1 -> 0 can cause a falling edge in the joint signal.
|
|
144
|
+
# - Changing the TAC Clock Select (Bits 1-0) can also cause a falling edge
|
|
145
|
+
# if the previous bit selected from the counter was 1, and the new one is 0.
|
|
146
|
+
#
|
|
147
|
+
# @param value [Integer] 8-bit value to store in the TAC register.
|
|
148
|
+
def tac=(value)
|
|
149
|
+
old_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
150
|
+
|
|
151
|
+
@tac = value & 0xFF
|
|
152
|
+
@tac_enable_bit = @tac[2]
|
|
153
|
+
@tac_clock_select_bits = @tac & 0b11
|
|
154
|
+
@counter_watched_bit_pos = COUNTER_BITS_TO_WATCH[@tac_clock_select_bits]
|
|
155
|
+
|
|
156
|
+
new_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
157
|
+
|
|
158
|
+
increment_tima if falling_edge?(old_signal, new_signal)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Advances the system counter, increments TIMA if needed and handles TIMA overflow logic.
|
|
162
|
+
#
|
|
163
|
+
# - Counter value should wrap around 0xFFFF (16-bit).
|
|
164
|
+
# - The Counter is always counting independent from all the other logic.
|
|
165
|
+
# - The tick is being implemented in M-cycle precision, so each tick is 4 T-cycles.
|
|
166
|
+
# - After TIMA overflows, there is a 1 M-cycle delay before
|
|
167
|
+
# setting TMA into TIMA and requesting the Timer interrupt.
|
|
168
|
+
def tick
|
|
169
|
+
old_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
170
|
+
@counter = (@counter + T_CYCLES) & 0xFFFF
|
|
171
|
+
new_signal = @tac_enable_bit & @counter[@counter_watched_bit_pos]
|
|
172
|
+
|
|
173
|
+
case @state
|
|
174
|
+
when :running
|
|
175
|
+
increment_tima if falling_edge?(old_signal, new_signal)
|
|
176
|
+
when :tima_reload_pending
|
|
177
|
+
@tima = @tma
|
|
178
|
+
@interrupts.request(:timer)
|
|
179
|
+
@state = :tima_reloaded
|
|
180
|
+
when :tima_reloaded
|
|
181
|
+
@state = :running
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
log_state(old_signal, new_signal) if @trace_timer
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
# Increments TIMA once, the register is 8-bit so it wraps around 0xFF.
|
|
190
|
+
# Sets a new Timer state when the overflow occurs to be handled in the M-cycle after.
|
|
191
|
+
def increment_tima
|
|
192
|
+
@tima = (@tima + 1) & 0xFF
|
|
193
|
+
@state = :tima_reload_pending if @tima.zero?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Checks for a falling edge (1 -> 0) in the joint signal.
|
|
197
|
+
# Joint signal is a bitwise AND between:
|
|
198
|
+
# - TAC enable bit (Bit 2)
|
|
199
|
+
# - Currently selected bit in the system counter
|
|
200
|
+
#
|
|
201
|
+
# @param old_signal [Integer] Either 0 or 1.
|
|
202
|
+
# @param new_signal [Integer] Either 0 or 1.
|
|
203
|
+
# @return [Boolean]
|
|
204
|
+
def falling_edge?(old_signal, new_signal)
|
|
205
|
+
old_signal == 1 && new_signal == 0
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Logs the state of the Timer for each M-cycle if the Timer trace is set.
|
|
209
|
+
#
|
|
210
|
+
# @param old_signal [Integer] Either 1 or 0.
|
|
211
|
+
# @param new_signal [Integer] Either 1 or 0.
|
|
212
|
+
def log_state(old_signal, new_signal)
|
|
213
|
+
$stdout.printf(
|
|
214
|
+
'COUNTER: $%<n>04X (%<n>06d) || :%<state>-20s || ' \
|
|
215
|
+
'TIMA: $%<tima>02X || TMA: $%<tma>02X || TAC: %<tac>08b || ' \
|
|
216
|
+
'OLD SIGNAL: %<os>d => NEW SIGNAL: %<ns>d || ' \
|
|
217
|
+
"INTERRUPT: %<int>d\n",
|
|
218
|
+
n: @counter,
|
|
219
|
+
state: @state.upcase,
|
|
220
|
+
tima: @tima,
|
|
221
|
+
tma: @tma,
|
|
222
|
+
tac: @tac,
|
|
223
|
+
os: old_signal,
|
|
224
|
+
ns: new_signal,
|
|
225
|
+
int: @interrupts.if_register[2]
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Vram
|
|
6
|
+
# Models each Tile that lives in the VRAM.
|
|
7
|
+
class Tile
|
|
8
|
+
PIXEL_HEIGHT = 8
|
|
9
|
+
PIXEL_WIDTH = 8
|
|
10
|
+
SIZE_IN_BYTES = 16
|
|
11
|
+
SIZE_BIT_MASK = 0b111
|
|
12
|
+
|
|
13
|
+
# Pre-computes all possible 8 pixel values given the low and high bytes
|
|
14
|
+
# for the Game Boy address range (0x0000 - 0xFFFF).
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# Memory values: $3C (Low), $7E (High)
|
|
18
|
+
# PIXELS_LOOKUP[(0x7E << 8) | 0x3C] #=> [0, 2, 3, 3, 3, 3, 2, 0]
|
|
19
|
+
PIXELS_LOOKUP = Array.new(65_536) do |idx|
|
|
20
|
+
low_byte = idx & 0xFF
|
|
21
|
+
high_byte = (idx >> 8) & 0xFF
|
|
22
|
+
|
|
23
|
+
Array.new(8) do |i|
|
|
24
|
+
bit = 7 - i
|
|
25
|
+
low_bit = (low_byte >> bit) & 1
|
|
26
|
+
high_bit = (high_byte >> bit) & 1
|
|
27
|
+
|
|
28
|
+
(high_bit << 1) | low_bit
|
|
29
|
+
end.freeze
|
|
30
|
+
end.freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :data
|
|
33
|
+
|
|
34
|
+
def initialize(vram_data:, tile_index:)
|
|
35
|
+
@vram_data = vram_data
|
|
36
|
+
@tile_index = tile_index
|
|
37
|
+
@base_offset = tile_index * SIZE_IN_BYTES
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Integer] The low byte of the tile at a given row.
|
|
41
|
+
def data_low(current_y)
|
|
42
|
+
current_tile_y = current_y & SIZE_BIT_MASK
|
|
43
|
+
row_within_tile = current_tile_y * 2
|
|
44
|
+
|
|
45
|
+
@vram_data[@base_offset + row_within_tile]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Integer] The high byte of the tile at a given row.
|
|
49
|
+
def data_high(current_y)
|
|
50
|
+
current_tile_y = current_y & SIZE_BIT_MASK
|
|
51
|
+
row_within_tile = current_tile_y * 2
|
|
52
|
+
|
|
53
|
+
@vram_data[@base_offset + row_within_tile + 1]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pixel_row(low_byte, high_byte)
|
|
57
|
+
PIXELS_LOOKUP[(high_byte << 8) | low_byte]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def inspect
|
|
61
|
+
'#<Tile ' \
|
|
62
|
+
"@tile_index=#{@tile_index} " \
|
|
63
|
+
"@base_offset=#{@base_offset}>"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Vram
|
|
6
|
+
# Models the Tile Data that lives in the VRAM.
|
|
7
|
+
class TileData
|
|
8
|
+
TILE_SIZE = 16
|
|
9
|
+
TILE_ENTRIES = 384
|
|
10
|
+
|
|
11
|
+
attr_reader :tiles, :addressing_mode, :base_offset
|
|
12
|
+
|
|
13
|
+
# @param vram_data [Array] Original VRAM @data array object.
|
|
14
|
+
def initialize(vram_data:)
|
|
15
|
+
@addressing_mode = :unsigned
|
|
16
|
+
@base_offset = 0x0000
|
|
17
|
+
|
|
18
|
+
@tiles = Array.new(TILE_ENTRIES) do |index|
|
|
19
|
+
Tile.new(vram_data: vram_data, tile_index: index)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param mode [Symbol] Either :unsigned or :signed.
|
|
24
|
+
def addressing_mode=(mode)
|
|
25
|
+
@addressing_mode = mode
|
|
26
|
+
@base_offset = mode == :unsigned ? 0x0000 : 0x1000
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Fetches a Tile based on a given index, if the addressing mode
|
|
30
|
+
# is set to :signed, we need to sign the value before fetching
|
|
31
|
+
# the tile.
|
|
32
|
+
#
|
|
33
|
+
# @param tile_index [Integer] 8-bit value representing the tile index.
|
|
34
|
+
# @return [Vram::Tile]
|
|
35
|
+
def tile_at(tile_index)
|
|
36
|
+
tile_index = sign_value(tile_index) if @addressing_mode == :signed
|
|
37
|
+
tile_offset = @base_offset / Tile::SIZE_IN_BYTES
|
|
38
|
+
|
|
39
|
+
@tiles[tile_offset + tile_index]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# @param index [Integer] The current Tile index (8-bit value).
|
|
45
|
+
# @return [Integer] A value between -128 and +127.
|
|
46
|
+
def sign_value(index)
|
|
47
|
+
index >= 128 ? index - 256 : index
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Vram
|
|
6
|
+
# Models the Tile Map that live in VRAM.
|
|
7
|
+
#
|
|
8
|
+
# - Each Tile Map is a 32 x 32 grid of Tile indices.
|
|
9
|
+
# - Each Tile index is exactly 1 byte.
|
|
10
|
+
# - So each Tile Map has exactly 32 * 32 * 1 = 1024 bytes (1 KiB).
|
|
11
|
+
#
|
|
12
|
+
# Grid representation in 2 dimensions, each (X, Y) pair -> 1 byte:
|
|
13
|
+
#
|
|
14
|
+
# X = 0 X = 1 X = 2 X = 31
|
|
15
|
+
#
|
|
16
|
+
# Y = 0 (0, 0) (1, 0) (2, 0) ... (31, 0) <- Row 0
|
|
17
|
+
#
|
|
18
|
+
# Y = 1 (0, 1) (1, 1) (2, 1) ... (31, 1) <- Row 1
|
|
19
|
+
#
|
|
20
|
+
# Y = 2 (0, 2) (1, 2) (2, 2) ... (31, 2) <- Row 2
|
|
21
|
+
#
|
|
22
|
+
# ... ... ... ... ... ...
|
|
23
|
+
#
|
|
24
|
+
# Y = 31 (0, 31) (1, 31) (2, 31) ... (31, 31) <- Row 31
|
|
25
|
+
#
|
|
26
|
+
# ↑ ↑ ↑ ↑
|
|
27
|
+
#
|
|
28
|
+
# Column 0 Column 1 Column 2 ... Column 31
|
|
29
|
+
#
|
|
30
|
+
class TileMap
|
|
31
|
+
# Total number of columns in the grid.
|
|
32
|
+
GRID_WIDTH = 32
|
|
33
|
+
|
|
34
|
+
# Total number of rows in the grid.
|
|
35
|
+
GRID_HEIGHT = 32
|
|
36
|
+
|
|
37
|
+
# Mask to keep values between 0 and 31.
|
|
38
|
+
BIT_MASK_WRAP_VALUE = 0x1F
|
|
39
|
+
|
|
40
|
+
# @param vram_data [Array] Reference to the original VRAM data.
|
|
41
|
+
# @param base_offset [Integer] Tile map start address within the VRAM data.
|
|
42
|
+
def initialize(vram_data:, base_offset:)
|
|
43
|
+
@vram_data = vram_data
|
|
44
|
+
@base_offset = base_offset
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Fetches a Tile index from the Tile Map at a given (X, Y) position.
|
|
48
|
+
#
|
|
49
|
+
# Before looking up in which column or row the Tile index is,
|
|
50
|
+
# we need to wrap the values around 32 (0x1F).
|
|
51
|
+
#
|
|
52
|
+
# Since the Memory is a flat array (single dimension) we need to
|
|
53
|
+
# offset the given Y value by the Grid WIDTH to "jump over" the rows in
|
|
54
|
+
# between and reach the correct row.
|
|
55
|
+
#
|
|
56
|
+
# @param tile_x [Integer] Which column in the grid.
|
|
57
|
+
# @param tile_y [Integer] Which row in the grid.
|
|
58
|
+
# @return [Integer] 1 byte representing the Tile index at the given (X, Y).
|
|
59
|
+
def tile_index_at(tile_x:, tile_y:)
|
|
60
|
+
grid_row = tile_y & BIT_MASK_WRAP_VALUE
|
|
61
|
+
grid_column = tile_x & BIT_MASK_WRAP_VALUE
|
|
62
|
+
|
|
63
|
+
tile_index_row = grid_row * GRID_WIDTH
|
|
64
|
+
tile_index_column = grid_column
|
|
65
|
+
|
|
66
|
+
@vram_data[@base_offset + tile_index_row + tile_index_column]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
# Models the VRAM (Video RAM) from the DMG Game Boy.
|
|
6
|
+
#
|
|
7
|
+
# The VRAM address range can be divided into 2 main parts.
|
|
8
|
+
# 1. Tile Data:
|
|
9
|
+
# - Lives in 0x8000 -> 0x97FF (6144 bytes)
|
|
10
|
+
# - Each tile is composed of 8 x 8 = 64 pixels
|
|
11
|
+
# - Each pixel is encoded as 2 bits in the DMG Game Boy
|
|
12
|
+
# - So each tile has 64 * 2 = 128 bits (16 bytes)
|
|
13
|
+
# 2. Tile Maps:
|
|
14
|
+
# - Has a total of 2 Tile Maps (0x9800 -> 0x9BFF) / (0x9C00 -> 0x9FFF)
|
|
15
|
+
# - Each Tile Map is a 32 x 32 grid of Tile indices (each index is 1 byte)
|
|
16
|
+
# - So each Tile Map has exactly 1024 bytes (1 KiB)
|
|
17
|
+
class Vram < Ram
|
|
18
|
+
# VRAM Start Address in the Memory Map
|
|
19
|
+
START_ADDRESS = 0x8000
|
|
20
|
+
|
|
21
|
+
# VRAM End Address in the Memory Map
|
|
22
|
+
END_ADDRESS = 0x9FFF
|
|
23
|
+
|
|
24
|
+
# VRAM Size in bytes: 8192 bytes (8 KiB)
|
|
25
|
+
SIZE_IN_BYTES = (END_ADDRESS - START_ADDRESS) + 1
|
|
26
|
+
|
|
27
|
+
# Exposes the tile data and maps to the PPU
|
|
28
|
+
attr_reader :tile_data,
|
|
29
|
+
:tile_map_low,
|
|
30
|
+
:tile_map_high
|
|
31
|
+
|
|
32
|
+
# Creates "lens objects" passing the original VRAM @data Array,
|
|
33
|
+
# when a value is written into VRAM it will be correctly read
|
|
34
|
+
# by all the Tile Data and Tile Maps.
|
|
35
|
+
def initialize
|
|
36
|
+
super(size: SIZE_IN_BYTES, offset: START_ADDRESS)
|
|
37
|
+
|
|
38
|
+
@tile_data = TileData.new(vram_data: @data)
|
|
39
|
+
@tile_map_low = TileMap.new(vram_data: @data, base_offset: 0x1800)
|
|
40
|
+
@tile_map_high = TileMap.new(vram_data: @data, base_offset: 0x1C00)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inspect
|
|
44
|
+
'#<Vram ' \
|
|
45
|
+
"@tile_data=#{@tile_data} " \
|
|
46
|
+
"@tile_map_low=#{@tile_map_low} " \
|
|
47
|
+
"@tile_map_high=#{@tile_map_high}>"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module HAL
|
|
5
|
+
# Console display renderer.
|
|
6
|
+
class Console
|
|
7
|
+
LCD_WIDTH = 160
|
|
8
|
+
LCD_HEIGHT = 144
|
|
9
|
+
|
|
10
|
+
DOUBLE_CHAR = '▀'
|
|
11
|
+
CONSOLE_CHARS = [' ', '░', '▒', '█'].freeze
|
|
12
|
+
# CONSOLE_CHARS = [' ', ':', '#', '@'].freeze
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
$stdout.print("\e[?1049h")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def shutdown
|
|
19
|
+
$stdout.print("\e[?1049l")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
module Amaterasu
|
|
6
|
+
module HAL
|
|
7
|
+
class SDL2
|
|
8
|
+
# Implement all SDL2 bindings needed.
|
|
9
|
+
module Bindings
|
|
10
|
+
extend FFI::Library
|
|
11
|
+
|
|
12
|
+
ffi_lib 'SDL2'
|
|
13
|
+
|
|
14
|
+
INIT_VIDEO = 0x00000020
|
|
15
|
+
WINDOWPOS_CENTERED = 0x2FFF0000
|
|
16
|
+
WINDOW_SHOWN = 0x00000004
|
|
17
|
+
PIXELFORMAT_ARGB8888 = 0x16362004
|
|
18
|
+
TEXTUREACCESS_STREAMING = 1
|
|
19
|
+
RENDERER_ACCELERATED = 0x00000002
|
|
20
|
+
QUIT = 0x100
|
|
21
|
+
EVENT_SIZE = 56
|
|
22
|
+
|
|
23
|
+
SCANCODE_UP = 82
|
|
24
|
+
SCANCODE_DOWN = 81
|
|
25
|
+
SCANCODE_LEFT = 80
|
|
26
|
+
SCANCODE_RIGHT = 79
|
|
27
|
+
|
|
28
|
+
SCANCODE_Z = 29
|
|
29
|
+
SCANCODE_X = 27
|
|
30
|
+
SCANCODE_RETURN = 40
|
|
31
|
+
SCANCODE_RSHIFT = 229
|
|
32
|
+
|
|
33
|
+
attach_function :init, :SDL_Init, [:uint32], :int
|
|
34
|
+
attach_function :quit, :SDL_Quit, [], :void
|
|
35
|
+
attach_function :get_error, :SDL_GetError, [], :string
|
|
36
|
+
|
|
37
|
+
attach_function :create_window, :SDL_CreateWindow, %i[string int int int int uint32], :pointer
|
|
38
|
+
attach_function :set_window_title, :SDL_SetWindowTitle, %i[pointer string], :void
|
|
39
|
+
attach_function :destroy_window, :SDL_DestroyWindow, [:pointer], :void
|
|
40
|
+
|
|
41
|
+
attach_function :create_renderer, :SDL_CreateRenderer, %i[pointer int uint32], :pointer
|
|
42
|
+
attach_function :destroy_renderer, :SDL_DestroyRenderer, [:pointer], :void
|
|
43
|
+
|
|
44
|
+
attach_function :create_texture, :SDL_CreateTexture, %i[pointer uint32 int int int], :pointer
|
|
45
|
+
attach_function :update_texture, :SDL_UpdateTexture, %i[pointer pointer pointer int], :int
|
|
46
|
+
attach_function :destroy_texture, :SDL_DestroyTexture, [:pointer], :void
|
|
47
|
+
|
|
48
|
+
attach_function :render_copy, :SDL_RenderCopy, %i[pointer pointer pointer pointer], :int
|
|
49
|
+
attach_function :render_present, :SDL_RenderPresent, [:pointer], :void
|
|
50
|
+
attach_function :render_clear, :SDL_RenderClear, [:pointer], :int
|
|
51
|
+
|
|
52
|
+
attach_function :poll_event, :SDL_PollEvent, [:pointer], :int
|
|
53
|
+
|
|
54
|
+
attach_function :get_keyboard_state, :SDL_GetKeyboardState, [:pointer], :pointer
|
|
55
|
+
attach_function :pump_events, :SDL_PumpEvents, [], :void
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
module Amaterasu
|
|
6
|
+
module HAL
|
|
7
|
+
class SDL2
|
|
8
|
+
LCD = Bindings
|
|
9
|
+
LCD_WIDTH = 160
|
|
10
|
+
LCD_HEIGHT = 144
|
|
11
|
+
SCALE = 3
|
|
12
|
+
|
|
13
|
+
PALETTE = [0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000].freeze
|
|
14
|
+
|
|
15
|
+
attr_accessor :joypad
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
LCD.init(LCD::INIT_VIDEO)
|
|
19
|
+
@window = LCD.create_window(
|
|
20
|
+
'Amaterasu',
|
|
21
|
+
LCD::WINDOWPOS_CENTERED,
|
|
22
|
+
LCD::WINDOWPOS_CENTERED,
|
|
23
|
+
LCD_WIDTH * SCALE,
|
|
24
|
+
LCD_HEIGHT * SCALE,
|
|
25
|
+
LCD::WINDOW_SHOWN
|
|
26
|
+
)
|
|
27
|
+
@renderer = LCD.create_renderer(@window, -1, LCD::RENDERER_ACCELERATED)
|
|
28
|
+
@texture = LCD.create_texture(
|
|
29
|
+
@renderer,
|
|
30
|
+
LCD::PIXELFORMAT_ARGB8888,
|
|
31
|
+
LCD::TEXTUREACCESS_STREAMING,
|
|
32
|
+
LCD_WIDTH,
|
|
33
|
+
LCD_HEIGHT
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@pixel_buffer = FFI::MemoryPointer.new(:uint32, LCD_WIDTH * LCD_HEIGHT)
|
|
37
|
+
@event = FFI::MemoryPointer.new(:uint8, LCD::EVENT_SIZE)
|
|
38
|
+
@frame_count = 0
|
|
39
|
+
@fps_timer = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def draw(framebuffer)
|
|
43
|
+
@pixel_buffer.write_array_of_uint32(framebuffer.map { |shade| PALETTE[shade] })
|
|
44
|
+
LCD.update_texture(@texture, FFI::Pointer::NULL, @pixel_buffer, LCD_WIDTH * 4)
|
|
45
|
+
LCD.render_copy(@renderer, @texture, FFI::Pointer::NULL, FFI::Pointer::NULL)
|
|
46
|
+
LCD.render_present(@renderer)
|
|
47
|
+
|
|
48
|
+
@frame_count += 1
|
|
49
|
+
@elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @fps_timer
|
|
50
|
+
|
|
51
|
+
if @elapsed >= 1.0
|
|
52
|
+
fps = @frame_count / @elapsed
|
|
53
|
+
# LCD.set_window_title(@window, "Amaterasu | FPS: #{fps.round(2)}")
|
|
54
|
+
$stdout.print "\rFPS: #{fps.round(2)} "
|
|
55
|
+
$stdout.flush
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
while LCD.poll_event(@event) == 1
|
|
59
|
+
# first 4 bytes of the event struct are the event type (uint32)
|
|
60
|
+
type = @event.read_uint32
|
|
61
|
+
case type
|
|
62
|
+
when LCD::QUIT
|
|
63
|
+
shutdown
|
|
64
|
+
exit
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
keyboard_state = LCD.get_keyboard_state(nil)
|
|
69
|
+
|
|
70
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_UP) == 0
|
|
71
|
+
joypad.release_dpad(:up)
|
|
72
|
+
else
|
|
73
|
+
joypad.press_dpad(:up)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_DOWN) == 0
|
|
77
|
+
joypad.release_dpad(:down)
|
|
78
|
+
else
|
|
79
|
+
joypad.press_dpad(:down)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_RIGHT) == 0
|
|
83
|
+
joypad.release_dpad(:right)
|
|
84
|
+
else
|
|
85
|
+
joypad.press_dpad(:right)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_LEFT) == 0
|
|
89
|
+
joypad.release_dpad(:left)
|
|
90
|
+
else
|
|
91
|
+
joypad.press_dpad(:left)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_Z) == 0
|
|
95
|
+
joypad.release_face(:a)
|
|
96
|
+
else
|
|
97
|
+
joypad.press_face(:a)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_X) == 0
|
|
101
|
+
joypad.release_face(:b)
|
|
102
|
+
else
|
|
103
|
+
joypad.press_face(:b)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_RETURN) == 0
|
|
107
|
+
joypad.release_face(:start)
|
|
108
|
+
else
|
|
109
|
+
joypad.press_face(:start)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if keyboard_state.get_uint8(LCD::SCANCODE_RSHIFT) == 0
|
|
113
|
+
joypad.release_face(:select)
|
|
114
|
+
else
|
|
115
|
+
joypad.press_face(:select)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def shutdown
|
|
120
|
+
LCD.destroy_texture(@texture)
|
|
121
|
+
LCD.destroy_renderer(@renderer)
|
|
122
|
+
LCD.destroy_window(@window)
|
|
123
|
+
LCD.quit
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|