Ian Chamberlain
1 year ago
committed by
GitHub
26 changed files with 602 additions and 617 deletions
@ -0,0 +1,12 @@ |
|||||||
|
name: 'Run 3DS Executable' |
||||||
|
description: 'Run a given 3DS executable with citra and GDB' |
||||||
|
inputs: |
||||||
|
executable: |
||||||
|
description: > |
||||||
|
The 3DS executable(s) to run. Globs and space separated lists allowed. |
||||||
|
required: true |
||||||
|
runs: |
||||||
|
using: docker |
||||||
|
image: ../../../Dockerfile |
||||||
|
args: |
||||||
|
- ${{ inputs.executable }} |
@ -0,0 +1,46 @@ |
|||||||
|
name: Setup Rust3DS |
||||||
|
description: Set up CI environment for Rust + 3DS development |
||||||
|
|
||||||
|
inputs: |
||||||
|
toolchain: |
||||||
|
description: The Rust toolchain to use for the steps |
||||||
|
required: true |
||||||
|
default: nightly |
||||||
|
|
||||||
|
runs: |
||||||
|
using: composite |
||||||
|
steps: |
||||||
|
# https://github.com/nektos/act/issues/917#issuecomment-1074421318 |
||||||
|
- if: ${{ env.ACT }} |
||||||
|
shell: bash |
||||||
|
name: Hack container for local development |
||||||
|
run: | |
||||||
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - |
||||||
|
sudo apt-get install -y nodejs |
||||||
|
|
||||||
|
- name: Setup default Rust toolchain |
||||||
|
# Use this helper action so we get matcher support |
||||||
|
# https://github.com/actions-rust-lang/setup-rust-toolchain/pull/15 |
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1 |
||||||
|
with: |
||||||
|
components: clippy, rustfmt, rust-src |
||||||
|
toolchain: ${{ inputs.toolchain }} |
||||||
|
|
||||||
|
- name: Set up Rust cache |
||||||
|
uses: Swatinem/rust-cache@v2 |
||||||
|
with: |
||||||
|
shared-key: rust3ds |
||||||
|
|
||||||
|
- name: Install build tools for host |
||||||
|
shell: bash |
||||||
|
run: sudo apt-get update && sudo apt-get install -y build-essential |
||||||
|
|
||||||
|
- name: Install cargo-3ds |
||||||
|
shell: bash |
||||||
|
# TODO: replace with crates.io version once published |
||||||
|
run: cargo install --git https://github.com/rust3ds/cargo-3ds --branch feature/verbose-flag |
||||||
|
|
||||||
|
- name: Set PATH to include devkitARM |
||||||
|
shell: bash |
||||||
|
# For some reason devkitARM/bin is not part of the default PATH in the container |
||||||
|
run: echo "${DEVKITARM}/bin" >> $GITHUB_PATH |
@ -0,0 +1,84 @@ |
|||||||
|
name: CI |
||||||
|
|
||||||
|
on: |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- main |
||||||
|
pull_request: |
||||||
|
branches: |
||||||
|
- main |
||||||
|
workflow_dispatch: |
||||||
|
|
||||||
|
env: |
||||||
|
# actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS, which we don't want to use |
||||||
|
RUSTFLAGS: "" |
||||||
|
|
||||||
|
jobs: |
||||||
|
lint: |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
toolchain: |
||||||
|
- nightly-2023-06-01 |
||||||
|
|
||||||
|
runs-on: ubuntu-latest |
||||||
|
container: devkitpro/devkitarm |
||||||
|
steps: |
||||||
|
- name: Checkout branch |
||||||
|
uses: actions/checkout@v2 |
||||||
|
|
||||||
|
- uses: ./.github/actions/setup |
||||||
|
with: |
||||||
|
toolchain: ${{ matrix.toolchain }} |
||||||
|
|
||||||
|
- name: Check formatting |
||||||
|
run: cargo fmt --all --verbose -- --check |
||||||
|
|
||||||
|
- name: Run clippy |
||||||
|
# We have to build the test crate here since it's not included in build-std by default |
||||||
|
run: cargo 3ds clippy -Zbuild-std=std,test --color=always --verbose --all-targets |
||||||
|
|
||||||
|
test: |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
toolchain: |
||||||
|
# Oldest supported nightly |
||||||
|
- nightly-2023-06-01 |
||||||
|
- nightly |
||||||
|
|
||||||
|
continue-on-error: ${{ matrix.toolchain == 'nightly' }} |
||||||
|
runs-on: ubuntu-latest |
||||||
|
container: devkitpro/devkitarm |
||||||
|
steps: |
||||||
|
- name: Checkout branch |
||||||
|
uses: actions/checkout@v3 |
||||||
|
|
||||||
|
- uses: ./.github/actions/setup |
||||||
|
with: |
||||||
|
toolchain: ${{ matrix.toolchain }} |
||||||
|
|
||||||
|
- name: Build lib tests |
||||||
|
run: cargo 3ds test --no-run --lib |
||||||
|
|
||||||
|
- name: Build integration tests |
||||||
|
run: cargo 3ds test --no-run --test integration |
||||||
|
|
||||||
|
- name: Build doc tests |
||||||
|
run: cargo 3ds test --no-run --doc |
||||||
|
|
||||||
|
- name: Run lib + integration tests |
||||||
|
uses: ./.github/actions/citra |
||||||
|
with: |
||||||
|
executable: ./target/armv6k-nintendo-3ds/debug/deps/*.elf |
||||||
|
|
||||||
|
# TODO: run doc tests. We might be able to do something with e.g. |
||||||
|
# cargo's "runner" configuration, but it seems we also need a test |
||||||
|
# runtime and stuff for that to work. |
||||||
|
|
||||||
|
- name: Upload citra logs and capture videos |
||||||
|
uses: actions/upload-artifact@v3 |
||||||
|
if: success() || failure() |
||||||
|
with: |
||||||
|
name: citra-logs-${{ matrix.toolchain }} |
||||||
|
path: | |
||||||
|
target/armv6k-nintendo-3ds/debug/deps/*.txt |
||||||
|
target/armv6k-nintendo-3ds/debug/deps/*.webm |
@ -1,3 +1,14 @@ |
|||||||
# Ignore files generated by citra or the VNC client |
# Generated by Cargo |
||||||
citra/out |
# will have compiled files and executables |
||||||
driver/out |
debug/ |
||||||
|
target/ |
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries |
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html |
||||||
|
Cargo.lock |
||||||
|
|
||||||
|
# These are backup files generated by rustfmt |
||||||
|
**/*.rs.bk |
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information |
||||||
|
*.pdb |
||||||
|
@ -0,0 +1,14 @@ |
|||||||
|
[package] |
||||||
|
name = "test-runner" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[features] |
||||||
|
console = [] |
||||||
|
gdb = [] |
||||||
|
socket = [] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
ctru-rs = { git = "https://github.com/rust3ds/ctru-rs" } |
||||||
|
ctru-sys = { git = "https://github.com/rust3ds/ctru-rs" } |
||||||
|
libc = "0.2.147" |
@ -0,0 +1,34 @@ |
|||||||
|
FROM buildpack-deps:latest as builder |
||||||
|
|
||||||
|
ARG CITRA_CHANNEL=nightly |
||||||
|
ARG CITRA_RELEASE=1962 |
||||||
|
|
||||||
|
WORKDIR /tmp |
||||||
|
COPY ./docker/download_citra.sh /usr/local/bin/download_citra |
||||||
|
RUN apt-get update -y && apt-get install -y jq |
||||||
|
RUN download_citra ${CITRA_CHANNEL} ${CITRA_RELEASE} |
||||||
|
|
||||||
|
FROM devkitpro/devkitarm:latest as devkitarm |
||||||
|
|
||||||
|
FROM ubuntu:latest |
||||||
|
|
||||||
|
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \ |
||||||
|
apt-get update -y && \ |
||||||
|
apt-get install -y \ |
||||||
|
libswscale5 \ |
||||||
|
libsdl2-2.0-0 \ |
||||||
|
libavformat58 \ |
||||||
|
libavfilter7 \ |
||||||
|
xvfb |
||||||
|
|
||||||
|
COPY --from=devkitarm /opt/devkitpro /opt/devkitpro |
||||||
|
ENV PATH=/opt/devkitpro/devkitARM/bin:${PATH} |
||||||
|
|
||||||
|
COPY --from=builder /tmp/citra.AppImage /usr/local/bin/citra |
||||||
|
COPY ./docker/sdl2-config.ini /app/ |
||||||
|
COPY ./docker/test-runner.gdb /app/ |
||||||
|
COPY ./docker/entrypoint.sh /app/ |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/entrypoint.sh" ] |
@ -1,496 +0,0 @@ |
|||||||
[Audio] |
|
||||||
enable_audio_stretching=true |
|
||||||
enable_audio_stretching\default=true |
|
||||||
enable_dsp_lle=false |
|
||||||
enable_dsp_lle\default=true |
|
||||||
enable_dsp_lle_multithread=false |
|
||||||
enable_dsp_lle_multithread\default=true |
|
||||||
mic_input_device=Default |
|
||||||
mic_input_device\default=true |
|
||||||
mic_input_type=0 |
|
||||||
mic_input_type\default=true |
|
||||||
output_device=auto |
|
||||||
output_device\default=true |
|
||||||
output_engine=auto |
|
||||||
output_engine\default=true |
|
||||||
volume=@Variant(\0\0\0\x87?\x80\0\0) |
|
||||||
volume\default=true |
|
||||||
|
|
||||||
[Camera] |
|
||||||
camera_inner_config= |
|
||||||
camera_inner_config\default=true |
|
||||||
camera_inner_flip=0 |
|
||||||
camera_inner_flip\default=true |
|
||||||
camera_inner_name=blank |
|
||||||
camera_inner_name\default=true |
|
||||||
camera_outer_left_config= |
|
||||||
camera_outer_left_config\default=true |
|
||||||
camera_outer_left_flip=0 |
|
||||||
camera_outer_left_flip\default=true |
|
||||||
camera_outer_left_name=blank |
|
||||||
camera_outer_left_name\default=true |
|
||||||
camera_outer_right_config= |
|
||||||
camera_outer_right_config\default=true |
|
||||||
camera_outer_right_flip=0 |
|
||||||
camera_outer_right_flip\default=true |
|
||||||
camera_outer_right_name=blank |
|
||||||
camera_outer_right_name\default=true |
|
||||||
|
|
||||||
[Controls] |
|
||||||
profile=0 |
|
||||||
profile\default=true |
|
||||||
profiles\1\button_a="code:65,engine:keyboard" |
|
||||||
profiles\1\button_a\default=true |
|
||||||
profiles\1\button_b="code:83,engine:keyboard" |
|
||||||
profiles\1\button_b\default=true |
|
||||||
profiles\1\button_debug="code:79,engine:keyboard" |
|
||||||
profiles\1\button_debug\default=true |
|
||||||
profiles\1\button_down="code:71,engine:keyboard" |
|
||||||
profiles\1\button_down\default=true |
|
||||||
profiles\1\button_gpio14="code:80,engine:keyboard" |
|
||||||
profiles\1\button_gpio14\default=true |
|
||||||
profiles\1\button_home="code:66,engine:keyboard" |
|
||||||
profiles\1\button_home\default=true |
|
||||||
profiles\1\button_l="code:81,engine:keyboard" |
|
||||||
profiles\1\button_l\default=true |
|
||||||
profiles\1\button_left="code:70,engine:keyboard" |
|
||||||
profiles\1\button_left\default=true |
|
||||||
profiles\1\button_r="code:87,engine:keyboard" |
|
||||||
profiles\1\button_r\default=true |
|
||||||
profiles\1\button_right="code:72,engine:keyboard" |
|
||||||
profiles\1\button_right\default=true |
|
||||||
profiles\1\button_select="code:78,engine:keyboard" |
|
||||||
profiles\1\button_select\default=true |
|
||||||
profiles\1\button_start="code:77,engine:keyboard" |
|
||||||
profiles\1\button_start\default=true |
|
||||||
profiles\1\button_up="code:84,engine:keyboard" |
|
||||||
profiles\1\button_up\default=true |
|
||||||
profiles\1\button_x="code:90,engine:keyboard" |
|
||||||
profiles\1\button_x\default=true |
|
||||||
profiles\1\button_y="code:88,engine:keyboard" |
|
||||||
profiles\1\button_y\default=true |
|
||||||
profiles\1\button_zl="code:49,engine:keyboard" |
|
||||||
profiles\1\button_zl\default=true |
|
||||||
profiles\1\button_zr="code:50,engine:keyboard" |
|
||||||
profiles\1\button_zr\default=true |
|
||||||
profiles\1\c_stick="down:code$075$1engine$0keyboard,engine:analog_from_button,left:code$074$1engine$0keyboard,modifier:code$068$1engine$0keyboard,modifier_scale:0.500000,right:code$076$1engine$0keyboard,up:code$073$1engine$0keyboard" |
|
||||||
profiles\1\c_stick\default=true |
|
||||||
profiles\1\circle_pad="down:code$016777237$1engine$0keyboard,engine:analog_from_button,left:code$016777234$1engine$0keyboard,modifier:code$068$1engine$0keyboard,modifier_scale:0.500000,right:code$016777236$1engine$0keyboard,up:code$016777235$1engine$0keyboard" |
|
||||||
profiles\1\circle_pad\default=true |
|
||||||
profiles\1\motion_device="engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0" |
|
||||||
profiles\1\motion_device\default=true |
|
||||||
profiles\1\name=default |
|
||||||
profiles\1\name\default=true |
|
||||||
profiles\1\touch_device=engine:emu_window |
|
||||||
profiles\1\touch_device\default=true |
|
||||||
profiles\1\touch_from_button_map=0 |
|
||||||
profiles\1\touch_from_button_map\default=true |
|
||||||
profiles\1\udp_input_address=127.0.0.1 |
|
||||||
profiles\1\udp_input_address\default=true |
|
||||||
profiles\1\udp_input_port=26760 |
|
||||||
profiles\1\udp_input_port\default=true |
|
||||||
profiles\1\udp_pad_index=0 |
|
||||||
profiles\1\udp_pad_index\default=true |
|
||||||
profiles\1\use_touch_from_button=false |
|
||||||
profiles\1\use_touch_from_button\default=true |
|
||||||
profiles\size=1 |
|
||||||
touch_from_button_maps\1\entries\size=0 |
|
||||||
touch_from_button_maps\1\name=default |
|
||||||
touch_from_button_maps\1\name\default=true |
|
||||||
touch_from_button_maps\size=1 |
|
||||||
|
|
||||||
[Core] |
|
||||||
cpu_clock_percentage=100 |
|
||||||
cpu_clock_percentage\default=true |
|
||||||
use_cpu_jit=true |
|
||||||
use_cpu_jit\default=true |
|
||||||
|
|
||||||
[Data%20Storage] |
|
||||||
nand_directory=/root/.local/share/citra-emu/nand/ |
|
||||||
nand_directory\default=true |
|
||||||
sdmc_directory=/root/.local/share/citra-emu/sdmc/ |
|
||||||
sdmc_directory\default=true |
|
||||||
use_virtual_sd=true |
|
||||||
use_virtual_sd\default=true |
|
||||||
|
|
||||||
[Debugging] |
|
||||||
LLE\AC=false |
|
||||||
LLE\AC\default=true |
|
||||||
LLE\ACT=false |
|
||||||
LLE\ACT\default=true |
|
||||||
LLE\AM=false |
|
||||||
LLE\AM\default=true |
|
||||||
LLE\BOSS=false |
|
||||||
LLE\BOSS\default=true |
|
||||||
LLE\CAM=false |
|
||||||
LLE\CAM\default=true |
|
||||||
LLE\CDC=false |
|
||||||
LLE\CDC\default=true |
|
||||||
LLE\CECD=false |
|
||||||
LLE\CECD\default=true |
|
||||||
LLE\CFG=false |
|
||||||
LLE\CFG\default=true |
|
||||||
LLE\CSND=false |
|
||||||
LLE\CSND\default=true |
|
||||||
LLE\DLP=false |
|
||||||
LLE\DLP\default=true |
|
||||||
LLE\DSP=false |
|
||||||
LLE\DSP\default=true |
|
||||||
LLE\ERR=false |
|
||||||
LLE\ERR\default=true |
|
||||||
LLE\FRD=false |
|
||||||
LLE\FRD\default=true |
|
||||||
LLE\FS=false |
|
||||||
LLE\FS\default=true |
|
||||||
LLE\GPIO=false |
|
||||||
LLE\GPIO\default=true |
|
||||||
LLE\GSP=false |
|
||||||
LLE\GSP\default=true |
|
||||||
LLE\HID=false |
|
||||||
LLE\HID\default=true |
|
||||||
LLE\HTTP=false |
|
||||||
LLE\HTTP\default=true |
|
||||||
LLE\I2C=false |
|
||||||
LLE\I2C\default=true |
|
||||||
LLE\IR=false |
|
||||||
LLE\IR\default=true |
|
||||||
LLE\LDR=false |
|
||||||
LLE\LDR\default=true |
|
||||||
LLE\MCU=false |
|
||||||
LLE\MCU\default=true |
|
||||||
LLE\MIC=false |
|
||||||
LLE\MIC\default=true |
|
||||||
LLE\MP=false |
|
||||||
LLE\MP\default=true |
|
||||||
LLE\MVD=false |
|
||||||
LLE\MVD\default=true |
|
||||||
LLE\NDM=false |
|
||||||
LLE\NDM\default=true |
|
||||||
LLE\NEWS=false |
|
||||||
LLE\NEWS\default=true |
|
||||||
LLE\NFC=false |
|
||||||
LLE\NFC\default=true |
|
||||||
LLE\NIM=false |
|
||||||
LLE\NIM\default=true |
|
||||||
LLE\NS=false |
|
||||||
LLE\NS\default=true |
|
||||||
LLE\NWM=false |
|
||||||
LLE\NWM\default=true |
|
||||||
LLE\PDN=false |
|
||||||
LLE\PDN\default=true |
|
||||||
LLE\PM=false |
|
||||||
LLE\PM\default=true |
|
||||||
LLE\PS=false |
|
||||||
LLE\PS\default=true |
|
||||||
LLE\PTM=false |
|
||||||
LLE\PTM\default=true |
|
||||||
LLE\PXI=false |
|
||||||
LLE\PXI\default=true |
|
||||||
LLE\QTM=false |
|
||||||
LLE\QTM\default=true |
|
||||||
LLE\SOC=false |
|
||||||
LLE\SOC\default=true |
|
||||||
LLE\SPI=false |
|
||||||
LLE\SPI\default=true |
|
||||||
LLE\SSL=false |
|
||||||
LLE\SSL\default=true |
|
||||||
gdbstub_port=24689 |
|
||||||
gdbstub_port\default=true |
|
||||||
record_frame_times=false |
|
||||||
use_gdbstub=false |
|
||||||
use_gdbstub\default=true |
|
||||||
|
|
||||||
[Layout] |
|
||||||
custom_bottom_bottom=480 |
|
||||||
custom_bottom_bottom\default=true |
|
||||||
custom_bottom_left=40 |
|
||||||
custom_bottom_left\default=true |
|
||||||
custom_bottom_right=360 |
|
||||||
custom_bottom_right\default=true |
|
||||||
custom_bottom_top=240 |
|
||||||
custom_bottom_top\default=true |
|
||||||
custom_layout=false |
|
||||||
custom_layout\default=true |
|
||||||
custom_top_bottom=240 |
|
||||||
custom_top_bottom\default=true |
|
||||||
custom_top_left=0 |
|
||||||
custom_top_left\default=true |
|
||||||
custom_top_right=400 |
|
||||||
custom_top_right\default=true |
|
||||||
custom_top_top=0 |
|
||||||
custom_top_top\default=true |
|
||||||
factor_3d=0 |
|
||||||
factor_3d\default=true |
|
||||||
filter_mode=true |
|
||||||
filter_mode\default=true |
|
||||||
layout_option=0 |
|
||||||
pp_shader_name=none (builtin) |
|
||||||
pp_shader_name\default=true |
|
||||||
render_3d=0 |
|
||||||
render_3d\default=true |
|
||||||
swap_screen=false |
|
||||||
swap_screen\default=true |
|
||||||
upright_screen=false |
|
||||||
upright_screen\default=true |
|
||||||
|
|
||||||
[Miscellaneous] |
|
||||||
log_filter=*:Info |
|
||||||
log_filter\default=true |
|
||||||
|
|
||||||
[Renderer] |
|
||||||
bg_blue=0.545098066329956 |
|
||||||
bg_blue\default=false |
|
||||||
bg_green=0.545098066329956 |
|
||||||
bg_green\default=false |
|
||||||
bg_red=0.545098066329956 |
|
||||||
bg_red\default=false |
|
||||||
frame_limit=100 |
|
||||||
frame_limit\default=true |
|
||||||
frame_limit_alternate=200 |
|
||||||
frame_limit_alternate\default=true |
|
||||||
resolution_factor=1 |
|
||||||
resolution_factor\default=true |
|
||||||
shaders_accurate_mul=true |
|
||||||
shaders_accurate_mul\default=true |
|
||||||
texture_filter_name=none |
|
||||||
texture_filter_name\default=true |
|
||||||
use_disk_shader_cache=true |
|
||||||
use_disk_shader_cache\default=true |
|
||||||
use_frame_limit_alternate=false |
|
||||||
use_frame_limit_alternate\default=true |
|
||||||
use_hw_renderer=true |
|
||||||
use_hw_renderer\default=true |
|
||||||
use_hw_shader=true |
|
||||||
use_hw_shader\default=true |
|
||||||
use_shader_jit=true |
|
||||||
use_shader_jit\default=true |
|
||||||
use_vsync_new=true |
|
||||||
use_vsync_new\default=true |
|
||||||
|
|
||||||
[System] |
|
||||||
init_clock=0 |
|
||||||
init_clock\default=true |
|
||||||
init_time=946681277 |
|
||||||
init_time\default=true |
|
||||||
is_new_3ds=true |
|
||||||
is_new_3ds\default=true |
|
||||||
region_value=-1 |
|
||||||
region_value\default=true |
|
||||||
|
|
||||||
[UI] |
|
||||||
GameList\hideNoIcon=false |
|
||||||
GameList\hideNoIcon\default=true |
|
||||||
GameList\iconSize=2 |
|
||||||
GameList\iconSize\default=true |
|
||||||
GameList\row1=2 |
|
||||||
GameList\row1\default=true |
|
||||||
GameList\row2=0 |
|
||||||
GameList\row2\default=true |
|
||||||
GameList\singleLineMode=false |
|
||||||
GameList\singleLineMode\default=true |
|
||||||
Multiplayer\game_id=0 |
|
||||||
Multiplayer\game_id\default=true |
|
||||||
Multiplayer\host_type=0 |
|
||||||
Multiplayer\host_type\default=true |
|
||||||
Multiplayer\ip= |
|
||||||
Multiplayer\ip\default=true |
|
||||||
Multiplayer\ip_ban_list\size=0 |
|
||||||
Multiplayer\max_player=8 |
|
||||||
Multiplayer\max_player\default=true |
|
||||||
Multiplayer\nickname= |
|
||||||
Multiplayer\nickname\default=true |
|
||||||
Multiplayer\port=24872 |
|
||||||
Multiplayer\port\default=true |
|
||||||
Multiplayer\room_description= |
|
||||||
Multiplayer\room_description\default=true |
|
||||||
Multiplayer\room_name= |
|
||||||
Multiplayer\room_name\default=true |
|
||||||
Multiplayer\room_nickname= |
|
||||||
Multiplayer\room_nickname\default=true |
|
||||||
Multiplayer\room_port=24872 |
|
||||||
Multiplayer\room_port\default=true |
|
||||||
Multiplayer\username_ban_list\size=0 |
|
||||||
Paths\gamedirs\1\deep_scan=false |
|
||||||
Paths\gamedirs\1\deep_scan\default=true |
|
||||||
Paths\gamedirs\1\expanded=true |
|
||||||
Paths\gamedirs\1\expanded\default=true |
|
||||||
Paths\gamedirs\1\path=INSTALLED |
|
||||||
Paths\gamedirs\2\deep_scan=false |
|
||||||
Paths\gamedirs\2\deep_scan\default=true |
|
||||||
Paths\gamedirs\2\expanded=true |
|
||||||
Paths\gamedirs\2\expanded\default=true |
|
||||||
Paths\gamedirs\2\path=SYSTEM |
|
||||||
Paths\gamedirs\size=2 |
|
||||||
Paths\language=en |
|
||||||
Paths\language\default=false |
|
||||||
Paths\moviePlaybackPath= |
|
||||||
Paths\movieRecordPath= |
|
||||||
Paths\recentFiles=@Invalid() |
|
||||||
Paths\romsPath= |
|
||||||
Paths\screenshotPath= |
|
||||||
Paths\symbolsPath= |
|
||||||
Paths\videoDumpingPath= |
|
||||||
Shortcuts\Main%20Window\Advance%20Frame\Context=2 |
|
||||||
Shortcuts\Main%20Window\Advance%20Frame\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Advance%20Frame\KeySeq=\\ |
|
||||||
Shortcuts\Main%20Window\Advance%20Frame\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Capture%20Screenshot\Context=2 |
|
||||||
Shortcuts\Main%20Window\Capture%20Screenshot\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Capture%20Screenshot\KeySeq=Ctrl+P |
|
||||||
Shortcuts\Main%20Window\Capture%20Screenshot\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Continue\Pause%20Emulation\Context=1 |
|
||||||
Shortcuts\Main%20Window\Continue\Pause%20Emulation\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Continue\Pause%20Emulation\KeySeq=F4 |
|
||||||
Shortcuts\Main%20Window\Continue\Pause%20Emulation\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Decrease%20Speed%20Limit\Context=2 |
|
||||||
Shortcuts\Main%20Window\Decrease%20Speed%20Limit\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Decrease%20Speed%20Limit\KeySeq=- |
|
||||||
Shortcuts\Main%20Window\Decrease%20Speed%20Limit\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Exit%20Citra\Context=1 |
|
||||||
Shortcuts\Main%20Window\Exit%20Citra\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Exit%20Citra\KeySeq=Ctrl+Q |
|
||||||
Shortcuts\Main%20Window\Exit%20Citra\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Exit%20Fullscreen\Context=1 |
|
||||||
Shortcuts\Main%20Window\Exit%20Fullscreen\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Exit%20Fullscreen\KeySeq=Esc |
|
||||||
Shortcuts\Main%20Window\Exit%20Fullscreen\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Fullscreen\Context=1 |
|
||||||
Shortcuts\Main%20Window\Fullscreen\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Fullscreen\KeySeq=F11 |
|
||||||
Shortcuts\Main%20Window\Fullscreen\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Increase%20Speed%20Limit\Context=2 |
|
||||||
Shortcuts\Main%20Window\Increase%20Speed%20Limit\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Increase%20Speed%20Limit\KeySeq=+ |
|
||||||
Shortcuts\Main%20Window\Increase%20Speed%20Limit\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20Amiibo\Context=2 |
|
||||||
Shortcuts\Main%20Window\Load%20Amiibo\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20Amiibo\KeySeq=F2 |
|
||||||
Shortcuts\Main%20Window\Load%20Amiibo\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20File\Context=1 |
|
||||||
Shortcuts\Main%20Window\Load%20File\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20File\KeySeq=Ctrl+O |
|
||||||
Shortcuts\Main%20Window\Load%20File\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20from%20Newest%20Slot\Context=1 |
|
||||||
Shortcuts\Main%20Window\Load%20from%20Newest%20Slot\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Load%20from%20Newest%20Slot\KeySeq=Ctrl+V |
|
||||||
Shortcuts\Main%20Window\Load%20from%20Newest%20Slot\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Remove%20Amiibo\Context=2 |
|
||||||
Shortcuts\Main%20Window\Remove%20Amiibo\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Remove%20Amiibo\KeySeq=F3 |
|
||||||
Shortcuts\Main%20Window\Remove%20Amiibo\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Restart%20Emulation\Context=1 |
|
||||||
Shortcuts\Main%20Window\Restart%20Emulation\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Restart%20Emulation\KeySeq=F6 |
|
||||||
Shortcuts\Main%20Window\Restart%20Emulation\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Rotate%20Screens%20Upright\Context=1 |
|
||||||
Shortcuts\Main%20Window\Rotate%20Screens%20Upright\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Rotate%20Screens%20Upright\KeySeq=F8 |
|
||||||
Shortcuts\Main%20Window\Rotate%20Screens%20Upright\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Save%20to%20Oldest%20Slot\Context=1 |
|
||||||
Shortcuts\Main%20Window\Save%20to%20Oldest%20Slot\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Save%20to%20Oldest%20Slot\KeySeq=Ctrl+C |
|
||||||
Shortcuts\Main%20Window\Save%20to%20Oldest%20Slot\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Stop%20Emulation\Context=1 |
|
||||||
Shortcuts\Main%20Window\Stop%20Emulation\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Stop%20Emulation\KeySeq=F5 |
|
||||||
Shortcuts\Main%20Window\Stop%20Emulation\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Swap%20Screens\Context=1 |
|
||||||
Shortcuts\Main%20Window\Swap%20Screens\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Swap%20Screens\KeySeq=F9 |
|
||||||
Shortcuts\Main%20Window\Swap%20Screens\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Alternate%20Speed\Context=2 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Alternate%20Speed\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Alternate%20Speed\KeySeq=Ctrl+Z |
|
||||||
Shortcuts\Main%20Window\Toggle%20Alternate%20Speed\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Filter%20Bar\Context=1 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Filter%20Bar\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Filter%20Bar\KeySeq=Ctrl+F |
|
||||||
Shortcuts\Main%20Window\Toggle%20Filter%20Bar\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Frame%20Advancing\Context=2 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Frame%20Advancing\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Frame%20Advancing\KeySeq=Ctrl+A |
|
||||||
Shortcuts\Main%20Window\Toggle%20Frame%20Advancing\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Screen%20Layout\Context=1 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Screen%20Layout\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Screen%20Layout\KeySeq=F10 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Screen%20Layout\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Status%20Bar\Context=1 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Status%20Bar\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Status%20Bar\KeySeq=Ctrl+S |
|
||||||
Shortcuts\Main%20Window\Toggle%20Status%20Bar\KeySeq\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Texture%20Dumping\Context=2 |
|
||||||
Shortcuts\Main%20Window\Toggle%20Texture%20Dumping\Context\default=true |
|
||||||
Shortcuts\Main%20Window\Toggle%20Texture%20Dumping\KeySeq=Ctrl+D |
|
||||||
Shortcuts\Main%20Window\Toggle%20Texture%20Dumping\KeySeq\default=true |
|
||||||
UILayout\gameListHeaderState=@ByteArray(\0\0\0\xff\0\0\0\0\0\0\0\x1\0\0\0\x1\0\0\0\x5\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x4\x98\0\0\0\x5\x1\x1\0\x1\0\0\0\0\0\0\0\0\0\0\0\0h\xff\xff\xff\xff\0\0\0\x81\0\0\0\0\0\0\0\x5\0\0\x2\x80\0\0\0\x1\0\0\0\0\0\0\0h\0\0\0\x1\0\0\0\0\0\0\0h\0\0\0\x1\0\0\0\0\0\0\0h\0\0\0\x1\0\0\0\0\0\0\0\xe0\0\0\0\x1\0\0\0\0\0\0\x3\xe8\0\0\0\0h) |
|
||||||
UILayout\geometry="@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\xe3\0\0\0\x99\0\0\x4r\0\0\x2\x31\0\0\0\xe4\0\0\0\xad\0\0\x4q\0\0\x2,\0\0\0\0\0\0\0\0\x5V\0\0\0\xe4\0\0\0\xad\0\0\x4q\0\0\x2,)" |
|
||||||
UILayout\geometryRenderWindow=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x14\0\0\0\x63\0\0\0\x31\0\0\0\0\0\0\0\x14\0\0\0\x63\0\0\0\x31\0\0\0\0\0\0\0\0\x5V\0\0\0\0\0\0\0\x14\0\0\0\x63\0\0\0\x31) |
|
||||||
UILayout\microProfileDialogGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x14\0\0\x3\xe7\0\0\x2k\0\0\0\0\0\0\0\x14\0\0\x3\xe7\0\0\x2k\0\0\0\0\0\0\0\0\x5V\0\0\0\0\0\0\0\x14\0\0\x3\xe7\0\0\x2k) |
|
||||||
UILayout\microProfileDialogVisible=false |
|
||||||
UILayout\microProfileDialogVisible\default=true |
|
||||||
UILayout\state=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\x2\0\0\0\0\0\0\0\0\0\0\0\0\xfc\x2\0\0\0\x1\xfb\0\0\0\x1c\0W\0\x61\0i\0t\0T\0r\0\x65\0\x65\0W\0i\0\x64\0g\0\x65\0t\0\0\0\0\0\xff\xff\xff\xff\0\0\0V\0\xff\xff\xff\0\0\0\x1\0\0\0\0\0\0\0\0\xfc\x2\0\0\0\b\xfb\0\0\0\x18\0\x41\0R\0M\0R\0\x65\0g\0i\0s\0t\0\x65\0r\0s\0\0\0\0\0\xff\xff\xff\xff\0\0\0h\0\xff\xff\xff\xfb\0\0\0 \0G\0r\0\x61\0p\0h\0i\0\x63\0s\0\x44\0\x65\0\x62\0u\0g\0g\0\x65\0r\0\0\0\0\0\xff\xff\xff\xff\0\0\0V\0\xff\xff\xff\xfb\0\0\0\"\0P\0i\0\x63\0\x61\0 \0\x43\0o\0m\0m\0\x61\0n\0\x64\0 \0L\0i\0s\0t\0\0\0\0\0\xff\xff\xff\xff\0\0\0\x8d\0\xff\xff\xff\xfb\0\0\0*\0P\0i\0\x63\0\x61\0\x42\0r\0\x65\0\x61\0k\0P\0o\0i\0n\0t\0s\0W\0i\0\x64\0g\0\x65\0t\0\0\0\0\0\xff\xff\xff\xff\0\0\0\x8d\0\xff\xff\xff\xfb\0\0\0 \0P\0i\0\x63\0\x61\0V\0\x65\0r\0t\0\x65\0x\0S\0h\0\x61\0\x64\0\x65\0r\0\0\0\0\0\xff\xff\xff\xff\0\0\x1\xb8\0\xff\xff\xff\xfb\0\0\0\x12\0\x43\0i\0T\0r\0\x61\0\x63\0i\0n\0g\0\0\0\0\0\xff\xff\xff\xff\0\0\0G\0\xff\xff\xff\xfb\0\0\0.\0L\0L\0\x45\0S\0\x65\0r\0v\0i\0\x63\0\x65\0M\0o\0\x64\0u\0l\0\x65\0s\0W\0i\0\x64\0g\0\x65\0t\0\0\0\0\0\xff\xff\xff\xff\0\0\0V\0\xff\xff\xff\xfb\0\0\0\x16\0I\0P\0\x43\0R\0\x65\0\x63\0o\0r\0\x64\0\x65\0r\0\0\0\0\0\xff\xff\xff\xff\0\0\0\xcb\0\xff\xff\xff\0\0\x3\x8e\0\0\x1J\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\0) |
|
||||||
Updater\check_for_update_on_start=true |
|
||||||
Updater\check_for_update_on_start\default=true |
|
||||||
Updater\update_on_close=false |
|
||||||
Updater\update_on_close\default=true |
|
||||||
calloutFlags=1 |
|
||||||
calloutFlags\default=false |
|
||||||
confirmClose=true |
|
||||||
confirmClose\default=true |
|
||||||
displayTitleBars=true |
|
||||||
displayTitleBars\default=true |
|
||||||
enable_discord_presence=false |
|
||||||
enable_discord_presence\default=false |
|
||||||
firstStart=false |
|
||||||
firstStart\default=false |
|
||||||
fullscreen=false |
|
||||||
fullscreen\default=true |
|
||||||
hideInactiveMouse=false |
|
||||||
hideInactiveMouse\default=true |
|
||||||
pauseWhenInBackground=false |
|
||||||
pauseWhenInBackground\default=true |
|
||||||
screenshot_resolution_factor=0 |
|
||||||
screenshot_resolution_factor\default=true |
|
||||||
showConsole=false |
|
||||||
showConsole\default=true |
|
||||||
showFilterBar=true |
|
||||||
showFilterBar\default=true |
|
||||||
showStatusBar=true |
|
||||||
showStatusBar\default=true |
|
||||||
singleWindowMode=true |
|
||||||
singleWindowMode\default=true |
|
||||||
theme=colorful_dark |
|
||||||
theme\default=false |
|
||||||
|
|
||||||
[Utility] |
|
||||||
custom_textures=false |
|
||||||
custom_textures\default=true |
|
||||||
dump_textures=false |
|
||||||
dump_textures\default=true |
|
||||||
preload_textures=false |
|
||||||
preload_textures\default=true |
|
||||||
|
|
||||||
[VideoDumping] |
|
||||||
audio_bitrate=64000 |
|
||||||
audio_bitrate\default=true |
|
||||||
audio_encoder=libvorbis |
|
||||||
audio_encoder\default=true |
|
||||||
audio_encoder_options= |
|
||||||
audio_encoder_options\default=true |
|
||||||
format_options= |
|
||||||
output_format=webm |
|
||||||
output_format\default=true |
|
||||||
video_bitrate=2500000 |
|
||||||
video_bitrate\default=true |
|
||||||
video_encoder=libvpx-vp9 |
|
||||||
video_encoder\default=true |
|
||||||
video_encoder_options="quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1" |
|
||||||
video_encoder_options\default=true |
|
||||||
|
|
||||||
[WebService] |
|
||||||
citra_token= |
|
||||||
citra_username= |
|
||||||
enable_telemetry=false |
|
||||||
enable_telemetry\default=false |
|
||||||
web_api_url=https://api.citra-emu.org |
|
||||||
web_api_url\default=true |
|
@ -1,26 +0,0 @@ |
|||||||
version: '2' |
|
||||||
services: |
|
||||||
citra: |
|
||||||
build: |
|
||||||
context: . |
|
||||||
dockerfile: docker/citra.dockerfile |
|
||||||
shm_size: 2G |
|
||||||
environment: |
|
||||||
OPENBOX_ARGS: '--startup "citra-qt ${TEST_FILE:?}"' |
|
||||||
RESOLUTION: 1366x768 |
|
||||||
ports: |
|
||||||
- "6080:80" |
|
||||||
- "5900:5900" |
|
||||||
volumes: |
|
||||||
- "./citra:/tmp/citra" |
|
||||||
- "${TEST_FILE:?}:${TEST_FILE:?}:ro" |
|
||||||
|
|
||||||
driver: |
|
||||||
build: |
|
||||||
context: . |
|
||||||
dockerfile: docker/driver.dockerfile |
|
||||||
volumes: |
|
||||||
- "./driver:/tmp/driver" |
|
||||||
working_dir: /tmp/driver/out |
|
||||||
command: ["/usr/libexec/vncdo.sh", "/tmp/driver/main.vdo"] |
|
||||||
|
|
@ -1,20 +0,0 @@ |
|||||||
FROM dorowu/ubuntu-desktop-lxde-vnc |
|
||||||
|
|
||||||
RUN apt-get update -y && \ |
|
||||||
apt-get install -y \ |
|
||||||
libqt5gui5 \ |
|
||||||
libqt5multimedia5 |
|
||||||
|
|
||||||
|
|
||||||
ARG CITRA_RELEASE=nightly-1763 |
|
||||||
ARG CITRA_RELEASE_FILE=citra-linux-20220503-856b3d6.tar.xz |
|
||||||
|
|
||||||
WORKDIR /tmp |
|
||||||
RUN wget https://github.com/citra-emu/citra-nightly/releases/download/${CITRA_RELEASE}/${CITRA_RELEASE_FILE} |
|
||||||
RUN mkdir -p citra && \ |
|
||||||
tar --strip-components 1 -C citra -xvf ${CITRA_RELEASE_FILE} && \ |
|
||||||
cp citra/citra-qt citra/citra /usr/local/bin |
|
||||||
|
|
||||||
COPY citra/qt-config.ini /root/.config/citra-emu/ |
|
||||||
|
|
||||||
ENV OPENBOX_ARGS='--startup "citra-qt /root/hello-world.3dsx"' |
|
@ -0,0 +1,15 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
set -euxo pipefail |
||||||
|
|
||||||
|
CITRA_CHANNEL=$1 |
||||||
|
CITRA_RELEASE=$2 |
||||||
|
|
||||||
|
RELEASE_API="https://api.github.com/repos/citra-emu/citra-${CITRA_CHANNEL}/releases/tags/${CITRA_CHANNEL}-${CITRA_RELEASE}" |
||||||
|
|
||||||
|
curl "${RELEASE_API}" | |
||||||
|
jq --raw-output '.assets[].browser_download_url' | |
||||||
|
grep -E 'citra-linux-.*.tar.gz' | |
||||||
|
xargs wget -O citra-linux.tar.gz |
||||||
|
|
||||||
|
tar --strip-components 1 -xvf citra-linux.tar.gz |
@ -1,10 +0,0 @@ |
|||||||
FROM ghcr.io/ian-h-chamberlain/rust-devkitarm |
|
||||||
|
|
||||||
RUN apt-get update -y && \ |
|
||||||
apt-get install -y python3 python3-pip python3-pil |
|
||||||
|
|
||||||
RUN pip3 install vncdotool |
|
||||||
|
|
||||||
COPY driver/vncdo.sh /usr/libexec/ |
|
||||||
|
|
||||||
CMD [ "vncdo", "--version" ] |
|
@ -0,0 +1,50 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Clean up child processes on exit: https://stackoverflow.com/a/2173421/14436105 |
||||||
|
trap "pkill -P $$" EXIT INT TERM |
||||||
|
|
||||||
|
mkdir -p ~/.config/citra-emu |
||||||
|
cp /app/sdl2-config.ini ~/.config/citra-emu |
||||||
|
# For some reason, log file is only written when this dir already exists, |
||||||
|
# but it is only created after the first run of citra (our only run, in the container) |
||||||
|
mkdir -p ~/.local/share/citra-emu/ |
||||||
|
|
||||||
|
ERRS=0 |
||||||
|
# shellcheck disable=SC2068 |
||||||
|
for EXE in $@; do |
||||||
|
VIDEO_OUT="$(dirname "$EXE")/$(basename "$EXE" .elf)_capture.webm" |
||||||
|
|
||||||
|
# colored logs would be nice, but we can always just grab the plaintext log file |
||||||
|
xvfb-run citra \ |
||||||
|
--appimage-extract-and-run \ |
||||||
|
--dump-video="$VIDEO_OUT" \ |
||||||
|
"$EXE" \ |
||||||
|
&>/dev/null & |
||||||
|
PID=$! |
||||||
|
|
||||||
|
# Citra takes a little while to start up, so wait a little before we try to connect |
||||||
|
sleep 3 |
||||||
|
|
||||||
|
arm-none-eabi-gdb --silent --batch-silent --command /app/test-runner.gdb "$EXE" |
||||||
|
STATUS=$? |
||||||
|
if [ $STATUS -ne 0 ]; then |
||||||
|
echo >&2 "FAILED (exit status $STATUS): $EXE" |
||||||
|
ERRS=$(( ERRS + 1 )) |
||||||
|
fi |
||||||
|
|
||||||
|
kill -INT $PID &>/dev/null |
||||||
|
sleep 1 |
||||||
|
if kill -0 $PID &>/dev/null; then |
||||||
|
kill -KILL $PID &>/dev/null |
||||||
|
fi |
||||||
|
|
||||||
|
CITRA_LOG=~/.local/share/citra-emu/log/citra_log.txt |
||||||
|
CITRA_LOG_OUT="$(dirname "$EXE")/$(basename "$EXE" .elf)_citra_log.txt" |
||||||
|
if test -f "$CITRA_LOG"; then |
||||||
|
cp "$CITRA_LOG" "$CITRA_LOG_OUT" |
||||||
|
else |
||||||
|
echo "WARNING: citra log not found" |
||||||
|
fi |
||||||
|
done |
||||||
|
|
||||||
|
exit $ERRS |
@ -0,0 +1,12 @@ |
|||||||
|
[Miscellaneous] |
||||||
|
log_filter = *:Info Debug.Emulated:Debug |
||||||
|
|
||||||
|
[Debugging] |
||||||
|
use_gdbstub=true |
||||||
|
gdbstub_port=4000 |
||||||
|
|
||||||
|
[WebService] |
||||||
|
enable_telemetry = 0 |
||||||
|
|
||||||
|
[Video Dumping] |
||||||
|
output_format = webm |
@ -0,0 +1,13 @@ |
|||||||
|
# https://github.com/devkitPro/libctru/blob/master/libctru/source/system/stack_adjust.s#LL28C23-L28C23 |
||||||
|
# or should this be `_exit` ? |
||||||
|
break __ctru_exit |
||||||
|
commands |
||||||
|
# ARM calling convention will put the exit code in r0 when __ctru_exit is called. |
||||||
|
# Just tell GDB to exit with the same code, since it doesn't get passed back when |
||||||
|
# the program exits |
||||||
|
quit $r0 |
||||||
|
end |
||||||
|
|
||||||
|
target extended-remote :4000 |
||||||
|
continue |
||||||
|
quit |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 88 KiB |
@ -1,16 +0,0 @@ |
|||||||
# Wait for citra to finish startup |
|
||||||
sleep 3 |
|
||||||
|
|
||||||
# Main test logic |
|
||||||
key ctrl-p type "/tmp/citra/out/screenshot0.png" key enter |
|
||||||
keydown a keyup a |
|
||||||
keydown s keyup s |
|
||||||
keydown z keyup z |
|
||||||
keydown q |
|
||||||
keydown a keyup a |
|
||||||
keydown s keyup s |
|
||||||
keydown z keyup z |
|
||||||
keyup q |
|
||||||
key ctrl-p type "/tmp/citra/out/screenshot1.png" key enter |
|
||||||
|
|
||||||
capture final.png |
|
@ -1,8 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
while ! vncdo -s citra pause 0 &>/dev/null; do |
|
||||||
echo "waiting for VNC server..." |
|
||||||
sleep 1 |
|
||||||
done |
|
||||||
|
|
||||||
exec vncdo -s citra --nocursor --delay 400 "$@" |
|
@ -1,20 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
set -eux -o pipefail |
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then |
|
||||||
echo "Usage: run.sh 3DSX_FILE" |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
|
|
||||||
trap 'docker-compose down' EXIT |
|
||||||
|
|
||||||
rm -rf citra/out driver/out |
|
||||||
mkdir -p citra/out driver/out |
|
||||||
|
|
||||||
TEST_FILE=$(realpath "$1") |
|
||||||
export TEST_FILE |
|
||||||
|
|
||||||
docker-compose build |
|
||||||
docker-compose up -d citra |
|
||||||
docker-compose run driver |
|
@ -0,0 +1,51 @@ |
|||||||
|
use ctru::prelude::*; |
||||||
|
use ctru::services::gfx::{Flush, Swap}; |
||||||
|
|
||||||
|
use super::TestRunner; |
||||||
|
|
||||||
|
pub struct ConsoleRunner { |
||||||
|
gfx: Gfx, |
||||||
|
hid: Hid, |
||||||
|
apt: Apt, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for ConsoleRunner { |
||||||
|
fn default() -> Self { |
||||||
|
let gfx = Gfx::new().unwrap(); |
||||||
|
let hid = Hid::new().unwrap(); |
||||||
|
let apt = Apt::new().unwrap(); |
||||||
|
|
||||||
|
gfx.top_screen.borrow_mut().set_wide_mode(true); |
||||||
|
|
||||||
|
Self { gfx, hid, apt } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TestRunner for ConsoleRunner { |
||||||
|
type Context<'this> = Console<'this>; |
||||||
|
|
||||||
|
fn setup(&mut self) -> Self::Context<'_> { |
||||||
|
Console::new(self.gfx.top_screen.borrow_mut()) |
||||||
|
} |
||||||
|
|
||||||
|
fn cleanup(mut self, _test_result: std::io::Result<bool>) { |
||||||
|
// We don't actually care about the test result, either way we'll stop
|
||||||
|
// and show the results to the user
|
||||||
|
|
||||||
|
// Wait to make sure the user can actually see the results before we exit
|
||||||
|
println!("Press START to exit."); |
||||||
|
|
||||||
|
while self.apt.main_loop() { |
||||||
|
let mut screen = self.gfx.top_screen.borrow_mut(); |
||||||
|
screen.flush_buffers(); |
||||||
|
screen.swap_buffers(); |
||||||
|
|
||||||
|
self.gfx.wait_for_vblank(); |
||||||
|
|
||||||
|
self.hid.scan_input(); |
||||||
|
if self.hid.keys_down().contains(KeyPad::START) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
use ctru::error::ResultCode; |
||||||
|
|
||||||
|
use super::TestRunner; |
||||||
|
|
||||||
|
#[derive(Default)] |
||||||
|
pub struct GdbRunner; |
||||||
|
|
||||||
|
impl Drop for GdbRunner { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { ctru_sys::gdbHioDevExit() } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TestRunner for GdbRunner { |
||||||
|
type Context<'this> = (); |
||||||
|
|
||||||
|
fn setup(&mut self) -> Self::Context<'_> { |
||||||
|
// TODO: `ctru` expose safe API to do this and call that instead
|
||||||
|
|| -> ctru::Result<()> { |
||||||
|
unsafe { |
||||||
|
ResultCode(ctru_sys::gdbHioDevInit())?; |
||||||
|
// TODO: should we actually redirect stdin or nah?
|
||||||
|
ResultCode(ctru_sys::gdbHioDevRedirectStdStreams(true, true, true))?; |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
}() |
||||||
|
.expect("failed to redirect I/O streams to GDB"); |
||||||
|
} |
||||||
|
|
||||||
|
fn cleanup(self, test_result: std::io::Result<bool>) { |
||||||
|
// GDB actually has the opportunity to inspect the exit code,
|
||||||
|
// unlike other runners, so let's follow the default behavior of the
|
||||||
|
// stdlib test runner.
|
||||||
|
match test_result { |
||||||
|
Ok(success) => { |
||||||
|
if success { |
||||||
|
std::process::exit(0); |
||||||
|
} else { |
||||||
|
std::process::exit(101); |
||||||
|
} |
||||||
|
} |
||||||
|
Err(err) => { |
||||||
|
eprintln!("Error: {err}"); |
||||||
|
std::process::exit(101); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
//! Custom test runner for building/running tests on the 3DS.
|
||||||
|
//!
|
||||||
|
//! This library can be used with
|
||||||
|
//! [`custom_test_frameworks`](https://doc.rust-lang.org/unstable-book/language-features/custom-test-frameworks.html)
|
||||||
|
//! to enable normal Rust testing workflows for 3DS homebrew.
|
||||||
|
|
||||||
|
#![feature(test)] |
||||||
|
#![feature(custom_test_frameworks)] |
||||||
|
#![test_runner(run_gdb)] |
||||||
|
|
||||||
|
extern crate test; |
||||||
|
|
||||||
|
mod console; |
||||||
|
mod gdb; |
||||||
|
mod socket; |
||||||
|
|
||||||
|
use console::ConsoleRunner; |
||||||
|
use gdb::GdbRunner; |
||||||
|
use socket::SocketRunner; |
||||||
|
|
||||||
|
use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; |
||||||
|
|
||||||
|
/// Show test output in GDB, using the [File I/O Protocol] (called HIO in some 3DS
|
||||||
|
/// homebrew resources). Both stdout and stderr will be printed to the GDB console.
|
||||||
|
///
|
||||||
|
/// [File I/O Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Overview.html#File_002dI_002fO-Overview
|
||||||
|
pub fn run_gdb(tests: &[&TestDescAndFn]) { |
||||||
|
run::<GdbRunner>(tests) |
||||||
|
} |
||||||
|
|
||||||
|
/// Run tests using the `ctru` [`Console`] (print results to the 3DS screen).
|
||||||
|
/// This is mostly useful for running tests manually, especially on real hardware.
|
||||||
|
///
|
||||||
|
/// [`Console`]: ctru::console::Console
|
||||||
|
pub fn run_console(tests: &[&TestDescAndFn]) { |
||||||
|
run::<ConsoleRunner>(tests) |
||||||
|
} |
||||||
|
|
||||||
|
/// Show test output via a network socket to `3dslink`. This runner is only useful
|
||||||
|
/// on real hardware, since `3dslink` doesn't work with emulators.
|
||||||
|
///
|
||||||
|
/// See [`Soc::redirect_to_3dslink`] for more details.
|
||||||
|
///
|
||||||
|
/// [`Soc::redirect_to_3dslink`]: ctru::services::soc::Soc::redirect_to_3dslink
|
||||||
|
pub fn run_socket(tests: &[&TestDescAndFn]) { |
||||||
|
run::<SocketRunner>(tests) |
||||||
|
} |
||||||
|
|
||||||
|
fn run<Runner: TestRunner>(tests: &[&TestDescAndFn]) { |
||||||
|
std::env::set_var("RUST_BACKTRACE", "1"); |
||||||
|
|
||||||
|
let mut runner = Runner::default(); |
||||||
|
let ctx = runner.setup(); |
||||||
|
|
||||||
|
let opts = TestOpts { |
||||||
|
force_run_in_process: true, |
||||||
|
run_tests: true, |
||||||
|
// TODO: color doesn't work because of TERM/TERMINFO.
|
||||||
|
// With RomFS we might be able to fake this out nicely...
|
||||||
|
color: ColorConfig::AlwaysColor, |
||||||
|
format: OutputFormat::Pretty, |
||||||
|
test_threads: Some(1), |
||||||
|
// Hopefully this interface is more stable vs specifying individual options,
|
||||||
|
// and parsing the empty list of args should always work, I think.
|
||||||
|
// TODO Ideally we could pass actual std::env::args() here too
|
||||||
|
..test::test::parse_opts(&[]).unwrap().unwrap() |
||||||
|
}; |
||||||
|
|
||||||
|
let tests = tests.iter().map(|t| make_owned_test(t)).collect(); |
||||||
|
let result = test::run_tests_console(&opts, tests); |
||||||
|
|
||||||
|
drop(ctx); |
||||||
|
|
||||||
|
runner.cleanup(result); |
||||||
|
} |
||||||
|
|
||||||
|
/// Adapted from [`test::make_owned_test`].
|
||||||
|
/// Clones static values for putting into a dynamic vector, which `test_main()`
|
||||||
|
/// needs to hand out ownership of tests to parallel test runners.
|
||||||
|
///
|
||||||
|
/// This will panic when fed any dynamic tests, because they cannot be cloned.
|
||||||
|
fn make_owned_test(test: &TestDescAndFn) -> TestDescAndFn { |
||||||
|
let testfn = match test.testfn { |
||||||
|
TestFn::StaticTestFn(f) => TestFn::StaticTestFn(f), |
||||||
|
TestFn::StaticBenchFn(f) => TestFn::StaticBenchFn(f), |
||||||
|
_ => panic!("non-static tests passed to test::test_main_static"), |
||||||
|
}; |
||||||
|
|
||||||
|
TestDescAndFn { |
||||||
|
testfn, |
||||||
|
desc: test.desc.clone(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A helper trait to make the behavior of test runners consistent.
|
||||||
|
trait TestRunner: Sized + Default { |
||||||
|
/// Any context the test runner needs to remain alive for the duration of
|
||||||
|
/// the test. This can be used for things that need to borrow the test runner
|
||||||
|
/// itself.
|
||||||
|
// TODO: with associated type defaults this could be `= ();`
|
||||||
|
type Context<'this> |
||||||
|
where |
||||||
|
Self: 'this; |
||||||
|
|
||||||
|
/// Create the [`Context`](Self::Context), if any.
|
||||||
|
fn setup(&mut self) -> Self::Context<'_>; |
||||||
|
|
||||||
|
/// Handle the results of the test and perform any necessary cleanup.
|
||||||
|
/// The [`Context`](Self::Context) will be dropped just before this is called.
|
||||||
|
fn cleanup(self, test_result: std::io::Result<bool>); |
||||||
|
} |
||||||
|
|
||||||
|
/// This module has stubs needed to link the test library, but they do nothing
|
||||||
|
/// because we don't actually need them for the runner to work.
|
||||||
|
mod link_fix { |
||||||
|
#[no_mangle] |
||||||
|
extern "C" fn execvp( |
||||||
|
_argc: *const libc::c_char, |
||||||
|
_argv: *mut *const libc::c_char, |
||||||
|
) -> libc::c_int { |
||||||
|
-1 |
||||||
|
} |
||||||
|
|
||||||
|
#[no_mangle] |
||||||
|
extern "C" fn pipe(_fildes: *mut libc::c_int) -> libc::c_int { |
||||||
|
-1 |
||||||
|
} |
||||||
|
|
||||||
|
#[no_mangle] |
||||||
|
extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int { |
||||||
|
-1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Verify that doctests work as expected
|
||||||
|
/// ```
|
||||||
|
/// assert_eq!(2 + 2, 4);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// assert_eq!(2 + 2, 5);
|
||||||
|
/// ```
|
||||||
|
#[cfg(doctest)] |
||||||
|
struct Dummy; |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
#[test] |
||||||
|
fn it_works() { |
||||||
|
assert_eq!(2 + 2, 4); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
#[should_panic] |
||||||
|
fn it_fails() { |
||||||
|
assert_eq!(2 + 2, 5); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
use ctru::prelude::*; |
||||||
|
|
||||||
|
use super::TestRunner; |
||||||
|
|
||||||
|
pub struct SocketRunner { |
||||||
|
soc: Soc, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for SocketRunner { |
||||||
|
fn default() -> Self { |
||||||
|
Self { |
||||||
|
soc: Soc::new().expect("failed to initialize network service"), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TestRunner for SocketRunner { |
||||||
|
type Context<'this> = (); |
||||||
|
|
||||||
|
fn setup(&mut self) -> Self::Context<'_> { |
||||||
|
self.soc |
||||||
|
.redirect_to_3dslink(true, true) |
||||||
|
.expect("failed to redirect to socket"); |
||||||
|
} |
||||||
|
|
||||||
|
fn cleanup(self, _test_result: std::io::Result<bool>) {} |
||||||
|
} |
Loading…
Reference in new issue