Skip to content
Snippets Groups Projects
Verified Commit a914056c authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

Merge branch 'master' into joamag/color

# Conflicts:
#	frontends/sdl/src/main.rs
parents 4f513eed 459f7167
No related branches found
No related tags found
1 merge request!16Support for Game Boy Color (CGB) 😎🖍️
Pipeline #2553 passed
Showing
with 641 additions and 28 deletions
......@@ -19,6 +19,7 @@ jobs:
"1.65.0",
"1.66.0",
"1.67.0",
"1.68.0",
"latest"
]
runs-on: ubuntu-latest
......@@ -46,11 +47,11 @@ jobs:
strategy:
matrix:
rust-version: [
"1.63.0",
"1.64.0",
"1.65.0",
"1.66.0",
"1.67.0",
"1.68.0",
"latest"
]
node-version: ["16"]
......@@ -79,3 +80,44 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Build and lint Web code
run: cd frontends/web && npm install && npm run build && npm run lint
build-sdl:
name: Build SDL
timeout-minutes: 30
strategy:
matrix:
rust-version: [
"1.61.0",
"1.62.0",
"1.63.0",
"1.64.0",
"1.65.0",
"1.66.0",
"1.67.0",
"1.68.0",
"latest"
]
runs-on: ubuntu-latest
container: rust:${{ matrix.rust-version }}
steps:
- name: Checkout code from repository
uses: actions/checkout@v3
- name: Install Dependencies
run: |
apt-get update
apt-get install -y -q zip
- name: Install Rust components
run: |
rustup component add rustfmt
rustup component add clippy
- name: Print Rust information
run: rustc --version
- name: Install SDL dependencies
run: cd frontends/sdl && cargo install cargo-vcpkg && cargo vcpkg build
- name: Verify Rust code format
run: cd frontends/sdl && cargo fmt --all -- --check
- name: Verify Rust code linting
run: cd frontends/sdl && cargo clippy -- -D warnings -A unknown-lints
- name: Build development version
run: cd frontends/sdl && cargo build
- name: Build release version
run: cd frontends/sdl && cargo build --release
......@@ -42,7 +42,7 @@ build-wasm:
stage: build
parallel:
matrix:
- RUST_VERSION: ["1.63.0"]
- RUST_VERSION: ["1.64.0"]
script:
- rustup toolchain install $RUST_VERSION
- rustup override set $RUST_VERSION
......@@ -117,6 +117,8 @@ deploy-cloudfare-prod:
- cd frontends/web/dist
- cp -rp ../static/* .
- npm_config_yes=true npx wrangler pages publish . --project-name=boytacean --branch prod
- npm_config_yes=true npx wrangler pages publish . --project-name=boytacean --branch production
- npm_config_yes=true npx wrangler pages publish . --project-name=boytacean --branch main
dependencies:
- build-wasm
only:
......
......@@ -19,6 +19,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
*
## [0.8.0] - 2023-04-20
### Added
* Support for serial data transfer - [#19](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/19)
* Support for printing of images using Printer emulation - [#19](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/19)
* Support for display of logger and printer in Web panels
* Converted serial-sections strategy to event driven
### Fixed
* `ButtonSwitch` issues by updating the value strategy nad bumping `emukit`
* `AudioGB` with display of canvas with no visibility
## [0.7.5] - 2023-04-11
### Added
* Support for variable clock speed for APU, means variable audio speed
* Moved debug into the base emulator (from emukit)
## [0.7.4] - 2023-04-08
### Added
* Support for audio channel 4 (noise) 🔈
* Better trigger support for audio channels 🔈
### Changed
* Added CH4 public API method for WASM
### Fixed
* Envelope support for both channel 2 and 4 🔈
* Issue related to the wave length stop flag 🔈
## [0.7.3] - 2023-04-02
### Added
* Support for CGB flag parsing
* Waveform plotting support
### Fixed
* Major JoyPad issue with Action/Select read in register
* Small issue with channel 3 audio and DAC disable
## [0.7.2] - 2023-03-04
### Added
* Support for stereo sound 🔊
### Changed
* APU `clock()` method with `cycles` parameter, improving performance by an order of magnitude 💪
### Fixed
* Added reset of APU, which fixes annoying "garbage" data in buffer when restarting the state of the emulator
## [0.7.1] - 2023-03-02
### Changed
......
[package]
name = "boytacean"
description = "A Game Boy emulator that is written in Rust."
version = "0.7.1"
version = "0.8.0"
authors = ["João Magalhães <joamag@gmail.com>"]
license = "Apache-2.0"
repository = "https://github.com/joamag/boytacean"
......
......@@ -12,6 +12,8 @@ A Game Boy emulator that is written in Rust 🦀.
* Simple navigable source-code
* Web and SDL front-ends
* Audio, with a pretty accurate APU
* Serial Data Transfer (Link Cable) support
* Game Boy Printer emulation
* Support for multiple MBCs: MBC1, MBC2, MBC3, and MBC5
* Variable CPU clock speed
* Accurate PPU - passes [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) tests
......
......@@ -20,21 +20,29 @@
* [GitHub - LIJI32/SameBoy (C)](https://github.com/LIJI32/SameBoy)
* [GitHub - binji/binjgb (C)](https://github.com/binji/binjgb)
* [GitHub - 7thSamurai/Azayaka (C++)](https://github.com/7thSamurai/Azayaka)
* [GitHub - feo-boy/feo-boy (Rust)](https://github.com/feo-boy/feo-boy)
* [GitHub - Rodrigodd/gameroy (Rust)](https://github.com/Rodrigodd/gameroy)
* [GitHub - simias/gb-rs (Rust)](https://github.com/simias/gb-rs)
* [GitHub - RubenG123/frosty (Rust)](https://github.com/RubenG123/frosty)
* [GitHub - calvinbaart/gameboy (TypeScript)](https://github.com/calvinbaart/gameboy)
* [GitHub - HFO4/gameboy.live (Go)](https://github.com/HFO4/gameboy.live)
* [GitHub - djhworld/gomeboycolor-wasm (Go)](https://github.com/djhworld/gomeboycolor-wasm)
* [GitHub - torch2424/wasmboy (WASM)](https://github.com/torch2424/wasmboy)
## Videos
* [YouTube - The Ultimate Game Boy Talk (33c3)](https://www.youtube.com/watch?v=HyzD8pNlpwI)
## Peripherals
* [In Depth: The Game Boy Printer](https://shonumi.github.io/articles/art2.html)
## Other
* [GitHub - gbdev/awesome-gbdev](https://github.com/gbdev/awesome-gbdev)
* [GitHub - Hacktix/Bootix](https://github.com/Hacktix/Bootix)
* [GitHub - gbdk-2020/gbdk-2020 (Best GB C SDK)](https://github.com/gbdk-2020/gbdk-2020)
* [GitHub - fcambus/jsemu (Emulators written in JavaScript)](https://github.com/fcambus/jsemu)
* [Gameboy Doctor: debug and fix your gameboy emulator](https://robertheaton.com/gameboy-doctor)
* [Game Boy Boot ROMs](https://gbdev.gg8.se/files/roms/bootroms)
[package]
name = "boytacean-sdl"
version = "0.7.1"
version = "0.8.0"
authors = ["João Magalhães <joamag@gmail.com>"]
description = "An SDL frontend for Boytacen"
license = "Apache-2.0"
keywords = ["gameboy", "emulator", "rust", "sdl"]
edition = "2018"
[features]
debug = ["boytacean/debug"]
[dependencies.boytacean]
path = "../.."
[dependencies.image]
version = "0.24"
[dependencies.chrono]
version = "0.4"
[dependencies.sdl2]
version = "0.35"
features = ["ttf", "image", "gfx", "mixer", "static-link", "use-vcpkg"]
......
......@@ -16,3 +16,16 @@ Then you can use the following command to build and run Boytacean SDL:
cargo build
cargo run
```
To reload the code continuously use the cargo watch tool:
```bash
cargo install cargo-watch
cargo watch -x run
```
There are some feature flags that control the verbosity of the emulator to run in debug mode use:
```bash
cargo run --features debug
```
......@@ -6,13 +6,16 @@ pub mod graphics;
use audio::Audio;
use boytacean::{
devices::printer::PrinterDevice,
gb::{AudioProvider, GameBoy},
pad::PadKey,
ppu::{PaletteInfo, PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH},
};
use chrono::Utc;
use graphics::{surface_from_bytes, Graphics};
use image::ColorType;
use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl};
use std::{cmp::max, time::SystemTime};
use std::{cmp::max, path::Path, time::SystemTime};
/// The scale at which the screen is going to be drawn
/// meaning the ratio between Game Boy resolution and
......@@ -20,11 +23,11 @@ use std::{cmp::max, time::SystemTime};
const SCREEN_SCALE: f32 = 2.0;
/// The base title to be used in the window.
static TITLE: &str = "Boytacean";
const TITLE: &str = "Boytacean";
/// Base audio volume to be used as the basis of the
/// amplification level of the volume
static VOLUME: f32 = 64.0;
const VOLUME: f32 = 64.0;
pub struct Benchmark {
count: usize,
......@@ -47,12 +50,13 @@ pub struct Emulator {
graphics: Option<Graphics>,
audio: Option<Audio>,
title: &'static str,
rom_path: String,
logic_frequency: u32,
visual_frequency: f32,
next_tick_time: f32,
next_tick_time_i: u32,
features: Vec<&'static str>,
palettes: [PaletteInfo; 3],
palettes: [PaletteInfo; 7],
palette_index: usize,
}
......@@ -63,6 +67,7 @@ impl Emulator {
graphics: None,
audio: None,
title: TITLE,
rom_path: String::from("invalid"),
logic_frequency: GameBoy::CPU_FREQ,
visual_frequency: GameBoy::VISUAL_FREQ,
next_tick_time: 0.0,
......@@ -96,6 +101,42 @@ impl Emulator {
[0x53, 0x4d, 0x57],
],
),
PaletteInfo::new(
"goldsilver",
[
[0xc5, 0xc6, 0x6d],
[0x97, 0xa1, 0xb0],
[0x58, 0x5e, 0x67],
[0x23, 0x52, 0x29],
],
),
PaletteInfo::new(
"pacman",
[
[0xff, 0xff, 0x00],
[0xff, 0xb8, 0x97],
[0x37, 0x32, 0xff],
[0x00, 0x00, 0x00],
],
),
PaletteInfo::new(
"mariobros",
[
[0xf7, 0xce, 0xc3],
[0xcc, 0x9e, 0x22],
[0x92, 0x34, 0x04],
[0x00, 0x00, 0x00],
],
),
PaletteInfo::new(
"pokemon",
[
[0xf8, 0x78, 0x00],
[0xb8, 0x60, 0x00],
[0x78, 0x38, 0x00],
[0x00, 0x00, 0x00],
],
),
],
palette_index: 0,
}
......@@ -127,8 +168,9 @@ impl Emulator {
self.audio = Some(Audio::new(sdl));
}
pub fn load_rom(&mut self, path: &str) {
let rom = self.system.load_rom_file(path);
pub fn load_rom(&mut self, path: Option<&str>) {
let path_res = path.unwrap_or(&self.rom_path);
let rom = self.system.load_rom_file(path_res);
println!(
"========= Cartridge =========\n{}\n=============================",
rom
......@@ -139,6 +181,13 @@ impl Emulator {
.window_mut()
.set_title(format!("{} [{}]", self.title, rom.title()).as_str())
.unwrap();
self.rom_path = String::from(path_res);
}
pub fn reset(&mut self) {
self.system.reset();
self.system.load_boot_default();
self.load_rom(None);
}
pub fn benchmark(&mut self, params: Benchmark) {
......@@ -163,7 +212,7 @@ impl Emulator {
}
pub fn toggle_audio(&mut self) {
let apu_enabled = self.system.get_apu_enabled();
let apu_enabled = self.system.apu_enabled();
self.system.set_apu_enabled(!apu_enabled);
}
......@@ -226,6 +275,10 @@ impl Emulator {
keycode: Some(Keycode::Escape),
..
} => break 'main,
Event::KeyDown {
keycode: Some(Keycode::R),
..
} => self.reset(),
Event::KeyDown {
keycode: Some(Keycode::B),
..
......@@ -264,8 +317,8 @@ impl Emulator {
}
Event::DropFile { filename, .. } => {
self.system.reset();
self.system.load_cgb(true);
self.load_rom(&filename);
self.system.load_boot_cgb();
self.load_rom(Some(&filename));
}
_ => (),
}
......@@ -318,9 +371,9 @@ impl Emulator {
last_frame = self.system.ppu_frame();
}
// in case the audio subsystem is enabled, then the audio buffer
// must be queued into the SDL audio subsystem
if let Some(audio) = self.audio.as_mut() {
// obtains the new audio buffer and queues it into the audio
// subsystem ready to be processed
let audio_buffer = self
.system
.audio_buffer()
......@@ -331,7 +384,7 @@ impl Emulator {
}
// clears the audio buffer to prevent it from
// "exploding" in size
// "exploding" in size, this is required GC operation
self.system.clear_audio_buffer();
}
......@@ -394,14 +447,27 @@ fn main() {
// creates a new Game Boy instance and loads both the boot ROM
// and the initial game ROM to "start the engine"
let mut game_boy = GameBoy::new();
game_boy.load_cgb(true);
let mut printer = Box::<PrinterDevice>::default();
printer.set_callback(|image_buffer| {
let file_name = format!("printer-{}.png", Utc::now().format("%Y%m%d-%H%M%S"));
image::save_buffer(
Path::new(&file_name),
image_buffer,
160,
(image_buffer.len() / 4 / 160) as u32,
ColorType::Rgba8,
)
.unwrap();
});
game_boy.attach_serial(printer);
game_boy.load_boot_cgb();
// creates a new generic emulator structure then starts
// both the video and audio sub-systems, loads default
// ROM file and starts running it
let mut emulator = Emulator::new(game_boy);
emulator.start(SCREEN_SCALE);
emulator.load_rom("../../res/roms/pocket.gb");
emulator.load_rom(Some("../../res/roms/demo/pocket.gb"));
emulator.toggle_palette();
emulator.run();
}
......
......@@ -2,6 +2,6 @@
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"],
"*.gb": ["@parcel/transformer-raw"]
"*.{gb,gbc}": ["@parcel/transformer-raw"]
}
}
......@@ -50,5 +50,12 @@ const BACKGROUNDS = [
background: background,
backgrounds: BACKGROUNDS
});
// sets the emulator in the global scope this is useful
// to be able to access the emulator from global functions
window.emulator = emulator;
// starts the emulator with the provided ROM URL, this is
// going to run the main emulator (infinite) loop
await emulator.main({ romUrl: romUrl });
})();
{
"name": "boytacean-web",
"version": "0.7.1",
"version": "0.8.0",
"description": "The web version of Boytacean",
"repository": {
"type": "git",
......@@ -19,18 +19,19 @@
"source": "index.ts",
"devDependencies": {
"@parcel/transformer-typescript-tsc": "^2.8.3",
"@types/react": "^18.0.28",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"emukit": "^0.7.1",
"eslint": "^8.35.0",
"nodemon": "^2.0.20",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"emukit": "^0.8.6",
"eslint": "^8.38.0",
"nodemon": "^2.0.22",
"parcel": "^2.8.3",
"prettier": "^2.8.4",
"prettier": "^2.8.7",
"process": "^0.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^4.9.5"
"typescript": "^5.0.4",
"webgl-plot": "^0.7.0"
}
}
.audio-gb > .section {
display: inline-block;
vertical-align: top;
}
.audio-gb > .section > .audio-wave {
display: inline-block;
margin-right: 5px;
}
.audio-gb > .section > .audio-wave > h4 {
margin: 4px 0px 4px 0px;
}
import React, { FC, useEffect, useRef, useState } from "react";
import { Canvas, CanvasStructure, PixelFormat } from "emukit";
import { WebglPlot, WebglLine, ColorRGBA } from "webgl-plot";
import "./audio-gb.css";
type AudioGBProps = {
getAudioOutput: () => Record<string, number>;
interval?: number;
drawInterval?: number;
color?: number;
range?: number;
rangeVolume?: number;
engine?: "webgl" | "canvas";
style?: string[];
renderWave?: (name: string, key: string, styles?: string[]) => JSX.Element;
};
export const AudioGB: FC<AudioGBProps> = ({
getAudioOutput,
interval = 1,
drawInterval = 1000 / 60,
color = 0x50cb93ff,
range = 128,
rangeVolume = 32,
engine = "webgl",
style = [],
renderWave
}) => {
const classes = () => ["audio-gb", ...style].join(" ");
const [audioOutput, setAudioOutput] = useState<Record<string, number[]>>(
{}
);
const intervalsRef = useRef<number>();
const intervalsExtraRef = useRef<number>();
useEffect(() => {
const updateAudioOutput = () => {
const _audioOutput = getAudioOutput();
for (const [key, value] of Object.entries(_audioOutput)) {
const values = audioOutput[key] ?? new Array(range).fill(0);
values.push(value);
if (values.length > range) {
values.shift();
}
audioOutput[key] = values;
}
setAudioOutput(audioOutput);
};
setInterval(() => updateAudioOutput(), interval);
updateAudioOutput();
return () => {
if (intervalsRef.current) {
clearInterval(intervalsRef.current);
}
if (intervalsExtraRef.current) {
clearInterval(intervalsExtraRef.current);
}
};
}, []);
const renderAudioWave = (
name: string,
key: string,
styles: string[] = []
) => {
const classes = ["audio-wave", ...styles].join(" ");
const onCanvas = (structure: CanvasStructure) => {
const drawWave = () => {
const values = audioOutput[key];
if (!values) {
return;
}
structure.canvasImage.data.fill(0);
values.forEach((value, index) => {
const valueN = Math.min(value, rangeVolume - 1);
const line = rangeVolume - 1 - valueN;
const offset = (line * range + index) * PixelFormat.RGBA;
structure.canvasBuffer.setUint32(offset, color);
});
structure.canvasOffScreenContext.putImageData(
structure.canvasImage,
0,
0
);
structure.canvasContext.clearRect(0, 0, range, rangeVolume);
structure.canvasContext.drawImage(
structure.canvasOffScreen,
0,
0
);
};
drawWave();
intervalsExtraRef.current = setInterval(
() => drawWave(),
drawInterval
);
};
return (
<div className={classes}>
<h4>{name}</h4>
<Canvas
width={range}
height={rangeVolume}
onCanvas={onCanvas}
/>
</div>
);
};
const renderAudioWaveWgl = (
name: string,
key: string,
styles: string[] = []
) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const classes = ["audio-wave", ...styles].join(" ");
useEffect(() => {
if (!canvasRef.current) return;
// converts the canvas to the expected size according
// to the device pixel ratio value
const devicePixelRatio = window.devicePixelRatio || 1;
canvasRef.current.width = range * devicePixelRatio;
canvasRef.current.height = rangeVolume * devicePixelRatio;
// creates the WGL Plot object with the canvas element
// that is associated with the current audio wave
const wglPlot = new WebglPlot(canvasRef.current);
const colorRgba = new ColorRGBA(...intToColor2(color));
const line = new WebglLine(colorRgba, range);
line.arrangeX();
wglPlot.addLine(line);
const drawWave = () => {
const values = audioOutput[key];
if (!values) {
return;
}
values.forEach((value, index) => {
const valueN = Math.min(value, rangeVolume - 1);
line.setY(index, valueN / rangeVolume - 1);
});
wglPlot.update();
};
drawWave();
intervalsExtraRef.current = setInterval(
() => drawWave(),
drawInterval
);
}, [canvasRef]);
return (
<div className={classes}>
<h4>{name}</h4>
<Canvas
canvasRef={canvasRef}
width={range}
height={rangeVolume}
init={false}
/>
</div>
);
};
let renderMethod =
engine === "webgl" ? renderAudioWaveWgl : renderAudioWave;
renderMethod = renderWave ?? renderMethod;
return (
<div className={classes()}>
<div className="section">
{renderMethod("Master", "master")}
{renderMethod("CH1", "ch1")}
{renderMethod("CH2", "ch2")}
{renderMethod("CH3", "ch3")}
{renderMethod("CH4", "ch4")}
</div>
</div>
);
};
const intToColor = (int: number): [number, number, number, number] => {
const r = (int >> 24) & 0xff;
const g = (int >> 16) & 0xff;
const b = (int >> 8) & 0xff;
const a = int & 0xff;
return [r, g, b, a];
};
const intToColor2 = (int: number): [number, number, number, number] => {
const color = intToColor(int);
return color.map((v) => v / 255) as [number, number, number, number];
};
export default AudioGB;
import React, { FC } from "react";
import { AudioGB } from "../audio-gb/audio-gb";
import { RegistersGB } from "../registers-gb/registers-gb";
import { TilesGB } from "../tiles-gb/tiles-gb";
import { GameboyEmulator } from "../../../ts";
import "./debug.css";
type EmulatorProps = {
emulator: GameboyEmulator;
};
export const DebugVideo: FC<EmulatorProps> = ({ emulator }) => {
return (
<>
{emulator.getTile && (
<div
style={{
display: "inline-block",
verticalAlign: "top",
marginRight: 32,
width: 256
}}
>
<h3>VRAM Tiles</h3>
<TilesGB
getTile={(index) =>
emulator.getTile
? emulator.getTile(index)
: new Uint8Array()
}
tileCount={384}
width={"100%"}
contentBox={false}
/>
</div>
)}
<div
style={{
display: "inline-block",
verticalAlign: "top"
}}
>
<h3>Registers</h3>
<RegistersGB getRegisters={() => emulator.registers} />
</div>
</>
);
};
export const DebugAudio: FC<EmulatorProps> = ({ emulator }) => {
return (
<>
<div
style={{
display: "inline-block",
verticalAlign: "top"
}}
>
<h3>Audio Waveform</h3>
<AudioGB getAudioOutput={() => emulator.audioOutput} />
</div>
</>
);
};
export * from "./audio-gb/audio-gb";
export * from "./debug/debug";
export * from "./help/help";
export * from "./registers-gb/registers-gb";
export * from "./serial-section/serial-section";
export * from "./tiles-gb/tiles-gb";
.registers-gb > .section {
display: inline-block;
margin-right: 32px;
vertical-align: top;
}
.registers-gb > .section:last-child {
margin-right: 0px;
}
.registers-gb > .section > h4 {
font-size: 22px;
margin: 0px 0px 8px 0px;
}
.registers-gb > .section > .register {
font-size: 0px;
line-height: 22px;
}
.registers-gb > .section > .register > .register-key {
display: inline-block;
font-size: 20px;
width: 40px;
}
.registers-gb > .section > .register > .register-value {
display: inline-block;
font-size: 20px;
text-align: right;
width: 66px;
}
import React, { FC, useEffect, useRef, useState } from "react";
import "./registers-gb.css";
type RegistersGBProps = {
getRegisters: () => Record<string, string | number>;
interval?: number;
style?: string[];
};
export const RegistersGB: FC<RegistersGBProps> = ({
getRegisters,
interval = 50,
style = []
}) => {
const classes = () => ["registers-gb", ...style].join(" ");
const [registers, setRegisters] = useState<Record<string, string | number>>(
{}
);
const intervalsRef = useRef<number>();
useEffect(() => {
const updateRegisters = () => {
const registers = getRegisters();
setRegisters(registers);
};
setInterval(() => updateRegisters(), interval);
updateRegisters();
return () => {
if (intervalsRef.current) {
clearInterval(intervalsRef.current);
}
};
}, []);
const renderRegister = (
key: string,
value?: number,
size = 2,
styles: string[] = []
) => {
const classes = ["register", ...styles].join(" ");
const valueS =
value?.toString(16).toUpperCase().padStart(size, "0") ?? value;
return (
<div className={classes}>
<span className="register-key">{key}</span>
<span className="register-value">
{valueS ? `0x${valueS}` : "-"}
</span>
</div>
);
};
return (
<div className={classes()}>
<div className="section">
<h4>CPU</h4>
{renderRegister("PC", registers.pc as number, 4)}
{renderRegister("SP", registers.sp as number, 4)}
{renderRegister("A", registers.a as number)}
{renderRegister("B", registers.b as number)}
{renderRegister("C", registers.c as number)}
{renderRegister("D", registers.d as number)}
{renderRegister("E", registers.e as number)}
{renderRegister("H", registers.h as number)}
{renderRegister("L", registers.l as number)}
</div>
<div className="section">
<h4>PPU</h4>
{renderRegister("SCY", registers.scy as number)}
{renderRegister("SCX", registers.scx as number)}
{renderRegister("WY", registers.wy as number)}
{renderRegister("WX", registers.wx as number)}
{renderRegister("LY", registers.ly as number)}
{renderRegister("LYC", registers.lyc as number)}
</div>
</div>
);
};
export default RegistersGB;
.serial-section .printer > .printer-lines {
font-size: 0px;
}
.serial-section .printer > .printer-lines > .printer-line {
display: block;
}
.serial-section .printer > .printer-lines > .placeholder {
font-size: initial;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment