Corwin 2024-05-22 20:56:59 +01:00 committed by GitHub
commit e06230efd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 616 additions and 30 deletions

View file

@ -26,6 +26,7 @@ members = [
"emulator/mgba",
"emulator/mgba-sys",
"emulator/test-runner",
"emulator/screenshot-generator",
"website/backtrace",
]

View file

@ -1,25 +0,0 @@
#![no_std]
#![no_main]
#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn __RUST_INTERRUPT_HANDLER(_: u16) {}
// implementation of tonc's "My first GBA demo"
// https://coranac.com/tonc/text/first.htm
#[no_mangle]
pub fn main() -> ! {
unsafe {
*(0x0400_0000 as *mut u32) = 0x0403;
let video = 0x0600_0000 as *mut u16;
*video.offset(120 + 80 * 240) = 0x001F;
*video.offset(136 + 80 * 240) = 0x03E0;
*video.offset(120 + 96 * 240) = 0x7C00;
}
loop {}
}

View file

@ -0,0 +1,13 @@
[package]
name = "screenshot-generator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mgba = { path = "../mgba" }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
image = { version = "0.24", default-features = false, features = [ "png", "bmp" ] }
agb-gbafix = { path = "../../agb-gbafix" }

View file

@ -0,0 +1,23 @@
use image::{DynamicImage, GenericImage, Rgba};
const WIDTH: usize = 240;
const HEIGHT: usize = 160;
pub fn generate_image(video_buffer: &[u32]) -> DynamicImage {
let mut dynamic_image = DynamicImage::new(
WIDTH.try_into().unwrap(),
HEIGHT.try_into().unwrap(),
image::ColorType::Rgba8,
);
for y in 0..HEIGHT {
for x in 0..WIDTH {
let video_pixel = video_buffer[x + y * WIDTH];
let mut pixels = video_pixel.to_le_bytes();
pixels[3] = 255;
dynamic_image.put_pixel(x.try_into().unwrap(), y.try_into().unwrap(), Rgba(pixels));
}
}
dynamic_image
}

View file

@ -0,0 +1,96 @@
use std::{
error::Error,
fs::File,
io::{BufWriter, Read},
path::{Path, PathBuf},
};
use anyhow::anyhow;
use clap::Parser;
use image::DynamicImage;
use image_generate::generate_image;
use mgba::{LogLevel, Logger, MCore, MemoryBacked, VFile};
mod image_generate;
static LOGGER: Logger = Logger::new(my_logger);
fn my_logger(_category: &str, _level: LogLevel, _s: String) {}
#[derive(Parser)]
struct CliArguments {
#[arg(long)]
rom: PathBuf,
#[arg(long)]
frames: usize,
#[arg(long)]
output: PathBuf,
}
struct ScreenshotGenerator {
mgba: MCore,
}
impl ScreenshotGenerator {
fn new<V: VFile>(rom: V) -> Result<Self, Box<dyn Error>> {
let mut mgba = MCore::new().ok_or(anyhow!("cannot create core"))?;
mgba::set_global_default_logger(&LOGGER);
mgba.load_rom(rom);
Ok(Self { mgba })
}
fn run(mut self, frames: usize) -> DynamicImage {
for _ in 0..frames {
self.mgba.frame();
}
generate_image(self.mgba.video_buffer())
}
}
fn main() -> Result<(), Box<dyn Error>> {
let args = CliArguments::parse();
let rom = load_rom(args.rom)?;
let rom = MemoryBacked::new(rom);
let image = ScreenshotGenerator::new(rom)?.run(args.frames);
let mut output = BufWriter::new(
File::options()
.write(true)
.create(true)
.truncate(true)
.open(args.output)?,
);
image.write_to(&mut output, image::ImageOutputFormat::Png)?;
Ok(())
}
fn load_rom<P: AsRef<Path>>(path: P) -> anyhow::Result<Vec<u8>> {
let mut input_file = File::open(path)?;
let mut input_file_buffer = Vec::new();
input_file.read_to_end(&mut input_file_buffer)?;
let mut elf_buffer = Vec::new();
let inculde_debug_info = false;
if agb_gbafix::write_gba_file(
&input_file_buffer,
Default::default(),
agb_gbafix::PaddingBehaviour::DoNotPad,
inculde_debug_info,
&mut elf_buffer,
)
.is_ok()
{
Ok(elf_buffer)
} else {
Ok(input_file_buffer)
}
}

View file

@ -112,8 +112,35 @@ build-combo-rom-site:
mkdir -p website/agb/src/roms
gzip -9 -c examples/target/examples/combo.gba > website/agb/src/roms/combo.gba.gz
generate-screenshot *args:
(cd emulator/screenshot-generator && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/screenshot-generator" {{args}})
setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace
build-site-examples: build-release
#!/usr/bin/env bash
set -euxo pipefail
mkdir -p website/agb/src/roms/examples
EXAMPLES="$(cd agb/examples; ls *.rs)"
EXAMPLE_DEFINITIONS="export const Examples: {url: URL, example_name: string, screenshot: StaticImageData }[] = [" > website/agb/src/roms/examples/examples.ts
EXAMPLE_IMAGE_IMPORTS="import { StaticImageData } from 'next/image';";
for EXAMPLE_NAME in $EXAMPLES; do
EXAMPLE="${EXAMPLE_NAME%.rs}"
just gbafix "$CARGO_TARGET_DIR/thumbv4t-none-eabi/release/examples/$EXAMPLE" --output="$CARGO_TARGET_DIR/thumbv4t-none-eabi/release/examples/$EXAMPLE.gba"
gzip -9 -c $CARGO_TARGET_DIR/thumbv4t-none-eabi/release/examples/$EXAMPLE.gba > website/agb/src/roms/examples/$EXAMPLE.gba.gz
just generate-screenshot --rom="$CARGO_TARGET_DIR/thumbv4t-none-eabi/release/examples/$EXAMPLE.gba" --frames=10 --output=website/agb/src/roms/examples/$EXAMPLE.png
EXAMPLE_IMAGE_IMPORTS="$EXAMPLE_IMAGE_IMPORTS import $EXAMPLE from './$EXAMPLE.png';"
EXAMPLE_DEFINITIONS="$EXAMPLE_DEFINITIONS {url: new URL('./$EXAMPLE.gba.gz', import.meta.url), example_name: '$EXAMPLE', screenshot: $EXAMPLE},"
done
EXAMPLE_DEFINITIONS="$EXAMPLE_DEFINITIONS ];"
echo "$EXAMPLE_IMAGE_IMPORTS" > website/agb/src/roms/examples/examples.ts
echo "$EXAMPLE_DEFINITIONS" >> website/agb/src/roms/examples/examples.ts
setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace build-site-examples
(cd website/agb && npm install --no-save --prefer-offline --no-audit)
build-site-app: setup-app-build

View file

@ -38,4 +38,5 @@ next-env.d.ts
vendor
*.gba
*.gba.gz
*.gba.gz
src/roms

View file

@ -11,6 +11,7 @@
"next": "14.2.3",
"react": "^18",
"react-dom": "^18",
"react-syntax-highlighter": "^15.5.0",
"sharp": "^0.33.3",
"styled-components": "^6.1.8"
},
@ -18,6 +19,7 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/styled-components": "^5.1.34",
"eslint": "^8",
"eslint-config-next": "14.2.3",
@ -37,7 +39,6 @@
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@ -861,6 +862,15 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^2"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
@ -911,6 +921,16 @@
"@types/react": "*"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/styled-components": {
"version": "5.1.34",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz",
@ -927,6 +947,12 @@
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
},
"node_modules/@types/unist": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
"license": "MIT"
},
"node_modules/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
@ -1453,6 +1479,36 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@ -1495,6 +1551,16 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -2355,6 +2421,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -2440,6 +2519,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -2742,6 +2829,42 @@
"node": ">= 0.4"
}
},
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"license": "MIT",
"dependencies": {
"@types/hast": "^2.0.0",
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -2815,6 +2938,30 @@
"node": ">= 0.4"
}
},
"node_modules/is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@ -2933,6 +3080,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -2990,6 +3147,16 @@
"node": ">=0.10.0"
}
},
"node_modules/is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@ -3352,6 +3519,20 @@
"loose-envify": "cli.js"
}
},
"node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
@ -3684,6 +3865,24 @@
"node": ">=6"
}
},
"node_modules/parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"license": "MIT",
"dependencies": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -3809,6 +4008,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -3820,6 +4028,19 @@
"react-is": "^16.13.1"
}
},
"node_modules/property-information": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -3878,6 +4099,22 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"node_modules/react-syntax-highlighter": {
"version": "15.5.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
"integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"refractor": "^3.6.0"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
@ -3899,11 +4136,34 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/refractor": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
"license": "MIT",
"dependencies": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.27.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
@ -4247,6 +4507,16 @@
"node": ">=0.10.0"
}
},
"node_modules/space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -4936,6 +5206,15 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View file

@ -12,6 +12,7 @@
"next": "14.2.3",
"react": "^18",
"react-dom": "^18",
"react-syntax-highlighter": "^15.5.0",
"sharp": "^0.33.3",
"styled-components": "^6.1.8"
},
@ -19,6 +20,7 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/styled-components": "^5.1.34",
"eslint": "^8",
"eslint-config-next": "14.2.3",

View file

@ -0,0 +1,21 @@
"use client";
import MgbaWrapper from "@/components/mgba/mgbaWrapper";
import { Examples } from "@/roms/examples/examples";
import { slugify } from "@/sluggify";
import { useMemo } from "react";
function gameUrl(exampleName: string) {
const example = Examples.find((x) => slugify(x.example_name) === exampleName);
if (!example) {
throw new Error(`cannot find example ${exampleName}`);
}
return example.url;
}
export function Emulator({ exampleName }: { exampleName: string }) {
const example = useMemo(() => gameUrl(exampleName), [exampleName]);
return <MgbaWrapper gameUrl={example} />;
}

View file

@ -0,0 +1,58 @@
import { Examples } from "@/roms/examples/examples";
import { slugify } from "@/sluggify";
import { Emulator } from "./emulator";
import { ContentBlock } from "@/components/contentBlock";
import * as fs from "node:fs/promises";
import { BackToExampleLink, Code } from "./styles";
export async function generateStaticParams() {
return Examples.map((example) => ({
example: slugify(example.example_name),
}));
}
function getExample(sluggedExample: string) {
const example = Examples.find(
(x) => slugify(x.example_name) === sluggedExample
);
if (!example) {
throw new Error(`cannot find example ${sluggedExample}`);
}
return example;
}
async function loadSourceCode(exampleName: string) {
const source = await fs.readFile(`../../agb/examples/${exampleName}.rs`);
return source.toString();
}
export default async function Page({
params,
}: {
params: { example: string };
}) {
const exmaple = getExample(params.example);
const source = await loadSourceCode(exmaple.example_name);
return (
<>
<ContentBlock color="#9fa6db">
<h1>Example: {params.example}</h1>
<BackToExampleLink href={`../examples#${params.example}`}>
<strong>&lt;</strong> Back to examples
</BackToExampleLink>
</ContentBlock>
<ContentBlock>
<Emulator exampleName={params.example} />
</ContentBlock>
<ContentBlock>
<Code language="rust">{source}</Code>
</ContentBlock>
<ContentBlock color="#f5755e">
<></>
</ContentBlock>
</>
);
}

View file

@ -0,0 +1,14 @@
"use client";
import Link from "next/link";
import SyntaxHighlighter from "react-syntax-highlighter";
import { styled } from "styled-components";
export const Code = styled(SyntaxHighlighter)`
font-size: 0.8rem;
`;
export const BackToExampleLink = styled(Link)`
text-decoration: none;
color: black;
`;

View file

@ -0,0 +1,42 @@
import { Metadata } from "next";
import { ContentBlock } from "@/components/contentBlock";
import { slugify } from "@/sluggify";
import { GameDisplay, GameGrid, GameImage } from "./styles";
import { Examples } from "@/roms/examples/examples";
export const metadata: Metadata = {
title: "Examples - agb",
};
export default function ShowcasePage() {
return (
<>
<ContentBlock color="#9fa6db">
<h1>Examples</h1>
</ContentBlock>
<ContentBlock uncentered>
<GameGrid>
{Examples.map((example, idx) => (
<Game key={idx} example={example} />
))}
</GameGrid>
</ContentBlock>
</>
);
}
function Game({ example }: { example: (typeof Examples)[number] }) {
const screenshot = example.screenshot;
return (
<GameDisplay
href={`./examples/${slugify(example.example_name)}`}
id={slugify(example.example_name)}
>
<GameImage
src={screenshot}
alt={`Screenshot of ${example.example_name}`}
/>
<h2>{example.example_name}</h2>
</GameDisplay>
);
}

View file

@ -0,0 +1,34 @@
"use client";
import Link from "next/link";
import styled from "styled-components";
import Image from "next/image";
export const GameGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, min(100vw, 600px));
justify-content: center;
gap: 48px;
`;
export const GameImage = styled(Image)`
width: 100%;
width: max(
round(down, 100%, calc(240 * var(--device-pixel))),
min(calc(240 * var(--device-pixel)), 100vw)
);
height: auto;
image-rendering: pixelated;
`;
export const GameDisplay = styled(Link)`
width: 100%;
text-align: center;
color: black;
text-decoration: none;
h2 {
margin: 0;
margin-top: 8px;
}
`;