"]
5 | edition = "2018"
6 | license = "MPL-2.0"
7 | description = "A macro to allow matching, binding, and packing the individual bits of integers."
8 | repository = "https://github.com/porglezomp/bitmatch"
9 | documentation = "https://docs.rs/crate/bitmatch"
10 | readme = "README.md"
11 | keywords = ["bitpacking", "binary", "decoder", "matching", "macro"]
12 | categories = ["parsing", "no-std"]
13 |
14 | [lib]
15 | proc-macro = true
16 |
17 | [dependencies]
18 | syn = {version = "1.0", features = ["full", "visit", "visit-mut"] }
19 | quote = "1.0"
20 | boolean_expression = "0.3.10"
21 | proc-macro2 = "1.0"
22 |
23 | [badges]
24 |
25 | maintenance.status = "passively-maintained"
26 |
--------------------------------------------------------------------------------
/cores/gga/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gga"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | common = { path = "../../common" }
10 | armchair = { path = "../../components/armchair" }
11 |
12 | log.workspace = true
13 | serde = { workspace = true, optional = true }
14 | serde_arrays = { workspace = true, optional = true }
15 | arrayvec.workspace = true
16 | modular-bitfield.workspace = true
17 | bitflags.workspace = true
18 |
19 | elf_rs = "0.3.1"
20 |
21 | [features]
22 | default = ["std"]
23 | serde = [
24 | "dep:serde",
25 | "dep:serde_arrays",
26 | "arrayvec/serde",
27 | "common/serde",
28 | "armchair/serde",
29 | "bitflags/serde",
30 | ]
31 | std = ["common/std"]
32 |
--------------------------------------------------------------------------------
/cores/gga/src/tests/jsmolka/unsafe_README.md:
--------------------------------------------------------------------------------
1 | # Warning
2 | I don't feel comfortable putting these tests into the test suite. The failures on hardware are probably caused by the flash card.
3 |
4 | ## SRAM Mirror
5 | This test checks the following GBATEK statement:
6 |
7 | > The 64K SRAM area is mirrored across the whole 32MB area at E000000h-FFFFFFFh, also, inside of the 64K SRAM field, 32K SRAM chips are repeated twice.
8 |
9 | It passes on No$GBA and mGBA but not on real hardware.
10 |
11 | ## Unused ROM Reads
12 | This test checks the following GBATEK statement:
13 |
14 | > Because Gamepak uses the same signal-lines for both 16bit data and for lower 16bit halfword address, the entire gamepak ROM area is effectively filled by incrementing 16bit values (Address/2 AND FFFFh).
15 |
16 | It passes on mGBA but not on No$GBA or real hardware.
17 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build Release
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | release:
9 | name: release ${{ matrix.target }}
10 | runs-on: ubuntu-latest
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | include:
15 | - target: x86_64-unknown-linux-musl
16 | archive: tar.gz tar.xz tar.zst
17 | steps:
18 | - uses: actions/checkout@master
19 | - name: Compile and release
20 | uses: rust-build/rust-build.action@v1.4.5
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | with:
24 | RUSTTARGET: ${{ matrix.target }}
25 | ARCHIVE_TYPES: ${{ matrix.archive }}
26 | EXTRA_FILES: "README.md LICENSE-GPL LICENSE-MPL"
27 | MINIFY: true
28 | TOOLCHAIN_VERSION: nightly
29 | SRC_DIR: gamegirl-egui
30 |
--------------------------------------------------------------------------------
/cores/nds/src/cpu/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | //! CPU implementations.
10 | //! Note that when it comes to timing, the ARM9 runs on the scheduler until
11 | //! the ARM7 is behind, which then runs outside the scheduler until the ARM9 is
12 | //! behind. This is repeated in a loop.
13 | //! Effectively, the ARM9 is the one handling the scheduling, with the ARM7
14 | //! being dragged along.
15 |
16 | pub mod cp15;
17 | pub mod math;
18 | mod nds7;
19 | mod nds9;
20 |
21 | pub const NDS9_CLOCK: u32 = 67_027_964;
22 |
--------------------------------------------------------------------------------
/common/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "common"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | log.workspace = true
10 | rubato = { version = "0.16", optional = true }
11 |
12 | serde = { workspace = true, optional = true }
13 | serde_arrays = { workspace = true, optional = true }
14 |
15 | bincode = { version = "2.0.1", optional = true }
16 | zstd = { version = "0.13.3", default-features = false, optional = true }
17 |
18 | [target.'cfg(target_arch = "wasm32")'.dependencies]
19 | web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
20 | base64 = "0.22.1"
21 |
22 | [features]
23 | serde = [
24 | "dep:serde",
25 | "dep:serde_arrays",
26 | "dep:bincode",
27 | "bincode/serde",
28 | "serde_config",
29 | ]
30 | serde_config = ["dep:serde"]
31 | std = ["dep:rubato"]
32 |
--------------------------------------------------------------------------------
/components/armchair/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "armchair"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | common = { path = "../../common" }
10 |
11 | log.workspace = true
12 | serde = { workspace = true, optional = true }
13 | bitmatch.workspace = true
14 | hashbrown = "0.15"
15 | num-traits = { version = "0.2", default-features = false }
16 | num-derive = { version = "0.4", default-features = false }
17 |
18 | cranelift = { version = "0.118", optional = true }
19 | cranelift-jit = { version = "0.118", optional = true }
20 | cranelift-module = { version = "0.118", optional = true }
21 | cranelift-native = { version = "0.118", optional = true }
22 |
23 | [features]
24 | default = ["jit"]
25 | serde = ["dep:serde"]
26 | jit = ["cranelift", "cranelift-jit", "cranelift-module", "cranelift-native"]
27 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "common",
4 | "components/*",
5 | "cores/ggc",
6 | "cores/gga",
7 | "cores/nds",
8 | "gamegirl",
9 | "frontends/*",
10 | ]
11 | resolver = "2"
12 |
13 | [profile.release]
14 | panic = "abort"
15 | opt-level = 3
16 |
17 | [profile.release-unwind]
18 | inherits = "release"
19 | panic = "unwind"
20 |
21 | [profile.release-debug]
22 | inherits = "release"
23 | debug = true
24 |
25 | [profile.release-fast]
26 | inherits = "release"
27 | lto = false
28 | opt-level = 2
29 | incremental = true
30 |
31 | [profile.dev]
32 | opt-level = 1
33 |
34 | [workspace.dependencies]
35 | log = { version = "0.4.27", default-features = false }
36 | serde = { version = "1.0.219", features = ["derive", "rc"] }
37 | serde_arrays = "0.2.0"
38 | arrayvec = { version = "0.7.6", default-features = false }
39 | bitflags = "2.9.0"
40 | modular-bitfield = "0.11"
41 | bitmatch = { path = "./components/bitmatch" }
42 | glow = "0.16"
43 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/tests/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | mod gb;
10 | mod gba;
11 |
12 | use gb::*;
13 | use gba::*;
14 |
15 | use crate::testsuite::TestSuite;
16 |
17 | pub const SUITES: &[(&str, fn() -> TestSuite)] = &[
18 | ("Blargg", blargg),
19 | ("Blargg-Sound", blargg_sound),
20 | ("Mooneye Acceptance", || mooneye("acceptance")),
21 | ("Mooneye EmuOnly", || mooneye("emulator-only")),
22 | ("Mooneye Misc", || mooneye("misc")),
23 | ("GBMicrotest", || gbmicrotest()),
24 | ("{cgb,dmg}-acid2", || acid2()),
25 | ("jsmolka", || jsmolka()),
26 | ("fuzzarm", || fuzzarm()),
27 | ];
28 |
--------------------------------------------------------------------------------
/cores/gga/src/tests/jsmolka/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Julian Smolka
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/testing/tests/mooneye/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2022 Joonas Javanainen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/resources/dragon-solid-symbolic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/armchair/src/misc/mod.rs:
--------------------------------------------------------------------------------
1 | use alloc::{fmt::Debug, format, string::String};
2 |
3 | use crate::Address;
4 |
5 | mod alu;
6 | mod operations;
7 |
8 | #[derive(Copy, Clone, Debug, PartialEq, Eq)]
9 | pub enum InstructionKind {
10 | Arm = 0,
11 | Thumb = 1,
12 | }
13 |
14 | impl InstructionKind {
15 | /// Returns the width of an instruction in bytes.
16 | pub fn width(self) -> u32 {
17 | 4 - (((self == Self::Thumb) as u32) << 1)
18 | }
19 |
20 | /// Returns the width of an instruction expressed as an address.
21 | pub fn addr_width(self) -> Address {
22 | Address(self.width())
23 | }
24 | }
25 |
26 | pub fn condition_mnemonic(cond: u16) -> &'static str {
27 | match cond {
28 | 0x0 => "eq",
29 | 0x1 => "ne",
30 | 0x2 => "cs",
31 | 0x3 => "cc",
32 | 0x4 => "mi",
33 | 0x5 => "pl",
34 | 0x6 => "vs",
35 | 0x7 => "vc",
36 | 0x8 => "hi",
37 | 0x9 => "ls",
38 | 0xA => "ge",
39 | 0xB => "lt",
40 | 0xC => "gt",
41 | 0xD => "le",
42 | 0xE => "",
43 | _ => "nv",
44 | }
45 | }
46 |
47 | pub fn print_op(item: T) -> String {
48 | format!("{item:?}").to_lowercase()
49 | }
50 |
--------------------------------------------------------------------------------
/cores/nes/src/apu.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::TimeS;
10 |
11 | use crate::{
12 | scheduling::{ApuEvent, NesEvent},
13 | Nes, CLOCK_HZ,
14 | };
15 |
16 | const SAMPLE_EVERY_N_CLOCKS: TimeS = CLOCK_HZ as TimeS / 48000;
17 |
18 | #[derive(Default)]
19 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20 | pub struct Apu {
21 | pub buffer: Vec,
22 | }
23 |
24 | impl Apu {
25 | pub fn handle_event(nes: &mut Nes, event: ApuEvent, late_by: TimeS) {
26 | match event {
27 | ApuEvent::PushSample => {
28 | nes.apu.buffer.push(0.0);
29 | nes.apu.buffer.push(0.0);
30 | nes.scheduler.schedule(
31 | NesEvent::ApuEvent(ApuEvent::PushSample),
32 | SAMPLE_EVERY_N_CLOCKS - late_by,
33 | )
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/resources/gamepad-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/cores/nds/src/hw/audio.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::TimeS;
10 |
11 | use crate::{cpu::NDS9_CLOCK, scheduling::ApuEvent, Nds};
12 |
13 | pub const SAMPLE_EVERY_N_CLOCKS: TimeS = (NDS9_CLOCK / 48000) as TimeS;
14 |
15 | #[derive(Default)]
16 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17 | pub struct Apu {
18 | pub bias: u16,
19 | pub control: u16
20 | }
21 |
22 | impl Apu {
23 | /// Handle event. Since all APU events reschedule themselves, this
24 | /// function returns the time after which the event should repeat.
25 | pub fn handle_event(ds: &mut Nds, event: ApuEvent, late_by: TimeS) -> TimeS {
26 | match event {
27 | ApuEvent::PushSample => {
28 | ds.c.audio_buffer.input[0].push(0.0);
29 | ds.c.audio_buffer.input[1].push(0.0);
30 | SAMPLE_EVERY_N_CLOCKS - late_by
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/src/debug/nds.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use eframe::egui::{Context, Ui};
10 | use gamegirl::nds::Nds;
11 |
12 | use super::Windows;
13 | use crate::App;
14 |
15 | pub fn ui_menu(app: &mut App, ui: &mut eframe::egui::Ui) {
16 | app.debugger_window_states[0] ^= ui.button("Debugger ARM9").clicked();
17 | app.debugger_window_states[1] ^= ui.button("Debugger ARM7").clicked();
18 | app.debugger_window_states[2] ^= ui.button("Cartridge Viewer").clicked();
19 | }
20 |
21 | pub fn get_windows() -> Windows {
22 | &[
23 | ("Debugger ARM9", |a, b, c, d| {
24 | super::armchair::debugger(&mut a.cpu9, b, c, d)
25 | }),
26 | ("Debugger ARM7", |a, b, c, d| {
27 | super::armchair::debugger(&mut a.cpu7, b, c, d)
28 | }),
29 | ("Cartridge", cart_info),
30 | ]
31 | }
32 |
33 | /// Window showing information about the loaded ROM/cart.
34 | pub fn cart_info(ds: &mut Nds, ui: &mut Ui, _: &mut App, _: &Context) {
35 | ui.label(format!("{:#?}", ds.cart.header()));
36 | }
37 |
--------------------------------------------------------------------------------
/gamegirl/src/frontend/filter.rs:
--------------------------------------------------------------------------------
1 | use std::vec::Vec;
2 |
3 | #[derive(Default)]
4 | pub struct ScreenBuffer {
5 | pub buffer: Vec>,
6 | }
7 |
8 | impl ScreenBuffer {
9 | pub fn next_frame(&mut self, next: Vec<[u8; 4]>, blend: Blend) -> Vec<[u8; 4]> {
10 | match (blend, self.buffer.last_mut()) {
11 | (Blend::None, _) => next,
12 |
13 | (Blend::Soften, Some(last)) => {
14 | let pixels = last
15 | .iter()
16 | .zip(next.iter())
17 | .map(|(a, b)| blend_pixel(*a, *b))
18 | .collect();
19 | *last = next.into();
20 | pixels
21 | }
22 |
23 | (Blend::Accumulate, Some(last)) => {
24 | let pixels: Vec<[u8; 4]> = last
25 | .iter()
26 | .zip(next.iter())
27 | .map(|(a, b)| blend_pixel(*a, *b))
28 | .collect();
29 | *last = pixels.clone();
30 | pixels
31 | }
32 |
33 | _ => {
34 | self.buffer.push(next.clone());
35 | next
36 | }
37 | }
38 | }
39 | }
40 |
41 | fn blend_pixel(mut a: [u8; 4], b: [u8; 4]) -> [u8; 4] {
42 | for i in 0..3 {
43 | a[i] = ((a[i] as u16 + b[i] as u16) / 2) as u8;
44 | }
45 | a
46 | }
47 |
48 | #[derive(Copy, Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
49 | pub enum Blend {
50 | None,
51 | Soften,
52 | Accumulate,
53 | }
54 |
--------------------------------------------------------------------------------
/cores/psx/src/scheduling.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{components::scheduler::Kind, TimeS};
10 |
11 | use crate::{PlayStation, FRAME_CLOCK, SAMPLE_CLOCK};
12 |
13 | #[derive(Copy, Clone, Eq, PartialEq)]
14 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15 | pub enum PsxEvent {
16 | PauseEmulation,
17 | OutputFrame,
18 | ProduceSample,
19 | }
20 |
21 | impl PsxEvent {
22 | pub fn dispatch(self, ps: &mut PlayStation, _late_by: TimeS) {
23 | match self {
24 | PsxEvent::PauseEmulation => ps.ticking = false,
25 | PsxEvent::OutputFrame => {
26 | ps.ppu.output_frame();
27 | ps.scheduler.schedule(PsxEvent::OutputFrame, FRAME_CLOCK);
28 | }
29 | PsxEvent::ProduceSample => {
30 | ps.apu.buffer.push(0.0);
31 | ps.apu.buffer.push(0.0);
32 | ps.scheduler.schedule(PsxEvent::ProduceSample, SAMPLE_CLOCK);
33 | }
34 | }
35 | }
36 | }
37 |
38 | impl Kind for PsxEvent {}
39 |
40 | impl Default for PsxEvent {
41 | fn default() -> Self {
42 | Self::PauseEmulation
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/tests/gba.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use crate::testsuite::{TestStatus, TestSuite};
10 |
11 | pub fn jsmolka() -> TestSuite {
12 | TestSuite::new("jsmolka", 10, |gg| {
13 | let regs = gg.get_registers();
14 | let hash = TestSuite::screen_hash(gg);
15 |
16 | if regs[13] == 0x03008014 {
17 | let ones = regs[10];
18 | let tens = regs[9];
19 | let hundreds = regs[8];
20 | let test = ones + (tens * 10) + (hundreds * 100);
21 | TestStatus::FailedAt(test.to_string())
22 | } else if [
23 | 0x20974E0091874964,
24 | 0x94F4D344B975EB0C,
25 | 0x1A8992654BCDC4D8,
26 | 0x63E68B6E5115B556,
27 | ]
28 | .contains(&hash)
29 | {
30 | TestStatus::Success
31 | } else {
32 | TestStatus::Running
33 | }
34 | })
35 | }
36 |
37 | pub fn fuzzarm() -> TestSuite {
38 | TestSuite::new("fuzzarm", 20, |gg| {
39 | if TestSuite::screen_hash(gg) == 0xD5170621BA472629 {
40 | TestStatus::Success
41 | } else {
42 | TestStatus::Running
43 | }
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/cores/nes/src/scheduling.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{components::scheduler::Kind, TimeS};
10 | use NesEvent::*;
11 |
12 | use crate::{apu::Apu, Nes};
13 |
14 | /// All scheduler events on the NES.
15 | #[derive(Copy, Clone, Eq, PartialEq, Default)]
16 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17 | #[repr(u16)]
18 | pub enum NesEvent {
19 | /// Pause the emulation. Used by `advance_delta` to advance by a certain
20 | /// amount.
21 | #[default]
22 | PauseEmulation,
23 | ApuEvent(ApuEvent),
24 | }
25 |
26 | impl NesEvent {
27 | /// Handle the event by delegating to the appropriate handler.
28 | pub fn dispatch(&self, nes: &mut Nes, late_by: TimeS) {
29 | match self {
30 | PauseEmulation => nes.ticking = false,
31 | ApuEvent(event) => Apu::handle_event(nes, *event, late_by),
32 | }
33 | }
34 | }
35 |
36 | impl Kind for NesEvent {}
37 |
38 | /// Events the APU generates.
39 | #[derive(Copy, Clone, Eq, PartialEq)]
40 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
41 | #[repr(u16)]
42 | pub enum ApuEvent {
43 | // Push a sample to the output.
44 | PushSample,
45 | }
46 |
--------------------------------------------------------------------------------
/components/armchair/src/tracing.rs:
--------------------------------------------------------------------------------
1 | use alloc::{
2 | format,
3 | string::{String, ToString},
4 | };
5 | use core::fmt::Write;
6 |
7 | use common::numutil::NumExt;
8 |
9 | use crate::{
10 | arm::ArmInst,
11 | interface::{Bus, CpuVersion},
12 | state::{Flag, Register},
13 | thumb::ThumbInst,
14 | Cpu, CpuState,
15 | };
16 |
17 | impl Cpu {
18 | pub fn trace_inst(&mut self, inst: u32) {
19 | let cpsr = self.state.cpsr();
20 | let mnem = self.state.get_inst_mnemonic(inst);
21 |
22 | let mut buf = String::with_capacity(100);
23 | let num = ('4' as u8 + S::Version::IS_V5 as u8) as char;
24 | buf.push(num);
25 | if !self.state.pipeline_valid {
26 | buf.push('!');
27 | }
28 | for reg in Register::from_rlist(u16::MAX) {
29 | write!(buf, "{:08X} ", self.state[reg]).ok();
30 | }
31 |
32 | if TY::WIDTH == 2 {
33 | self.bus.debugger().add_traced_instruction(|| {
34 | format!("{buf}cpsr: {cpsr:08X} | {inst:04X}: {mnem}")
35 | });
36 | } else {
37 | self.bus
38 | .debugger()
39 | .add_traced_instruction(|| format!("{buf}cpsr: {cpsr:08X} | {inst:08X}: {mnem}"));
40 | }
41 | }
42 | }
43 |
44 | impl CpuState {
45 | pub fn get_inst_mnemonic(&mut self, inst: u32) -> String {
46 | if self.is_flag(Flag::Thumb) {
47 | ThumbInst::of(inst.u16()).to_string()
48 | } else {
49 | ArmInst::of(inst.u32()).to_string()
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/components/bitmatch/README.md:
--------------------------------------------------------------------------------
1 | # Bitmatch
2 |
3 | [](https://crates.io/crates/bitmatch)
4 | [](https://www.mozilla.org/en-US/MPL/2.0/)
5 | [](https://docs.rs/bitmatch/)
6 |
7 | The bitmatch crate provides tools for packing and unpacking integers as
8 | sequences of bits. Supports `#![no_std]`.
9 |
10 | ## Examples
11 |
12 | Using `#[bitmatch]` with a `let` unpacks the bits into separate
13 | single-character variables for each letter you use.
14 |
15 | Using `bitpack!()` re-packs the bits from those single-character variables
16 | into a single integer.
17 |
18 | ```rust
19 | use bitmatch::bitmatch;
20 |
21 | #[bitmatch]
22 | fn interleave(n: u8) -> u8 {
23 | #[bitmatch]
24 | let "xxxx_yyyy" = n;
25 | bitpack!("xyxy_xyxy")
26 | }
27 |
28 | fn main() {
29 | assert_eq!(interleave(0xF0), 0xAA);
30 | }
31 | ```
32 |
33 | You can use `#[bitmatch]` on a `match` as well, and it will ensure that the
34 | literal portions match, and bind the variable portions.
35 |
36 | ```rust
37 | use bitmatch::bitmatch;
38 | #[bitmatch]
39 | fn decode(inst: u8) -> String {
40 | #[bitmatch]
41 | match inst {
42 | "00oo_aabb" => format!("Op {}, {}, {}", o, a, b),
43 | "0100_aaii" => format!("Val {}, {}", a, i),
44 | "01??_????" => format!("Invalid"),
45 | "10ii_aabb" => format!("Ld {}, {}, {}", a, b, i),
46 | "11ii_aabb" => format!("St {}, {}, {}", a, b, i),
47 | }
48 | }
49 | ```
50 |
51 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/src/gui/mod.rs:
--------------------------------------------------------------------------------
1 | use adw::subclass::prelude::ObjectSubclassIsExt;
2 | use gamegirl::frontend::input::{InputAction, InputSource};
3 | use gtk::EventControllerKey;
4 | use window::GameGirlWindow;
5 |
6 | pub mod actions;
7 | pub mod canvas;
8 | pub mod input;
9 | pub mod settings;
10 | pub mod window;
11 |
12 | pub fn key_controller(window: &GameGirlWindow) -> EventControllerKey {
13 | let window1 = window.clone();
14 | let window2 = window.clone();
15 | let core1 = window.core();
16 | let core2 = window.core();
17 |
18 | let controller = EventControllerKey::new();
19 | controller.connect_key_pressed(move |_, key, _, _| {
20 | let key = key.to_upper();
21 | if let Some(InputAction::Button(button)) = window1
22 | .imp()
23 | .state
24 | .borrow_mut()
25 | .options
26 | .input
27 | .key_triggered(InputSource::Key(key.into()))
28 | {
29 | core1.lock().unwrap().c_mut().input.set(0, button, true);
30 | gtk::glib::Propagation::Stop
31 | } else {
32 | gtk::glib::Propagation::Proceed
33 | }
34 | });
35 | controller.connect_key_released(move |_, key, _, _| {
36 | let key = key.to_upper();
37 | if let Some(InputAction::Button(button)) = window2
38 | .imp()
39 | .state
40 | .borrow_mut()
41 | .options
42 | .input
43 | .key_triggered(InputSource::Key(key.into()))
44 | {
45 | core2.lock().unwrap().c_mut().input.set(0, button, false);
46 | }
47 | });
48 | controller
49 | }
50 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/resources/cat-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/cores/ggc/src/io/joypad.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{numutil::NumExt, TimeS};
10 |
11 | use super::scheduling::GGEvent;
12 | use crate::{cpu::Interrupt, io::addr::JOYP, GameGirl, T_CLOCK_HZ};
13 |
14 | /// Joypad of the console.
15 | #[derive(Default)]
16 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17 | pub struct Joypad {
18 | key_states: u8,
19 | }
20 |
21 | impl Joypad {
22 | pub fn read(&self, joyp: u8) -> u8 {
23 | let row_start = match joyp & 0x30 {
24 | 0x10 => 0,
25 | 0x20 => 4,
26 | _ => return 0xCF,
27 | };
28 | let mut res = 0;
29 | for key in (0..8).skip(row_start).take(4).rev() {
30 | let key = self.key_states.is_bit(key);
31 | res <<= 1;
32 | res += (!key) as u8;
33 | }
34 | res | (joyp & 0x30) | 0b1100_0000
35 | }
36 |
37 | pub fn update(gg: &mut GameGirl) {
38 | gg.joypad.key_states = gg.c.input.state(gg.scheduler.now()).0 as u8;
39 | gg.scheduler
40 | .schedule(GGEvent::UpdateKeypad, (T_CLOCK_HZ / 120) as TimeS);
41 | let read = gg.joypad.read(gg[JOYP]);
42 | if read & 0x0F != 0x0F {
43 | gg.request_interrupt(Interrupt::Joypad);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gamegirl-egui"
3 | default-run = "gamegirl_bin"
4 | version = "0.1.0"
5 | edition = "2021"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [[bin]]
10 | name = "gamegirl_bin"
11 | path = "src/main.rs"
12 |
13 | [lib]
14 | crate-type = ["cdylib", "rlib"]
15 |
16 | [dependencies]
17 | gamegirl = { path = "../../gamegirl", features = [
18 | "ggc",
19 | "gga",
20 | "nds",
21 | "frontend",
22 | ] }
23 |
24 | egui = "0.31"
25 | egui_extras = "0.31"
26 | eframe = { version = "0.31", default-features = false, features = [
27 | "default_fonts",
28 | "glow",
29 | "persistence",
30 | "wayland",
31 | ] }
32 | egui-notify = { git = "https://github.com/ItsEthra/egui-notify", branch = "master" }
33 |
34 | rfd = "0.15.3"
35 | gilrs = { version = "0.11.0", features = ["serde-serialize"] }
36 |
37 | log.workspace = true
38 | serde.workspace = true
39 |
40 | once_cell = { version = "*", optional = true }
41 | hqx = { git = "https://github.com/CryZe/wasmboy-rs", tag = "v0.1.3", optional = true }
42 | ehttp = { version = "0.5", optional = true }
43 |
44 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
45 | env_logger = "0.11.7"
46 | futures-executor = "0.3.31"
47 | gamegirl = { path = "../../gamegirl", features = ["zstd"] }
48 |
49 | [target.'cfg(target_arch = "wasm32")'.dependencies]
50 | console_error_panic_hook = "0.1.7"
51 | tracing-wasm = "0.2.1"
52 | wasm-bindgen-futures = "0.4.50"
53 | cpal = { version = "0.15.3", features = ["wasm-bindgen"] }
54 | web-sys = "0.3.77"
55 |
56 | [features]
57 | default = ["dep:ehttp"]
58 | savestates = ["gamegirl/serde"]
59 | remote-debugger = ["gamegirl/remote-debugger", "once_cell"]
60 | filters = ["hqx"]
61 | dynamic = ["gamegirl/dynamic"]
62 |
--------------------------------------------------------------------------------
/gamegirl/src/frontend/cpal.rs:
--------------------------------------------------------------------------------
1 | use core::time::Duration;
2 | use std::{
3 | boxed::Box,
4 | sync::{Arc, Mutex},
5 | };
6 |
7 | use common::Core;
8 | use cpal::{
9 | traits::{DeviceTrait, HostTrait, StreamTrait},
10 | BufferSize, SampleRate, Stream, StreamConfig,
11 | };
12 |
13 | pub struct AudioStream(#[allow(dead_code)] Option);
14 |
15 | impl AudioStream {
16 | pub fn empty() -> Self {
17 | Self(None)
18 | }
19 | }
20 |
21 | /// Setup audio playback on the default audio device using CPAL.
22 | /// Will automatically poll the gg for audio when needed on a separate thread.
23 | /// *NOT* used for synchronization, since audio samples are requested less than
24 | /// 60 times per second, which would lead to choppy display.
25 | /// Make sure to keep the returned Stream around to prevent the audio playback
26 | /// thread from closing.
27 | pub fn setup(sys: Arc>>) -> AudioStream {
28 | let sr = {
29 | let core = sys.lock().unwrap();
30 | core.c().config.sample_rate as u32
31 | };
32 | let device = cpal::default_host().default_output_device().unwrap();
33 | if let Some(stream) = device
34 | .build_output_stream(
35 | &StreamConfig {
36 | channels: 2,
37 | sample_rate: SampleRate(sr),
38 | buffer_size: BufferSize::Fixed(sr / 30),
39 | },
40 | move |data: &mut [f32], _| {
41 | let mut core = sys.lock().unwrap();
42 | core.produce_samples(data)
43 | },
44 | move |err| panic!("{err}"),
45 | Some(Duration::from_secs(1)),
46 | )
47 | .ok()
48 | {
49 | stream.play().ok();
50 | AudioStream(Some(stream))
51 | } else {
52 | AudioStream(None)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/src/main.rs:
--------------------------------------------------------------------------------
1 | mod config;
2 | mod gui;
3 |
4 | use std::path::PathBuf;
5 |
6 | use adw::{Application, subclass::prelude::ObjectSubclassIsExt};
7 | use config::Options;
8 | use gamegirl::frontend::rewinder::Rewinder;
9 | use gtk::{
10 | gio::{
11 | self,
12 | prelude::{ApplicationExt, ApplicationExtManual},
13 | },
14 | glib::{self},
15 | prelude::{GtkWindowExt, WidgetExt},
16 | };
17 | use gui::window::GameGirlWindow;
18 |
19 | /// App state. This struct is contained in the main app window.
20 | pub struct AppState {
21 | /// User config state.
22 | options: Options,
23 | /// Path of the currently loaded ROM.
24 | current_rom_path: Option,
25 | /// Current rewinder state.
26 | rewinder: Rewinder<5>,
27 | }
28 |
29 | impl Default for AppState {
30 | fn default() -> Self {
31 | let options = Options::default();
32 | let rewinder = Rewinder::new(options.rewind.rewind_buffer_size);
33 | Self {
34 | options,
35 | current_rom_path: Default::default(),
36 | rewinder,
37 | }
38 | }
39 | }
40 |
41 | fn main() -> glib::ExitCode {
42 | gio::resources_register_include!("gamegirl.gresource").expect("Failed to register resources.");
43 |
44 | let app = Application::builder()
45 | .application_id("eu.catin.gamegirl")
46 | .build();
47 | app.connect_activate(build_ui);
48 | app.connect_startup(|_| {
49 | adw::init().unwrap();
50 | });
51 | app.run()
52 | }
53 |
54 | fn build_ui(app: &Application) {
55 | let window = GameGirlWindow::new(app);
56 | window.add_controller(gui::key_controller(&window));
57 | window.connect_destroy(|window| {
58 | window.save_game();
59 | window.imp().state.borrow().options.to_disk();
60 | });
61 | window.present();
62 | }
63 |
--------------------------------------------------------------------------------
/common/src/common/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use debugger::Debugger;
10 | use input::Input;
11 | use options::{EmulateOptions, SystemConfig};
12 | use video::FrameBuffer;
13 |
14 | use self::audio::AudioBuffer;
15 |
16 | pub mod audio;
17 | pub mod debugger;
18 | pub mod input;
19 | pub mod options;
20 | pub mod time;
21 | pub mod video;
22 |
23 | /// Common fields shared by all systems.
24 | #[derive(Default)]
25 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
26 | pub struct Common {
27 | #[cfg_attr(feature = "serde", serde(skip, default))]
28 | pub debugger: Debugger,
29 | pub options: EmulateOptions,
30 | pub config: SystemConfig,
31 | pub in_tick: bool,
32 |
33 | #[cfg_attr(feature = "serde", serde(skip, default))]
34 | pub video_buffer: FrameBuffer,
35 | #[cfg_attr(feature = "serde", serde(skip, default))]
36 | pub audio_buffer: AudioBuffer,
37 | pub input: Input,
38 | }
39 |
40 | impl Common {
41 | pub fn with_config(config: SystemConfig) -> Self {
42 | Self {
43 | audio_buffer: AudioBuffer::with_config(&config),
44 | config,
45 | ..Default::default()
46 | }
47 | }
48 |
49 | pub fn restore_from(&mut self, old: Self) {
50 | self.debugger = old.debugger;
51 | self.options = old.options;
52 | self.config = old.config;
53 | self.audio_buffer = old.audio_buffer;
54 | self.audio_buffer.reinit_sampler();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/cores/nds/src/graphics/engine3d/registers.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::numutil::{hword, word, NumExt, U16Ext, U32Ext};
10 | use modular_bitfield::{bitfield, specifiers::*, BitfieldSpecifier};
11 |
12 | #[bitfield]
13 | #[repr(u16)]
14 | #[derive(Debug, Default, Copy, Clone)]
15 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16 | pub struct Disp3dCnt {
17 | pub tex_mapping_en: bool,
18 | pub shading: PolygonShading,
19 | pub alpha_test: bool,
20 | pub alpha_blend: bool,
21 | pub antialias: bool,
22 | pub edge_mark: bool,
23 | pub fog_color: FogColor,
24 | pub fog_en: bool,
25 | pub fog_shift: B4,
26 | pub color_underflow_ack: bool,
27 | pub polygon_overflow_ack: bool,
28 | pub rear_plane: RearPlane,
29 | #[skip]
30 | __: B1,
31 | }
32 |
33 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
34 | #[bits = 1]
35 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
36 | pub enum PolygonShading {
37 | Toon = 0,
38 | Highlight = 1,
39 | }
40 |
41 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
42 | #[bits = 1]
43 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
44 | pub enum FogColor {
45 | AlphaColor = 0,
46 | Alpha = 1,
47 | }
48 |
49 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
50 | #[bits = 1]
51 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
52 | pub enum RearPlane {
53 | Blank = 0,
54 | Bitmap = 1,
55 | }
56 |
--------------------------------------------------------------------------------
/gamegirl/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gamegirl"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [lib]
9 | crate-type = ["cdylib", "lib"]
10 |
11 | [dependencies]
12 | common = { path = "../common" }
13 | ggc = { path = "../cores/ggc", optional = true }
14 | gga = { path = "../cores/gga", optional = true }
15 | nds = { path = "../cores/nds", optional = true }
16 | # psx = { path = "../cores/psx", optional = true }
17 | # nes = { path = "../cores/nes", optional = true }
18 | log.workspace = true
19 |
20 | gdbstub = { version = "0.7.5", optional = true }
21 | gdbstub_arch = { version = "0.3.1", optional = true }
22 | glow = { workspace = true, optional = true }
23 | zip = { version = "2.5", default-features = false, optional = true, features = [
24 | "deflate",
25 | "deflate64",
26 | "lzma",
27 | ] }
28 | thiserror = { version = "2.0", default-features = false }
29 |
30 | libloading = { version = "0.8", optional = true }
31 | notify = { version = "8.0.0", optional = true }
32 |
33 | cpal = { version = "0.15.3", optional = true }
34 | gilrs = { version = "0.11.0", features = ["serde-serialize"], optional = true }
35 | serde = { workspace = true, optional = true }
36 |
37 | [target.'cfg(target_arch = "wasm32")'.dependencies]
38 | web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
39 | base64 = "0.22.1"
40 |
41 | [features]
42 | serde = [
43 | "common/serde",
44 | "ggc?/serde",
45 | "gga?/serde",
46 | "nds?/serde",
47 | # "nes?/serde",
48 | # "psx?/serde",
49 | ]
50 | serde-config = ["common/serde_config"]
51 |
52 | std = ["common/std", "gga/std", "nds/std", "dep:zip"]
53 | zstd = ["common/zstd"]
54 | remote-debugger = ["dep:gdbstub", "dep:gdbstub_arch", "std"]
55 | dynamic = ["dep:libloading", "dep:notify", "std"]
56 | frontend = ["dep:cpal", "dep:gilrs", "dep:serde", "std", "serde-config"]
57 |
--------------------------------------------------------------------------------
/common/src/testing.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use core::hash::Hasher;
10 | use std::{boxed::Box, hash::DefaultHasher, string::String, vec::Vec};
11 |
12 | use crate::{common::options::SystemConfig, components::storage::GameCart, Core};
13 |
14 | pub type TestInspector = fn(&mut Box) -> TestStatus;
15 |
16 | pub fn run_test(rom: &[u8], inspector: TestInspector) {
17 | let rom = rom.into();
18 | let mut core = S::try_new(
19 | &mut Some(GameCart { rom, save: None }),
20 | &SystemConfig::default(),
21 | )
22 | .unwrap();
23 | for _ in 0..30 {
24 | core.advance_delta(1.0);
25 | let status = (inspector)(&mut core);
26 | match status {
27 | TestStatus::Running => continue,
28 | TestStatus::Success => return,
29 | TestStatus::Failed => panic!("Test failed!"),
30 | TestStatus::FailedAt(msg) => panic!("Test failed: {msg}!"),
31 | }
32 | }
33 | panic!("Test timed out!")
34 | }
35 |
36 | pub fn screen_hash(core: &mut Box) -> u64 {
37 | let mut hasher = DefaultHasher::new();
38 | if let Some(frame) = core.c_mut().video_buffer.pop_recent() {
39 | hasher.write(
40 | &frame
41 | .into_iter()
42 | .flat_map(|t| t.into_iter())
43 | .collect::>(),
44 | )
45 | }
46 | hasher.finish()
47 | }
48 |
49 | #[derive(PartialEq)]
50 | pub enum TestStatus {
51 | Running,
52 | Success,
53 | Failed,
54 | FailedAt(String),
55 | }
56 |
--------------------------------------------------------------------------------
/cores/nds/src/hw/bios/drastic_bios_readme.txt:
--------------------------------------------------------------------------------
1 | Custom NDS ARM7/ARM9 BIOS replacement
2 | Copyright (c) 2013, Gilead Kutnick
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1) Redistributions of source code must retain the above copyright notice,
9 | this list of conditions and the following disclaimer.
10 | 2) Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | POSSIBILITY OF SUCH DAMAGE.
25 |
26 | -- Info --
27 |
28 | This archive contains source code and assembly for a custom BIOS replacement
29 | for the Nintendo DS system. This code is in no way affiliated with Nintendo
30 | and is not derived from Nintendo's BIOS implementation but has been implemented
31 | using publically available documentation.
32 |
33 | It can be assembled using the included Makefile along with a proper ARM gcc
34 | toolchain. Change the first four lines to point to the proper toolchain of your
35 | choice.
36 |
37 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | mod app;
10 | mod gui;
11 | mod tests;
12 | mod testsuite;
13 |
14 | use std::{
15 | path::PathBuf,
16 | sync::{Arc, Mutex},
17 | };
18 |
19 | use eframe::{egui::ViewportBuilder, emath::History};
20 | use gamegirl::{
21 | common::Core,
22 | dummy_core,
23 | dynamic::{DynamicContext, NewCoreFn},
24 | };
25 | use testsuite::TestSuiteResult;
26 |
27 | use crate::app::App;
28 |
29 | pub struct DCore {
30 | c: Box,
31 | suites: Vec,
32 | bench: History,
33 | bench_iso: Arc>>,
34 | loader: NewCoreFn,
35 | idx: Option,
36 | name: String,
37 | }
38 |
39 | fn main() {
40 | env_logger::init();
41 | let options = eframe::NativeOptions {
42 | viewport: ViewportBuilder::default().with_transparent(true),
43 | ..Default::default()
44 | };
45 | eframe::run_native(
46 | "gamegirl core workbench",
47 | options,
48 | Box::new(|ctx| Ok(App::new(ctx))),
49 | )
50 | .unwrap()
51 | }
52 |
53 | fn load_core(ctx: &mut DynamicContext, path: PathBuf) -> Result {
54 | ctx.load_core(&path).map(|idx| DCore {
55 | c: dummy_core(),
56 | suites: vec![],
57 | bench: History::new(10..5000, 30.0),
58 | bench_iso: Arc::new(Mutex::new(History::new(10..5000, 100.0))),
59 | loader: ctx.get_core(idx).loader,
60 | idx: Some(idx),
61 | name: path.file_name().unwrap().to_string_lossy().to_string(),
62 | })
63 | }
64 |
--------------------------------------------------------------------------------
/cores/nds/src/graphics/ppu/render/modes.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use super::{super::OverflowMode, pixels::affine_transform_point, xy2d, PpuRender, HEIGHT, WIDTH};
10 |
11 | impl PpuRender {
12 | pub fn render_mode0(&mut self) {
13 | for bg in 0..4 {
14 | self.render_bg_text(bg);
15 | }
16 | self.finalize_scanline(0..=3);
17 | }
18 |
19 | pub fn render_mode1(&mut self) {
20 | self.render_bg_text(0);
21 | self.render_bg_text(1);
22 | self.render_bg_text(2);
23 | self.render_bg_affine(3);
24 | self.finalize_scanline(0..=3);
25 | }
26 |
27 | pub fn render_mode2(&mut self) {
28 | self.render_bg_text(0);
29 | self.render_bg_text(1);
30 | self.render_bg_affine(2);
31 | self.render_bg_affine(3);
32 | self.finalize_scanline(0..=3);
33 | }
34 |
35 | pub fn render_mode3(&mut self) {
36 | self.render_bg_text(0);
37 | self.render_bg_text(1);
38 | self.render_bg_text(2);
39 | self.render_bg_ext(3);
40 | self.finalize_scanline(0..=3);
41 | }
42 |
43 | pub fn render_mode4(&mut self) {
44 | self.render_bg_text(0);
45 | self.render_bg_text(1);
46 | self.render_bg_affine(2);
47 | self.render_bg_ext(3);
48 | self.finalize_scanline(0..=3);
49 | }
50 |
51 | pub fn render_mode5(&mut self) {
52 | self.render_bg_text(0);
53 | self.render_bg_text(1);
54 | self.render_bg_ext(2);
55 | self.render_bg_ext(3);
56 | self.finalize_scanline(0..=3);
57 | }
58 |
59 | pub fn render_mode6(&mut self) {
60 | // TODO 3D stuff
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/cores/gga/src/hw/input.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | //! Input handler.
10 | //! Luckily, GGA input is dead simple compared to even GG.
11 |
12 | #![allow(unused_braces)] // modular_bitfield issue
13 |
14 | use armchair::Interrupt;
15 | use common::TimeS;
16 | use modular_bitfield::{bitfield, specifiers::B14};
17 |
18 | use crate::{
19 | cpu::{GgaFullBus, CPU_CLOCK},
20 | scheduling::AdvEvent,
21 | };
22 |
23 | #[bitfield]
24 | #[repr(u16)]
25 | #[derive(Default, Copy, Clone, Debug)]
26 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
27 | pub struct KeyControl {
28 | irq_enables: B14,
29 | global_irq: bool,
30 | irq_is_and: bool,
31 | }
32 |
33 | impl GgaFullBus {
34 | pub fn keyinput(&self) -> u16 {
35 | // GGA input is active low
36 | 0x3FF ^ self.c.input.state(self.scheduler.now()).0
37 | }
38 |
39 | /// Check if KEYCNT should cause a joypad IRQ.
40 | pub fn check_keycnt(&mut self) {
41 | let input = 0x3FF ^ self.keyinput();
42 | let cnt = self.memory.keycnt;
43 | if cnt.global_irq() {
44 | let cond = cnt.irq_enables();
45 | let fire = (input != self.memory.keys_prev)
46 | && if !cnt.irq_is_and() {
47 | cond & input != 0
48 | } else {
49 | cond & input == cond
50 | };
51 | if fire {
52 | self.cpu.request_interrupt(&mut self.bus, Interrupt::Joypad);
53 | }
54 |
55 | self.scheduler
56 | .schedule(AdvEvent::UpdateKeypad, (CPU_CLOCK / 120.0) as TimeS);
57 | }
58 |
59 | self.memory.keys_prev = input;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/cores/nds/src/cpu/nds7.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use armchair::{
10 | interface::{Arm7Dtmi, Bus, BusCpuConfig, RwType},
11 | state::Flag::IrqDisable,
12 | Access, Address, Cpu, CpuState, Exception,
13 | };
14 | use common::{
15 | common::debugger::Debugger,
16 | components::memory_mapper::{MemoryMappedSystem, MemoryMapper},
17 | numutil::NumExt,
18 | Time,
19 | };
20 |
21 | use crate::{
22 | addr::{IE, IF, IME},
23 | Nds7, Nds9, NdsCpu,
24 | };
25 |
26 | impl Bus for Nds7 {
27 | type Version = Arm7Dtmi;
28 |
29 | const CONFIG: BusCpuConfig = BusCpuConfig {
30 | exception_vector_base_address: Address(0),
31 | };
32 |
33 | fn tick(&mut self, cycles: Time) {
34 | self.time_7 += cycles << 1;
35 | }
36 |
37 | fn handle_events(&mut self, cpu: &mut CpuState) {
38 | if self.scheduler.has_events() {
39 | while let Some(event) = self.scheduler.get_next_pending() {
40 | event.kind.dispatch(self, event.late_by);
41 | }
42 | }
43 | }
44 |
45 | fn exception_happened(&mut self, _cpu: &mut CpuState, _kind: Exception) {}
46 |
47 | fn pipeline_stalled(&mut self, _cpu: &mut CpuState) {}
48 |
49 | fn get(&mut self, _cpu: &mut CpuState, addr: Address) -> T {
50 | self.get(addr.0)
51 | }
52 |
53 | fn set(&mut self, _cpu: &mut CpuState, addr: Address, value: T) {
54 | self.set(addr.0, value)
55 | }
56 |
57 | fn wait_time(
58 | &mut self,
59 | _cpu: &mut CpuState,
60 | _addr: Address,
61 | _access: Access,
62 | ) -> u16 {
63 | 1
64 | }
65 |
66 | fn debugger(&mut self) -> &mut Debugger {
67 | &mut self.c.debugger
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/cores/nds/src/graphics/capture/registers.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::numutil::{hword, word, NumExt, U16Ext, U32Ext};
10 | use modular_bitfield::{bitfield, specifiers::*, BitfieldSpecifier};
11 |
12 | #[bitfield]
13 | #[repr(u32)]
14 | #[derive(Debug, Default, Copy, Clone)]
15 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16 | pub struct DispCapCnt {
17 | pub eva: B5,
18 | #[skip]
19 | __: B3,
20 | pub evb: B5,
21 | #[skip]
22 | __: B3,
23 | vram_write_block: B2,
24 | vram_write_offs: B2,
25 | capt_size: CaptureSize,
26 | #[skip]
27 | __: B2,
28 | source_a: SourceA,
29 | source_b: SourceB,
30 | vram_read_offs: B2,
31 | #[skip]
32 | __: B1,
33 | capt_source: CaptureSource,
34 | capt_en: bool,
35 | }
36 |
37 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
38 | #[bits = 2]
39 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
40 | pub enum CaptureSize {
41 | C128x128 = 0,
42 | C256x64 = 1,
43 | C256x128 = 2,
44 | C256x192 = 3,
45 | }
46 |
47 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
48 | #[bits = 1]
49 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
50 | pub enum SourceA {
51 | Graphics = 0,
52 | ThreeD = 1,
53 | }
54 |
55 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
56 | #[bits = 1]
57 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
58 | pub enum SourceB {
59 | Vram = 0,
60 | MemFifo = 1,
61 | }
62 |
63 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
64 | #[bits = 2]
65 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
66 | pub enum CaptureSource {
67 | SrcA = 0,
68 | SrcB = 1,
69 | SrcAB = 2,
70 | SrcBA = 3,
71 | }
72 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use gamegirl_egui::App;
10 |
11 | #[cfg(not(target_arch = "wasm32"))]
12 | fn main() {
13 | use eframe::egui::ViewportBuilder;
14 |
15 | env_logger::init();
16 | let options = eframe::NativeOptions {
17 | viewport: ViewportBuilder::default().with_transparent(true),
18 | ..Default::default()
19 | };
20 | eframe::run_native("gamegirl", options, Box::new(|ctx| Ok(App::new(ctx)))).unwrap()
21 | }
22 |
23 | #[cfg(target_arch = "wasm32")]
24 | fn main() {
25 | console_error_panic_hook::set_once();
26 | tracing_wasm::set_as_global_default();
27 | eframe::WebLogger::init(log::LevelFilter::Debug).ok();
28 |
29 | let web_options = eframe::WebOptions::default();
30 | wasm_bindgen_futures::spawn_local(async {
31 | let start_result = eframe::WebRunner::new()
32 | .start(
33 | "the_canvas_id",
34 | web_options,
35 | Box::new(|ctx| Ok(App::new(ctx))),
36 | )
37 | .await;
38 |
39 | // Remove the loading text and spinner:
40 | let loading_text = web_sys::window()
41 | .and_then(|w| w.document())
42 | .and_then(|d| d.get_element_by_id("loading_text"));
43 | if let Some(loading_text) = loading_text {
44 | match start_result {
45 | Ok(_) => {
46 | loading_text.remove();
47 | }
48 | Err(e) => {
49 | loading_text.set_inner_html(
50 | " The app has crashed. See the developer console for details.
",
51 | );
52 | panic!("Failed to start eframe: {e:?}");
53 | }
54 | }
55 | }
56 | });
57 | }
58 |
--------------------------------------------------------------------------------
/cores/nds/src/hw/input.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | //! Input handler.
10 |
11 | use armchair::{Cpu, Interrupt};
12 | use common::TimeS;
13 | use modular_bitfield::{bitfield, specifiers::B14};
14 |
15 | use crate::{cpu::NDS9_CLOCK, scheduling::NdsEvent, CpuDevice, Nds, NdsCpu};
16 |
17 | #[derive(Default)]
18 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
19 | pub struct Input {
20 | pub cnt: CpuDevice,
21 | keys_prev: u16,
22 | }
23 |
24 | #[bitfield]
25 | #[repr(u16)]
26 | #[derive(Default, Copy, Clone, Debug)]
27 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
28 | pub struct KeyControl {
29 | irq_enables: B14,
30 | global_irq: bool,
31 | irq_is_and: bool,
32 | }
33 |
34 | impl Nds {
35 | pub fn keyinput(&self) -> u16 {
36 | // NDS input is active low
37 | 0x3FF ^ (self.c.input.state(self.scheduler.now()).0 & 0x3FF)
38 | }
39 |
40 | pub fn keyinput_ext(&self) -> u16 {
41 | // NDS input is active low
42 | // TODO Touchscreen
43 | 0b0111_1100 | (0x3 ^ (self.c.input.state(self.scheduler.now()).0 >> 10))
44 | }
45 |
46 | /// Check if KEYCNT should cause a joypad IRQ.
47 | pub fn check_keycnt(ds: &mut DS) {
48 | let input = 0x3FF ^ ds.keyinput();
49 | let cnt = ds.input.cnt[DS::I];
50 | if cnt.global_irq() {
51 | let cond = cnt.irq_enables();
52 | let fire = (input != ds.input.keys_prev)
53 | && if !cnt.irq_is_and() {
54 | cond & input != 0
55 | } else {
56 | cond & input == cond
57 | };
58 | if fire {
59 | ds.cpu().request_interrupt(Interrupt::Joypad);
60 | }
61 | }
62 |
63 | ds.input.keys_prev = input;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/cores/nds/src/graphics/ppu/render/palette.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{
10 | numutil::{hword, NumExt},
11 | Colour,
12 | };
13 |
14 | use super::{super::PaletteMode, xy2dw, PpuRender};
15 |
16 | impl PpuRender {
17 | /// Given a tile address and tile pixel, get the palette.
18 | pub(super) fn get_palette_obj(
19 | &self,
20 | bank: u8,
21 | mode: PaletteMode,
22 | addr: u32,
23 | x: u32,
24 | y: u32,
25 | ) -> Option {
26 | Some(match mode {
27 | PaletteMode::Palettes16 => {
28 | let addr = addr.us() + xy2dw(x.us() / 2, y.us(), 4);
29 | let value: u8 = self.obj_vram(addr);
30 | let colour = if x.is_bit(0) { value >> 4 } else { value & 0xF };
31 | if colour == 0 {
32 | return None;
33 | }
34 | (bank * 0x10) + colour
35 | }
36 | PaletteMode::Single256 => {
37 | let addr = addr.us() + xy2dw(x.us(), y.us(), 8);
38 | let pal = self.obj_vram(addr);
39 | if pal == 0 {
40 | return None;
41 | }
42 | pal
43 | }
44 | })
45 | }
46 |
47 | /// Turn a palette index (0-255) into a colour.
48 | pub fn idx_to_palette(&self, idx: u8) -> Colour {
49 | let addr = (idx.us() << 1) + (OBJ as usize * 0x200);
50 | let lo = self.palette[addr];
51 | let hi = self.palette[addr + 1];
52 | Self::hword_to_colour(hword(lo, hi))
53 | }
54 |
55 | /// Extract a 5bit colour from a halfword.
56 | fn hword_to_colour(hword: u16) -> Colour {
57 | let r = hword.bits(0, 5).u8();
58 | let g = hword.bits(5, 5).u8();
59 | let b = hword.bits(10, 5).u8();
60 | [r, g, b, 255]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/cores/nes/src/joypad.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{components::input::Button, numutil::NumExt};
10 |
11 | use crate::Nes;
12 |
13 | /// Joypad 1 of the console.
14 | #[derive(Default)]
15 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16 | pub struct Joypad {
17 | key_states: [bool; 8],
18 | register: u8,
19 | strobe: bool,
20 | }
21 |
22 | impl Joypad {
23 | pub fn read(&mut self) -> u8 {
24 | let value = self.register & 1;
25 | self.register >>= 1;
26 | self.register.set_bit(7, true);
27 | value
28 | }
29 |
30 | pub fn write(&mut self, value: u8) {
31 | self.strobe = value.is_bit(0);
32 | if self.strobe {
33 | self.register = 0;
34 | self.register
35 | .set_bit(0, self.key_states[Button::A as usize]);
36 | self.register
37 | .set_bit(1, self.key_states[Button::B as usize]);
38 | self.register
39 | .set_bit(2, self.key_states[Button::Select as usize]);
40 | self.register
41 | .set_bit(3, self.key_states[Button::Start as usize]);
42 | self.register
43 | .set_bit(4, self.key_states[Button::Up as usize]);
44 | self.register
45 | .set_bit(5, self.key_states[Button::Down as usize]);
46 | self.register
47 | .set_bit(6, self.key_states[Button::Left as usize]);
48 | self.register
49 | .set_bit(7, self.key_states[Button::Right as usize]);
50 | }
51 | }
52 |
53 | /// To be called by GUI code; sets the state of a given button.
54 | pub fn set(nes: &mut Nes, button: Button, state: bool) {
55 | if button as usize >= 8 {
56 | return; // GGA buttons
57 | }
58 | nes.joypad.key_states[button as usize] = state;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/cores/nes/src/cpu/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | mod inst;
10 | use common::numutil::{NumExt, U16Ext};
11 | use modular_bitfield::bitfield;
12 |
13 | use self::inst::Inst;
14 | use crate::Nes;
15 |
16 | #[bitfield]
17 | #[repr(u8)]
18 | #[derive(Debug, Clone, Copy, Default)]
19 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20 | pub struct CpuStatus {
21 | carry: bool,
22 | zero: bool,
23 | interrupt_disable: bool,
24 | demimal_mode: bool,
25 | break_cmd: bool,
26 | overflow: bool,
27 | negative: bool,
28 | eight: bool,
29 | }
30 |
31 | impl CpuStatus {
32 | fn set_zn(&mut self, value: u8) {
33 | self.set_zero(value == 0);
34 | self.set_negative(value.is_bit(7));
35 | }
36 |
37 | fn set_znc(&mut self, value: u16) {
38 | self.set_zero(value.u8() == 0);
39 | self.set_negative(value.is_bit(7));
40 | self.set_carry(value & 0xFF != 0);
41 | }
42 | }
43 |
44 | #[derive(Default)]
45 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
46 | pub struct Cpu {
47 | pub pc: u16,
48 | regs: [u8; 4],
49 | status: CpuStatus,
50 | }
51 |
52 | impl Cpu {
53 | pub fn exec_next_inst(nes: &mut Nes) {
54 | let inst = nes.read_imm();
55 | inst::execute(nes, Inst(inst));
56 | }
57 |
58 | pub fn trigger_int(nes: &mut Nes) {
59 | nes.push(nes.cpu.pc.low());
60 | nes.push(nes.cpu.pc.high());
61 | nes.push(nes.cpu.status.into());
62 | nes.cpu.status.set_break_cmd(true);
63 | nes.cpu.pc = 0xFFFE;
64 | }
65 |
66 | pub fn get(&self, reg: Reg) -> u8 {
67 | self.regs[reg as usize]
68 | }
69 |
70 | pub fn set(&mut self, reg: Reg, value: u8) {
71 | self.regs[reg as usize] = value;
72 | }
73 | }
74 |
75 | #[derive(Copy, Clone)]
76 | pub enum Reg {
77 | A,
78 | X,
79 | Y,
80 | S,
81 | }
82 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/src/input/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use std::{collections::HashMap, fmt::Display};
10 |
11 | use eframe::egui;
12 | pub use file_dialog::File;
13 | use gamegirl::{
14 | common::common::input::Button::*,
15 | frontend::input::{
16 | InputAction::{self, *},
17 | InputSource, Key,
18 | },
19 | };
20 |
21 | pub mod file_dialog;
22 | mod hotkeys;
23 |
24 | pub use hotkeys::HOTKEYS;
25 |
26 | #[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
27 | pub struct EguiKey(egui::Key);
28 |
29 | impl From for EguiKey {
30 | fn from(value: egui::Key) -> Self {
31 | Self(value)
32 | }
33 | }
34 |
35 | impl Display for EguiKey {
36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 | write!(f, "{:?}", self.0)
38 | }
39 | }
40 |
41 | impl Key for EguiKey {
42 | fn is_escape(self) -> bool {
43 | self.0 == egui::Key::Escape
44 | }
45 |
46 | fn default_map() -> HashMap, InputAction> {
47 | HashMap::from([
48 | (InputSource::Key(Self(egui::Key::X)), Button(A)),
49 | (InputSource::Key(Self(egui::Key::Z)), Button(B)),
50 | (InputSource::Key(Self(egui::Key::Enter)), Button(Start)),
51 | (InputSource::Key(Self(egui::Key::Space)), Button(Select)),
52 | (InputSource::Key(Self(egui::Key::ArrowDown)), Button(Down)),
53 | (InputSource::Key(Self(egui::Key::ArrowUp)), Button(Up)),
54 | (InputSource::Key(Self(egui::Key::ArrowLeft)), Button(Left)),
55 | (InputSource::Key(Self(egui::Key::ArrowRight)), Button(Right)),
56 | (InputSource::Key(Self(egui::Key::A)), Button(L)),
57 | (InputSource::Key(Self(egui::Key::S)), Button(R)),
58 | (InputSource::Key(Self(egui::Key::R)), Hotkey(4)),
59 | ])
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/src/input/hotkeys.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use super::file_dialog;
10 | use crate::app::App;
11 |
12 | type HotkeyFn = fn(&mut App, bool);
13 | pub const HOTKEYS: &[(&str, HotkeyFn)] = &[
14 | ("Open ROM", |a, p| {
15 | pressed(a, p, |app| {
16 | file_dialog::open_rom(app.message_channel.0.clone())
17 | })
18 | }),
19 | ("Reset", |a, p| pressed(a, p, App::reset)),
20 | ("Pause", |a, p| pressed(a, p, App::pause)),
21 | ("Save", |a, p| pressed(a, p, |app| app.save_game())),
22 | ("Fast Forward (Hold)", |app, pressed| {
23 | let mut core = app.core.lock().unwrap();
24 | let c = core.c_mut();
25 | if pressed {
26 | c.options.speed_multiplier = app.state.options.rewinder.ff_hold_speed;
27 | } else {
28 | c.options.speed_multiplier = 1;
29 | }
30 | c.video_buffer.frameskip = c.options.speed_multiplier - 1;
31 | }),
32 | ("Fast Forward (Toggle)", |a, p| {
33 | pressed(a, p, |app| {
34 | let mut core = app.core.lock().unwrap();
35 | let c = core.c_mut();
36 |
37 | app.fast_forward_toggled = !app.fast_forward_toggled;
38 | if app.fast_forward_toggled {
39 | c.options.speed_multiplier = app.state.options.rewinder.ff_toggle_speed;
40 | } else {
41 | c.options.speed_multiplier = 1;
42 | }
43 | c.video_buffer.frameskip = c.options.speed_multiplier - 1;
44 | });
45 | }),
46 | ("Rewind (Hold)", |app, pressed| {
47 | app.rewinder.rewinding = pressed;
48 | app.core
49 | .lock()
50 | .unwrap()
51 | .c_mut()
52 | .options
53 | .invert_audio_samples = pressed;
54 | }),
55 | ];
56 |
57 | fn pressed(app: &mut App, pressed: bool, inner: fn(&mut App)) {
58 | if pressed {
59 | inner(app);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/common/src/serialize.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use alloc::vec::Vec;
10 |
11 | use bincode::config;
12 |
13 | /// Serialize an object that can be loaded with [deserialize].
14 | /// It is (optionally zstd-compressed) bincode.
15 | #[cfg(feature = "zstd")]
16 | pub fn serialize(thing: &T, with_zstd: bool) -> Vec {
17 | if with_zstd {
18 | let mut dest = Vec::new();
19 | let mut writer = zstd::stream::Encoder::new(&mut dest, 3).unwrap();
20 | bincode::serde::encode_into_std_write(thing, &mut writer, config::legacy()).unwrap();
21 | writer.finish().unwrap();
22 | dest
23 | } else {
24 | bincode::serde::encode_to_vec(thing, config::legacy()).unwrap()
25 | }
26 | }
27 |
28 | /// Deserialize an object that was made with [serialize].
29 | /// It is (optionally zstd-compressed) bincode.
30 | #[cfg(feature = "zstd")]
31 | pub fn deserialize(state: &[u8], with_zstd: bool) -> T {
32 | if with_zstd {
33 | let mut decoder = zstd::stream::Decoder::new(state).unwrap();
34 | bincode::serde::decode_from_std_read(&mut decoder, config::legacy()).unwrap()
35 | } else {
36 | bincode::serde::decode_from_slice(state, config::legacy())
37 | .unwrap()
38 | .0
39 | }
40 | }
41 |
42 | /// Serialize an object that can be loaded with [deserialize].
43 | /// It is (optionally zstd-compressed) bincode.
44 | #[cfg(not(feature = "zstd"))]
45 | pub fn serialize(thing: &T, _with_zstd: bool) -> Vec {
46 | bincode::serde::encode_to_vec(thing, config::legacy()).unwrap()
47 | }
48 |
49 | /// Deserialize an object that was made with [serialize].
50 | /// It is (optionally zstd-compressed) bincode.
51 | #[cfg(not(feature = "zstd"))]
52 | pub fn deserialize(state: &[u8], with_zstd: bool) -> T {
53 | bincode::serde::decode_from_slice(state, config::legacy()).unwrap()
54 | }
55 |
--------------------------------------------------------------------------------
/frontends/gamegirl-egui/src/gui/input.rs:
--------------------------------------------------------------------------------
1 | use egui::{Button, Context, Frame, Margin, RichText, Sense, Ui};
2 | use gamegirl::common::{common::input, Core};
3 |
4 | use crate::App;
5 |
6 | pub fn render(app: &mut App, ctx: &Context) {
7 | let mut core = app.core.lock().unwrap();
8 | button_win(ctx, &mut core, input::Button::A, "A");
9 | button_win(ctx, &mut core, input::Button::B, "B");
10 | button_win(ctx, &mut core, input::Button::L, "L");
11 | button_win(ctx, &mut core, input::Button::R, "R");
12 | button_win(ctx, &mut core, input::Button::Start, "START");
13 | button_win(ctx, &mut core, input::Button::Select, "SELECT");
14 |
15 | egui::Window::new("dpad")
16 | .title_bar(false)
17 | .resizable(false)
18 | .show(ctx, |ui| {
19 | egui::Grid::new("dpadgrid").show(ui, |ui| {
20 | ui.label("");
21 | button_ui(ui, &mut core, input::Button::Up, "^");
22 | ui.label("");
23 | ui.end_row();
24 |
25 | button_ui(ui, &mut core, input::Button::Left, "<");
26 | ui.label("");
27 | button_ui(ui, &mut core, input::Button::Right, ">");
28 | ui.end_row();
29 |
30 | ui.label("");
31 | button_ui(ui, &mut core, input::Button::Down, "v");
32 | ui.label("");
33 | ui.end_row();
34 | });
35 | });
36 | }
37 |
38 | fn button_win(ctx: &Context, core: &mut Box, button: input::Button, text: &str) {
39 | egui::Window::new(text)
40 | .title_bar(false)
41 | .resizable(false)
42 | .frame(Frame::window(&ctx.style()).inner_margin(Margin::same(10)))
43 | .show(ctx, |ui| button_ui(ui, core, button, text));
44 | }
45 |
46 | fn button_ui(ui: &mut Ui, core: &mut Box, button: input::Button, text: &str) {
47 | ui.spacing_mut().button_padding = [10.0, 10.0].into();
48 | let btn = ui.add(
49 | Button::new(RichText::new(text).size(40.0))
50 | .corner_radius(50.0)
51 | .sense(Sense::drag())
52 | .min_size([60.0; 2].into()),
53 | );
54 |
55 | if btn.drag_stopped() {
56 | core.c_mut().input.set(0, button, false);
57 | }
58 | if btn.drag_started() {
59 | core.c_mut().input.set(0, button, true);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/src/config.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs::{self, File},
3 | io::{BufReader, BufWriter},
4 | };
5 |
6 | use gamegirl::{
7 | SystemConfig,
8 | frontend::{filter::Blend, input::Input, rewinder::RewinderConfig},
9 | };
10 |
11 | use crate::gui::input::GtkKey;
12 |
13 | /// User-configurable options.
14 | #[derive(serde::Deserialize, serde::Serialize)]
15 | pub struct Options {
16 | /// Options passed to the system when loading a ROM.
17 | pub sys: SystemConfig,
18 | /// Input configuration.
19 | pub input: Input,
20 | /// Rewinder configuration.
21 | pub rewind: RewinderConfig,
22 | /// Texture filter to use.
23 | pub texture_filter: TextureFilter,
24 | /// Blending filter to use.
25 | pub blend_filter: Blend,
26 | /// If aspect ration should be preserved.
27 | pub preserve_aspect_ratio: bool,
28 | }
29 |
30 | impl Options {
31 | pub fn empty() -> Self {
32 | Self {
33 | sys: Default::default(),
34 | input: Input::new(),
35 | rewind: RewinderConfig::default(),
36 | texture_filter: TextureFilter::Nearest,
37 | blend_filter: Blend::None,
38 | preserve_aspect_ratio: true,
39 | }
40 | }
41 |
42 | pub fn from_disk() -> Self {
43 | let path = dirs::config_dir().unwrap().join("gamegirl/config.bin");
44 | let data = File::open(&path).ok().and_then(|file| {
45 | bincode::serde::decode_from_reader(&mut BufReader::new(file), bincode::config::legacy())
46 | .ok()
47 | });
48 | data.unwrap_or_else(|| Self::empty())
49 | }
50 |
51 | pub fn to_disk(&self) {
52 | let path = dirs::config_dir().unwrap().join("gamegirl/config.bin");
53 | fs::create_dir(&path.parent().unwrap()).ok();
54 | File::create(path).ok().and_then(|file| {
55 | bincode::serde::encode_into_std_write(
56 | self,
57 | &mut BufWriter::new(file),
58 | bincode::config::legacy(),
59 | )
60 | .ok()
61 | });
62 | }
63 | }
64 |
65 | impl Default for Options {
66 | fn default() -> Self {
67 | Self::from_disk()
68 | }
69 | }
70 |
71 | #[derive(Copy, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
72 | pub enum TextureFilter {
73 | Nearest,
74 | Linear,
75 | Trilinear,
76 | }
77 |
--------------------------------------------------------------------------------
/cores/gga/src/addr.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | // General
10 | pub const IE: u32 = 0x200;
11 | pub const IF: u32 = 0x202;
12 | pub const WAITCNT: u32 = 0x204;
13 | pub const HALTCNT: u32 = 0x301;
14 | pub const IME: u32 = 0x208;
15 | pub const POSTFLG: u32 = 0x300;
16 |
17 | // PPU
18 | pub const DISPCNT: u32 = 0x0;
19 | pub const GREENSWAP: u32 = 0x2;
20 | pub const DISPSTAT: u32 = 0x4;
21 | pub const VCOUNT: u32 = 0x6;
22 | pub const BG0CNT: u32 = 0x8;
23 | pub const BG1CNT: u32 = 0xA;
24 | pub const BG2CNT: u32 = 0xC;
25 | pub const BG3CNT: u32 = 0xE;
26 | pub const BG0HOFS: u32 = 0x10;
27 | pub const BG0VOFS: u32 = 0x12;
28 | pub const BG3VOFS: u32 = 0x1E;
29 | pub const BG2PA: u32 = 0x20;
30 | pub const BG2PB: u32 = 0x22;
31 | pub const BG2PC: u32 = 0x24;
32 | pub const BG2PD: u32 = 0x26;
33 | pub const BG2XL: u32 = 0x28;
34 | pub const BG2XH: u32 = 0x2A;
35 | pub const BG2YL: u32 = 0x2C;
36 | pub const BG2YH: u32 = 0x2E;
37 | pub const BG3PA: u32 = 0x30;
38 | pub const WIN0H: u32 = 0x40;
39 | pub const WIN1H: u32 = 0x42;
40 | pub const WIN0V: u32 = 0x44;
41 | pub const WIN1V: u32 = 0x46;
42 | pub const WININ: u32 = 0x48;
43 | pub const WINOUT: u32 = 0x4A;
44 | pub const MOSAIC: u32 = 0x4C;
45 | pub const BLDCNT: u32 = 0x50;
46 | pub const BLDALPHA: u32 = 0x52;
47 | pub const BLDY: u32 = 0x54;
48 |
49 | // Input
50 | pub const KEYINPUT: u32 = 0x130;
51 | pub const KEYCNT: u32 = 0x132;
52 |
53 | // Timers
54 | pub const TM0CNT_L: u32 = 0x100;
55 | pub const TM1CNT_L: u32 = 0x104;
56 | pub const TM2CNT_L: u32 = 0x108;
57 | pub const TM3CNT_L: u32 = 0x10C;
58 | pub const TM0CNT_H: u32 = 0x102;
59 | pub const TM1CNT_H: u32 = 0x106;
60 | pub const TM2CNT_H: u32 = 0x10A;
61 | pub const TM3CNT_H: u32 = 0x10E;
62 |
63 | // Audio
64 | pub const SOUNDCNT_H: u32 = 0x82;
65 | pub const SOUNDBIAS_L: u32 = 0x88;
66 | pub const FIFO_A_L: u32 = 0xA0;
67 | pub const FIFO_A_H: u32 = 0xA2;
68 | pub const FIFO_B_L: u32 = 0xA4;
69 | pub const FIFO_B_H: u32 = 0xA6;
70 |
71 | // Serial
72 | pub const SIOCNT: u32 = 0x128;
73 | pub const RCNT: u32 = 0x134;
74 |
--------------------------------------------------------------------------------
/cores/gga/src/audio/psg/envelope.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | #[derive(Default)]
10 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
11 | pub struct EnvelopGenerator {
12 | starting_volume: u8,
13 | current_volume: u8,
14 | sweep_increase: bool,
15 | period: u8,
16 |
17 | envelope_can_run: bool,
18 |
19 | counter: u8,
20 | }
21 |
22 | impl EnvelopGenerator {
23 | pub fn write_envelope_register(&mut self, data: u8) {
24 | // TODO: is initial volume different?
25 | self.starting_volume = data >> 4;
26 | self.current_volume = self.starting_volume;
27 | self.sweep_increase = (data >> 3) & 1 == 1;
28 | self.period = data & 7;
29 | self.counter = self.period;
30 | }
31 |
32 | pub fn read_envelope_register(&self) -> u8 {
33 | ((self.starting_volume & 0xF) << 4) | ((self.sweep_increase as u8) << 3) | (self.period & 7)
34 | }
35 |
36 | pub fn current_volume(&self) -> u8 {
37 | self.current_volume
38 | }
39 |
40 | pub fn clock(&mut self) {
41 | self.counter = self.counter.saturating_sub(1);
42 |
43 | if self.counter == 0 {
44 | self.counter = self.period;
45 | if self.counter == 0 {
46 | self.counter = 8;
47 | }
48 |
49 | if self.envelope_can_run && self.period != 0 {
50 | if self.sweep_increase {
51 | if self.current_volume < 15 {
52 | self.current_volume += 1;
53 | }
54 | } else {
55 | self.current_volume = self.current_volume.saturating_sub(1);
56 | }
57 |
58 | if self.current_volume == 0 || self.current_volume == 15 {
59 | self.envelope_can_run = false;
60 | }
61 | }
62 | }
63 | }
64 |
65 | pub fn trigger(&mut self) {
66 | self.counter = self.period;
67 | self.current_volume = self.starting_volume;
68 | self.envelope_can_run = true;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/cores/ggc/src/io/apu/envelope.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | #[derive(Default)]
10 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
11 | pub struct EnvelopGenerator {
12 | starting_volume: u8,
13 | current_volume: u8,
14 | sweep_increase: bool,
15 | period: u8,
16 |
17 | envelope_can_run: bool,
18 |
19 | counter: u8,
20 | }
21 |
22 | impl EnvelopGenerator {
23 | pub fn write_envelope_register(&mut self, data: u8) {
24 | // TODO: is initial volume different?
25 | self.starting_volume = data >> 4;
26 | self.current_volume = self.starting_volume;
27 | self.sweep_increase = (data >> 3) & 1 == 1;
28 | self.period = data & 7;
29 | self.counter = self.period;
30 | }
31 |
32 | pub fn read_envelope_register(&self) -> u8 {
33 | ((self.starting_volume & 0xF) << 4) | ((self.sweep_increase as u8) << 3) | (self.period & 7)
34 | }
35 |
36 | pub fn current_volume(&self) -> u8 {
37 | self.current_volume
38 | }
39 |
40 | pub fn clock(&mut self) {
41 | self.counter = self.counter.saturating_sub(1);
42 |
43 | if self.counter == 0 {
44 | self.counter = self.period;
45 | if self.counter == 0 {
46 | self.counter = 8;
47 | }
48 |
49 | if self.envelope_can_run && self.period != 0 {
50 | if self.sweep_increase {
51 | if self.current_volume < 15 {
52 | self.current_volume += 1;
53 | }
54 | } else {
55 | self.current_volume = self.current_volume.saturating_sub(1);
56 | }
57 |
58 | if self.current_volume == 0 || self.current_volume == 15 {
59 | self.envelope_can_run = false;
60 | }
61 | }
62 | }
63 | }
64 |
65 | pub fn trigger(&mut self) {
66 | self.counter = self.period;
67 | self.current_volume = self.starting_volume;
68 | self.envelope_can_run = true;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/common/src/macros.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | /// A macro that can be used by systems to implement common functions
10 | /// in a generic manner, as part of the system trait.
11 | /// This macro simply grew as it became clear that some functionality
12 | /// is easily shared between systems.
13 | #[macro_export]
14 | macro_rules! common_functions {
15 | ($clock:expr, $pause_event:expr, $size:expr) => {
16 | fn advance_delta(&mut self, delta: f32) {
17 | if !self.c.debugger.running {
18 | return;
19 | }
20 |
21 | let target =
22 | ($clock as f32 * delta * self.c.options.speed_multiplier as f32) as ::common::TimeS;
23 | self.scheduler.schedule($pause_event, target);
24 |
25 | self.c.in_tick = true;
26 | while self.c.debugger.running && self.c.in_tick {
27 | self.advance();
28 | }
29 |
30 | if self.c.audio_buffer.input[0].len() > 100_000 {
31 | self.c.audio_buffer.input[0].truncate(100);
32 | self.c.audio_buffer.input[1].truncate(100);
33 | }
34 | }
35 |
36 | #[cfg(feature = "serde")]
37 | fn save_state(&mut self) -> ::alloc::vec::Vec {
38 | ::common::serialize::serialize(self, self.c.config.compress_savestates)
39 | }
40 |
41 | #[cfg(feature = "serde")]
42 | fn load_state(&mut self, state: &[u8]) {
43 | let old_self = ::core::mem::replace(
44 | self,
45 | ::common::serialize::deserialize(state, self.c.config.compress_savestates),
46 | );
47 | self.restore_from(old_self);
48 | }
49 |
50 | fn get_time(&self) -> ::common::Time {
51 | self.scheduler.now()
52 | }
53 |
54 | fn screen_size(&self) -> [usize; 2] {
55 | $size
56 | }
57 |
58 | fn c(&self) -> &::common::Common {
59 | &self.c
60 | }
61 |
62 | fn c_mut(&mut self) -> &mut ::common::Common {
63 | &mut self.c
64 | }
65 | };
66 | }
67 |
--------------------------------------------------------------------------------
/cores/ggc/src/io/scheduling.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{components::scheduler::Kind, TimeS};
10 | use GGEvent::*;
11 |
12 | use super::joypad::Joypad;
13 | use crate::{
14 | io::{dma, dma::Hdma, ppu::Ppu},
15 | GameGirl,
16 | };
17 |
18 | /// All scheduler events on the GG.
19 | #[derive(Copy, Clone, Eq, PartialEq, Default)]
20 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
21 | #[repr(u16)]
22 | pub enum GGEvent {
23 | /// Pause the emulation. Used by `advance_delta` to advance by a certain
24 | /// amount.
25 | #[default]
26 | PauseEmulation,
27 | /// Update button inputs.
28 | UpdateKeypad,
29 | /// An event handled by the PPU.
30 | PpuEvent(PpuEvent),
31 | /// A DMA transfer completion.
32 | DMAFinish,
33 | /// Advance HDMA transfer.
34 | HdmaTransferStep,
35 | /// A GDMA transfer.
36 | GdmaTransfer,
37 | }
38 |
39 | impl GGEvent {
40 | /// Handle the event by delegating to the appropriate handler.
41 | pub fn dispatch(&self, gg: &mut GameGirl, late_by: TimeS) {
42 | match self {
43 | PauseEmulation => gg.c.in_tick = false,
44 | UpdateKeypad => Joypad::update(gg),
45 | PpuEvent(evt) => Ppu::handle_event(gg, *evt, late_by),
46 | DMAFinish => dma::do_oam_dma(gg),
47 | HdmaTransferStep => Hdma::handle_hdma(gg),
48 | GdmaTransfer => Hdma::handle_gdma(gg),
49 | }
50 | }
51 | }
52 |
53 | impl Kind for GGEvent {}
54 |
55 | /// Events the PPU generates.
56 | #[derive(Copy, Clone, Eq, PartialEq)]
57 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
58 | #[repr(u16)]
59 | pub enum PpuEvent {
60 | OamScanEnd,
61 | UploadEnd,
62 | HblankEnd,
63 | VblankEnd,
64 | // This happens a little after HBlank end
65 | LYIncrement,
66 | }
67 |
68 | impl PpuEvent {
69 | pub(crate) fn ordinal(self) -> u8 {
70 | // ehhh
71 | match self {
72 | PpuEvent::HblankEnd => 0,
73 | PpuEvent::VblankEnd => 1,
74 | PpuEvent::OamScanEnd => 2,
75 | PpuEvent::UploadEnd => 3,
76 | PpuEvent::LYIncrement => panic!("Not applicable!"),
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/cores/gga/src/tests/mod.rs:
--------------------------------------------------------------------------------
1 | use alloc::{boxed::Box, string::ToString};
2 |
3 | use common::testing::{self, TestStatus};
4 |
5 | use crate::GameGirlAdv;
6 |
7 | #[test]
8 | fn jsmolka_arm() {
9 | testing::run_test(include_bytes!("jsmolka/arm.gba"), inspect_jsmolka);
10 | }
11 |
12 | #[test]
13 | fn jsmolka_bios() {
14 | testing::run_test(include_bytes!("jsmolka/bios.gba"), inspect_jsmolka);
15 | }
16 |
17 | #[test]
18 | fn jsmolka_memory() {
19 | testing::run_test(include_bytes!("jsmolka/memory.gba"), inspect_jsmolka);
20 | }
21 |
22 | #[test]
23 | fn jsmolka_nes() {
24 | testing::run_test(include_bytes!("jsmolka/nes.gba"), inspect_jsmolka);
25 | }
26 |
27 | #[test]
28 | fn jsmolka_thumb() {
29 | testing::run_test(include_bytes!("jsmolka/thumb.gba"), inspect_jsmolka);
30 | }
31 |
32 | #[test]
33 | fn jsmolka_unsafe() {
34 | testing::run_test(include_bytes!("jsmolka/unsafe.gba"), inspect_jsmolka);
35 | }
36 |
37 | #[test]
38 | fn fuzzarm_arm_any() {
39 | testing::run_test(include_bytes!("fuzzarm/ARM_Any.gba"), inspect_fuzzarm);
40 | }
41 |
42 | #[test]
43 | fn fuzzarm_arm_dataprocessing() {
44 | testing::run_test(
45 | include_bytes!("fuzzarm/ARM_DataProcessing.gba"),
46 | inspect_fuzzarm,
47 | );
48 | }
49 |
50 | #[test]
51 | fn fuzzarm_all() {
52 | testing::run_test(include_bytes!("fuzzarm/FuzzARM.gba"), inspect_fuzzarm);
53 | }
54 |
55 | #[test]
56 | fn fuzzarm_thumb_any() {
57 | testing::run_test(include_bytes!("fuzzarm/THUMB_Any.gba"), inspect_fuzzarm);
58 | }
59 |
60 | #[test]
61 | fn fuzzarm_thumb_dataprocessing() {
62 | testing::run_test(
63 | include_bytes!("fuzzarm/THUMB_DataProcessing.gba"),
64 | inspect_fuzzarm,
65 | );
66 | }
67 |
68 | fn inspect_jsmolka(gg: &mut Box) -> TestStatus {
69 | let hash = testing::screen_hash(gg);
70 | let regs = &gg.cpu.state.registers;
71 |
72 | if regs[13] == 0x03008014 {
73 | let ones = regs[10];
74 | let tens = regs[9];
75 | let hundreds = regs[8];
76 | let test = ones + (tens * 10) + (hundreds * 100);
77 | TestStatus::FailedAt(test.to_string())
78 | } else if [
79 | 0x20974E0091874964,
80 | 0x94F4D344B975EB0C,
81 | 0x1A8992654BCDC4D8,
82 | 0x63E68B6E5115B556,
83 | ]
84 | .contains(&hash)
85 | {
86 | TestStatus::Success
87 | } else {
88 | TestStatus::Running
89 | }
90 | }
91 |
92 | pub fn inspect_fuzzarm(gg: &mut Box) -> TestStatus {
93 | if testing::screen_hash(gg) == 0xD5170621BA472629 {
94 | TestStatus::Success
95 | } else {
96 | TestStatus::Running
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/gui/file_dialog.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use std::{future::Future, path::PathBuf, sync::mpsc};
10 |
11 | use rfd::FileHandle;
12 |
13 | use crate::app::Message;
14 |
15 | /// A file picked by the user.
16 | pub struct File {
17 | /// File content in bytes
18 | pub content: Vec,
19 | }
20 |
21 | /// Open a file dialog. This operation is async and returns immediately,
22 | /// sending a [Message] once the user has picked a file.
23 | pub fn open_rom(sender: mpsc::Sender) {
24 | let task = rfd::AsyncFileDialog::new()
25 | .add_filter("GameGirl games", &["gb", "gbc", "gba", "nds", "elf", "iso"])
26 | .pick_file();
27 |
28 | execute(async move {
29 | let file = task.await;
30 | if let Some(file) = file {
31 | let content = file.read().await;
32 | sender.send(Message::RomOpen(File { content })).ok();
33 | }
34 | });
35 | }
36 |
37 | /// Open a file dialog. This operation is async and returns immediately,
38 | /// sending a [Message] once the user has picked a file.
39 | pub fn open_replay(sender: mpsc::Sender) {
40 | let task = rfd::AsyncFileDialog::new()
41 | .add_filter("GameGirl replays", &["rpl"])
42 | .pick_file();
43 |
44 | execute(async move {
45 | let file = task.await;
46 | if let Some(file) = file {
47 | let content = file.read().await;
48 | sender.send(Message::ReplayOpen(File { content })).ok();
49 | }
50 | });
51 | }
52 |
53 | /// Open a file dialog. This operation is async and returns immediately,
54 | /// sending a [Message] once the user has picked a file.
55 | pub fn open_core(sender: mpsc::Sender) {
56 | let task = rfd::AsyncFileDialog::new()
57 | .add_filter("GameGirl cores", &["so"])
58 | .pick_file();
59 |
60 | execute(async move {
61 | let file = task.await;
62 | if let Some(file) = file {
63 | let path = path(&file);
64 | sender.send(Message::CoreOpen(path.unwrap())).ok();
65 | }
66 | });
67 | }
68 |
69 | fn path(f: &FileHandle) -> Option {
70 | Some(f.path().to_path_buf())
71 | }
72 |
73 | fn execute + Send + 'static>(f: F) {
74 | std::thread::spawn(move || futures_executor::block_on(f));
75 | }
76 |
--------------------------------------------------------------------------------
/cores/gga/src/ppu/render/palette.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{
10 | numutil::{hword, NumExt},
11 | Colour,
12 | };
13 |
14 | use super::{xy2dw, PpuRender};
15 | use crate::ppu::PaletteMode;
16 |
17 | impl PpuRender {
18 | /// Given a tile address and tile pixel, get the palette.
19 | pub(super) fn get_palette(
20 | &self,
21 | bank: u8,
22 | mode: PaletteMode,
23 | addr: u32,
24 | x: u32,
25 | y: u32,
26 | ) -> Option {
27 | Some(match mode {
28 | PaletteMode::Palettes16 => {
29 | let addr = addr.us() + xy2dw(x.us() / 2, y.us(), 4);
30 | if addr >= self.vram.len() {
31 | return None;
32 | }
33 | let value = self.vram[addr];
34 | let colour = if x.is_bit(0) { value >> 4 } else { value & 0xF };
35 | if colour == 0 {
36 | return None;
37 | }
38 | (bank * 0x10) + colour
39 | }
40 | PaletteMode::Single256 => {
41 | let addr = addr.us() + xy2dw(x.us(), y.us(), 8);
42 | if addr >= self.vram.len() {
43 | return None;
44 | }
45 | let pal = self.vram[addr];
46 | if pal == 0 {
47 | return None;
48 | }
49 | pal
50 | }
51 | })
52 | }
53 |
54 | /// Turn a halfword in VRAM into a 5bit colour.
55 | pub fn hword_to_colour_vram(&self, addr: usize) -> Colour {
56 | let lo = self.vram[addr];
57 | let hi = self.vram[addr + 1];
58 | Self::hword_to_colour(hword(lo, hi))
59 | }
60 |
61 | /// Turn a palette index (0-255) into a colour.
62 | pub fn idx_to_palette(&self, idx: u8) -> Colour {
63 | let addr = (idx.us() << 1) + (OBJ as usize * 0x200);
64 | let lo = self.palette[addr];
65 | let hi = self.palette[addr + 1];
66 | Self::hword_to_colour(hword(lo, hi))
67 | }
68 |
69 | /// Extract a 5bit colour from a halfword.
70 | fn hword_to_colour(hword: u16) -> Colour {
71 | let r = hword.bits(0, 5).u8();
72 | let g = hword.bits(5, 5).u8();
73 | let b = hword.bits(10, 5).u8();
74 | [r, g, b, 255]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/cores/gga/src/scheduling.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::{components::scheduler::Kind, TimeS};
10 | use AdvEvent::*;
11 |
12 | use crate::{
13 | audio::{psg::GenApuEvent, Apu},
14 | cpu::GgaFullBus,
15 | hw::timer::Timers,
16 | ppu::Ppu,
17 | };
18 |
19 | /// All scheduler events on the GGA.
20 | #[derive(Copy, Clone, Eq, PartialEq, Default)]
21 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
22 | pub enum AdvEvent {
23 | /// Pause the emulation. Used by `advance_delta` to advance by a certain
24 | /// amount.
25 | #[default]
26 | PauseEmulation,
27 | /// Update button inputs.
28 | UpdateKeypad,
29 | /// An event handled by the PPU.
30 | PpuEvent(PpuEvent),
31 | /// An event handled by the APU.
32 | ApuEvent(ApuEvent),
33 | /// A timer overflow.
34 | TimerOverflow(u8),
35 | }
36 |
37 | impl GgaFullBus {
38 | /// Handle the event by delegating to the appropriate handler.
39 | pub fn dispatch(&mut self, event: AdvEvent, late_by: TimeS) {
40 | match event {
41 | PauseEmulation => self.bus.c.in_tick = false,
42 | UpdateKeypad => self.check_keycnt(),
43 | PpuEvent(evt) => Ppu::handle_event(self, evt, late_by),
44 | ApuEvent(evt) => {
45 | let time = Apu::handle_event(self, evt, late_by);
46 | self.scheduler.schedule(event, time);
47 | }
48 | TimerOverflow(idx) => Timers::handle_overflow_event(self, idx, late_by),
49 | }
50 | }
51 | }
52 |
53 | impl Kind for AdvEvent {}
54 |
55 | /// Events the APU generates.
56 | #[derive(Copy, Clone, Eq, PartialEq)]
57 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
58 | pub enum ApuEvent {
59 | /// Event from the generic CGB APU.
60 | Gen(GenApuEvent),
61 | /// Tick the CGB sequencer.
62 | Sequencer,
63 | /// Push a sample to the output.
64 | PushSample,
65 | }
66 |
67 | /// Events the PPU generates.
68 | #[derive(Copy, Clone, Eq, PartialEq)]
69 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
70 | pub enum PpuEvent {
71 | /// Start of HBlank.
72 | HblankStart,
73 | /// Set HBlank flag in DISPSTAT (this is delayed by 46 cycles)
74 | SetHblank,
75 | /// End of HBlank, which is the start of the next scanline.
76 | HblankEnd,
77 | }
78 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/tests/gb.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use gamegirl::common::common::debugger::Width;
10 |
11 | use crate::testsuite::{TestStatus, TestSuite};
12 |
13 | pub fn blargg() -> TestSuite {
14 | TestSuite::new("blargg", 15, |gg| {
15 | let screen = TestSuite::screen_hash(gg);
16 | let serial = &gg.c().debugger.serial_output;
17 |
18 | // 2 tests don't properly write to serial
19 | if serial.contains("Passed") || [0xC595AEECEFF2C241, 0x115124ABCB508E19].contains(&screen) {
20 | TestStatus::Success
21 | } else if serial.contains("Failed") {
22 | TestStatus::FailedAt(serial.lines().last().unwrap().to_string())
23 | } else {
24 | TestStatus::Running
25 | }
26 | })
27 | }
28 |
29 | pub fn blargg_sound() -> TestSuite {
30 | TestSuite::new("blargg_sound", 30, |gg| {
31 | if gg.get_memory(0xA000, Width::Byte) == 0 {
32 | TestStatus::Success
33 | } else {
34 | TestStatus::Running
35 | }
36 | })
37 | }
38 |
39 | pub fn mooneye(subdir: &str) -> TestSuite {
40 | TestSuite::new(&format!("mooneye/{subdir}"), 10, |gg| {
41 | let regs = gg.get_registers();
42 | if regs[1] == 0x03
43 | && regs[2] == 0x05
44 | && regs[3] == 0x08
45 | && regs[4] == 0x0D
46 | && regs[6] == 0x15
47 | && regs[7] == 0x22
48 | {
49 | TestStatus::Success
50 | } else if regs[1] == 0x42 && regs[2] == 0x42 && regs[3] == 0x42 {
51 | TestStatus::Failed
52 | } else {
53 | TestStatus::Running
54 | }
55 | })
56 | }
57 |
58 | pub fn gbmicrotest() -> TestSuite {
59 | TestSuite::new("c-sp/gbmicrotest", 5, |gg| {
60 | if gg.get_memory(0xFF82, Width::Byte) == 0x01 {
61 | TestStatus::Success
62 | } else if gg.get_memory(0xFF82, Width::Byte) == 0xFF {
63 | TestStatus::Failed
64 | } else {
65 | TestStatus::Running
66 | }
67 | })
68 | }
69 |
70 | pub fn acid2() -> TestSuite {
71 | TestSuite::new("acid2", 5, |gg| {
72 | let hash = TestSuite::screen_hash(gg);
73 | if [0xB60125B2D40BCBD9, 0xD0F0889F5971A43E].contains(&hash) {
74 | TestStatus::Success
75 | } else {
76 | TestStatus::Running
77 | }
78 | })
79 | }
80 |
--------------------------------------------------------------------------------
/cores/psx/src/cpu/cop0.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::numutil::NumExt;
10 |
11 | use crate::{
12 | cpu::inst::{Inst, InstructionHandler},
13 | PlayStation,
14 | };
15 |
16 | type CopLut = [InstructionHandler; 32];
17 | const COP0: CopLut = PlayStation::cop0_table();
18 |
19 | #[derive(Default)]
20 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
21 | pub struct Cop0 {
22 | pub(crate) sr: u32,
23 | pub(crate) cause: u32,
24 | pub(crate) epc: u32,
25 | }
26 |
27 | impl PlayStation {
28 | const fn cop0_table() -> CopLut {
29 | let mut lut: CopLut = [Self::cop0_inst; 32];
30 | lut[0x00] = Self::mfc0;
31 | lut[0x02] = Self::cfc0;
32 | lut[0x04] = Self::mtc0;
33 | lut[0x06] = Self::ctc0;
34 | lut[0x08] = Self::bc0;
35 | lut[0x10] = Self::rfe;
36 | lut
37 | }
38 | }
39 |
40 | impl PlayStation {
41 | pub fn cop0(&mut self, inst: Inst) {
42 | let cop0 = inst.rs();
43 | let handler = COP0[cop0.us()];
44 | handler(self, inst);
45 | }
46 |
47 | fn mfc0(&mut self, inst: Inst) {
48 | match inst.rd() {
49 | 12 => self.cpu.set_reg(inst.rt(), self.cpu.cop0.sr),
50 | 13 => self.cpu.set_reg(inst.rt(), self.cpu.cop0.cause),
51 | 14 => self.cpu.set_reg(inst.rt(), self.cpu.cop0.epc),
52 | unknown => log::debug!("Unhandled read from COP0 register {unknown}, ignoring"),
53 | }
54 | }
55 |
56 | fn cfc0(&mut self, inst: Inst) {
57 | todo!();
58 | }
59 |
60 | fn mtc0(&mut self, inst: Inst) {
61 | match inst.rd() {
62 | 12 => self.cpu.cop0.sr = self.cpu.reg(inst.rt()),
63 | unknown => log::debug!("Unhandled write to COP0 register {unknown}, ignoring"),
64 | }
65 | }
66 |
67 | fn ctc0(&mut self, inst: Inst) {
68 | todo!();
69 | }
70 |
71 | fn bc0(&mut self, inst: Inst) {
72 | todo!();
73 | }
74 |
75 | fn rfe(&mut self, inst: Inst) {
76 | if inst.0 & 0x3F != 0x10 {
77 | log::warn!("COP0 virtual memory instruction encountered, executing as RFE");
78 | }
79 |
80 | let context = self.cpu.cop0.sr & 0x3F;
81 | self.cpu.cop0.sr &= !0x3F;
82 | self.cpu.cop0.sr |= context >> 2;
83 | }
84 |
85 | fn cop0_inst(&mut self, inst: Inst) {
86 | todo!();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/cores/nes/src/memory.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::numutil::{hword, NumExt};
10 |
11 | use crate::{cartridge::Cartridge, cpu::Reg::*, Nes};
12 |
13 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
14 | pub struct Memory {
15 | #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
16 | iram: [u8; 0x800],
17 | ppu_regs: [u8; 0x8],
18 | other_regs: [u8; 0x15],
19 | }
20 |
21 | impl Nes {
22 | pub fn read_imm(&mut self) -> u8 {
23 | let value = self.read(self.cpu.pc);
24 | self.cpu.pc += 1;
25 | value
26 | }
27 |
28 | pub fn read(&mut self, addr: u16) -> u8 {
29 | self.advance_clock(1);
30 | self.get(addr)
31 | }
32 |
33 | pub fn write(&mut self, addr: u16, value: u8) {
34 | self.advance_clock(1);
35 | self.set(addr, value);
36 | }
37 |
38 | pub fn push(&mut self, value: u8) {
39 | let stack = self.cpu.get(S);
40 | self.write(hword(0x01, stack), value);
41 | self.cpu.set(S, stack.wrapping_sub(1));
42 | self.advance_clock(1);
43 | }
44 |
45 | pub fn pop(&mut self) -> u8 {
46 | self.advance_clock(2);
47 | let stack = self.cpu.get(S).wrapping_add(1);
48 | self.cpu.set(S, stack);
49 | self.read(hword(0x01, stack))
50 | }
51 |
52 | pub fn get(&mut self, addr: u16) -> u8 {
53 | match addr {
54 | 0x0000..=0x1FFF => self.mem.iram[addr.us() & 0x7FF],
55 | 0x2000..=0x3FFF => self.mem.ppu_regs[addr.us() & 0x8],
56 | 0x4000..=0x4015 => self.mem.other_regs[addr.us() - 0x4000],
57 | 0x4016 => self.joypad.read() | 0x40,
58 | 0x4020..=0xFFFF => Cartridge::read(self, addr),
59 | _ => 0xFF,
60 | }
61 | }
62 |
63 | pub fn set(&mut self, addr: u16, value: u8) {
64 | match addr {
65 | 0x0000..=0x1FFF => self.mem.iram[addr.us() & 0x7FF] = value,
66 | 0x2000..=0x3FFF => self.mem.ppu_regs[addr.us() & 0x8] = value,
67 | 0x4000..=0x4015 => self.mem.other_regs[addr.us() - 0x4000] = value,
68 | 0x4016 => self.joypad.write(value),
69 | 0x4020..=0xFFFF => Cartridge::write(self, addr, value),
70 | _ => (),
71 | }
72 | }
73 | }
74 |
75 | impl Default for Memory {
76 | fn default() -> Self {
77 | Self {
78 | iram: [0; 0x800],
79 | ppu_regs: [0; 0x8],
80 | other_regs: [0; 0x15],
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/frontends/gamegirl-gtk/src/gui/input.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, fmt::Display};
2 |
3 | use gamegirl::{
4 | common::common::input::Button::*,
5 | frontend::input::{
6 | InputAction::{self, *},
7 | InputSource, Key,
8 | },
9 | };
10 | use gtk::gdk;
11 | use serde::{
12 | Deserialize, Serialize,
13 | de::{self, Visitor},
14 | };
15 |
16 | #[derive(Copy, Clone, PartialEq, Eq, Hash)]
17 | pub struct GtkKey(gdk::Key);
18 |
19 | impl From for GtkKey {
20 | fn from(value: gdk::Key) -> Self {
21 | Self(value)
22 | }
23 | }
24 |
25 | impl Display for GtkKey {
26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 | write!(f, "{}", self.0.name().unwrap())
28 | }
29 | }
30 |
31 | impl Key for GtkKey {
32 | fn is_escape(self) -> bool {
33 | self == gdk::Key::Escape.into()
34 | }
35 |
36 | fn default_map() -> HashMap, InputAction> {
37 | HashMap::from([
38 | (InputSource::Key(Self(gdk::Key::X)), Button(A)),
39 | (InputSource::Key(Self(gdk::Key::Z)), Button(B)),
40 | (InputSource::Key(Self(gdk::Key::Return)), Button(Start)),
41 | (InputSource::Key(Self(gdk::Key::space)), Button(Select)),
42 | (InputSource::Key(Self(gdk::Key::Down)), Button(Down)),
43 | (InputSource::Key(Self(gdk::Key::Up)), Button(Up)),
44 | (InputSource::Key(Self(gdk::Key::Left)), Button(Left)),
45 | (InputSource::Key(Self(gdk::Key::Right)), Button(Right)),
46 | (InputSource::Key(Self(gdk::Key::A)), Button(L)),
47 | (InputSource::Key(Self(gdk::Key::S)), Button(R)),
48 | (InputSource::Key(Self(gdk::Key::R)), Hotkey(4)),
49 | ])
50 | }
51 | }
52 |
53 | impl Serialize for GtkKey {
54 | fn serialize(&self, serializer: S) -> Result
55 | where
56 | S: serde::Serializer,
57 | {
58 | serializer.collect_str(&self.0.name().unwrap())
59 | }
60 | }
61 |
62 | impl<'de> Deserialize<'de> for GtkKey {
63 | fn deserialize(deserializer: D) -> Result
64 | where
65 | D: serde::Deserializer<'de>,
66 | {
67 | struct StrVisitor;
68 | impl<'de> Visitor<'de> for StrVisitor {
69 | type Value = GtkKey;
70 |
71 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
72 | formatter.write_str("String repr of key")
73 | }
74 |
75 | fn visit_string(self, v: String) -> Result
76 | where
77 | E: serde::de::Error,
78 | {
79 | Ok(GtkKey(
80 | gdk::Key::from_name(v).ok_or(de::Error::custom("Invalid key"))?,
81 | ))
82 | }
83 | }
84 | deserializer.deserialize_string(StrVisitor)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/cores/nds/src/hw/bios/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use alloc::vec::Vec;
10 |
11 | use modular_bitfield::prelude::*;
12 |
13 | // DraStic BIOS as found in MelonDS sources:
14 | // https://github.com/melonDS-emu/melonDS/tree/5eadd67df6da429891fdfba02bf650f4fefe4ab6/freebios
15 | // Thank you to it's developers!
16 | pub const FREEBIOS7: &[u8] = include_bytes!("drastic_bios_arm7.bin");
17 | pub const FREEBIOS9: &[u8] = include_bytes!("drastic_bios_arm9.bin");
18 |
19 | #[derive(Debug)]
20 | #[repr(packed)]
21 | pub struct UserSettings {
22 | pub version: u16,
23 | pub color: u8,
24 | pub birthday_month: u8,
25 | pub birthday_day: u8,
26 | pub zero1: u8,
27 | pub nickname_utf16: [u16; 10],
28 | pub nickname_len: u16,
29 | pub message_utf16: [u16; 26],
30 | pub message_len: u16,
31 | pub alarm_hour: u8,
32 | pub alarm_minute: u8,
33 | pub unused: u16,
34 | pub alarm_enable: u8,
35 | pub zero2: u8,
36 | pub touch_calibration: [u8; 12],
37 | pub language: LanguageFlags,
38 | pub year: u8,
39 | pub zero3: u8,
40 | pub rtc_offset: u32,
41 | pub ff: u32,
42 | pub update_counter: u16,
43 | pub crc16: u16,
44 | }
45 |
46 | #[bitfield]
47 | #[repr(u16)]
48 | #[derive(Debug, Default, Copy, Clone)]
49 | pub struct LanguageFlags {
50 | language: B3,
51 | gba_on_lower: bool,
52 | backlight_level: B2,
53 | autostart_cart: bool,
54 | #[skip]
55 | __: B2,
56 | settings_lost: bool,
57 | settings_okay: B6,
58 | }
59 |
60 | impl UserSettings {
61 | pub fn get_bogus() -> UserSettings {
62 | let utf16 = "leela".encode_utf16();
63 | let utf16 = utf16.collect::>();
64 | UserSettings {
65 | version: 5,
66 | color: 3,
67 | birthday_month: 11,
68 | birthday_day: 26,
69 | zero1: 0,
70 | nickname_utf16: utf16.clone().try_into().unwrap(),
71 | nickname_len: 5,
72 | message_utf16: utf16.try_into().unwrap(),
73 | message_len: 5,
74 | alarm_hour: 23,
75 | alarm_minute: 31,
76 | unused: 0,
77 | alarm_enable: 0,
78 | zero2: 0,
79 | touch_calibration: [0; 12],
80 | language: LanguageFlags::default()
81 | .with_language(1)
82 | .with_settings_okay(0x3F),
83 | year: 24,
84 | zero3: 0,
85 | rtc_offset: 0,
86 | ff: 0xFFFFFFFF,
87 | update_counter: 23,
88 | crc16: 0,
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/components/armchair/src/optimizations/jit/fields.rs:
--------------------------------------------------------------------------------
1 | use core::mem;
2 |
3 | use cranelift::prelude::*;
4 | use types::*;
5 |
6 | use super::InstructionTranslator;
7 | use crate::{
8 | interface::Bus,
9 | state::{LowRegister, Register},
10 | Cpu,
11 | };
12 |
13 | macro_rules! cpu_field {
14 | ($path:expr, $typ:expr, $getter:ident, $setter:ident) => {
15 | pub fn $getter(&mut self) -> Value {
16 | self.load_at_offset($typ, mem::offset_of!(Cpu, $path))
17 | }
18 | pub fn $setter(&mut self, value: Value) {
19 | self.store_at_offset(value, mem::offset_of!(Cpu, $path))
20 | }
21 | };
22 | }
23 |
24 | macro_rules! cpu_register {
25 | ($index:expr, $getter:ident, $setter:ident) => {
26 | pub fn $getter(&mut self) -> Value {
27 | self.load_at_offset(
28 | types::I32,
29 | mem::offset_of!(Cpu, state.registers) + $index * mem::size_of::(),
30 | )
31 | }
32 | pub fn $setter(&mut self, value: Value) {
33 | self.store_at_offset(
34 | value,
35 | mem::offset_of!(Cpu, state.registers) + $index * mem::size_of::(),
36 | )
37 | }
38 | };
39 | }
40 |
41 | impl InstructionTranslator<'_, '_, '_, S> {
42 | fn load_at_offset(&mut self, typ: Type, offset: usize) -> Value {
43 | self.builder
44 | .ins()
45 | .load(typ, MemFlags::new(), self.vals.sys, offset as i32)
46 | }
47 |
48 | fn store_at_offset(&mut self, value: Value, offset: usize) {
49 | self.builder
50 | .ins()
51 | .store(MemFlags::new(), value, self.vals.sys, offset as i32);
52 | }
53 |
54 | fn get_pointer(&mut self, offset: usize) -> Value {
55 | let offset_const = self.builder.ins().iconst(types::I64, offset as i64);
56 | self.builder.ins().iadd(self.vals.sys, offset_const)
57 | }
58 |
59 | pub fn load_reg(&mut self, reg: Register) -> Value {
60 | self.load_at_offset(
61 | types::I32,
62 | mem::offset_of!(Cpu, state.registers) + reg.0 as usize * mem::size_of::(),
63 | )
64 | }
65 |
66 | pub fn store_reg(&mut self, reg: Register, value: Value) {
67 | self.store_at_offset(
68 | value,
69 | mem::offset_of!(Cpu, state.registers) + reg.0 as usize * mem::size_of::(),
70 | )
71 | }
72 |
73 | pub fn load_lreg(&mut self, reg: LowRegister) -> Value {
74 | self.load_at_offset(
75 | types::I32,
76 | mem::offset_of!(Cpu, state.registers) + reg.0 as usize * mem::size_of::(),
77 | )
78 | }
79 |
80 | pub fn store_lreg(&mut self, reg: LowRegister, value: Value) {
81 | self.store_at_offset(
82 | value,
83 | mem::offset_of!(Cpu, state.registers) + reg.0 as usize * mem::size_of::(),
84 | )
85 | }
86 |
87 | cpu_field!(state.pipeline_valid, I8, get_valid, set_valid);
88 | cpu_field!(state.cpsr, I32, get_cpsr, set_cpsr);
89 |
90 | cpu_register!(13, load_sp, store_sp);
91 | cpu_register!(14, load_lr, store_lr);
92 | cpu_register!(15, load_pc, store_pc);
93 | }
94 |
--------------------------------------------------------------------------------
/cores/nes/src/cartridge.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use std::iter;
10 |
11 | use common::numutil::NumExt;
12 | use modular_bitfield::{
13 | bitfield,
14 | specifiers::{B2, B4},
15 | };
16 |
17 | use crate::Nes;
18 |
19 | #[bitfield]
20 | #[repr(u16)]
21 | #[derive(Debug, Clone, Copy)]
22 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
23 | pub struct INesHeader {
24 | mirror_is_vertical: bool,
25 | has_battery: bool,
26 | has_trainer: bool,
27 | ignore_mirroring: bool,
28 | mapper_lower: B4,
29 | vs_unisystem: bool,
30 | playchoice: bool,
31 | ines_2_hint: B2,
32 | mapper_higher: B4,
33 | }
34 |
35 | #[bitfield]
36 | #[repr(u16)]
37 | #[derive(Debug, Clone, Copy)]
38 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
39 | pub struct Nes2Header {
40 | mapper_higherer: B4,
41 | submapper: B4,
42 | prg_rom_size_msb: B4,
43 | chr_rom_size_msb: B4,
44 | }
45 |
46 | #[derive(Default)]
47 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
48 | pub struct Cartridge {
49 | prg_rom: Vec,
50 | prg_ram: Vec,
51 | chr_rom: Vec,
52 | mapper: Mapper,
53 | }
54 |
55 | impl Cartridge {
56 | pub fn read(nes: &Nes, addr: u16) -> u8 {
57 | match addr {
58 | 0x6000..=0x7FFF => nes.cart.prg_ram[addr.us() - 0x6000],
59 | 0x8000..=0xFFFF => {
60 | nes.cart.prg_rom[(addr.us() - 0x8000) & (nes.cart.prg_rom.len() - 1)]
61 | }
62 | _ => panic!(),
63 | }
64 | }
65 |
66 | pub fn write(_: &Nes, _: u16, _value: u8) {}
67 |
68 | pub fn from_rom(rom: Vec) -> Self {
69 | let prg_size = rom[4].us() * 16_384;
70 | let chr_size = rom[5].us() * 8_192;
71 | let header = INesHeader::from_bytes(rom[6..8].try_into().unwrap());
72 |
73 | let addr = 16 + (header.has_trainer() as usize * 512);
74 | let prg_rom = rom[addr..(addr + prg_size)].to_vec();
75 | let addr = addr + prg_size;
76 | let chr_rom = rom[addr..(addr + chr_size)].to_vec();
77 |
78 | let mapper = header.mapper_lower() | (header.mapper_higher() << 4);
79 | let mapper = match mapper {
80 | 0 => Mapper::Nrom,
81 | _ => panic!("Unknown mapper!"),
82 | };
83 |
84 | let mut cart = Self {
85 | prg_rom,
86 | prg_ram: Vec::new(),
87 | chr_rom,
88 | mapper,
89 | };
90 | cart.prg_ram.extend(iter::repeat(0).take(0x2000));
91 | cart
92 | }
93 | }
94 |
95 | #[derive(Default)]
96 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
97 | pub enum Mapper {
98 | #[default]
99 | Nrom,
100 | }
101 |
--------------------------------------------------------------------------------
/common/src/common/video.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use alloc::{collections::VecDeque, vec::Vec};
10 |
11 | use crate::Colour;
12 |
13 | /// Frame buffer for video output. Also used to implement frameskip.
14 | #[derive(Default)]
15 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16 | pub struct FrameBuffer {
17 | /// Buffer of frames to be displayed.
18 | #[cfg_attr(feature = "serde", serde(skip, default))]
19 | buffer: VecDeque>,
20 | /// Number of frames to skip before adding a frame to the buffer.
21 | pub frameskip: usize,
22 | /// Number of frames until the next frame is added to the buffer.
23 | n_until_next: usize,
24 | }
25 |
26 | impl FrameBuffer {
27 | /// Get the oldest frame in the buffer.
28 | pub fn pop(&mut self) -> Option> {
29 | self.buffer.pop_front()
30 | }
31 |
32 | /// Get the newest framen in the buffer.
33 | pub fn pop_recent(&mut self) -> Option> {
34 | self.buffer.pop_back()
35 | }
36 |
37 | /// Notify the buffer that the system is starting to render the next frame.
38 | pub fn start_next_frame(&mut self) {
39 | if self.n_until_next == 0 {
40 | self.n_until_next = self.frameskip;
41 | } else {
42 | self.n_until_next -= 1;
43 | }
44 | }
45 |
46 | /// Returns true if the current frame should be rendered, false if it is to
47 | /// be skipped.
48 | pub fn should_render_this_frame(&self) -> bool {
49 | self.frameskip == 0 || self.n_until_next == 0
50 | }
51 |
52 | /// Push a new frame to the buffer.
53 | pub fn push(&mut self, frame: Vec) {
54 | self.buffer.push_back(frame);
55 | if self.buffer.len() > 4 {
56 | self.pop(); // Drop oldest frame to prevent large buffer and
57 | // outdated frames
58 | }
59 | }
60 |
61 | /// Do we have a frame?
62 | pub fn has_frame(&self) -> bool {
63 | !self.buffer.is_empty()
64 | }
65 | }
66 |
67 | #[cfg(test)]
68 | mod test {
69 | use super::*;
70 |
71 | #[test]
72 | fn test_frameskip_0() {
73 | let mut fb = FrameBuffer::default();
74 |
75 | assert_eq!(fb.should_render_this_frame(), true);
76 | fb.start_next_frame();
77 | assert_eq!(fb.should_render_this_frame(), true);
78 | fb.start_next_frame();
79 | assert_eq!(fb.should_render_this_frame(), true);
80 | }
81 |
82 | #[test]
83 | fn test_frameskip_1() {
84 | let mut fb = FrameBuffer::default();
85 | fb.frameskip = 1;
86 |
87 | assert_eq!(fb.should_render_this_frame(), true);
88 | fb.start_next_frame();
89 | assert_eq!(fb.should_render_this_frame(), false);
90 | fb.start_next_frame();
91 | assert_eq!(fb.should_render_this_frame(), true);
92 | fb.start_next_frame();
93 | assert_eq!(fb.should_render_this_frame(), false);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/components/armchair/src/thumb/mod.rs:
--------------------------------------------------------------------------------
1 | pub use decode::ThumbInst;
2 | use decode::{Thumb1Op, Thumb2Op, Thumb3Op, Thumb4Op, ThumbStrLdrOp};
3 |
4 | use crate::{
5 | interface::{Bus, InstructionSet},
6 | memory::{Address, RelativeOffset},
7 | state::{LowRegister, Register},
8 | };
9 |
10 | pub(crate) mod decode;
11 | mod interpret;
12 | mod jit;
13 | mod trace;
14 |
15 | pub type ThumbHandler = fn(&mut C, ThumbInst);
16 | pub type ThumbInstructionSet = InstructionSet;
17 |
18 | pub const fn instruction_set() -> ThumbInstructionSet {
19 | InstructionSet {
20 | interpreter_lut: decode::get_lut_table(),
21 | cache_handler_lookup: |i| decode::get_instruction_handler(i),
22 | }
23 | }
24 |
25 | pub(crate) trait ThumbVisitor {
26 | type Output;
27 |
28 | fn thumb_unknown_opcode(&mut self, inst: ThumbInst) -> Self::Output;
29 | fn thumb_alu_imm(
30 | &mut self,
31 | kind: Thumb1Op,
32 | d: LowRegister,
33 | s: LowRegister,
34 | n: u32,
35 | ) -> Self::Output;
36 | fn thumb_2_reg(
37 | &mut self,
38 | kind: Thumb2Op,
39 | d: LowRegister,
40 | s: LowRegister,
41 | n: LowRegister,
42 | ) -> Self::Output;
43 | fn thumb_3(&mut self, kind: Thumb3Op, d: LowRegister, n: u32) -> Self::Output;
44 | fn thumb_alu(&mut self, kind: Thumb4Op, d: LowRegister, s: LowRegister) -> Self::Output;
45 | fn thumb_hi_add(&mut self, r: (Register, Register)) -> Self::Output;
46 | fn thumb_hi_cmp(&mut self, r: (Register, Register)) -> Self::Output;
47 | fn thumb_hi_mov(&mut self, r: (Register, Register)) -> Self::Output;
48 | fn thumb_hi_bx(&mut self, s: Register, blx: bool) -> Self::Output;
49 | fn thumb_ldr6(&mut self, d: LowRegister, offset: Address) -> Self::Output;
50 | fn thumb_ldrstr78(
51 | &mut self,
52 | op: ThumbStrLdrOp,
53 | d: LowRegister,
54 | b: LowRegister,
55 | o: LowRegister,
56 | ) -> Self::Output;
57 | fn thumb_ldrstr9(
58 | &mut self,
59 | op: ThumbStrLdrOp,
60 | d: LowRegister,
61 | b: LowRegister,
62 | offset: Address,
63 | ) -> Self::Output;
64 | fn thumb_ldrstr10(
65 | &mut self,
66 | str: bool,
67 | d: LowRegister,
68 | b: LowRegister,
69 | offset: Address,
70 | ) -> Self::Output;
71 | fn thumb_str_sp(&mut self, d: LowRegister, offset: Address) -> Self::Output;
72 | fn thumb_ldr_sp(&mut self, d: LowRegister, offset: Address) -> Self::Output;
73 | fn thumb_rel_addr(&mut self, sp: bool, d: LowRegister, offset: Address) -> Self::Output;
74 | fn thumb_sp_offs(&mut self, offset: RelativeOffset) -> Self::Output;
75 | fn thumb_push(&mut self, reg_list: u8, lr: bool) -> Self::Output;
76 | fn thumb_pop(&mut self, reg_list: u8, pc: bool) -> Self::Output;
77 | fn thumb_stmia(&mut self, b: LowRegister, reg_list: u8) -> Self::Output;
78 | fn thumb_ldmia(&mut self, b: LowRegister, reg_list: u8) -> Self::Output;
79 | fn thumb_bcond(&mut self, cond: u16, offset: RelativeOffset) -> Self::Output;
80 | fn thumb_swi(&mut self) -> Self::Output;
81 | fn thumb_br(&mut self, offset: RelativeOffset) -> Self::Output;
82 | fn thumb_set_lr(&mut self, offset: RelativeOffset) -> Self::Output;
83 | fn thumb_bl(&mut self, offset: Address, thumb: bool) -> Self::Output;
84 | }
85 |
--------------------------------------------------------------------------------
/cores/gga/src/audio/psg/noise_channel.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use common::TimeS;
10 |
11 | use super::{envelope::EnvelopGenerator, Channel, GenApuEvent};
12 |
13 | #[derive(Default)]
14 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15 | pub struct NoiseChannel {
16 | shift_clock_frequency: u8,
17 | step_mode_7_bits: bool,
18 | divisor_code: u8,
19 |
20 | feedback_shift_register: u16,
21 |
22 | envelope: EnvelopGenerator,
23 |
24 | channel_enabled: bool,
25 | dac_enable: bool,
26 | }
27 |
28 | impl NoiseChannel {
29 | pub fn write_noise_register(&mut self, data: u8) {
30 | self.shift_clock_frequency = data >> 4;
31 | self.step_mode_7_bits = (data >> 3) & 1 == 1;
32 | self.divisor_code = data & 7;
33 | }
34 |
35 | pub fn read_noise_register(&self) -> u8 {
36 | (self.shift_clock_frequency << 4) | ((self.step_mode_7_bits as u8) << 3) | self.divisor_code
37 | }
38 |
39 | pub fn envelope(&self) -> &EnvelopGenerator {
40 | &self.envelope
41 | }
42 |
43 | pub fn envelope_mut(&mut self) -> &mut EnvelopGenerator {
44 | &mut self.envelope
45 | }
46 |
47 | pub fn clock(&mut self) -> u32 {
48 | self.clock_feedback_register();
49 | (self.get_frequency() as u32) << 2
50 | }
51 | }
52 |
53 | impl NoiseChannel {
54 | fn get_frequency(&self) -> u16 {
55 | (self.base_divisor() << self.shift_clock_frequency) / 4
56 | }
57 |
58 | fn base_divisor(&self) -> u16 {
59 | if self.divisor_code == 0 {
60 | 8
61 | } else {
62 | self.divisor_code as u16 * 16
63 | }
64 | }
65 |
66 | fn clock_feedback_register(&mut self) {
67 | let xor_result =
68 | (self.feedback_shift_register & 1) ^ ((self.feedback_shift_register >> 1) & 1);
69 |
70 | self.feedback_shift_register >>= 1;
71 |
72 | self.feedback_shift_register |= xor_result << 14;
73 |
74 | if self.step_mode_7_bits {
75 | self.feedback_shift_register &= !0x40;
76 | self.feedback_shift_register |= xor_result << 6;
77 | }
78 | }
79 | }
80 |
81 | impl Channel for NoiseChannel {
82 | fn output(&self) -> u8 {
83 | ((self.feedback_shift_register & 1) ^ 1) as u8 * self.envelope.current_volume()
84 | }
85 |
86 | fn muted(&self) -> bool {
87 | self.envelope.current_volume() == 0
88 | }
89 |
90 | fn trigger(&mut self, _sched: &mut impl FnMut(GenApuEvent, TimeS)) {
91 | self.envelope.trigger();
92 | // because its 15 bits
93 | self.feedback_shift_register = 0x7FFF;
94 | }
95 |
96 | fn set_enable(&mut self, enabled: bool) {
97 | self.channel_enabled = enabled;
98 | }
99 |
100 | fn enabled(&self) -> bool {
101 | self.channel_enabled
102 | }
103 |
104 | fn set_dac_enable(&mut self, enabled: bool) {
105 | self.dac_enable = enabled;
106 | }
107 |
108 | fn dac_enabled(&self) -> bool {
109 | self.dac_enable
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/frontends/corebench-egui/src/gui/tests.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024 Leela Aurelia, git@elia.garden
2 | //
3 | // Unless otherwise noted, this file is released and thus subject to the
4 | // terms of the Mozilla Public License Version 2.0 (MPL-2.0) or the
5 | // GNU General Public License Version 3 (GPL-3).
6 | // If a copy of these licenses was not distributed with this file, you can
7 | // obtain them at https://mozilla.org/MPL/2.0/ and http://www.gnu.org/licenses/.
8 |
9 | use std::{sync::Arc, thread, time::Instant};
10 |
11 | use eframe::egui::{Context, Ui};
12 | use egui_plot::{Legend, Line, Plot, PlotPoints};
13 |
14 | use crate::{app::App, tests::SUITES};
15 |
16 | pub(super) fn suites(app: &mut App, _ctx: &Context, ui: &mut Ui) {
17 | ui.label("Add suites:");
18 | for suite in SUITES {
19 | if ui.button(suite.0).clicked() {
20 | app.suites.push(Arc::new(suite.1()));
21 | app.update_test_suites();
22 | }
23 | }
24 |
25 | ui.separator();
26 | ui.label("Currently loaded suites:");
27 | for suite in &app.suites {
28 | ui.horizontal(|ui| {
29 | ui.label(&suite.name);
30 | });
31 | }
32 | }
33 |
34 | pub(super) fn bench(app: &mut App, _ctx: &Context, ui: &mut Ui) {
35 | if ui.button("Start isolated benchmark").clicked() {
36 | let cores = app
37 | .cores
38 | .iter()
39 | .map(|c| {
40 | c.bench_iso.lock().unwrap().clear();
41 | ((c.loader)(app.rom.clone().unwrap()), c.bench_iso.clone())
42 | })
43 | .collect::>();
44 | thread::spawn(|| {
45 | for (mut core, bench) in cores {
46 | for time in 0..500 {
47 | let delta = time as f64 / 5.0;
48 | let time = Instant::now();
49 | core.advance_delta(0.1);
50 | let elapsed = time.elapsed().as_micros() as f64;
51 | bench.lock().unwrap().add(delta, elapsed / 1000.0);
52 | }
53 | }
54 | });
55 | }
56 |
57 | ui.checkbox(&mut app.bench_iso, "Graph: Show Isolated Benchmark");
58 |
59 | if app.bench_iso {
60 | Plot::new("benchmark")
61 | .legend(Legend::default())
62 | .allow_scroll(false)
63 | .allow_drag(false)
64 | .include_x(100.0)
65 | .x_axis_label("Emulated Time")
66 | .y_axis_label("Time to emulate 0.2s in ms")
67 | .show(ui, |ui| {
68 | for core in app.cores.iter() {
69 | ui.line(
70 | Line::new(PlotPoints::from_iter(
71 | core.bench_iso.lock().unwrap().iter().map(|(t, s)| [t, s]),
72 | ))
73 | .name(&core.name),
74 | );
75 | }
76 | });
77 | } else {
78 | Plot::new("benchmark")
79 | .legend(Legend::default())
80 | .allow_scroll(false)
81 | .allow_drag(false)
82 | .include_x(30.0)
83 | .x_axis_label("Real Time")
84 | .y_axis_label("Time to emulate 0.2s in ms")
85 | .show(ui, |ui| {
86 | for core in app.cores.iter() {
87 | ui.line(
88 | Line::new(PlotPoints::from_iter(
89 | core.bench.iter().map(|(t, s)| [t, s]),
90 | ))
91 | .name(&core.name),
92 | );
93 | }
94 | });
95 | }
96 | }
97 |
--------------------------------------------------------------------------------