├── .gitignore ├── .gitmodules ├── .rgignore ├── Cargo.toml ├── LICENSE ├── README.md ├── bindgen.sh ├── cargo-psp ├── Cargo.toml └── src │ ├── bin │ ├── mksfo.rs │ ├── pack-pbp.rs │ └── prxgen.rs │ └── main.rs ├── ci ├── Dockerfile-ppsspp ├── concourse │ ├── build-rust.sh │ ├── build-rust.yml │ ├── concourse.yml │ ├── run-tests.sh │ └── run-tests.yml ├── std_verification │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── time_test.rs └── tests │ ├── Cargo.toml │ ├── assets │ ├── blank_screenshot.bmp │ └── embedded_graphics_triangle.bmp │ └── src │ ├── bmp_screenshot_test.rs │ ├── main.rs │ ├── math_test.rs │ └── vram_test.rs ├── demo.gif ├── examples ├── clock-speed │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── cube │ ├── .gitignore │ ├── Cargo.toml │ ├── ferris.bin │ ├── ferris.png │ └── src │ │ └── main.rs ├── embedded-graphics │ ├── .gitignore │ ├── Cargo.toml │ ├── assets │ │ └── ferris.bmp │ └── src │ │ └── main.rs ├── gu-background │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── gu-debug-print │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── hello-world │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── msg-dialog │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── paint-mode │ ├── Cargo.toml │ ├── Psp.toml │ └── src │ │ ├── analog_stick_to_delta.rs │ │ ├── background.rs │ │ ├── debug_textbox.rs │ │ ├── drawobject.rs │ │ ├── lib.rs │ │ └── main.rs ├── rainbow │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── rust-std-hello-world │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── time │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── vfpu-addition │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── vfpu-context-switching │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs └── wlan │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── main.rs ├── libunwind ├── .gitignore ├── Makefile └── no-sdc1.patch ├── prx.md └── psp ├── Cargo.toml ├── build.rs ├── libunwind.a └── src ├── alloc_impl.rs ├── benchmark.rs ├── constants.rs ├── debug.rs ├── eabi.rs ├── embedded_graphics.rs ├── lib.rs ├── math ├── mod.rs └── trig.rs ├── msxfont.bin ├── panic.rs ├── screenshot.rs ├── sys ├── atrac.rs ├── audio.rs ├── codec.rs ├── ctrl.rs ├── debugfont.bin ├── display.rs ├── font.rs ├── ge.rs ├── gu.rs ├── gum.rs ├── hprm.rs ├── io.rs ├── jpeg.rs ├── kernel │ ├── mod.rs │ └── thread.rs ├── macros.rs ├── mod.rs ├── mp3.rs ├── mpeg.rs ├── nand.rs ├── net.rs ├── openpsid.rs ├── power.rs ├── registry.rs ├── rtc.rs ├── sircs.rs ├── types.rs ├── umd.rs ├── usb.rs ├── utility.rs ├── vfpu_context.rs └── wlan.rs ├── test_runner.rs ├── vfpu.rs └── vram_alloc.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libunwind/rustc"] 2 | path = libunwind/rustc 3 | url = https://github.com/rust-lang/rust 4 | -------------------------------------------------------------------------------- /.rgignore: -------------------------------------------------------------------------------- 1 | libunwind/rustc 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ "psp", "cargo-psp" ] 4 | exclude = [ "examples", "ci" ] 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2020 Marko Mijalkovic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | 11 | This project also uses the PSPSDK as a reference: 12 | 13 | Copyright (c) 2005 adresd 14 | Copyright (c) 2005 Marcus R. Brown 15 | Copyright (c) 2005 James Forshaw 16 | Copyright (c) 2005 John Kelley 17 | Copyright (c) 2005 Jesper Svennevid 18 | All rights reserved. 19 | 20 | Redistribution and use in source and binary forms, with or without 21 | modification, are permitted provided that the following conditions 22 | are met: 23 | 1. Redistributions of source code must retain the above copyright 24 | notice, this list of conditions and the following disclaimer. 25 | 2. Redistributions in binary form must reproduce the above copyright 26 | notice, this list of conditions and the following disclaimer in the 27 | documentation and/or other materials provided with the distribution. 28 | 3. The names of the authors may not be used to endorse or promote products 29 | derived from this software without specific prior written permission. 30 | 31 | THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 32 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 33 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 34 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 35 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 37 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 38 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 39 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 40 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

rust-psp

2 | 3 |

4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 |

16 | A library for building full PSP modules, including both PRX plugins and regular 17 | homebrew apps. 18 |

19 | 20 | ```rust 21 | #![no_std] 22 | #![no_main] 23 | 24 | psp::module!("sample_module", 1, 1); 25 | 26 | fn psp_main() { 27 | psp::enable_home_button(); 28 | psp::dprintln!("Hello PSP from rust!"); 29 | } 30 | ``` 31 | 32 | See `examples` directory for sample programs. 33 | 34 | ## What about PSPSDK? 35 | 36 | This project is a completely new SDK, with no dependency on the original C/C++ 37 | PSPSDK. It aims to be a **complete** replacement, with more efficient 38 | implementations of graphics functions, and the addition of missing libraries. 39 | 40 | ## Features / Roadmap 41 | 42 | - [x] `core` support 43 | - [x] PSP system library support 44 | - [x] `alloc` support 45 | - [x] `panic = "unwind"` support 46 | - [x] Macro-based VFPU assembler 47 | - [x] Full 3D graphics support (faster than PSPSDK in some cases!) 48 | - [x] No dependency on PSPSDK / PSPToolchain 49 | - [x] Reach full parity with user mode support in PSPSDK 50 | - [x] Port definitions to `libc` crate 51 | - [ ] Add support for creating kernel mode modules 52 | - [ ] Add `std` support 53 | - [ ] Automatically sign EBOOT.PBP files to run on unmodified PSPs 54 | - [ ] Implement / reverse undiscovered libraries 55 | 56 | ## Dependencies 57 | 58 | To compile for the PSP, you will need a Rust **nightly** version equal to or 59 | later than `2020-06-05` and the `rust-src` component. Please install Rust using 60 | https://rustup.rs/ 61 | 62 | Use the following if you are new to Rust. (Feel free to set an override manually 63 | per-project instead). 64 | 65 | ```sh 66 | $ rustup default nightly && rustup component add rust-src 67 | ``` 68 | 69 | You also need `cargo-psp` installed: 70 | 71 | ```sh 72 | $ cargo install cargo-psp 73 | ``` 74 | 75 | ## Running Examples 76 | 77 | Enter one of the example directories, `examples/hello-world` for instance, and 78 | run `cargo psp`. 79 | 80 | This will create an `EBOOT.PBP` file under `target/mipsel-sony-psp/debug/` 81 | 82 | Assuming you have a PSP with custom firmware installed, you can simply copy this 83 | file into a new directory under `PSP/GAME` on your memory stick, and it will 84 | show up in your XMB menu. 85 | 86 | ``` 87 | . 88 | └── PSP 89 | └── GAME 90 | └── hello-world 91 | └── EBOOT.PBP 92 | ``` 93 | 94 | If you do not have a PSP, we recommend using the [PPSSPP emulator](http://ppsspp.org). 95 | Note that graphics code is very sensitive so if you're writing graphics code we 96 | recommend developing on real hardware. PPSSPP is more relaxed in some aspects. 97 | 98 | ### Advanced usage: `PRXEncrypter` 99 | 100 | If you don't have a PSP with CFW installed, you can manually sign the PRX using 101 | `PRXEncrypter`, and then re-package it using `pack-pbp`. 102 | 103 | ### Advanced usage: PSPLink 104 | 105 | If you have the PSPSDK installed and have built a working copy PSPLink manually, 106 | you can also use `psplink` and `pspsh` to run the `.prx` under 107 | `target/mipsel-sony-psp/debug/` if you prefer. Refer to the installation and 108 | usage guides for those programs. 109 | 110 | ### Debugging 111 | 112 | `psp-gdb` is currently too old to support printing Rust types. `rust-lldb` may 113 | be possible but it has not be experimented with yet. 114 | 115 | ## Usage 116 | 117 | To use the `psp` crate in your own Rust programs, add it to `Cargo.toml` like 118 | any other dependency: 119 | 120 | ```toml 121 | [dependencies] 122 | psp = "x.y.z" 123 | ``` 124 | 125 | In your `main.rs` file, you need to setup a basic skeleton like so: 126 | 127 | ```rust 128 | #![no_std] 129 | #![no_main] 130 | 131 | // Create a module named "sample_module" with version 1.0 132 | psp::module!("sample_module", 1, 0); 133 | 134 | fn psp_main() { 135 | psp::enable_home_button(); 136 | psp::dprintln!("Hello PSP from rust!"); 137 | } 138 | ``` 139 | 140 | Now you can simply run `cargo psp` to build your `EBOOT.PBP` file. You can also 141 | invoke `cargo psp --release` to create a release build. 142 | 143 | If you would like to customize your EBOOT with e.g. an icon or new title, you 144 | can create a `Psp.toml` file in the root of your project. Note that all keys are 145 | optional: 146 | 147 | ```toml 148 | title = "XMB title" 149 | xmb_icon_png = "path/to/24bit_144x80_image.png" 150 | xmb_background_png = "path/to/24bit_480x272_background.png" 151 | xmb_music_at3 = "path/to/ATRAC3_audio.at3" 152 | ``` 153 | 154 | More options can be found in the schema defintion [here](/cargo-psp/src/main.rs#L11-L91). 155 | 156 | ## Known Bugs 157 | 158 | This crate **breaks** on builds with `opt-level=0`. Likely due to a bug in EABI 159 | interop. `cargo-psp` patches over this by passing `-C opt-level=3`. 160 | 161 | ## `error[E0460]: found possibly newer version of crate ...` 162 | 163 | If you get an error like this: 164 | 165 | ``` 166 | error[E0460]: found possibly newer version of crate `panic_unwind` which `psp` depends on 167 | --> src/main.rs:4:5 168 | | 169 | 4 | use psp::dprintln; 170 | | ^^^ 171 | | 172 | = note: perhaps that crate needs to be recompiled? 173 | ``` 174 | 175 | Simply clean your target directory and it will be fixed: 176 | 177 | ```sh 178 | $ cargo clean 179 | ``` 180 | -------------------------------------------------------------------------------- /bindgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Quick and dirty script to generate psp_extern! bindings from a PSPSDK header. 4 | 5 | header() { 6 | bindgen $1 --no-layout-tests --whitelist-function 'sce.*' -- \ 7 | -I /usr/local/pspdev/lib/gcc/psp/9.3.0/include \ 8 | -I /usr/local/pspdev/psp/sdk/include \ 9 | -I /usr/local/pspdev/psp/include \ 10 | -include sys/types.h -include pspkerneltypes.h \ 11 | | sed -re ' 12 | # Format doc comments as triple slashes 13 | s/#\[doc = "([^"]*)"\]/\/\/\/\1/g 14 | 15 | # Remove redundant raw types 16 | s/::std::os::raw::c_int/i32/g 17 | s/::std::os::raw::c_uint/u32/g 18 | s/::std::os::raw::c_void/c_void/g 19 | s/::std::os::raw::c_char/u8/g 20 | s/::std::os::raw::c_uchar/u8/g 21 | s/::std::os::raw::c_ulong/u32/g 22 | s/::std::os::raw::c_long/i32/g 23 | s/::std::os::raw::c_ushort/u16/g 24 | s/::std::os::raw::c_short/i16/g 25 | s/::std::option::Option/Option/g 26 | 27 | s/uint/u32/g 28 | 29 | s/SceSize/usize/g 30 | s/SceSSize/isize/g 31 | s/SceVoid/c_void/g 32 | 33 | s/SceInt32/i32/g 34 | s/SceShort16/i16/g 35 | 36 | s/SceUInt32/u32/g 37 | s/SceUChar8/u8/g 38 | 39 | s/@param (\w*)\s+- /- `\1`: / 40 | s/@return (.*)/# Return Value\n \/\/\/\n \/\/\/ \1/ 41 | 42 | # Delete redundant types resulting from previous replacements 43 | /pub type (\w+) = (\1);/d 44 | /pub type (usize|isize) =/d 45 | ' \ 46 | | awk ' 47 | # Bindgen generates multiple extern blocks so we want to merge them. 48 | /^}$/ { if (found_extern) { printf "\n"; next } } 49 | /^extern/ { 50 | if (!found_extern) { 51 | found_extern = 1; 52 | printf "\npsp_extern! {\n"; 53 | } 54 | 55 | next; 56 | } 57 | 58 | # Add parameters header 59 | /\/\/\/ - `/ { if (!params) printf " /// # Parameters\n ///\n" } 60 | { if ($0 ~ /\s*\/\/\/ - `/) params = 1; else params = 0 } 61 | 62 | { print } 63 | 64 | END { print "}" } 65 | ' \ 66 | | rustfmt 67 | } 68 | 69 | pspModule() { 70 | grep 'IMPORT_' < $1 \ 71 | | sed ' 72 | s/.*IMPORT_START\s*// 73 | s/.*IMPORT_FUNC.*",// 74 | ' \ 75 | | cat - <(echo) # Add newline 76 | } 77 | 78 | # Utility function for awk 79 | AWK_CAMEL=' 80 | function camelToSnake(s, last, i) { 81 | out = "" 82 | 83 | for (i = 0; i < length(s); i++) { 84 | char = substr(s, i + 1, 1) 85 | 86 | if (tolower(char) != char) { 87 | if (last != i - 1) out = out "_" 88 | last = i 89 | out = out tolower(char) 90 | } else { 91 | out = out char 92 | } 93 | } 94 | 95 | return out 96 | } 97 | ' 98 | 99 | cat <(pspModule $2) <(header $1) \ 100 | | awk "$AWK_CAMEL"' 101 | /^$/ { header_done = 1 } 102 | 103 | { 104 | if (!name) { 105 | name = substr($0, 1, match($0, ",") - 1) 106 | flags = substr($0, match($0, ",") + 1 + 2, 4) 107 | maj_ver = substr($0, match($0, ",") + 1 + 6, 2) 108 | min_ver = substr($0, match($0, ",") + 1 + 8, 2) 109 | 110 | next 111 | } else if (!header_done) { 112 | nid_name = substr($0, match($0, ",") + 1) 113 | nid_hex = substr($0, 1, match($0, ",") - 1) 114 | 115 | # Remove carriage returns 116 | gsub("\r", "", nid_name) 117 | 118 | nid_map[nid_name] = nid_hex 119 | 120 | next 121 | } 122 | } 123 | 124 | # Fix camel case struct fields 125 | /\s*pub \w*:/ { 126 | # field name start and end index 127 | start = match($0, "pub ") + 4 128 | end = match($0, ":") 129 | camel = substr($0, start, end - start) 130 | 131 | printf "%s%s%s\n", substr($0, 1, start - 1), camelToSnake(camel), substr($0, end) 132 | 133 | next 134 | } 135 | 136 | /psp_extern!/ { 137 | print 138 | print " #![name = " name "]" 139 | print " #![flags = 0x" flags "]" 140 | print " #![version = (0x" maj_ver ", 0x" min_ver ")]" 141 | printf "\n" 142 | 143 | RS = "\n\n" 144 | 145 | next 146 | } 147 | 148 | /pub fn/ { 149 | # Function name start and end index 150 | fn_start = match($0, "fn ") + 3 151 | fn_end = match(substr($0, fn_start), "\\(") + fn_start 152 | fn_name = substr($0, fn_start, fn_end - fn_start - 1) 153 | 154 | printf " #[psp(" nid_map[fn_name] ")]\n" 155 | printf substr($0, 1, fn_start - 1) 156 | printf fn_name "(" 157 | 158 | # Substring of the argument list 159 | args = substr($0, fn_end, match(substr($0, fn_end), ")") - 1) 160 | 161 | args_multiline = match(args, "\n") 162 | fn_return = fn_end + length(args) 163 | 164 | if (args_multiline) printf "\n" 165 | 166 | # Fix the argument names 167 | while (1) { 168 | # These are indices 169 | arg_start = match(args, "[^[:space:]]") 170 | arg_colon = match(args, ":") 171 | arg_comma = match(args, ",") 172 | 173 | # Exit if no more arguments 174 | if (!arg_start) break 175 | 176 | arg_len = arg_comma - arg_start 177 | 178 | # Get the camelCase and snake_case variants of the argument name 179 | arg_camel = substr(args, arg_start, arg_colon - arg_start) 180 | arg_snake = camelToSnake(arg_camel) 181 | 182 | # Recreate the argument definition with the correct case 183 | if (arg_comma) arg = substr(args, arg_start, arg_len) 184 | else arg = substr(args, arg_start) 185 | sub(arg_camel, arg_snake, arg) 186 | 187 | if (args_multiline) printf " " arg ",\n" 188 | else if (arg_comma) printf arg ", " 189 | else printf arg 190 | 191 | # Advance the argument substring onto the next one 192 | if (arg_comma) args = substr(args, arg_comma + 1) 193 | else break 194 | } 195 | 196 | if (args_multiline) printf " " 197 | 198 | print substr($0, fn_return) 199 | printf "\n" 200 | 201 | next 202 | } 203 | 204 | { print } 205 | ' \ 206 | | awk "$AWK_CAMEL"' 207 | # Fix camel case doc comment parameter names 208 | /\s*\/\/\/ - `/ { 209 | start = index($0, "`") + 1 210 | end = index(substr($0, start), "`") + start 211 | camel = substr($0, start, end - start) 212 | 213 | sub(camel, camelToSnake(camel), $0) 214 | 215 | print $0 216 | next 217 | } 218 | 219 | { print } 220 | ' 221 | -------------------------------------------------------------------------------- /cargo-psp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-psp" 3 | version = "0.1.1" 4 | description = "`cargo build` wrapper for creating Sony PSP executables" 5 | repository = "https://github.com/overdrivenpotato/rust-psp" 6 | license = "MIT" 7 | authors = [ 8 | "Marko Mijalkovic ", 9 | "Paul Sajna " 10 | ] 11 | edition = "2018" 12 | default-run = "cargo-psp" 13 | 14 | [[bin]] 15 | name = "prxgen" 16 | 17 | [[bin]] 18 | name = "pack-pbp" 19 | 20 | [[bin]] 21 | name = "mksfo" 22 | 23 | [dependencies] 24 | clap = "2.33.1" 25 | goblin = "0.2.3" 26 | scroll = "0.10.1" 27 | cargo_metadata = "0.10.0" 28 | rustc_version = "0.2.3" 29 | 30 | serde = "1.0.111" 31 | serde_derive = "1.0.111" 32 | toml = "0.5.6" 33 | -------------------------------------------------------------------------------- /cargo-psp/src/bin/pack-pbp.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg, AppSettings}; 2 | use std::{fs, mem}; 3 | 4 | const SIGNATURE: [u8; 4] = *b"\0PBP"; 5 | const VERSION: u32 = 0x1_0000; 6 | 7 | struct PbpHeader { 8 | signature: [u8; 4], 9 | version: u32, 10 | offsets: [u32; 8], 11 | } 12 | 13 | impl PbpHeader { 14 | fn to_bytes(self) -> [u8; mem::size_of::()] { 15 | let mut bytes = [0; mem::size_of::()]; 16 | 17 | bytes[0..4].copy_from_slice(&self.signature); 18 | bytes[4..8].copy_from_slice(&self.version.to_le_bytes()); 19 | 20 | for (i, offset) in self.offsets.iter().enumerate() { 21 | let idx = i * 4 + 8; 22 | bytes[idx..idx + 4].copy_from_slice(&offset.to_le_bytes()); 23 | } 24 | 25 | bytes 26 | } 27 | } 28 | 29 | fn main() { 30 | let matches = App::new("pack-pbp") 31 | .version("0.1") 32 | .author("Marko Mijalkovic ") 33 | .about("Create Sony PSP packages") 34 | .setting(AppSettings::ColoredHelp) 35 | .arg( 36 | Arg::with_name("output.pbp") 37 | .takes_value(true) 38 | .help("Output PBP file") 39 | .required(true) 40 | ) 41 | .arg( 42 | Arg::with_name("param.sfo") 43 | .takes_value(true) 44 | .help("Input PARAM.SFO file created with mksfo") 45 | .required(true) 46 | ) 47 | .arg( 48 | Arg::with_name("icon0.png") 49 | .takes_value(true) 50 | .help("Optional XMB icon") 51 | .required(true) 52 | ) 53 | .arg( 54 | Arg::with_name("icon1.pmf") 55 | .takes_value(true) 56 | .help("Optional animated XMB icon") 57 | .required(true) 58 | ) 59 | .arg( 60 | Arg::with_name("pic0.png") 61 | .takes_value(true) 62 | .help("Optional XMB background (overlayed on top of PIC1.PNG)") 63 | .required(true) 64 | ) 65 | .arg( 66 | Arg::with_name("pic1.png") 67 | .takes_value(true) 68 | .help("Optional XMB background") 69 | .required(true) 70 | ) 71 | .arg( 72 | Arg::with_name("snd0.at3") 73 | .takes_value(true) 74 | .help("Optional XMB music (when present, sound from ICON1.PMF is ignored)") 75 | .required(true) 76 | ) 77 | .arg( 78 | Arg::with_name("data.psp") 79 | .takes_value(true) 80 | .help("Executable file") 81 | .required(true) 82 | ) 83 | .arg( 84 | Arg::with_name("data.psar") 85 | .takes_value(true) 86 | .help("Optional archive data") 87 | .required(true) 88 | ) 89 | .get_matches(); 90 | 91 | let read = |name: &str| { 92 | let value = matches.value_of(name).unwrap(); 93 | 94 | if value == "NULL" { 95 | return None; 96 | } 97 | 98 | match fs::read(value) { 99 | Ok(b) => Some(b), 100 | Err(e) => panic!("failed to read {}: {}", value, e), 101 | } 102 | }; 103 | 104 | let output_path = matches.value_of("output.pbp").unwrap(); 105 | 106 | let files = vec![ 107 | read("param.sfo"), 108 | read("icon0.png"), 109 | read("icon1.pmf"), 110 | read("pic0.png"), 111 | read("pic1.png"), 112 | read("snd0.at3"), 113 | read("data.psp"), 114 | read("data.psar"), 115 | ]; 116 | 117 | let mut payload = Vec::new(); 118 | let mut offsets = [0; 8]; 119 | let mut current_offset = mem::size_of::() as u32; 120 | 121 | for (i, bytes) in files.into_iter().enumerate() { 122 | offsets[i] = current_offset; 123 | 124 | if let Some(bytes) = bytes { 125 | let len = bytes.len(); 126 | payload.extend(bytes); 127 | current_offset += len as u32; 128 | } 129 | } 130 | 131 | let header = PbpHeader { 132 | signature: SIGNATURE, 133 | version: VERSION, 134 | offsets, 135 | }; 136 | 137 | let mut output = Vec::new(); 138 | output.extend(&header.to_bytes()[..]); 139 | output.extend(payload); 140 | 141 | if let Err(e) = fs::write(output_path, output) { 142 | panic!("couldn't write to {}: {}", output_path, e); 143 | } 144 | 145 | println!("Saved to {}", output_path); 146 | } 147 | -------------------------------------------------------------------------------- /ci/Dockerfile-ppsspp: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest as ppsspp 2 | 3 | ENV DEBIAN_FRONTEND="noninteractive" 4 | 5 | RUN apt-get -y update 6 | RUN apt-get install -y build-essential cmake git libsdl2-dev python libglew-dev 7 | RUN git clone https://github.com/hrydgard/ppsspp --recursive 8 | WORKDIR /ppsspp/build-sdl 9 | RUN cmake .. \ 10 | -DCMAKE_BUILD_TYPE=Release \ 11 | -DCMAKE_SKIP_RPATH=ON \ 12 | -DHEADLESS=ON \ 13 | -DUSE_SYSTEM_LIBZIP=ON 14 | RUN make 15 | RUN make install 16 | -------------------------------------------------------------------------------- /ci/concourse/build-rust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # If NO_CACHE is *not* set, then setup the cache directories 6 | if [ -z "${NO_CACHE:-}" ]; then 7 | # Cache only for normal builds 8 | export CARGO_HOME="$(pwd)"/.cargo 9 | export XARGO_HOME="$(pwd)"/.xargo 10 | export RUSTUP_HOME="$(pwd)"/.rustup 11 | fi 12 | 13 | rustup set profile minimal 14 | rustup update --no-self-update $RUSTUP_TOOLCHAIN 15 | 16 | # Install rust-src if needed. 17 | if ! rustup component list --installed | grep -q rust-src; then 18 | rustup component add rust-src 19 | fi 20 | 21 | pushd repo/cargo-psp/ 22 | cargo build 23 | popd 24 | 25 | PATH="$(realpath repo)/target/debug:$PATH" 26 | 27 | pushd repo/ci/tests 28 | cargo psp 29 | popd 30 | 31 | cp -r repo/ci/tests/target/mipsel-sony-psp/debug/* rust-build-dir 32 | -------------------------------------------------------------------------------- /ci/concourse/build-rust.yml: -------------------------------------------------------------------------------- 1 | platform: linux 2 | 3 | image_resource: 4 | type: docker-image 5 | source: 6 | repository: rust 7 | 8 | # The version is irrelevant here. This is a small base image that contains 9 | # `rustup`. The actual toolchain is selected with `RUSTUP_TOOLCHAIN` below, 10 | # and should be cached with `RUSTUP_HOME`. 11 | tag: 1.44-slim 12 | 13 | params: 14 | RUSTFLAGS: "-C link-dead-code" 15 | RUSTUP_TOOLCHAIN: nightly-2020-06-05 16 | 17 | inputs: 18 | - name: repo 19 | 20 | outputs: 21 | - name: rust-build-dir 22 | 23 | caches: 24 | - path: .cargo 25 | - path: .xargo 26 | - path: .rustup 27 | - path: repo/ci/tests/target/ 28 | - path: repo/target/ 29 | 30 | run: 31 | path: repo/ci/concourse/build-rust.sh 32 | -------------------------------------------------------------------------------- /ci/concourse/concourse.yml: -------------------------------------------------------------------------------- 1 | resource_types: 2 | - name: pull-request 3 | type: docker-image 4 | source: 5 | repository: teliaoss/github-pr-resource 6 | - name: cron-resource 7 | type: docker-image 8 | source: 9 | repository: cftoolsmiths/cron-resource 10 | - name: slack-alert 11 | type: docker-image 12 | source: 13 | repository: arbourd/concourse-slack-alert-resource 14 | 15 | resources: 16 | - name: master-branch 17 | type: git 18 | source: 19 | uri: https://github.com/overdrivenpotato/rust-psp 20 | branch: master 21 | - name: nightly-trigger 22 | type: cron-resource 23 | source: 24 | # Trigger at 9AM New York time 25 | expression: "0 9 * * *" 26 | location: "America/New_York" 27 | - name: discord-channel 28 | type: slack-alert 29 | source: 30 | url: ((webhook-url)) 31 | - name: pull-request 32 | type: pull-request 33 | source: 34 | repository: overdrivenpotato/rust-psp 35 | access_token: ((gh-access-token)) 36 | 37 | jobs: 38 | - name: run-tests-for-master 39 | public: true 40 | plan: 41 | - do: 42 | - get: repo 43 | resource: master-branch 44 | params: { submodules: none } 45 | trigger: true 46 | - task: build-rust 47 | file: repo/ci/concourse/build-rust.yml 48 | - task: run-tests 49 | file: repo/ci/concourse/run-tests.yml 50 | 51 | - name: run-tests-for-pr 52 | public: true 53 | plan: 54 | - do: 55 | - get: repo 56 | resource: pull-request 57 | version: every 58 | trigger: true 59 | - put: repo 60 | resource: pull-request 61 | params: 62 | path: repo 63 | status: pending 64 | - task: build-rust 65 | file: repo/ci/concourse/build-rust.yml 66 | - task: run-tests 67 | file: repo/ci/concourse/run-tests.yml 68 | on_failure: 69 | put: repo 70 | resource: pull-request 71 | params: 72 | path: repo 73 | status: failure 74 | on_success: 75 | put: repo 76 | resource: pull-request 77 | params: 78 | path: repo 79 | status: success 80 | 81 | - name: test-rustc-nightly 82 | public: true 83 | plan: 84 | - do: 85 | - get: nightly-trigger 86 | trigger: true 87 | - get: repo 88 | resource: master-branch 89 | params: { submodules: none } 90 | - task: build-rust 91 | file: repo/ci/concourse/build-rust.yml 92 | params: 93 | RUSTUP_TOOLCHAIN: nightly 94 | NO_CACHE: true 95 | - task: run-tests 96 | file: repo/ci/concourse/run-tests.yml 97 | on_failure: 98 | put: discord-channel 99 | params: 100 | message: Rust-PSP failed with latest nightly 101 | type: failed 102 | color: '#ff0000' 103 | -------------------------------------------------------------------------------- /ci/concourse/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | /ppsspp/build-sdl/PPSSPPHeadless rust-build-dir/EBOOT.PBP --timeout=10 -r . 6 | 7 | cat psp_output_file.log 8 | 9 | if [ "$(tail -n 1 psp_output_file.log)" == FINAL_SUCCESS ]; then 10 | echo Test passed 11 | else 12 | echo Test failed 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /ci/concourse/run-tests.yml: -------------------------------------------------------------------------------- 1 | platform: linux 2 | 3 | image_resource: 4 | type: docker-image 5 | source: 6 | repository: rustpsp/ppsspp-headless 7 | 8 | inputs: 9 | - name: repo 10 | - name: rust-build-dir 11 | 12 | outputs: 13 | - name: rust-build-dir 14 | 15 | run: 16 | path: repo/ci/concourse/run-tests.sh 17 | -------------------------------------------------------------------------------- /ci/std_verification/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "std_test_cases" 3 | version = "0.1.0" 4 | authors = ["Glenn Hope "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp", features = ["std"] } 9 | -------------------------------------------------------------------------------- /ci/std_verification/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(restricted_std)] 2 | #![no_main] 3 | 4 | use psp::test_runner::TestRunner; 5 | 6 | mod time_test; 7 | 8 | psp::module!("std_verification", 1, 1); 9 | 10 | fn psp_main() { 11 | let tests = &[ 12 | time_test::test_main, 13 | ]; 14 | 15 | let mut runner = TestRunner::new_dprintln_runner(); 16 | runner.start_run(); 17 | 18 | for test in tests { 19 | runner.run(test); 20 | } 21 | 22 | runner.finish_run(); 23 | } 24 | -------------------------------------------------------------------------------- /ci/std_verification/src/time_test.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Instant, SystemTime, Duration, UNIX_EPOCH}; 2 | use psp::test_runner::TestRunner; 3 | 4 | const BEFORE_PSP: Duration = Duration::from_secs(1000212400); 5 | 6 | pub fn test_main(test_runner: &mut TestRunner) { 7 | let now = SystemTime::now(); 8 | test_runner.check_list(&[ 9 | ("system_time_sane", (now - BEFORE_PSP) > UNIX_EPOCH, true), 10 | ("instant_increments", Instant::now() < Instant::now(), true), 11 | ]); 12 | } 13 | -------------------------------------------------------------------------------- /ci/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_cases" 3 | version = "0.1.0" 4 | authors = ["Glenn Hope "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp", features = ["embedded-graphics"] } 9 | embedded-graphics = "0.6.2" 10 | -------------------------------------------------------------------------------- /ci/tests/assets/blank_screenshot.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/ci/tests/assets/blank_screenshot.bmp -------------------------------------------------------------------------------- /ci/tests/assets/embedded_graphics_triangle.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/ci/tests/assets/embedded_graphics_triangle.bmp -------------------------------------------------------------------------------- /ci/tests/src/bmp_screenshot_test.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::ffi::c_void; 3 | 4 | use embedded_graphics::prelude::*; 5 | use embedded_graphics::pixelcolor::Rgb888; 6 | use embedded_graphics::primitives::{rectangle::Rectangle, triangle::Triangle}; 7 | use embedded_graphics::style::PrimitiveStyleBuilder; 8 | 9 | use psp::embedded_graphics::Framebuffer; 10 | use psp::test_runner::TestRunner; 11 | 12 | const BLANK_SCREENSHOT: &[u8] = include_bytes!("../assets/blank_screenshot.bmp"); 13 | const EG_TRIANGLE_SCREENSHOT: &[u8] = include_bytes!("../assets/embedded_graphics_triangle.bmp"); 14 | 15 | pub fn test_main(test_runner: &mut TestRunner) { 16 | test_runner.check_large_collection("blank_screenshot", BLANK_SCREENSHOT, &blank_screenshot()); 17 | 18 | test_runner.check_large_collection( 19 | "embedded_graphics_triangle", 20 | EG_TRIANGLE_SCREENSHOT, 21 | &eg_triangle_screenshot(), 22 | ); 23 | } 24 | 25 | // NOTE: This does not clear the screen, so running it 26 | // after other tests will most likely fail until that is added. 27 | fn blank_screenshot() -> Vec { 28 | psp::screenshot_bmp() 29 | } 30 | 31 | fn eg_triangle_screenshot() -> Vec { 32 | let mut disp = Framebuffer::new(); 33 | 34 | let style = PrimitiveStyleBuilder::new() 35 | .fill_color(Rgb888::BLACK) 36 | .build(); 37 | let black_backdrop = Rectangle::new(Point::new(0, 0), Point::new(160, 80)).into_styled(style); 38 | black_backdrop.draw(&mut disp).unwrap(); 39 | Triangle::new( 40 | Point::new(8, 66 + 16), 41 | Point::new(8 + 16, 66 + 16), 42 | Point::new(8 + 8, 66), 43 | ) 44 | .into_styled( 45 | PrimitiveStyleBuilder::new() 46 | .stroke_color(Rgb888::RED) 47 | .stroke_width(1) 48 | .build(), 49 | ) 50 | .draw(&mut disp) 51 | .unwrap(); 52 | 53 | psp::screenshot_bmp() 54 | } 55 | 56 | // Useful for generating bmp files for comparison. 57 | fn _write_bmp_helper(screenshot: &[u8]) { 58 | unsafe { 59 | let fd = psp::sys::sceIoOpen( 60 | b"host0:/TEST_OUTPUT.bmp\0" as *const u8, 61 | psp::sys::IoOpenFlags::CREAT | psp::sys::IoOpenFlags::RD_WR, 62 | 0o777, 63 | ); 64 | psp::sys::sceIoWrite( 65 | fd, 66 | screenshot as *const _ as *const c_void, 67 | screenshot.len(), 68 | ); 69 | psp::sys::sceIoClose(fd); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ci/tests/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | #[cfg(not(feature = "stub-only"))] extern crate alloc; 5 | 6 | use psp::test_runner::TestRunner; 7 | 8 | mod bmp_screenshot_test; 9 | mod math_test; 10 | mod vram_test; 11 | 12 | psp::module!("ci_tests", 1, 1); 13 | 14 | fn psp_main() { 15 | let tests = &[ 16 | bmp_screenshot_test::test_main, 17 | vram_test::test_main, 18 | math_test::test_main, 19 | ]; 20 | 21 | let mut runner = TestRunner::new_file_runner(); 22 | runner.start_run(); 23 | 24 | for test in tests { 25 | runner.run(test); 26 | } 27 | 28 | runner.finish_run(); 29 | } 30 | -------------------------------------------------------------------------------- /ci/tests/src/math_test.rs: -------------------------------------------------------------------------------- 1 | use psp::math; 2 | use psp::test_runner::TestRunner; 3 | 4 | pub fn test_main(test_runner: &mut TestRunner) { 5 | test_runner.check_list(&[ 6 | ("cos_2.5", test_cos(2.5), -0.8011436), 7 | ("cos_0", test_cos(0.0), 1.0), 8 | ("cos_pi", test_cos(psp::sys::GU_PI), -1.0), 9 | ]); 10 | } 11 | 12 | fn test_cos(num: f32) -> f32 { 13 | unsafe { math::cosf32(num) } 14 | } 15 | -------------------------------------------------------------------------------- /ci/tests/src/vram_test.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::null_mut; 2 | use psp::test_runner::TestRunner; 3 | use psp::vram_alloc::get_vram_allocator; 4 | 5 | pub fn test_main(test_runner: &mut TestRunner) { 6 | let mut alloc = get_vram_allocator().unwrap(); 7 | test_runner.pass("allocator_initialization", "Received VRAM allocator."); 8 | 9 | let fake_alloc = get_vram_allocator(); 10 | match fake_alloc { 11 | Ok(_) => test_runner.fail( 12 | "allocator_doubling_prevention", 13 | "Received second VRAM allocator! Singleton is not working.", 14 | ), 15 | Err(_) => test_runner.pass( 16 | "allocator_doubling_prevention", 17 | "VRAM allocator singleton functional.", 18 | ), 19 | } 20 | 21 | unsafe { 22 | let zero_ptr = null_mut(); 23 | 24 | let chunk1 = alloc.alloc_sized::<[u8; 4]>(1); 25 | let chunk2 = alloc.alloc_sized::<[u8; 4]>(1); 26 | 27 | test_runner.check_list(&[ 28 | ( 29 | "first_chunk_addr_zero", 30 | chunk1.as_mut_ptr_direct_to_vram(), 31 | psp::sys::sceGeEdramGetAddr(), 32 | ), 33 | ( 34 | "second_chunk_addr_zero", 35 | chunk2.as_mut_ptr_direct_to_vram(), 36 | psp::sys::sceGeEdramGetAddr().offset(4), 37 | ), 38 | ( 39 | "first_chunk_addr_direct", 40 | chunk1.as_mut_ptr_from_zero(), 41 | zero_ptr, 42 | ), 43 | ( 44 | "second_chunk_addr_direct", 45 | chunk2.as_mut_ptr_from_zero(), 46 | zero_ptr.offset(4), 47 | ), 48 | ]); 49 | 50 | let muh_item = alloc.move_to_vram([69u8; 16]); 51 | 52 | test_runner.check( 53 | "vram_moved_addr", 54 | muh_item.as_mut_ptr(), 55 | 0x4000008 as *const u8 as _, 56 | ); 57 | 58 | test_runner.check("vram_storage_len", muh_item.len(), 16); 59 | test_runner.check("vram_storage_integrity1", muh_item[4], 69); 60 | muh_item[15] = 42; 61 | test_runner.check("vram_storage_integrity2", muh_item[15], 42); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/demo.gif -------------------------------------------------------------------------------- /examples/clock-speed/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/clock-speed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-clock-speed-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/clock-speed/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | psp::module!("sample_clock_speed", 1, 1); 5 | 6 | fn psp_main() { 7 | psp::enable_home_button(); 8 | 9 | unsafe { 10 | let cpu = psp::sys::scePowerGetCpuClockFrequency(); 11 | let bus = psp::sys::scePowerGetBusClockFrequency(); 12 | 13 | psp::dprintln!("PSP is operating at {}/{}MHz", cpu, bus); 14 | psp::dprintln!("Setting clock speed to maximum..."); 15 | 16 | psp::sys::scePowerSetClockFrequency(333, 333, 166); 17 | 18 | let cpu = psp::sys::scePowerGetCpuClockFrequency(); 19 | let bus = psp::sys::scePowerGetBusClockFrequency(); 20 | 21 | psp::dprintln!("PSP is now operating at {}/{}MHz", cpu, bus); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/cube/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/cube/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-cube-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/cube/ferris.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/examples/cube/ferris.bin -------------------------------------------------------------------------------- /examples/cube/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/examples/cube/ferris.png -------------------------------------------------------------------------------- /examples/cube/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::{ptr, f32::consts::PI}; 5 | use psp::Align16; 6 | use psp::sys::{ 7 | self, ScePspFVector3, DisplayPixelFormat, GuContextType, GuSyncMode, GuSyncBehavior, 8 | GuPrimitive, TextureFilter, TextureEffect, TextureColorComponent, 9 | FrontFaceDirection, ShadingModel, GuState, TexturePixelFormat, DepthFunc, 10 | VertexType, ClearBuffer, MipmapLevel, 11 | }; 12 | use psp::vram_alloc::get_vram_allocator; 13 | use psp::{BUF_WIDTH, SCREEN_WIDTH, SCREEN_HEIGHT}; 14 | 15 | psp::module!("sample_cube", 1, 1); 16 | 17 | // Both width and height, this is a square image. 18 | const IMAGE_SIZE: usize = 128; 19 | 20 | // The image data *must* be aligned to a 16 byte boundary. 21 | static FERRIS: Align16<[u8; IMAGE_SIZE * IMAGE_SIZE * 4]> = Align16(*include_bytes!("../ferris.bin")); 22 | 23 | static mut LIST: Align16<[u32; 0x40000]> = Align16([0; 0x40000]); 24 | 25 | #[repr(C, align(4))] 26 | struct Vertex { 27 | u: f32, 28 | v: f32, 29 | x: f32, 30 | y: f32, 31 | z: f32, 32 | } 33 | 34 | static VERTICES: Align16<[Vertex; 12 * 3]> = Align16([ 35 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: 1.0}, // 0 36 | Vertex { u: 1.0, v: 0.0, x: -1.0, y: 1.0, z: 1.0}, // 4 37 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 5 38 | 39 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: 1.0}, // 0 40 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 5 41 | Vertex { u: 0.0, v: 1.0, x: 1.0, y: -1.0, z: 1.0}, // 1 42 | 43 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 3 44 | Vertex { u: 1.0, v: 0.0, x: 1.0, y: -1.0, z: -1.0}, // 2 45 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: -1.0}, // 6 46 | 47 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 3 48 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: -1.0}, // 6 49 | Vertex { u: 0.0, v: 1.0, x: -1.0, y: 1.0, z: -1.0}, // 7 50 | 51 | Vertex { u: 0.0, v: 0.0, x: 1.0, y: -1.0, z: -1.0}, // 0 52 | Vertex { u: 1.0, v: 0.0, x: 1.0, y: -1.0, z: 1.0}, // 3 53 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 7 54 | 55 | Vertex { u: 0.0, v: 0.0, x: 1.0, y: -1.0, z: -1.0}, // 0 56 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 7 57 | Vertex { u: 0.0, v: 1.0, x: 1.0, y: 1.0, z: -1.0}, // 4 58 | 59 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 0 60 | Vertex { u: 1.0, v: 0.0, x: -1.0, y: 1.0, z: -1.0}, // 3 61 | Vertex { u: 1.0, v: 1.0, x: -1.0, y: 1.0, z: 1.0}, // 7 62 | 63 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 0 64 | Vertex { u: 1.0, v: 1.0, x: -1.0, y: 1.0, z: 1.0}, // 7 65 | Vertex { u: 0.0, v: 1.0, x: -1.0, y: -1.0, z: 1.0}, // 4 66 | 67 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: 1.0, z: -1.0}, // 0 68 | Vertex { u: 1.0, v: 0.0, x: 1.0, y: 1.0, z: -1.0}, // 1 69 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 2 70 | 71 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: 1.0, z: -1.0}, // 0 72 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: 1.0, z: 1.0}, // 2 73 | Vertex { u: 0.0, v: 1.0, x: -1.0, y: 1.0, z: 1.0}, // 3 74 | 75 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 4 76 | Vertex { u: 1.0, v: 0.0, x: -1.0, y: -1.0, z: 1.0}, // 7 77 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: -1.0, z: 1.0}, // 6 78 | 79 | Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: -1.0}, // 4 80 | Vertex { u: 1.0, v: 1.0, x: 1.0, y: -1.0, z: 1.0}, // 6 81 | Vertex { u: 0.0, v: 1.0, x: 1.0, y: -1.0, z: -1.0}, // 5 82 | ]); 83 | 84 | fn psp_main() { 85 | unsafe { psp_main_inner() } 86 | } 87 | 88 | unsafe fn psp_main_inner() { 89 | psp::enable_home_button(); 90 | 91 | let mut allocator = get_vram_allocator().unwrap(); 92 | let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); 93 | let fbp1 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); 94 | let zbp = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444).as_mut_ptr_from_zero(); 95 | 96 | sys::sceGumLoadIdentity(); 97 | 98 | sys::sceGuInit(); 99 | 100 | sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut [u32; 0x40000] as *mut _); 101 | sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, fbp0 as _, BUF_WIDTH as i32); 102 | sys::sceGuDispBuffer(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32, fbp1 as _, BUF_WIDTH as i32); 103 | sys::sceGuDepthBuffer(zbp as _, BUF_WIDTH as i32); 104 | sys::sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2)); 105 | sys::sceGuViewport(2048, 2048, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); 106 | sys::sceGuDepthRange(65535, 0); 107 | sys::sceGuScissor(0, 0, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); 108 | sys::sceGuEnable(GuState::ScissorTest); 109 | sys::sceGuDepthFunc(DepthFunc::GreaterOrEqual); 110 | sys::sceGuEnable(GuState::DepthTest); 111 | sys::sceGuFrontFace(FrontFaceDirection::Clockwise); 112 | sys::sceGuShadeModel(ShadingModel::Smooth); 113 | sys::sceGuEnable(GuState::CullFace); 114 | sys::sceGuEnable(GuState::Texture2D); 115 | sys::sceGuEnable(GuState::ClipPlanes); 116 | sys::sceGuFinish(); 117 | sys::sceGuSync(GuSyncMode::Finish, GuSyncBehavior::Wait); 118 | 119 | psp::sys::sceDisplayWaitVblankStart(); 120 | 121 | sys::sceGuDisplay(true); 122 | 123 | // run sample 124 | 125 | let mut val = 0.0; 126 | 127 | loop { 128 | sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut [u32; 0x40000] as *mut _); 129 | 130 | // clear screen 131 | sys::sceGuClearColor(0xff554433); 132 | sys::sceGuClearDepth(0); 133 | sys::sceGuClear(ClearBuffer::COLOR_BUFFER_BIT | ClearBuffer::DEPTH_BUFFER_BIT); 134 | 135 | // setup matrices for cube 136 | 137 | sys::sceGumMatrixMode(sys::MatrixMode::Projection); 138 | sys::sceGumLoadIdentity(); 139 | sys::sceGumPerspective(75.0, 16.0 / 9.0, 0.5, 1000.0); 140 | 141 | sys::sceGumMatrixMode(sys::MatrixMode::View); 142 | sys::sceGumLoadIdentity(); 143 | 144 | sys::sceGumMatrixMode(sys::MatrixMode::Model); 145 | sys::sceGumLoadIdentity(); 146 | 147 | { 148 | let pos = ScePspFVector3 { x: 0.0, y: 0.0, z: -2.5 }; 149 | let rot = ScePspFVector3 { 150 | x: val * 0.79 * (PI / 180.0), 151 | y: val * 0.98 * (PI / 180.0), 152 | z: val * 1.32 * (PI / 180.0), 153 | }; 154 | 155 | sys::sceGumTranslate(&pos); 156 | sys::sceGumRotateXYZ(&rot); 157 | } 158 | 159 | // setup texture 160 | 161 | sys::sceGuTexMode(TexturePixelFormat::Psm8888, 0, 0, 0); 162 | sys::sceGuTexImage(MipmapLevel::None, 128, 128, 128, &FERRIS as *const _ as *const _); 163 | sys::sceGuTexFunc(TextureEffect::Replace, TextureColorComponent::Rgb); 164 | sys::sceGuTexFilter(TextureFilter::Linear, TextureFilter::Linear); 165 | sys::sceGuTexScale(1.0, 1.0); 166 | sys::sceGuTexOffset(0.0, 0.0); 167 | 168 | // draw cube 169 | 170 | sys::sceGumDrawArray( 171 | GuPrimitive::Triangles, 172 | VertexType::TEXTURE_32BITF | VertexType::VERTEX_32BITF | VertexType::TRANSFORM_3D, 173 | 12 * 3, 174 | ptr::null_mut(), 175 | &VERTICES as *const Align16<_> as *const _, 176 | ); 177 | 178 | sys::sceGuFinish(); 179 | sys::sceGuSync(GuSyncMode::Finish, GuSyncBehavior::Wait); 180 | 181 | sys::sceDisplayWaitVblankStart(); 182 | sys::sceGuSwapBuffers(); 183 | 184 | val += 1.0; 185 | } 186 | 187 | // sys::sceGuTerm(); 188 | // psp::sys::sceKernelExitGame(); 189 | } 190 | -------------------------------------------------------------------------------- /examples/embedded-graphics/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/embedded-graphics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-embedded-graphics-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp", features = ["embedded-graphics"] } 9 | embedded-graphics = "0.6.2" 10 | tinybmp = { version = "0.2.2", features = ["graphics"] } 11 | -------------------------------------------------------------------------------- /examples/embedded-graphics/assets/ferris.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/examples/embedded-graphics/assets/ferris.bmp -------------------------------------------------------------------------------- /examples/embedded-graphics/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embedded_graphics::fonts::{Font6x8, Text}; 5 | use embedded_graphics::image::Image; 6 | use embedded_graphics::pixelcolor::Rgb888; 7 | use embedded_graphics::prelude::*; 8 | use embedded_graphics::primitives::{circle::Circle, rectangle::Rectangle, triangle::Triangle}; 9 | use embedded_graphics::style::PrimitiveStyleBuilder; 10 | use embedded_graphics::style::TextStyleBuilder; 11 | use tinybmp::Bmp; 12 | 13 | use psp::embedded_graphics::Framebuffer; 14 | 15 | psp::module!("sample_emb_gfx", 1, 1); 16 | 17 | fn psp_main() { 18 | psp::enable_home_button(); 19 | let mut disp = Framebuffer::new(); 20 | 21 | let style = PrimitiveStyleBuilder::new() 22 | .fill_color(Rgb888::BLACK) 23 | .build(); 24 | let black_backdrop = Rectangle::new(Point::new(0, 0), Point::new(160, 80)).into_styled(style); 25 | black_backdrop.draw(&mut disp).unwrap(); 26 | 27 | // draw ferris 28 | let bmp = Bmp::from_slice(include_bytes!("../assets/ferris.bmp")).unwrap(); 29 | let image: Image = Image::new(&bmp, Point::zero()); 30 | image.draw(&mut disp).unwrap(); 31 | 32 | Triangle::new( 33 | Point::new(8, 66 + 16), 34 | Point::new(8 + 16, 66 + 16), 35 | Point::new(8 + 8, 66), 36 | ) 37 | .into_styled( 38 | PrimitiveStyleBuilder::new() 39 | .stroke_color(Rgb888::RED) 40 | .stroke_width(1) 41 | .build(), 42 | ) 43 | .draw(&mut disp) 44 | .unwrap(); 45 | 46 | Rectangle::new(Point::new(36, 66), Point::new(36 + 16, 66 + 16)) 47 | .into_styled( 48 | PrimitiveStyleBuilder::new() 49 | .stroke_color(Rgb888::GREEN) 50 | .stroke_width(1) 51 | .build(), 52 | ) 53 | .draw(&mut disp) 54 | .unwrap(); 55 | 56 | Circle::new(Point::new(72, 66 + 8), 8) 57 | .into_styled( 58 | PrimitiveStyleBuilder::new() 59 | .stroke_color(Rgb888::BLUE) 60 | .stroke_width(1) 61 | .build(), 62 | ) 63 | .draw(&mut disp) 64 | .unwrap(); 65 | 66 | let rust = Rgb888::new(0xff, 0x07, 0x00); 67 | Text::new("Hello Rust!", Point::new(0, 86)) 68 | .into_styled(TextStyleBuilder::new(Font6x8).text_color(rust).build()) 69 | .draw(&mut disp) 70 | .unwrap(); 71 | } 72 | -------------------------------------------------------------------------------- /examples/gu-background/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/gu-background/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-gu-background-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/gu-background/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A basic graphics example that only clears the screen. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use core::ffi::c_void; 7 | use psp::sys::{self, GuState, TexturePixelFormat, DisplayPixelFormat}; 8 | use psp::vram_alloc::get_vram_allocator; 9 | use psp::{BUF_WIDTH, SCREEN_WIDTH, SCREEN_HEIGHT}; 10 | 11 | psp::module!("sample_gu_background", 1, 1); 12 | 13 | static mut LIST: psp::Align16<[u32; 0x40000]> = psp::Align16([0; 0x40000]); 14 | 15 | fn psp_main() { 16 | psp::enable_home_button(); 17 | 18 | let mut allocator = get_vram_allocator().unwrap(); 19 | let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); 20 | let fbp1 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); 21 | let zbp = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444).as_mut_ptr_from_zero(); 22 | 23 | unsafe { 24 | 25 | sys::sceGuInit(); 26 | sys::sceGuStart( 27 | sys::GuContextType::Direct, 28 | &mut LIST as *mut _ as *mut c_void, 29 | ); 30 | sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, fbp0 as _, BUF_WIDTH as i32); 31 | sys::sceGuDispBuffer(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32, fbp1 as _, BUF_WIDTH as i32); 32 | sys::sceGuDepthBuffer(zbp as _, BUF_WIDTH as i32); 33 | sys::sceGuOffset(2048 - (SCREEN_WIDTH/2), 2048 - (SCREEN_HEIGHT/2)); 34 | sys::sceGuViewport(2048, 2048, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); 35 | sys::sceGuDepthRange(65535, 0); 36 | sys::sceGuScissor(0, 0, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); 37 | sys::sceGuEnable(GuState::ScissorTest); 38 | sys::sceGuFinish(); 39 | sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); 40 | sys::sceDisplayWaitVblankStart(); 41 | sys::sceGuDisplay(true); 42 | 43 | loop { 44 | sys::sceGuStart( 45 | sys::GuContextType::Direct, 46 | &mut LIST as *mut _ as *mut c_void 47 | ); 48 | sys::sceGuClearColor(0xff554433); 49 | sys::sceGuClearDepth(0); 50 | sys::sceGuClear( 51 | sys::ClearBuffer::COLOR_BUFFER_BIT | 52 | sys::ClearBuffer::DEPTH_BUFFER_BIT 53 | ); 54 | sys::sceGuFinish(); 55 | sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); 56 | sys::sceDisplayWaitVblankStart(); 57 | sys::sceGuSwapBuffers(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/gu-debug-print/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/gu-debug-print/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-gu-debug-example" 3 | version = "0.1.0" 4 | authors = ["Paul Sajna "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/gu-debug-print/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A basic example of sceGuDebugPrint functionality 2 | //! Prints "Hello World" in red at 100, 100 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | use core::ffi::c_void; 8 | use psp::sys::{self, TexturePixelFormat, DisplayPixelFormat}; 9 | use psp::vram_alloc::get_vram_allocator; 10 | use psp::{BUF_WIDTH, SCREEN_HEIGHT}; 11 | 12 | psp::module!("sample_gu_debug", 1, 1); 13 | 14 | static mut LIST: psp::Align16<[u32; 0x40000]> = psp::Align16([0; 0x40000]); 15 | 16 | fn psp_main() { 17 | psp::enable_home_button(); 18 | 19 | let mut allocator = get_vram_allocator().unwrap(); 20 | let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); 21 | 22 | unsafe { 23 | sys::sceGuInit(); 24 | sys::sceGuStart( 25 | sys::GuContextType::Direct, 26 | &mut LIST as *mut _ as *mut c_void, 27 | ); 28 | sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, fbp0 as _, BUF_WIDTH as i32); 29 | sys::sceGuDebugPrint(100, 100, 0xff0000ff, b"Hello World\0" as *const u8); 30 | sys::sceGuDebugFlush(); 31 | 32 | sys::sceGuFinish(); 33 | sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); 34 | sys::sceDisplayWaitVblankStart(); 35 | sys::sceGuDisplay(true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-hello-world-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | psp::module!("sample_module", 1, 1); 5 | 6 | fn psp_main() { 7 | psp::enable_home_button(); 8 | psp::dprint!("Hello PSP from rust!"); 9 | } 10 | -------------------------------------------------------------------------------- /examples/msg-dialog/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/msg-dialog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-msg-dialog" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/msg-dialog/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use psp::sys::{ 5 | UtilityDialogCommon, UtilityMsgDialogParams, UtilityMsgDialogMode, 6 | UtilityMsgDialogPressed, SystemParamLanguage, UtilityDialogButtonAccept, 7 | UtilityMsgDialogOption, self, 8 | DisplayPixelFormat, GuContextType, GuState, DepthFunc, FrontFaceDirection, 9 | ShadingModel, GuSyncMode, GuSyncBehavior 10 | }; 11 | 12 | use core::ffi::c_void; 13 | 14 | psp::module!("sample_module", 1, 1); 15 | 16 | static mut LIST: psp::Align16<[u32; 262144]> = psp::Align16([0;262144]); 17 | const SCR_WIDTH: i32 = 480; 18 | const SCR_HEIGHT: i32 = 272; 19 | const BUF_WIDTH: i32 = 512; 20 | 21 | unsafe fn setup_gu() { 22 | sys::sceGuInit(); 23 | sys::sceGuStart(GuContextType::Direct, &mut LIST as *mut _ as *mut c_void); 24 | sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, core::ptr::null_mut(), BUF_WIDTH); 25 | sys::sceGuDispBuffer(SCR_WIDTH, SCR_HEIGHT, 0x88000 as *mut c_void, BUF_WIDTH); 26 | sys::sceGuDepthBuffer(0x110000 as *mut c_void, BUF_WIDTH); 27 | sys::sceGuOffset(2048 - (SCR_WIDTH as u32 /2), 2048 - (SCR_HEIGHT as u32 /2)); 28 | sys::sceGuViewport(2048, 2048, SCR_WIDTH, SCR_HEIGHT); 29 | sys::sceGuDepthRange(0xc350, 0x2710); 30 | sys::sceGuScissor(0, 0, SCR_WIDTH, SCR_HEIGHT); 31 | sys::sceGuEnable(GuState::ScissorTest); 32 | sys::sceGuDepthFunc(DepthFunc::GreaterOrEqual); 33 | sys::sceGuEnable(GuState::DepthTest); 34 | sys::sceGuFrontFace(FrontFaceDirection::Clockwise); 35 | sys::sceGuShadeModel(ShadingModel::Smooth); 36 | sys::sceGuEnable(GuState::CullFace); 37 | sys::sceGuEnable(GuState::ClipPlanes); 38 | sys::sceGuFinish(); 39 | sys::sceGuSync(GuSyncMode::Finish, GuSyncBehavior::Wait); 40 | 41 | sys::sceDisplayWaitVblankStart(); 42 | sys::sceGuDisplay(true); 43 | } 44 | 45 | fn psp_main() { 46 | psp::enable_home_button(); 47 | 48 | unsafe { 49 | setup_gu(); 50 | } 51 | 52 | let dialog_size = core::mem::size_of::(); 53 | let base = UtilityDialogCommon { 54 | size: dialog_size as u32, 55 | language: SystemParamLanguage::English, 56 | button_accept: UtilityDialogButtonAccept::Cross, // X to accept 57 | graphics_thread: 0x11, // magic number stolen from pspsdk example 58 | access_thread: 0x13, 59 | font_thread: 0x12, 60 | sound_thread: 0x10, 61 | result: 0, 62 | reserved: [0i32; 4], 63 | }; 64 | 65 | let mut msg: [u8; 512] = [0u8; 512]; 66 | msg[..40].copy_from_slice(b"Hello from a Rust-created PSP Msg Dialog"); 67 | 68 | let mut msg_dialog = UtilityMsgDialogParams { 69 | base, 70 | unknown: 0, 71 | mode: UtilityMsgDialogMode::Text, 72 | error_value: 0, 73 | message: msg, 74 | options: UtilityMsgDialogOption::TEXT, 75 | button_pressed: UtilityMsgDialogPressed::Unknown1, 76 | }; 77 | 78 | unsafe { 79 | sys::sceUtilityMsgDialogInitStart( 80 | &mut msg_dialog as *mut UtilityMsgDialogParams 81 | ); 82 | } 83 | 84 | loop { 85 | let status = unsafe {sys::sceUtilityMsgDialogGetStatus()}; 86 | match status { 87 | 2 => unsafe{sys::sceUtilityMsgDialogUpdate(1)}, 88 | 3 => unsafe{sys::sceUtilityMsgDialogShutdownStart()}, 89 | 0 => {break}, 90 | _ => (), 91 | } 92 | unsafe { 93 | sys::sceGuStart(GuContextType::Direct, &mut LIST as *mut _ as *mut c_void); 94 | sys::sceGuFinish(); 95 | sys::sceGuSync(GuSyncMode::Finish, sys::GuSyncBehavior::Wait); 96 | sys::sceDisplayWaitVblankStart(); 97 | sys::sceGuSwapBuffers(); 98 | } 99 | } 100 | unsafe { sys::sceKernelExitGame(); } 101 | } 102 | -------------------------------------------------------------------------------- /examples/paint-mode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-paint-mode" 3 | version = "0.1.0" 4 | authors = ["Glenn Hope "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp", features = ["embedded-graphics"] } 9 | embedded-graphics = "0.6.2" 10 | numtoa = "0.2" 11 | -------------------------------------------------------------------------------- /examples/paint-mode/Psp.toml: -------------------------------------------------------------------------------- 1 | title = "Paint Mode" 2 | -------------------------------------------------------------------------------- /examples/paint-mode/src/analog_stick_to_delta.rs: -------------------------------------------------------------------------------- 1 | // Number of "pixels" (out of 127 in each direction) 2 | // to ignore in the center of the analog stick, because 3 | // it's very common to have the stick rest slighly off-center. 4 | const DEADZONE: i32 = 10; 5 | 6 | // The maximum number of pixels to move in a single tick, 7 | // essentially the "mouse sensitivity" of the analog stick. 8 | const MAX_SPEED: i32 = 4; 9 | 10 | // Carve a number of rings around our deadzone equal to MAX_SPEED. 11 | const SPEED_MODIFIER: i32 = 127 / MAX_SPEED; 12 | 13 | // Convert the analog stick position to a number of pixels to move 14 | // in the direction it is being held. 15 | // 16 | // First, we need to treat 127, 127 as 0,0 on a Cartesian plane. 17 | // We receive coordinates from the PSP control stick like this: 18 | // 19 | // +--------------------+ 20 | // |0,0 255,0| 21 | // | | 22 | // | | 23 | // | | 24 | // | 127,127 | 25 | // | | 26 | // | | 27 | // | | 28 | // |0,255 255,255| 29 | // +--------------------+ 30 | // 31 | // So we subtract 127 to make our values look like this: 32 | // 33 | // +--------------------+ 34 | // |-127,-127 127,-127| 35 | // | | 36 | // | | 37 | // | | 38 | // | 0,0 | 39 | // | | 40 | // | | 41 | // | | 42 | // |-127,127 127,127| 43 | // +--------------------+ 44 | // 45 | // Then, we carve out a "deadzone" around 0,0 where inputs are equal to zero. 46 | // So, for a DEADZONE of 8, we would have: 47 | // 48 | // +--------------------+ 49 | // | | 50 | // | | 51 | // | -8,-8 8,-8 | 52 | // | +------+ | 53 | // | | | | 54 | // | | | | 55 | // | +------+ | 56 | // | -8,8 8,8 | 57 | // | | 58 | // | | 59 | // +--------------------+ 60 | // 61 | // Now, we use MAX_SPEED as the number of "rings" around 0,0. 62 | // So for a MAX_SPEED value of 3, this would look like: 63 | // 64 | // For Y: - For X: + 65 | // +--------------------+ +--------------------+ 66 | // |33333333333333333333| |33221100000000112233| 67 | // -|22222222222222222222| |33221100000000112233| 68 | // |11111111111111111111| |33221100000000112233| 69 | // |000000+------+000000| |332211+------+112233| 70 | // |000000| |000000| |332211| |112233| 71 | // |000000| |000000| |332211| |112233| 72 | // |000000+------+000000| |332211+------+112233| 73 | // |11111111111111111111| |33221100000000112233| 74 | // +|22222222222222222222| |33221100000000112233| 75 | // |33333333333333333333| |33221100000000112233| 76 | // +--------------------+ +--------------------+ 77 | // 78 | // And for a MAX_SPEED of 6, something like this: 79 | // 80 | // For Y: - For X: + 81 | // +--------------------+ +--------------------+ 82 | // |66666666666666666666| |65432100000000123456| 83 | // -|44444444444444444444| |65432100000000123456| 84 | // |22222222222222222222| |65432100000000123456| 85 | // |000000+------+000000| |654321+------+123456| 86 | // |000000| |000000| |654321| |123456| 87 | // |000000| |000000| |654321| |123456| 88 | // |000000+------+000000| |654321+------+123456| 89 | // |22222222222222222222| |65432100000000123456| 90 | // +|44444444444444444444| |65432100000000123456| 91 | // |66666666666666666666| |65432100000000123456| 92 | // +--------------------+ +--------------------+ 93 | // 94 | // Or, visualized differently for a MAX_SPEED of 8: 95 | // 96 | // +--------------------+ 97 | // | | 98 | // | | 99 | // | 0,-1 | 100 | // | +------+ | 101 | // | | 0,0 |1,0 | 102 | // | | | | 103 | // | +------+ | 104 | // | | 105 | // | -4,4 | 106 | // | 8,8| 107 | // +--------------------+ 108 | // 109 | pub fn convert_analog_to_delta_with_sensitivity_deadzone(raw_val: u8) -> i32 { 110 | let delta_val = (raw_val as i32) - 127; 111 | 112 | // Zero out a "deadzone" around 0,0 to adjust for joysticks that sit off-center. 113 | let distance_without_deadzone = if delta_val > -DEADZONE && delta_val < DEADZONE { 114 | 0 115 | } else if delta_val < -DEADZONE { 116 | delta_val + DEADZONE 117 | } else { 118 | delta_val - DEADZONE 119 | }; 120 | 121 | distance_without_deadzone / SPEED_MODIFIER 122 | } 123 | -------------------------------------------------------------------------------- /examples/paint-mode/src/background.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | pixelcolor::Rgb888, 3 | prelude::*, 4 | primitives::rectangle::Rectangle, 5 | style::{PrimitiveStyle, PrimitiveStyleBuilder, Styled}, 6 | }; 7 | 8 | use psp::{SCREEN_HEIGHT, SCREEN_WIDTH}; 9 | 10 | pub fn get_background() -> Styled> { 11 | let style = PrimitiveStyleBuilder::new() 12 | .fill_color(Rgb888::BLACK) 13 | .build(); 14 | Rectangle::new( 15 | Point::new(0, 0), 16 | Point::new(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32), 17 | ) 18 | .into_styled(style) 19 | } 20 | -------------------------------------------------------------------------------- /examples/paint-mode/src/debug_textbox.rs: -------------------------------------------------------------------------------- 1 | use core::str; 2 | use numtoa::NumToA; 3 | 4 | use embedded_graphics::{ 5 | fonts::{Font6x8, Text}, 6 | pixelcolor::Rgb888, 7 | prelude::*, 8 | primitives::rectangle::Rectangle, 9 | style::{PrimitiveStyle, PrimitiveStyleBuilder, Styled, TextStyle}, 10 | }; 11 | 12 | use psp::embedded_graphics::Framebuffer; 13 | use psp::sys::SceCtrlData; 14 | use psp::{SCREEN_HEIGHT, SCREEN_WIDTH}; 15 | pub fn get_textbox<'a>() -> Styled, TextStyle> { 16 | Text::new("", get_textbox_top_left()).into_styled(TextStyle::new(Font6x8, Rgb888::WHITE)) 17 | } 18 | 19 | fn get_textbox_top_left() -> Point { 20 | Point::new(SCREEN_WIDTH as i32 - 42, SCREEN_HEIGHT as i32 - 8) 21 | } 22 | 23 | fn get_textbox_wipe_rect() -> Styled> { 24 | let style = PrimitiveStyleBuilder::new() 25 | .fill_color(Rgb888::BLACK) 26 | .build(); 27 | Rectangle::new( 28 | get_textbox_top_left(), 29 | Point::new(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32), 30 | ) 31 | .into_styled(style) 32 | } 33 | 34 | pub fn draw_debug_textbox(disp: &mut Framebuffer, pad_data: &SceCtrlData) { 35 | // Create a str holding our analog pad X and Y values 36 | let mut holder = [' ' as u8; 7]; 37 | holder[3] = ':' as u8; 38 | pad_data.lx.numtoa(10, &mut holder[..3]); 39 | pad_data.ly.numtoa(10, &mut holder[4..]); 40 | 41 | let pad_debug_data_str = unsafe { 42 | // We can be extremely sure that our array holds nothing 43 | // but ASCII values, so we can safely skip UTF-8 checks. 44 | str::from_utf8_unchecked(&holder) 45 | }; 46 | 47 | // Instantiate our textboxes 48 | let textbox_wipe = get_textbox_wipe_rect(); 49 | let mut textbox = get_textbox(); 50 | textbox.primitive.text = pad_debug_data_str; 51 | 52 | // Actually clear and redraw the textbox on screen 53 | textbox_wipe.draw(disp).unwrap(); 54 | textbox.draw(disp).unwrap(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/paint-mode/src/drawobject.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics::{ 2 | pixelcolor::Rgb888, 3 | prelude::*, 4 | primitives::{circle::Circle, line::Line, rectangle::Rectangle, triangle::Triangle}, 5 | style::{PrimitiveStyle, PrimitiveStyleBuilder, Styled}, 6 | }; 7 | 8 | use psp::embedded_graphics::Framebuffer; 9 | 10 | type StyledRect = Styled>; 11 | type StyledCirc = Styled>; 12 | type StyledTri = Styled>; 13 | type StyledX = [Styled>; 2]; 14 | 15 | pub enum DrawObject { 16 | Circ(StyledCirc), 17 | Rect(StyledRect), 18 | Tri(StyledTri), 19 | X(StyledX), 20 | } 21 | 22 | impl DrawObject { 23 | pub fn size(&self) -> u32 { 24 | match self { 25 | Self::Circ(circ) => circ.primitive.radius, 26 | Self::Tri(tri) => tri.primitive.size().height / 2, 27 | Self::Rect(rect) => rect.primitive.size().height / 2, 28 | Self::X(x) => x[0].primitive.size().height / 2, 29 | } 30 | } 31 | 32 | pub fn draw(&self, disp: &mut Framebuffer) { 33 | match self { 34 | Self::Circ(circ) => circ.draw(disp).unwrap(), 35 | Self::Tri(tri) => tri.draw(disp).unwrap(), 36 | Self::Rect(rect) => rect.draw(disp).unwrap(), 37 | Self::X(x) => { 38 | for line in x { 39 | line.draw(disp).unwrap(); 40 | } 41 | } 42 | }; 43 | } 44 | 45 | pub fn translate_mut(&mut self, point: Point) { 46 | match self { 47 | Self::Circ(circ) => { 48 | circ.translate_mut(point); 49 | } 50 | Self::Tri(tri) => { 51 | tri.translate_mut(point); 52 | } 53 | Self::Rect(rect) => { 54 | rect.translate_mut(point); 55 | } 56 | Self::X(x) => { 57 | for line in x { 58 | line.translate_mut(point); 59 | } 60 | } 61 | }; 62 | } 63 | 64 | pub fn grow(&mut self) { 65 | let size = self.size(); 66 | if size < 31 { 67 | match self { 68 | Self::Circ(circ) => circ.primitive.radius += 1, 69 | Self::Tri(tri) => grow_triangle(tri), 70 | Self::Rect(rect) => grow_rectangle(rect), 71 | Self::X(x) => grow_x(x), 72 | } 73 | } 74 | } 75 | 76 | pub fn shrink(&mut self) { 77 | let size = self.size(); 78 | if size > 2 { 79 | match self { 80 | Self::Circ(circ) => circ.primitive.radius -= 1, 81 | Self::Tri(tri) => shrink_triangle(tri), 82 | Self::Rect(rect) => shrink_rectangle(rect), 83 | Self::X(x) => shrink_x(x), 84 | } 85 | } 86 | } 87 | 88 | pub fn center(&self) -> Point { 89 | match self { 90 | Self::Circ(circ) => circ.primitive.center, 91 | Self::Tri(tri) => Point::new( 92 | tri.primitive.p1.x, 93 | (tri.primitive.p1.y + tri.primitive.p2.y) / 2, 94 | ), 95 | Self::Rect(rect) => Point::new( 96 | (rect.primitive.top_left.x + rect.primitive.bottom_right.x) / 2, 97 | (rect.primitive.top_left.y + rect.primitive.bottom_right.y) / 2, 98 | ), 99 | 100 | Self::X(x) => Point::new( 101 | (x[0].primitive.start.x + x[0].primitive.end.x) / 2, 102 | (x[0].primitive.start.y + x[0].primitive.end.y) / 2, 103 | ), 104 | } 105 | } 106 | 107 | pub fn move_by(&mut self, delta_x_pixels: i32, delta_y_pixels: i32, max_x: i32, max_y: i32) { 108 | let existing_center = self.center(); 109 | let requested_delta = Point::new(delta_x_pixels, delta_y_pixels); 110 | let mut target_location: Point = existing_center + requested_delta; 111 | target_location.x = target_location.x.clamp(0, max_x); 112 | target_location.y = target_location.y.clamp(0, max_y); 113 | let actual_delta = target_location - existing_center; 114 | self.translate_mut(actual_delta); 115 | } 116 | 117 | pub fn new_circle(point: Point, size: u32) -> Self { 118 | Self::Circ( 119 | Circle::new(point, size).into_styled( 120 | PrimitiveStyleBuilder::new() 121 | .stroke_color(Rgb888::RED) 122 | .stroke_width(1) 123 | .build(), 124 | ), 125 | ) 126 | } 127 | 128 | pub fn new_triangle(point: Point, size: u32) -> Self { 129 | let mut tri = Triangle::new(point, point, point).into_styled( 130 | PrimitiveStyleBuilder::new() 131 | .stroke_color(Rgb888::GREEN) 132 | .stroke_width(1) 133 | .build(), 134 | ); 135 | grow_triangle_by(&mut tri, size as _); 136 | Self::Tri(tri) 137 | } 138 | 139 | pub fn new_rectangle(point: Point, size: u32) -> Self { 140 | let mut rect = Rectangle::new(point, point).into_styled( 141 | PrimitiveStyleBuilder::new() 142 | .stroke_color(Rgb888::MAGENTA) 143 | .stroke_width(1) 144 | .build(), 145 | ); 146 | grow_rectangle_by(&mut rect, size as _); 147 | Self::Rect(rect) 148 | } 149 | 150 | pub fn new_x(point: Point, size: u32) -> Self { 151 | let line_1 = Line::new(point, point).into_styled( 152 | PrimitiveStyleBuilder::new() 153 | .stroke_color(Rgb888::BLUE) 154 | .stroke_width(1) 155 | .build(), 156 | ); 157 | let line_2 = line_1.clone(); 158 | let mut x = [line_1, line_2]; 159 | grow_x_by(&mut x, size as _); 160 | Self::X(x) 161 | } 162 | } 163 | 164 | fn grow_triangle(styled_tri: &mut StyledTri) { 165 | grow_triangle_by(styled_tri, 1) 166 | } 167 | 168 | fn shrink_triangle(styled_tri: &mut StyledTri) { 169 | grow_triangle_by(styled_tri, -1) 170 | } 171 | 172 | fn grow_triangle_by(styled_tri: &mut StyledTri, grow_by: i32) { 173 | let mut tri = &mut styled_tri.primitive; 174 | tri.p1.y -= grow_by; 175 | 176 | tri.p2.x -= grow_by; 177 | tri.p2.y += grow_by; 178 | 179 | tri.p3.x += grow_by; 180 | tri.p3.y += grow_by; 181 | } 182 | 183 | fn grow_rectangle(styled_rect: &mut StyledRect) { 184 | grow_rectangle_by(styled_rect, 1) 185 | } 186 | 187 | fn shrink_rectangle(styled_rect: &mut StyledRect) { 188 | grow_rectangle_by(styled_rect, -1) 189 | } 190 | 191 | fn grow_rectangle_by(styled_rect: &mut StyledRect, grow_by: i32) { 192 | let mut rect = &mut styled_rect.primitive; 193 | rect.top_left.x -= grow_by; 194 | rect.top_left.y -= grow_by; 195 | 196 | rect.bottom_right.x += grow_by; 197 | rect.bottom_right.y += grow_by; 198 | } 199 | 200 | fn grow_x(styled_x: &mut StyledX) { 201 | grow_x_by(styled_x, 1) 202 | } 203 | 204 | fn shrink_x(styled_x: &mut StyledX) { 205 | grow_x_by(styled_x, -1) 206 | } 207 | 208 | fn grow_x_by(styled_x: &mut StyledX, grow_by: i32) { 209 | let mut line_1 = &mut styled_x[0].primitive; 210 | line_1.start.x -= grow_by; 211 | line_1.start.y -= grow_by; 212 | line_1.end.x += grow_by; 213 | line_1.end.y += grow_by; 214 | 215 | let mut line_2 = &mut styled_x[1].primitive; 216 | line_2.start.x -= grow_by; 217 | line_2.start.y += grow_by; 218 | line_2.end.x += grow_by; 219 | line_2.end.y -= grow_by; 220 | } 221 | -------------------------------------------------------------------------------- /examples/paint-mode/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(clamp)] 3 | 4 | pub mod drawobject; 5 | pub use drawobject::DrawObject; 6 | 7 | pub mod debug_textbox; 8 | pub use debug_textbox::draw_debug_textbox; 9 | 10 | pub mod background; 11 | pub use background::get_background; 12 | 13 | pub mod analog_stick_to_delta; 14 | pub use analog_stick_to_delta::convert_analog_to_delta_with_sensitivity_deadzone; 15 | -------------------------------------------------------------------------------- /examples/paint-mode/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(slice_fill)] 4 | #![feature(exclusive_range_pattern)] 5 | #![feature(half_open_range_patterns)] 6 | 7 | use psp_paint_mode::{ 8 | convert_analog_to_delta_with_sensitivity_deadzone, draw_debug_textbox, get_background, 9 | DrawObject, 10 | }; 11 | 12 | use embedded_graphics::prelude::*; 13 | 14 | use psp::embedded_graphics::Framebuffer; 15 | use psp::sys::{CtrlButtons, CtrlMode, SceCtrlData}; 16 | use psp::{SCREEN_HEIGHT, SCREEN_WIDTH}; 17 | 18 | psp::module!("Paint Mode Example", 0, 1); 19 | 20 | fn psp_main() { 21 | psp::enable_home_button(); 22 | 23 | let disp = &mut Framebuffer::new(); 24 | let background = get_background(); 25 | let mut cur_size = 1; 26 | let mut draw_obj = DrawObject::new_circle(get_midpoint(), cur_size); 27 | let mut cur_location = draw_obj.center(); 28 | 29 | let mut i = 0; 30 | 31 | background.draw(disp).unwrap(); 32 | 33 | unsafe { 34 | psp::sys::sceCtrlSetSamplingCycle(0); 35 | psp::sys::sceCtrlSetSamplingMode(CtrlMode::Analog); 36 | }; 37 | 38 | let pad_data = &mut SceCtrlData::default(); 39 | loop { 40 | unsafe { 41 | // Read button/analog input 42 | psp::sys::sceCtrlReadBufferPositive(pad_data, 1); 43 | } 44 | 45 | if pad_data.buttons.contains(CtrlButtons::START) { 46 | // Wipe the screen 47 | background.draw(disp).unwrap(); 48 | } 49 | 50 | if pad_data.buttons.contains(CtrlButtons::RTRIGGER) { 51 | draw_obj.grow(); 52 | } 53 | 54 | if pad_data.buttons.contains(CtrlButtons::LTRIGGER) { 55 | draw_obj.shrink(); 56 | } 57 | 58 | if pad_data.buttons.contains(CtrlButtons::CIRCLE) { 59 | draw_obj = DrawObject::new_circle(cur_location, cur_size); 60 | } 61 | 62 | if pad_data.buttons.contains(CtrlButtons::TRIANGLE) { 63 | draw_obj = DrawObject::new_triangle(cur_location, cur_size); 64 | } 65 | 66 | if pad_data.buttons.contains(CtrlButtons::SQUARE) { 67 | draw_obj = DrawObject::new_rectangle(cur_location, cur_size); 68 | } 69 | 70 | if pad_data.buttons.contains(CtrlButtons::CROSS) { 71 | draw_obj = DrawObject::new_x(cur_location, cur_size); 72 | } 73 | 74 | let delta_x_pixels = convert_analog_to_delta_with_sensitivity_deadzone(pad_data.lx); 75 | let delta_y_pixels = convert_analog_to_delta_with_sensitivity_deadzone(pad_data.ly); 76 | draw_obj.move_by( 77 | delta_x_pixels, 78 | delta_y_pixels, 79 | SCREEN_WIDTH as i32, 80 | SCREEN_HEIGHT as i32, 81 | ); 82 | draw_obj.draw(disp); 83 | 84 | if i < 10 { 85 | i += 1; 86 | } else { 87 | draw_debug_textbox(disp, pad_data); 88 | i = 0; 89 | } 90 | 91 | cur_location = draw_obj.center(); 92 | cur_size = draw_obj.size(); 93 | } 94 | } 95 | 96 | fn get_midpoint() -> Point { 97 | Point::new(SCREEN_WIDTH as i32 / 2, SCREEN_HEIGHT as i32 / 2) 98 | } 99 | -------------------------------------------------------------------------------- /examples/rainbow/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/rainbow/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-rainbow-example" 3 | version = "0.1.0" 4 | authors = [ 5 | "Paul Sajna ", 6 | "Marko Mijalkovic " 7 | ] 8 | edition = "2018" 9 | 10 | [dependencies] 11 | psp = { path = "../../psp" } 12 | -------------------------------------------------------------------------------- /examples/rainbow/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use psp::sys; 5 | use psp::{SCREEN_WIDTH, SCREEN_HEIGHT, BUF_WIDTH}; 6 | 7 | psp::module!("sample_module", 1, 1); 8 | 9 | 10 | static mut VRAM: *mut u32 = 0x4000_0000 as *mut u32; 11 | 12 | fn psp_main() { 13 | psp::enable_home_button(); 14 | unsafe { 15 | sys::sceDisplaySetMode(sys::DisplayMode::Lcd, SCREEN_WIDTH as usize, SCREEN_HEIGHT as usize); 16 | 17 | // Cache-through address 18 | VRAM = (0x4000_0000u32 | sys::sceGeEdramGetAddr() as u32) as *mut u32; 19 | 20 | sys::sceDisplaySetFrameBuf( 21 | VRAM as *const u8, 22 | BUF_WIDTH as usize, 23 | sys::DisplayPixelFormat::Psm8888, 24 | sys::DisplaySetBufSync::NextFrame, 25 | ); 26 | 27 | loop { 28 | sys::sceDisplayWaitVblankStart(); 29 | for pos in 0..255 { 30 | let color = wheel(pos); 31 | 32 | for i in 0..(BUF_WIDTH * SCREEN_HEIGHT) { 33 | *VRAM.add(i as usize) = color; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | fn wheel(mut pos: u8) -> u32 { 41 | pos = 255 - pos; 42 | if pos < 85 { 43 | u32::from_be_bytes([255 - pos * 3, 0, pos * 3, 255]) 44 | } else if pos < 170 { 45 | pos -= 85; 46 | u32::from_be_bytes([0, pos * 3, 255 - pos * 3, 255]) 47 | } else { 48 | pos -= 170; 49 | u32::from_be_bytes([pos * 3, 255 - pos * 3, 0, 255]) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/rust-std-hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-std-hello-world" 3 | version = "0.1.0" 4 | authors = ["Glenn Hope "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp", features = ["std"] } 9 | -------------------------------------------------------------------------------- /examples/rust-std-hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(restricted_std)] 2 | #![no_main] 3 | use std::string::String; 4 | 5 | psp::module!("rust_std_hello_world", 1, 1); 6 | 7 | fn psp_main() { 8 | psp::enable_home_button(); 9 | 10 | let yeet = String::from("Yeeteth! I am inside a String!"); 11 | psp::dprintln!("{}", yeet); 12 | 13 | let people = vec!["sajattack", "overdrivenpotato", "iridescence"]; 14 | for person in people { 15 | let x = format!( 16 | "Hello, {}! I'm coming to you live from the standard library!\n", 17 | person 18 | ); 19 | psp::dprint!("{}", x); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/time/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/time/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-time-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/time/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::mem::MaybeUninit; 5 | 6 | psp::module!("sample_time", 1, 1); 7 | 8 | fn psp_main() { 9 | psp::enable_home_button(); 10 | 11 | unsafe { 12 | let mut tick = 0; 13 | psp::sys::sceRtcGetCurrentTick(&mut tick); 14 | 15 | // Convert the tick to an instance of `ScePspDateTime` 16 | let mut date = MaybeUninit::uninit(); 17 | psp::sys::sceRtcSetTick(date.as_mut_ptr(), &tick); 18 | let date = date.assume_init(); 19 | 20 | psp::dprintln!( 21 | "Current time is {:02}:{:02}:{:02} UTC", 22 | date.hour, 23 | date.minutes, 24 | date.seconds 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/vfpu-addition/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/vfpu-addition/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-vfpu-addition-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/vfpu-addition/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(llvm_asm)] 4 | 5 | psp::module!("vfpu_test", 1, 1); 6 | 7 | fn vfpu_add(a: i32, b: i32) -> i32 { 8 | let out; 9 | 10 | unsafe { 11 | psp::vfpu_asm! ( 12 | // Convert `a` to float 13 | .mips "mtc1 $$a0, $3"; 14 | .mips "cvt.s.w $3, $3"; 15 | .mips "mfc1 $$a0, $3"; 16 | 17 | // Convert `b` to float 18 | .mips "mtc1 $$a1, $3"; 19 | .mips "cvt.s.w $3, $3"; 20 | .mips "mfc1 $$a1, $3"; 21 | 22 | // Perform addition 23 | mtv a0, S000; 24 | mtv a1, S001; 25 | vadd_s S000, S000, S001; 26 | mfv v0, S000; 27 | 28 | // Convert result to `i32` 29 | .mips "mtc1 $$v0, $3"; 30 | .mips "cvt.w.s $3, $3"; 31 | .mips "mfc1 $$v0, $3"; 32 | 33 | : "={2}"(out) 34 | : "{4}"(a), "{5}"(b) 35 | : "f" 36 | ); 37 | } 38 | 39 | out 40 | } 41 | 42 | fn psp_main() { 43 | psp::enable_home_button(); 44 | 45 | // Enable the VFPU 46 | unsafe { 47 | use psp::sys::{self, ThreadAttributes}; 48 | sys::sceKernelChangeCurrentThreadAttr(0, ThreadAttributes::VFPU); 49 | } 50 | 51 | psp::dprintln!("Testing VFPU..."); 52 | psp::dprintln!("VFPU 123 + 4 = {}", vfpu_add(123, 4)); 53 | } 54 | -------------------------------------------------------------------------------- /examples/vfpu-context-switching/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/vfpu-context-switching/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-vfpu-context-switching-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/vfpu-context-switching/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(llvm_asm)] 4 | 5 | use psp::sys::vfpu_context::{Context, MatrixSet}; 6 | 7 | psp::module!("vfpu_context_test", 1, 1); 8 | 9 | #[no_mangle] 10 | #[inline(never)] 11 | extern fn psp_main() { 12 | psp::enable_home_button(); 13 | psp::dprintln!("Testing VFPU context switcher..."); 14 | 15 | let mut context = Context::new(); 16 | 17 | unsafe { 18 | context.prepare(MatrixSet::VMAT3, MatrixSet::VMAT0); 19 | psp::vfpu_asm! { 20 | vmzero_q M000; 21 | 22 | viim_s S000, 1; 23 | viim_s S001, 2; 24 | viim_s S002, 3; 25 | viim_s S003, 4; 26 | 27 | vmmov_q M300, M000; 28 | 29 | : : : : "volatile" 30 | } 31 | 32 | // Clobber M300 and M000 33 | context.prepare(MatrixSet::empty(), MatrixSet::VMAT0 | MatrixSet::VMAT3); 34 | psp::vfpu_asm! { 35 | vmzero_q M000; 36 | vmzero_q M300; 37 | 38 | : : : : "volatile" 39 | } 40 | 41 | // Read M300 back from the context, and clobber M000. 42 | context.prepare(MatrixSet::VMAT3, MatrixSet::VMAT0); 43 | let mut out: i32; 44 | psp::vfpu_asm! { 45 | vmmov_q M000, M300; 46 | 47 | mfv t0, S000; 48 | .mips "mtc1 $$t0, $$f0"; 49 | .mips "cvt.w.s $$f0, $$f0"; 50 | .mips "mfc1 $$t0, $$f0"; 51 | .mips "addu $0, $$zero, $$t0"; 52 | 53 | mfv t0, S001; 54 | .mips "mtc1 $$t0, $$f0"; 55 | .mips "cvt.w.s $$f0, $$f0"; 56 | .mips "mfc1 $$t0, $$f0"; 57 | .mips "addu $0, $0, $$t0"; 58 | 59 | mfv t0, S002; 60 | .mips "mtc1 $$t0, $$f0"; 61 | .mips "cvt.w.s $$f0, $$f0"; 62 | .mips "mfc1 $$t0, $$f0"; 63 | .mips "addu $0, $0, $$t0"; 64 | 65 | mfv t0, S003; 66 | .mips "mtc1 $$t0, $$f0"; 67 | .mips "cvt.w.s $$f0, $$f0"; 68 | .mips "mfc1 $$t0, $$f0"; 69 | .mips "addu $0, $0, $$t0"; 70 | 71 | : "=r"(out) : : "t0", "f0" : "volatile" 72 | } 73 | 74 | psp::dprintln!("1 + 2 + 3 + 4 = {}", out); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/wlan/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/wlan/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp-wlan-example" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | psp = { path = "../../psp" } 9 | -------------------------------------------------------------------------------- /examples/wlan/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This example only demonstrates functionality regarding the WLAN chip. It is 2 | //! not a networking example. You might want to look into `sceNet*` functions 3 | //! for actual network access. 4 | 5 | #![no_std] 6 | #![no_main] 7 | 8 | psp::module!("sample_wlan", 1, 1); 9 | 10 | fn psp_main() { 11 | psp::enable_home_button(); 12 | 13 | unsafe { 14 | let wlan_power = psp::sys::sceWlanDevIsPowerOn() == 1; 15 | let wlan_switch = psp::sys::sceWlanGetSwitchState() == 1; 16 | 17 | let mut buf = [0; 8]; 18 | psp::sys::sceWlanGetEtherAddr(&mut buf[0]); 19 | 20 | psp::dprintln!( 21 | "WLAN switch enabled: {}, WLAN active: {}, \ 22 | MAC address: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", 23 | wlan_power, wlan_switch, 24 | buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libunwind/.gitignore: -------------------------------------------------------------------------------- 1 | src 2 | include 3 | libunwind.a 4 | *.o 5 | -------------------------------------------------------------------------------- /libunwind/Makefile: -------------------------------------------------------------------------------- 1 | CC := clang 2 | CXX := clang++ 3 | AR := llvm-ar 4 | ARFLAGS := rcs 5 | 6 | CPP_OBJLIST := libunwind.o 7 | C_OBJLIST := UnwindLevel1.o UnwindLevel1-gcc-ext.o 8 | S_OBJLIST := UnwindRegistersRestore.o UnwindRegistersSave.o 9 | 10 | CFLAGS := -std=c99 11 | CXXFLAGS := -std=c++11 -nostdinc++ -fno-exceptions -fno-rtti 12 | CPPFLAGS := -target mipsel-unknown-unknown -mcpu=mips2 -msingle-float \ 13 | -fstrict-aliasing -funwind-tables -O3 \ 14 | -D __LITTLE_ENDIAN__ -D __ELF__ -D _LIBUNWIND_IS_BAREMETAL \ 15 | -D _LIBUNWIND_HAS_NO_THREADS -D _LIBUNWIND_IS_NATIVE_ONLY \ 16 | -DNDEBUG \ 17 | -I /usr/local/pspdev/psp/include/ \ 18 | -I include 19 | 20 | ../psp/libunwind.a: libunwind.a 21 | cp libunwind.a ../psp/ 22 | touch ../psp/build.rs 23 | 24 | libunwind.a: $(CPP_OBJLIST) $(C_OBJLIST) $(S_OBJLIST) 25 | $(AR) $(ARFLAGS) libunwind.a $^ 26 | 27 | $(CPP_OBJLIST): %.o: src/%.cpp 28 | $(COMPILE.cc) $^ 29 | 30 | $(C_OBJLIST): %.o: src/%.c 31 | $(S_OBJLIST): %.o: src/%.S 32 | $(C_OBJLIST) $(S_OBJLIST): 33 | $(COMPILE.c) $^ 34 | 35 | .PHONY: clean patch 36 | 37 | patch: 38 | git submodule update --init --depth 1 -- ./rustc 39 | (cd rustc; git submodule update --init --depth 1 -- src/llvm-project) 40 | cp -r rustc/src/llvm-project/libunwind/{src,include} . 41 | patch -p0 < ./no-sdc1.patch 42 | 43 | clean: 44 | rm -r *.o libunwind.a 45 | -------------------------------------------------------------------------------- /libunwind/no-sdc1.patch: -------------------------------------------------------------------------------- 1 | diff -u ../../rustc/src/llvm-project/libunwind/src/UnwindRegistersRestore.S src/UnwindRegistersRestore.S 2 | --- ../../rustc/src/llvm-project/libunwind/src/UnwindRegistersRestore.S 2020-04-27 15:40:17.000000000 -0400 3 | +++ src/UnwindRegistersRestore.S 2020-05-04 14:29:35.000000000 -0400 4 | @@ -823,22 +823,38 @@ 5 | .set nomacro 6 | #ifdef __mips_hard_float 7 | #if __mips_fpr != 64 8 | - ldc1 $f0, (4 * 36 + 8 * 0)($4) 9 | - ldc1 $f2, (4 * 36 + 8 * 2)($4) 10 | - ldc1 $f4, (4 * 36 + 8 * 4)($4) 11 | - ldc1 $f6, (4 * 36 + 8 * 6)($4) 12 | - ldc1 $f8, (4 * 36 + 8 * 8)($4) 13 | - ldc1 $f10, (4 * 36 + 8 * 10)($4) 14 | - ldc1 $f12, (4 * 36 + 8 * 12)($4) 15 | - ldc1 $f14, (4 * 36 + 8 * 14)($4) 16 | - ldc1 $f16, (4 * 36 + 8 * 16)($4) 17 | - ldc1 $f18, (4 * 36 + 8 * 18)($4) 18 | - ldc1 $f20, (4 * 36 + 8 * 20)($4) 19 | - ldc1 $f22, (4 * 36 + 8 * 22)($4) 20 | - ldc1 $f24, (4 * 36 + 8 * 24)($4) 21 | - ldc1 $f26, (4 * 36 + 8 * 26)($4) 22 | - ldc1 $f28, (4 * 36 + 8 * 28)($4) 23 | - ldc1 $f30, (4 * 36 + 8 * 30)($4) 24 | + l.s $f0, (4 * 36 + 4 * 0)($4) 25 | + l.s $f1, (4 * 36 + 4 * 1)($4) 26 | + l.s $f2, (4 * 36 + 4 * 2)($4) 27 | + l.s $f3, (4 * 36 + 4 * 3)($4) 28 | + l.s $f4, (4 * 36 + 4 * 4)($4) 29 | + l.s $f5, (4 * 36 + 4 * 5)($4) 30 | + l.s $f6, (4 * 36 + 4 * 6)($4) 31 | + l.s $f7, (4 * 36 + 4 * 7)($4) 32 | + l.s $f8, (4 * 36 + 4 * 8)($4) 33 | + l.s $f9, (4 * 36 + 4 * 9)($4) 34 | + l.s $f10, (4 * 36 + 4 * 10)($4) 35 | + l.s $f11, (4 * 36 + 4 * 11)($4) 36 | + l.s $f12, (4 * 36 + 4 * 12)($4) 37 | + l.s $f13, (4 * 36 + 4 * 13)($4) 38 | + l.s $f14, (4 * 36 + 4 * 14)($4) 39 | + l.s $f15, (4 * 36 + 4 * 15)($4) 40 | + l.s $f16, (4 * 36 + 4 * 16)($4) 41 | + l.s $f17, (4 * 36 + 4 * 17)($4) 42 | + l.s $f18, (4 * 36 + 4 * 18)($4) 43 | + l.s $f19, (4 * 36 + 4 * 19)($4) 44 | + l.s $f20, (4 * 36 + 4 * 20)($4) 45 | + l.s $f21, (4 * 36 + 4 * 21)($4) 46 | + l.s $f22, (4 * 36 + 4 * 22)($4) 47 | + l.s $f23, (4 * 36 + 4 * 23)($4) 48 | + l.s $f24, (4 * 36 + 4 * 24)($4) 49 | + l.s $f25, (4 * 36 + 4 * 25)($4) 50 | + l.s $f26, (4 * 36 + 4 * 26)($4) 51 | + l.s $f27, (4 * 36 + 4 * 27)($4) 52 | + l.s $f28, (4 * 36 + 4 * 28)($4) 53 | + l.s $f29, (4 * 36 + 4 * 29)($4) 54 | + l.s $f30, (4 * 36 + 4 * 30)($4) 55 | + l.s $f31, (4 * 36 + 4 * 31)($4) 56 | #else 57 | ldc1 $f0, (4 * 36 + 8 * 0)($4) 58 | ldc1 $f1, (4 * 36 + 8 * 1)($4) 59 | diff -u ../../rustc/src/llvm-project/libunwind/src/UnwindRegistersSave.S src/UnwindRegistersSave.S 60 | --- ../../rustc/src/llvm-project/libunwind/src/UnwindRegistersSave.S 2020-04-27 15:40:17.000000000 -0400 61 | +++ src/UnwindRegistersSave.S 2020-05-04 14:29:35.000000000 -0400 62 | @@ -168,22 +168,38 @@ 63 | sw $8, (4 * 34)($4) 64 | #ifdef __mips_hard_float 65 | #if __mips_fpr != 64 66 | - sdc1 $f0, (4 * 36 + 8 * 0)($4) 67 | - sdc1 $f2, (4 * 36 + 8 * 2)($4) 68 | - sdc1 $f4, (4 * 36 + 8 * 4)($4) 69 | - sdc1 $f6, (4 * 36 + 8 * 6)($4) 70 | - sdc1 $f8, (4 * 36 + 8 * 8)($4) 71 | - sdc1 $f10, (4 * 36 + 8 * 10)($4) 72 | - sdc1 $f12, (4 * 36 + 8 * 12)($4) 73 | - sdc1 $f14, (4 * 36 + 8 * 14)($4) 74 | - sdc1 $f16, (4 * 36 + 8 * 16)($4) 75 | - sdc1 $f18, (4 * 36 + 8 * 18)($4) 76 | - sdc1 $f20, (4 * 36 + 8 * 20)($4) 77 | - sdc1 $f22, (4 * 36 + 8 * 22)($4) 78 | - sdc1 $f24, (4 * 36 + 8 * 24)($4) 79 | - sdc1 $f26, (4 * 36 + 8 * 26)($4) 80 | - sdc1 $f28, (4 * 36 + 8 * 28)($4) 81 | - sdc1 $f30, (4 * 36 + 8 * 30)($4) 82 | + s.s $f0, (4 * 36 + 4 * 0)($4) 83 | + s.s $f1, (4 * 36 + 4 * 1)($4) 84 | + s.s $f2, (4 * 36 + 4 * 2)($4) 85 | + s.s $f3, (4 * 36 + 4 * 3)($4) 86 | + s.s $f4, (4 * 36 + 4 * 4)($4) 87 | + s.s $f5, (4 * 36 + 4 * 5)($4) 88 | + s.s $f6, (4 * 36 + 4 * 6)($4) 89 | + s.s $f7, (4 * 36 + 4 * 7)($4) 90 | + s.s $f8, (4 * 36 + 4 * 8)($4) 91 | + s.s $f9, (4 * 36 + 4 * 9)($4) 92 | + s.s $f10, (4 * 36 + 4 * 10)($4) 93 | + s.s $f11, (4 * 36 + 4 * 11)($4) 94 | + s.s $f12, (4 * 36 + 4 * 12)($4) 95 | + s.s $f13, (4 * 36 + 4 * 13)($4) 96 | + s.s $f14, (4 * 36 + 4 * 14)($4) 97 | + s.s $f15, (4 * 36 + 4 * 15)($4) 98 | + s.s $f16, (4 * 36 + 4 * 16)($4) 99 | + s.s $f17, (4 * 36 + 4 * 17)($4) 100 | + s.s $f18, (4 * 36 + 4 * 18)($4) 101 | + s.s $f19, (4 * 36 + 4 * 19)($4) 102 | + s.s $f20, (4 * 36 + 4 * 20)($4) 103 | + s.s $f21, (4 * 36 + 4 * 21)($4) 104 | + s.s $f22, (4 * 36 + 4 * 22)($4) 105 | + s.s $f23, (4 * 36 + 4 * 23)($4) 106 | + s.s $f24, (4 * 36 + 4 * 24)($4) 107 | + s.s $f25, (4 * 36 + 4 * 25)($4) 108 | + s.s $f26, (4 * 36 + 4 * 26)($4) 109 | + s.s $f27, (4 * 36 + 4 * 27)($4) 110 | + s.s $f28, (4 * 36 + 4 * 28)($4) 111 | + s.s $f29, (4 * 36 + 4 * 29)($4) 112 | + s.s $f30, (4 * 36 + 4 * 30)($4) 113 | + s.s $f31, (4 * 36 + 4 * 31)($4) 114 | #else 115 | sdc1 $f0, (4 * 36 + 8 * 0)($4) 116 | sdc1 $f1, (4 * 36 + 8 * 1)($4) 117 | -------------------------------------------------------------------------------- /prx.md: -------------------------------------------------------------------------------- 1 | # PRX section structure 2 | 3 | ## .rodata.sceModuleInfo 4 | 5 | * Most important section 6 | * Details module information, and points to other sections 7 | 8 | ## .lib.ent 9 | 10 | * Details module exports 11 | * `module_start` 12 | * `SceModuleInfo` 13 | * `module_stop` 14 | * etc... 15 | 16 | ## .rodata.sceResident 17 | 18 | * Split into 2 uses: 19 | * The actual export table referenced in .lib.ent. The number of entries 20 | here is specified in the .lib.ent variable and function count fields. 21 | * Storing the names of imported resident libraries 22 | 23 | ## .lib.stub 24 | 25 | * Details modules to be imported 26 | * Unknown ATM if this is system imports only or can be additional user modules. 27 | 28 | ## .sceStub.text 29 | 30 | * Contains jump code for imported modules 31 | * 2 instructions per function, usually `jr $ra` followed by `nop`. 32 | 33 | ## .rodata.sceNid 34 | 35 | * Contains lists of NIDs, to be referenced in .lib.stub 36 | -------------------------------------------------------------------------------- /psp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "psp" 3 | version = "0.1.3" 4 | description = "A library for building full PSP modules, including both PRX plugins and regular homebrew apps." 5 | readme = "../README.md" 6 | repository = "https://github.com/overdrivenpotato/rust-psp" 7 | license = "MIT" 8 | authors = [ 9 | "Marko Mijalkovic ", 10 | "Paul Sajna " 11 | ] 12 | edition = "2018" 13 | build = "build.rs" 14 | 15 | [lib] 16 | crate-type = ["lib", "staticlib"] 17 | 18 | [features] 19 | default = [] 20 | std = [] 21 | # Compile this library as a stub provider. Useful to compile this as a static 22 | # library for other projects. 23 | stub-only = [] 24 | 25 | [dependencies] 26 | paste = "0.1.12" 27 | bitflags = "1.2.1" 28 | embedded-graphics = { version = "0.6.2", optional = true } 29 | 30 | [dependencies.num_enum] 31 | version = "0.5.0" 32 | default-features = false 33 | 34 | [dependencies.num_enum_derive] 35 | version = "0.5.0" 36 | default-features = false 37 | -------------------------------------------------------------------------------- /psp/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::Path}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | println!("cargo:rerun-if-changed=libunwind.a"); 6 | 7 | if env::var("CARGO_FEATURE_STUB_ONLY").is_ok() { 8 | return; 9 | } 10 | 11 | // TODO: Do we even need to copy the library over? Maybe we can just link 12 | // directly from the current directory. 13 | let out_dir = env::var("OUT_DIR").unwrap(); 14 | let out_file = Path::new(&out_dir).join("libunwind.a"); 15 | std::fs::copy("./libunwind.a", out_file).unwrap(); 16 | 17 | println!("cargo:rustc-link-lib=static=unwind"); 18 | println!("cargo:rustc-link-search=native={}", out_dir); 19 | } 20 | -------------------------------------------------------------------------------- /psp/libunwind.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/psp/libunwind.a -------------------------------------------------------------------------------- /psp/src/alloc_impl.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{Layout, GlobalAlloc}; 2 | use core::{ptr, mem}; 3 | use crate::sys::{self, SceUid, SceSysMemPartitionId, SceSysMemBlockTypes}; 4 | 5 | /// An allocator that hooks directly into the PSP OS memory allocator. 6 | struct SystemAlloc; 7 | 8 | unsafe impl GlobalAlloc for SystemAlloc { 9 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 10 | let size = layout.size() 11 | // We need to store the memory block ID. 12 | + mem::size_of::() 13 | 14 | // We also store padding bytes, in case the block returned from the 15 | // system is not aligned. The count of padding bytes is also stored 16 | // here, in the last byte. 17 | + layout.align(); 18 | 19 | // crate::debug::print_num(size); 20 | 21 | let id = sys::sceKernelAllocPartitionMemory( 22 | SceSysMemPartitionId::SceKernelPrimaryUserPartition, 23 | &b"block\0"[0], 24 | SceSysMemBlockTypes::Low, 25 | size as u32, 26 | ptr::null_mut(), 27 | ); 28 | 29 | // TODO: Error handling. 30 | let mut ptr: *mut u8 = sys::sceKernelGetBlockHeadAddr(id).cast(); 31 | *ptr.cast() = id; 32 | 33 | ptr = ptr.add(mem::size_of::()); 34 | 35 | // We must add at least one, to store this value. 36 | let align_padding = 1 + ptr.add(1).align_offset(layout.align()); 37 | *ptr.add(align_padding - 1) = align_padding as u8; 38 | ptr.add(align_padding) 39 | } 40 | 41 | #[inline(never)] 42 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 43 | let align_padding = *ptr.sub(1); 44 | 45 | let id = *ptr 46 | .sub(align_padding as usize) 47 | .cast::().offset(-1); 48 | 49 | // TODO: Error handling. 50 | sys::sceKernelFreePartitionMemory(id); 51 | } 52 | } 53 | 54 | #[global_allocator] 55 | static ALLOC: SystemAlloc = SystemAlloc; 56 | 57 | #[cfg(not(feature = "std"))] 58 | #[alloc_error_handler] 59 | fn aeh(_: Layout) -> ! { loop {} } 60 | 61 | #[no_mangle] 62 | #[cfg(not(feature = "stub-only"))] 63 | unsafe extern fn memset(ptr: *mut u8, value: u32, num: usize) -> *mut u8 { 64 | for i in 0..num { 65 | *ptr.add(i) = value as u8; 66 | } 67 | 68 | ptr 69 | } 70 | 71 | #[no_mangle] 72 | #[cfg(not(feature = "stub-only"))] 73 | unsafe extern fn memcpy(dst: *mut u8, src: *const u8, num: isize) -> *mut u8 { 74 | for i in 0..num { 75 | *dst.offset(i) = *src.offset(i); 76 | } 77 | 78 | dst 79 | } 80 | 81 | #[no_mangle] 82 | #[cfg(not(feature = "stub-only"))] 83 | unsafe extern fn memcmp(ptr1: *mut u8, ptr2: *mut u8, num: isize) -> i32 { 84 | for i in 0..num { 85 | let diff = ptr1.offset(i) as i32 - ptr2.offset(i) as i32; 86 | 87 | if diff != 0 { 88 | return diff; 89 | } 90 | } 91 | 92 | 0 93 | } 94 | 95 | #[no_mangle] 96 | #[cfg(not(feature = "stub-only"))] 97 | unsafe extern fn memmove(dst: *mut u8, src: *mut u8, num: isize) -> *mut u8 { 98 | if dst < src { 99 | for i in 0..num { 100 | *dst.offset(i) = *src.offset(i); 101 | } 102 | } else { 103 | for i in num-1..=0 { 104 | *dst.offset(i) = *src.offset(i); 105 | } 106 | } 107 | 108 | dst 109 | } 110 | -------------------------------------------------------------------------------- /psp/src/benchmark.rs: -------------------------------------------------------------------------------- 1 | /// Execute `f` `iterations` times and return average duration per iteration 2 | pub fn benchmark(mut f: F, iterations: usize) -> core::time::Duration { 3 | let mut loop_start: u64 = 0; 4 | let mut loop_end: u64 = 0; 5 | let avg_micros: u64; 6 | 7 | unsafe { 8 | crate::sys::sceRtcGetCurrentTick(&mut loop_start as *mut u64); 9 | 10 | for _ in 0..iterations { 11 | f(); 12 | } 13 | 14 | crate::sys::sceRtcGetCurrentTick(&mut loop_end as *mut u64); 15 | let avg_iter_ticks = (loop_end - loop_start) / iterations as u64; 16 | let ticks_per_sec = crate::sys::sceRtcGetTickResolution(); 17 | avg_micros = ((avg_iter_ticks as f64 / ticks_per_sec as f64) * 1_000_000.0) as u64; 18 | } 19 | 20 | core::time::Duration::from_micros(avg_micros) 21 | } 22 | -------------------------------------------------------------------------------- /psp/src/constants.rs: -------------------------------------------------------------------------------- 1 | /// PSP screen width in pixels 2 | pub const SCREEN_WIDTH: u32 = 480; 3 | /// PSP screen height in pixels 4 | pub const SCREEN_HEIGHT: u32 = 272; 5 | /// The screen buffer width is padded from 480 pixels to a power of 2 (512) 6 | pub const BUF_WIDTH: u32 = 512; 7 | -------------------------------------------------------------------------------- /psp/src/debug.rs: -------------------------------------------------------------------------------- 1 | //! Debug support. 2 | //! 3 | //! You should use the `dprintln!` and `dprint!` macros. 4 | 5 | use crate::sys; 6 | use core::fmt; 7 | 8 | /// Like `println!`, but prints to the PSP screen. 9 | #[macro_export] 10 | macro_rules! dprintln { 11 | ($($arg:tt)*) => { 12 | $crate::debug::print_args(core::format_args!($($arg)*)); 13 | $crate::debug::print_args(core::format_args!("\n")); 14 | } 15 | } 16 | 17 | /// Like `print!`, but prints to the PSP screen. 18 | #[macro_export] 19 | macro_rules! dprint { 20 | ($($arg:tt)*) => { 21 | $crate::debug::print_args(core::format_args!($($arg)*)) 22 | } 23 | } 24 | 25 | // TODO: Wrap this in some kind of a mutex. 26 | static mut CHARS: CharBuffer = CharBuffer::new(); 27 | 28 | /// Update the screen. 29 | fn update() { 30 | unsafe { 31 | init(); 32 | clear_screen(0); 33 | 34 | for (i, line) in CHARS.lines().enumerate() { 35 | put_str::( 36 | &line.chars[0..line.len], 37 | 0, 38 | i * MsxFont::CHAR_HEIGHT, 39 | 0xffff_ffff, 40 | ) 41 | } 42 | } 43 | } 44 | 45 | trait Font { 46 | const CHAR_WIDTH: usize; 47 | const CHAR_HEIGHT: usize; 48 | 49 | fn put_char(x: usize, y: usize, color: u32, c: u8); 50 | } 51 | 52 | struct MsxFont; 53 | 54 | impl Font for MsxFont { 55 | const CHAR_HEIGHT: usize = 10; 56 | const CHAR_WIDTH: usize = 6; 57 | 58 | fn put_char(x: usize, y: usize, color: u32, c: u8) { 59 | unsafe { 60 | let mut ptr = VRAM_BASE 61 | .offset(x as isize) 62 | .offset((y * BUFFER_WIDTH) as isize); 63 | 64 | for i in 0..8 { 65 | for j in 0..8 { 66 | if MSX_FONT[c as usize * 8 + i] & (0b1000_0000 >> j) != 0 { 67 | *ptr = color; 68 | } 69 | 70 | ptr = ptr.offset(1); 71 | } 72 | 73 | ptr = ptr.offset(-8).offset(BUFFER_WIDTH as isize); 74 | } 75 | } 76 | } 77 | } 78 | 79 | const BUFFER_WIDTH: usize = 512; 80 | const DISPLAY_HEIGHT: usize = 272; 81 | const DISPLAY_WIDTH: usize = 480; 82 | static mut VRAM_BASE: *mut u32 = 0 as *mut u32; 83 | 84 | unsafe fn clear_screen(color: u32) { 85 | let mut ptr = VRAM_BASE; 86 | 87 | for _ in 0..(BUFFER_WIDTH * DISPLAY_HEIGHT) { 88 | *ptr = color; 89 | ptr = ptr.offset(1); 90 | } 91 | } 92 | 93 | unsafe fn put_str(s: &[u8], x: usize, y: usize, color: u32) { 94 | if y > DISPLAY_HEIGHT { 95 | return; 96 | } 97 | 98 | for (i, c) in s.iter().enumerate() { 99 | if i >= (DISPLAY_WIDTH / T::CHAR_WIDTH) as usize { 100 | break; 101 | } 102 | 103 | if *c as u32 <= 255 && *c != b'\0' { 104 | T::put_char(T::CHAR_WIDTH * i + x, y, color, *c); 105 | } 106 | } 107 | } 108 | 109 | unsafe fn init() { 110 | // The OR operation here specifies the address bypasses cache. 111 | VRAM_BASE = (0x4000_0000u32 | sys::sceGeEdramGetAddr() as u32) as *mut u32; 112 | 113 | // TODO: Change sys types to usize. 114 | sys::sceDisplaySetMode(sys::DisplayMode::Lcd, DISPLAY_WIDTH, DISPLAY_HEIGHT); 115 | sys::sceDisplaySetFrameBuf( 116 | VRAM_BASE as *const u8, 117 | BUFFER_WIDTH, 118 | sys::DisplayPixelFormat::Psm8888, 119 | sys::DisplaySetBufSync::NextFrame, 120 | ); 121 | } 122 | 123 | #[doc(hidden)] 124 | pub fn print_args(arguments: core::fmt::Arguments<'_>) { 125 | use fmt::Write; 126 | 127 | unsafe { 128 | let _ = write!(CHARS, "{}", arguments); 129 | } 130 | 131 | update(); 132 | } 133 | 134 | // TODO: Move to font. 135 | const ROWS: usize = DISPLAY_HEIGHT / MsxFont::CHAR_HEIGHT; 136 | const COLS: usize = DISPLAY_WIDTH / MsxFont::CHAR_WIDTH; 137 | 138 | #[derive(Copy, Clone)] 139 | struct Line { 140 | chars: [u8; COLS], 141 | len: usize, 142 | } 143 | 144 | impl Line { 145 | const fn new() -> Self { 146 | Self { 147 | chars: [0; COLS], 148 | len: 0, 149 | } 150 | } 151 | } 152 | 153 | struct CharBuffer { 154 | lines: [Line; ROWS], 155 | written: usize, 156 | advance_next: bool, 157 | } 158 | 159 | impl CharBuffer { 160 | const fn new() -> Self { 161 | Self { 162 | lines: [Line::new(); ROWS], 163 | written: 0, 164 | advance_next: false, 165 | } 166 | } 167 | 168 | fn advance(&mut self) { 169 | self.written += 1; 170 | if self.written >= ROWS { 171 | *self.current_line() = Line::new(); 172 | } 173 | } 174 | 175 | fn current_line(&mut self) -> &mut Line { 176 | &mut self.lines[self.written % ROWS] 177 | } 178 | 179 | fn add(&mut self, c: u8) { 180 | if self.advance_next { 181 | self.advance_next = false; 182 | self.advance(); 183 | } 184 | 185 | match c { 186 | b'\n' => self.advance_next = true, 187 | b'\t' => { 188 | self.add(b' '); 189 | self.add(b' '); 190 | self.add(b' '); 191 | self.add(b' '); 192 | } 193 | 194 | _ => { 195 | if self.current_line().len == COLS { 196 | self.advance(); 197 | } 198 | 199 | let line = self.current_line(); 200 | line.chars[line.len] = c; 201 | line.len += 1; 202 | } 203 | } 204 | } 205 | 206 | fn lines(&self) -> LineIter<'_> { 207 | LineIter { 208 | buf: self, 209 | pos: 0, 210 | } 211 | } 212 | } 213 | 214 | impl fmt::Write for CharBuffer { 215 | fn write_str(&mut self, s: &str) -> fmt::Result { 216 | unsafe { 217 | for c in s.chars() { 218 | match c as u32 { 219 | 0..=255 => CHARS.add(c as u8), 220 | _ => CHARS.add(0), 221 | } 222 | } 223 | } 224 | 225 | Ok(()) 226 | } 227 | } 228 | 229 | struct LineIter<'a> { 230 | buf: &'a CharBuffer, 231 | pos: usize, 232 | } 233 | 234 | impl<'a> Iterator for LineIter<'a> { 235 | type Item = Line; 236 | 237 | fn next(&mut self) -> Option { 238 | if self.pos < core::cmp::min(self.buf.written + 1, ROWS) { 239 | let idx = if self.buf.written > ROWS { 240 | (self.buf.written + 1 + self.pos) % ROWS 241 | } else { 242 | self.pos 243 | }; 244 | 245 | let line = self.buf.lines[idx]; 246 | self.pos += 1; 247 | Some(line) 248 | } else { 249 | None 250 | } 251 | } 252 | } 253 | 254 | /// Raw MSX font. 255 | /// 256 | /// This is an 8bit x 256 black and white image. 257 | const MSX_FONT: [u8; 2048] = *include_bytes!("msxfont.bin"); 258 | -------------------------------------------------------------------------------- /psp/src/eabi.rs: -------------------------------------------------------------------------------- 1 | extern { 2 | /// Call a function accepting 5 32-bit integer arguments via the MIPS-EABI ABI. 3 | /// 4 | /// This is not safe to call with a function that expects any other ABI. 5 | pub fn i5(a: u32, b: u32, c: u32, d: u32, e: u32, ptr: extern fn(u32, u32, u32, u32, u32) -> u32) -> u32; 6 | 7 | /// Call a function accepting 6 32-bit integer arguments via the MIPS-EABI ABI. 8 | /// 9 | /// This is not safe to call with a function that expects any other ABI. 10 | pub fn i6(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, ptr: extern fn(u32, u32, u32, u32, u32, u32) -> u32) -> u32; 11 | 12 | /// Call a function accepting 7 32-bit integer arguments via the MIPS-EABI ABI. 13 | /// 14 | /// This is not safe to call with a function that expects any other ABI. 15 | pub fn i7(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, ptr: extern fn(u32, u32, u32, u32, u32, u32, u32) -> u32) -> u32; 16 | 17 | /// Call a function with the signature `fn(i32, i64, i32) -> i64` via the MIPS-EABI ABI. 18 | /// 19 | /// This is not safe to call with a function that expects any other ABI. 20 | pub fn i_ii_i_rii(a: u32, b: u64, c: u32, ptr: extern fn(u32, u64, u32) -> u64) -> u64; 21 | 22 | /// Call a function with the signature `fn(i32, i64, i32) -> i32` via the MIPS-EABI ABI. 23 | /// 24 | /// This is not safe to call with a function that expects any other ABI. 25 | pub fn i_ii_i_ri(a: u32, b: u64, c: u32, ptr: extern fn(u32, u64, u32) -> u32) -> u32; 26 | } 27 | 28 | // Potential resource: 29 | // (scroll down to table) https://www.linux-mips.org/wiki/P32_Linux_ABI 30 | // 31 | // Page 3-18: http://web.archive.org/web/20040930224745/http://www.caldera.com/developers/devspecs/mipsabi.pdf 32 | // Copied from PDF: 33 | // Despite the fact that some or all of the arguments to a function are passed 34 | // in registers, always allocate space on the stack for all arguments. This 35 | // stack space should be a structure large enough to contain all the arguments, 36 | // aligned according to normal structure rules (after promotion and structure 37 | // return pointer insertion). The locations within the stack frame used for 38 | // arguments are called the home locations. 39 | #[cfg(target_os = "psp")] 40 | global_asm!( 41 | r#" 42 | .section .text 43 | .global i5 44 | i5: 45 | // Store the return register as we are calling a function manually. 46 | addiu $sp, -32 47 | sw $ra, 8($sp) 48 | 49 | // Load argument 5 into register t0. In MIPS-EABI, t0 is actually a4. 50 | lw $t0, 48($sp) 51 | 52 | // Load and call the bridged function. 53 | lw $t1, 52($sp) 54 | jalr $t1 55 | 56 | // Restore the stack and return. 57 | lw $ra, 8($sp) 58 | addiu $sp, 32 59 | jr $ra 60 | 61 | .global i6 62 | i6: 63 | addiu $sp, -32 64 | sw $ra, 8($sp) 65 | 66 | lw $t0, 48($sp) 67 | lw $t1, 52($sp) 68 | 69 | lw $t2, 56($sp) 70 | jalr $t2 71 | 72 | lw $ra, 8($sp) 73 | addiu $sp, 32 74 | jr $ra 75 | 76 | .global i7 77 | i7: 78 | addiu $sp, -32 79 | sw $ra, 8($sp) 80 | 81 | lw $t0, 48($sp) 82 | lw $t1, 52($sp) 83 | lw $t2, 56($sp) 84 | 85 | lw $t3, 60($sp) 86 | jalr $t3 87 | 88 | lw $ra, 8($sp) 89 | addiu $sp, 32 90 | jr $ra 91 | 92 | .global i_ii_i_rii 93 | .global i_ii_i_ri 94 | i_ii_i_rii: 95 | i_ii_i_ri: 96 | addiu $sp, -32 97 | sw $ra, 8($sp) 98 | 99 | lw $t0, 48($sp) 100 | lw $t1, 52($sp) 101 | jalr $t1 102 | 103 | lw $ra, 8($sp) 104 | addiu $sp, 32 105 | jr $ra 106 | "# 107 | ); 108 | -------------------------------------------------------------------------------- /psp/src/embedded_graphics.rs: -------------------------------------------------------------------------------- 1 | //! Interop between the `psp` crate and the 2D `embedded-graphics` crate. 2 | 3 | use crate::sys; 4 | use crate::{SCREEN_WIDTH, SCREEN_HEIGHT, BUF_WIDTH}; 5 | use core::convert::TryInto; 6 | use embedded_graphics::{ 7 | drawable::Pixel, 8 | geometry::Size, 9 | pixelcolor::{Rgb888, RgbColor}, 10 | DrawTarget, 11 | }; 12 | 13 | pub struct Framebuffer { 14 | vram_base: *mut u16, 15 | } 16 | 17 | impl Framebuffer { 18 | pub fn new() -> Self { 19 | unsafe { 20 | sys::sceDisplaySetMode(sys::DisplayMode::Lcd, 480, 272); 21 | let vram_base = (0x4000_0000u32 | sys::sceGeEdramGetAddr() as u32) as *mut u16; 22 | sys::sceDisplaySetFrameBuf( 23 | vram_base as *const u8, 24 | BUF_WIDTH as usize, 25 | sys::DisplayPixelFormat::Psm8888, 26 | sys::DisplaySetBufSync::NextFrame, 27 | ); 28 | Framebuffer { vram_base } 29 | } 30 | } 31 | } 32 | 33 | impl DrawTarget for Framebuffer { 34 | type Error = core::convert::Infallible; 35 | 36 | fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { 37 | let Pixel(coord, color) = pixel; 38 | 39 | if let Ok((x @ 0..=SCREEN_WIDTH, y @ 0..=SCREEN_HEIGHT)) = coord.try_into() { 40 | unsafe { 41 | let ptr = (self.vram_base as *mut u32) 42 | .offset(x as isize) 43 | .offset((y * BUF_WIDTH) as isize); 44 | 45 | *ptr = (color.r() as u32) 46 | | ((color.g() as u32) << 8) 47 | | ((color.b() as u32) << 16); 48 | } 49 | } 50 | 51 | Ok(()) 52 | } 53 | 54 | fn size(&self) -> Size { 55 | Size::new(SCREEN_WIDTH, SCREEN_HEIGHT) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /psp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(stable_features)] 2 | #![feature( 3 | alloc_error_handler, 4 | llvm_asm, 5 | global_asm, 6 | untagged_unions, 7 | core_intrinsics, 8 | const_loop, 9 | const_if_match, 10 | const_generics, 11 | c_variadic, 12 | lang_items, 13 | )] 14 | 15 | // For unwinding support 16 | #![feature(std_internals, panic_info_message, panic_internals, unwind_attributes)] 17 | #![cfg_attr(not(feature = "stub-only"), feature(panic_unwind))] 18 | 19 | // For the `const_generics` feature. 20 | #![allow(incomplete_features)] 21 | 22 | #![cfg_attr(not(feature = "std"), no_std)] 23 | 24 | #[macro_use] extern crate paste; 25 | #[cfg(not(feature = "stub-only"))] extern crate alloc; 26 | #[cfg(not(feature = "stub-only"))] extern crate panic_unwind; 27 | 28 | #[macro_use] 29 | #[doc(hidden)] 30 | #[cfg(not(feature = "stub-only"))] 31 | pub mod debug; 32 | 33 | #[macro_use] mod vfpu; 34 | mod eabi; 35 | pub mod math; 36 | pub mod sys; 37 | #[cfg(not(feature = "stub-only"))] pub mod test_runner; 38 | #[cfg(not(feature = "stub-only"))] pub mod vram_alloc; 39 | 40 | #[cfg(not(feature = "stub-only"))] mod alloc_impl; 41 | #[cfg(not(feature = "stub-only"))] pub mod panic; 42 | 43 | #[cfg(not(feature = "stub-only"))] mod screenshot; 44 | #[cfg(not(feature = "stub-only"))] pub use screenshot::*; 45 | 46 | #[cfg(not(feature = "stub-only"))] mod benchmark; 47 | #[cfg(not(feature = "stub-only"))] pub use benchmark::*; 48 | 49 | #[cfg(not(feature = "stub-only"))] mod constants; 50 | #[cfg(not(feature = "stub-only"))] pub use constants::*; 51 | 52 | #[cfg(not(feature = "std"))] 53 | #[cfg(feature = "stub-only")] 54 | #[panic_handler] 55 | fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } 56 | 57 | #[cfg(not(feature = "std"))] 58 | #[no_mangle] 59 | extern "C" fn __rust_foreign_exception() -> ! { loop {} } 60 | 61 | #[cfg(feature = "std")] 62 | pub use std::panic::catch_unwind; 63 | 64 | #[cfg(all(not(feature = "std"), not(feature = "stub-only")))] 65 | pub use panic::catch_unwind; 66 | 67 | #[cfg(feature="embedded-graphics")] 68 | pub mod embedded_graphics; 69 | 70 | #[repr(align(16))] 71 | #[derive(Copy, Clone)] 72 | pub struct Align16(pub T); 73 | 74 | #[cfg(all(target_os = "psp", not(feature = "stub-only")))] 75 | global_asm!( 76 | r#" 77 | .section .lib.ent.top, "a", @progbits 78 | .align 2 79 | .word 0 80 | .global __lib_ent_top 81 | __lib_ent_top: 82 | .section .lib.ent.btm, "a", @progbits 83 | .align 2 84 | .global __lib_ent_bottom 85 | __lib_ent_bottom: 86 | .word 0 87 | 88 | .section .lib.stub.top, "a", @progbits 89 | .align 2 90 | .word 0 91 | .global __lib_stub_top 92 | __lib_stub_top: 93 | .section .lib.stub.btm, "a", @progbits 94 | .align 2 95 | .global __lib_stub_bottom 96 | __lib_stub_bottom: 97 | .word 0 98 | "# 99 | ); 100 | 101 | /// Declare a PSP module. 102 | /// 103 | /// You must also define a `fn psp_main() { ... }` function in conjunction with 104 | /// this macro. 105 | #[macro_export] 106 | macro_rules! module { 107 | ($name:expr, $version_major:expr, $version_minor: expr) => { 108 | #[doc(hidden)] 109 | mod __psp_module { 110 | #[no_mangle] 111 | #[link_section = ".rodata.sceModuleInfo"] 112 | #[used] 113 | static MODULE_INFO: $crate::Align16<$crate::sys::SceModuleInfo> = $crate::Align16( 114 | $crate::sys::SceModuleInfo { 115 | mod_attribute: 0, 116 | mod_version: [$version_major, $version_minor], 117 | mod_name: $crate::sys::SceModuleInfo::name($name), 118 | terminal: 0, 119 | gp_value: unsafe { &_gp }, 120 | stub_top: unsafe { &__lib_stub_top }, 121 | stub_end: unsafe { &__lib_stub_bottom }, 122 | ent_top: unsafe { &__lib_ent_top }, 123 | ent_end: unsafe { &__lib_ent_bottom }, 124 | } 125 | ); 126 | 127 | extern { 128 | static _gp: u8; 129 | static __lib_ent_bottom: u8; 130 | static __lib_ent_top: u8; 131 | static __lib_stub_bottom: u8; 132 | static __lib_stub_top: u8; 133 | } 134 | 135 | #[no_mangle] 136 | #[link_section = ".lib.ent"] 137 | #[used] 138 | static LIB_ENT: $crate::sys::SceLibraryEntry = $crate::sys::SceLibraryEntry { 139 | // TODO: Fix this? 140 | name: core::ptr::null(), 141 | version: ($version_major, $version_minor), 142 | attribute: $crate::sys::SceLibAttr::SCE_LIB_IS_SYSLIB, 143 | entry_len: 4, 144 | var_count: 1, 145 | func_count: 1, 146 | entry_table: &LIB_ENT_TABLE, 147 | }; 148 | 149 | #[no_mangle] 150 | #[link_section = ".rodata.sceResident"] 151 | #[used] 152 | static LIB_ENT_TABLE: $crate::sys::SceLibraryEntryTable = $crate::sys::SceLibraryEntryTable { 153 | module_start_nid: 0xd632acdb, // module_start 154 | module_info_nid: 0xf01d73a7, // SceModuleInfo 155 | module_start: module_start, 156 | module_info: &MODULE_INFO.0, 157 | }; 158 | 159 | #[no_mangle] 160 | extern "C" fn module_start(_argc: isize, _argv: *const *const u8) -> isize { 161 | use $crate::sys::ThreadAttributes; 162 | use core::ffi::c_void; 163 | 164 | unsafe { 165 | extern fn main_thread(_argc: usize, _argv: *mut c_void) -> i32 { 166 | // TODO: Maybe print any error to debug screen? 167 | let _ = $crate::catch_unwind(|| { 168 | super::psp_main(); 169 | }); 170 | 171 | 0 172 | } 173 | 174 | let id = $crate::sys::sceKernelCreateThread( 175 | &b"main_thread\0"[0], 176 | main_thread, 177 | // default priority of 32. 178 | 32, 179 | // 256kb stack 180 | 256 * 1024, 181 | ThreadAttributes::USER, 182 | core::ptr::null_mut(), 183 | ); 184 | 185 | $crate::sys::sceKernelStartThread(id, 0, core::ptr::null_mut()); 186 | } 187 | 188 | 0 189 | } 190 | } 191 | } 192 | } 193 | 194 | /// Enable the home button. 195 | /// 196 | /// This API does not have destructor support yet. You can manually setup an 197 | /// exit callback if you need this, see the source code of this function. 198 | pub fn enable_home_button() { 199 | use core::{ptr, ffi::c_void}; 200 | use sys::ThreadAttributes; 201 | 202 | unsafe { 203 | unsafe extern fn exit_thread(_args: usize, _argp: *mut c_void) -> i32 { 204 | unsafe extern fn exit_callback(_arg1: i32, _arg2: i32, _arg: *mut c_void) -> i32 { 205 | sys::sceKernelExitGame(); 206 | 0 207 | } 208 | 209 | let id = sys::sceKernelCreateCallback( 210 | &b"exit_callback\0"[0], 211 | exit_callback, 212 | ptr::null_mut(), 213 | ); 214 | 215 | sys::sceKernelRegisterExitCallback(id); 216 | sys::sceKernelSleepThreadCB(); 217 | 218 | 0 219 | } 220 | 221 | // Enable the home button. 222 | let id = sys::sceKernelCreateThread( 223 | &b"exit_thread\0"[0], 224 | exit_thread, 225 | 32, 226 | 0x1000, 227 | ThreadAttributes::empty(), 228 | ptr::null_mut(), 229 | ); 230 | 231 | sys::sceKernelStartThread(id, 0, ptr::null_mut()); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /psp/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | mod trig; 2 | pub use trig::*; 3 | -------------------------------------------------------------------------------- /psp/src/math/trig.rs: -------------------------------------------------------------------------------- 1 | // TODO: cosf vs cosf32? which makes intrinsics::cosf32 work? 2 | #[allow(non_snake_case)] 3 | #[no_mangle] 4 | pub unsafe extern "C" fn cosf32(rad: f32) -> f32 { 5 | let out; 6 | 7 | vfpu_asm!( 8 | .mips "mfc1 $$t0, $1"; 9 | mtv t0, S000; 10 | vcst_s S001, VFPU_2_PI; 11 | vmul_s S000, S000, S001; 12 | vcos_s S000, S000; 13 | mfv t0, S000; 14 | .mips "mtc1 $$t0, $0"; 15 | 16 | : "=f"(out) : "f"(rad) : "$8", "memory" : "volatile" 17 | ); 18 | 19 | out 20 | } 21 | -------------------------------------------------------------------------------- /psp/src/msxfont.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajattack/rust-psp/0152d8bee8f8c21a6515c65d12cb82f9bc07f592/psp/src/msxfont.bin -------------------------------------------------------------------------------- /psp/src/panic.rs: -------------------------------------------------------------------------------- 1 | //! Panic support for the PSP. 2 | 3 | // Most of the code here is lifted from `rustc/src/libstd/panicking.rs`. It has 4 | // been adapted to run on the PSP. 5 | 6 | #[cfg(not(feature = "std"))] 7 | use crate::sys; 8 | 9 | #[cfg(feature = "std")] 10 | use core::{mem::ManuallyDrop, any::Any}; 11 | #[cfg(not(feature = "std"))] 12 | use core::{mem::{self, ManuallyDrop}, any::Any, panic::{PanicInfo, BoxMeUp, Location}}; 13 | 14 | #[cfg(not(feature = "std"))] 15 | use core::fmt; 16 | 17 | #[cfg(not(feature = "std"))] 18 | use alloc::{boxed::Box, string::{String, ToString}}; 19 | 20 | #[link(name = "unwind", kind = "static")] 21 | extern {} 22 | 23 | #[cfg(not(feature = "std"))] 24 | fn print_and_die(s: String) -> ! { 25 | dprintln!("{}", s); 26 | 27 | unsafe { 28 | sys::sceKernelExitDeleteThread(1); 29 | core::intrinsics::unreachable() 30 | } 31 | } 32 | 33 | #[cfg(not(feature = "std"))] 34 | #[panic_handler] 35 | #[inline(never)] 36 | fn panic(info: &PanicInfo) -> ! { 37 | panic_impl(info) 38 | } 39 | 40 | #[inline(always)] 41 | #[cfg_attr(not(target_os = "psp"), allow(unused))] 42 | #[cfg(not(feature = "std"))] 43 | fn panic_impl(info: &PanicInfo) -> ! { 44 | struct PanicPayload<'a> { 45 | inner: &'a fmt::Arguments<'a>, 46 | string: Option, 47 | } 48 | 49 | impl<'a> PanicPayload<'a> { 50 | fn new(inner: &'a fmt::Arguments<'a>) -> PanicPayload<'a> { 51 | PanicPayload { inner, string: None } 52 | } 53 | 54 | fn fill(&mut self) -> &mut String { 55 | use fmt::Write; 56 | let inner = self.inner; 57 | self.string.get_or_insert_with(|| { 58 | let mut s = String::new(); 59 | drop(s.write_fmt(*inner)); 60 | s 61 | }) 62 | } 63 | } 64 | 65 | unsafe impl<'a> BoxMeUp for PanicPayload<'a> { 66 | fn take_box(&mut self) -> *mut (dyn Any + Send) { 67 | let contents = mem::take(self.fill()); 68 | Box::into_raw(Box::new(contents)) 69 | } 70 | 71 | fn get(&mut self) -> &(dyn Any + Send) { 72 | self.fill() 73 | } 74 | } 75 | 76 | let loc = info.location().unwrap(); 77 | let msg = info.message().unwrap(); 78 | rust_panic_with_hook(&mut PanicPayload::new(msg), info.message(), loc); 79 | } 80 | 81 | /// Central point for dispatching panics. 82 | /// 83 | /// Executes the primary logic for a panic, including checking for recursive 84 | /// panics, panic hooks, and finally dispatching to the panic runtime to either 85 | /// abort or unwind. 86 | #[cfg(not(feature = "std"))] 87 | fn rust_panic_with_hook( 88 | payload: &mut dyn BoxMeUp, 89 | message: Option<&fmt::Arguments<'_>>, 90 | location: &Location<'_>, 91 | ) -> ! { 92 | let panics = update_panic_count(1); 93 | 94 | fn die_nested() -> ! { 95 | print_and_die("thread panicked while processing panic. aborting.".into()); 96 | } 97 | 98 | let mut info = PanicInfo::internal_constructor(message, location); 99 | info.set_payload(payload.get()); 100 | 101 | dprintln!("{}", info.to_string()); 102 | 103 | if panics > 1 { 104 | // If a thread panics while it's already unwinding then we 105 | // have limited options. Currently our preference is to 106 | // just abort. In the future we may consider resuming 107 | // unwinding or otherwise exiting the thread cleanly. 108 | die_nested(); 109 | } 110 | 111 | rust_panic(payload) 112 | } 113 | 114 | fn update_panic_count(amt: isize) -> usize { 115 | // TODO: Make this thread local 116 | static mut PANIC_COUNT: usize = 0; 117 | 118 | unsafe { 119 | PANIC_COUNT = (PANIC_COUNT as isize + amt) as usize; 120 | PANIC_COUNT 121 | } 122 | } 123 | 124 | #[allow(improper_ctypes)] 125 | extern "C" { 126 | fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any + Send + 'static); 127 | #[unwind(allowed)] 128 | fn __rust_start_panic(payload: usize) -> u32; 129 | } 130 | 131 | #[inline(never)] 132 | #[no_mangle] 133 | #[cfg(not(feature = "std"))] 134 | fn rust_panic(mut msg: &mut dyn BoxMeUp) -> ! { 135 | let code = unsafe { 136 | let obj = &mut msg as *mut &mut dyn BoxMeUp; 137 | panic_unwind::__rust_start_panic(obj as _) 138 | }; 139 | 140 | print_and_die(alloc::format!("failed to initiate panic, error {}", code)) 141 | } 142 | 143 | #[cfg(not(test))] 144 | #[no_mangle] 145 | #[cfg(not(feature = "std"))] 146 | extern "C" fn __rust_drop_panic() -> ! { 147 | print_and_die("Rust panics must be rethrown".into()); 148 | } 149 | 150 | /// Invoke a closure, capturing the cause of an unwinding panic if one occurs. 151 | #[inline(never)] 152 | pub fn catch_unwind R>(f: F) -> Result> { 153 | // This whole function is directly lifted out of rustc. See comments there 154 | // for an explanation of how this actually works. 155 | 156 | union Data { 157 | f: ManuallyDrop, 158 | r: ManuallyDrop, 159 | p: ManuallyDrop>, 160 | } 161 | 162 | let mut data = Data { f: ManuallyDrop::new(f) }; 163 | 164 | let data_ptr = &mut data as *mut _ as *mut u8; 165 | 166 | return unsafe { 167 | if core::intrinsics::r#try(do_call::, data_ptr, do_catch::) == 0 { 168 | Ok(ManuallyDrop::into_inner(data.r)) 169 | } else { 170 | Err(ManuallyDrop::into_inner(data.p)) 171 | } 172 | }; 173 | 174 | #[cold] 175 | unsafe fn cleanup(payload: *mut u8) -> Box { 176 | let obj = Box::from_raw(__rust_panic_cleanup(payload)); 177 | update_panic_count(-1); 178 | obj 179 | } 180 | 181 | #[inline] 182 | fn do_call R, R>(data: *mut u8) { 183 | unsafe { 184 | let data = data as *mut Data; 185 | let data = &mut (*data); 186 | let f = ManuallyDrop::take(&mut data.f); 187 | data.r = ManuallyDrop::new(f()); 188 | } 189 | } 190 | 191 | #[inline] 192 | fn do_catch R, R>(data: *mut u8, payload: *mut u8) { 193 | unsafe { 194 | let data = data as *mut Data; 195 | let data = &mut (*data); 196 | let obj = cleanup(payload); 197 | data.p = ManuallyDrop::new(obj); 198 | } 199 | } 200 | } 201 | 202 | /// These symbols and functions should not actually be used. `libunwind`, 203 | /// however, requires them to be present so that it can link. 204 | // TODO: Patch these out of libunwind instead. 205 | #[cfg(all(target_os = "psp", not(feature = "stub-only")))] 206 | mod libunwind_shims { 207 | #[no_mangle] 208 | unsafe extern "C" fn fprintf(_stream: *const u8, _format: *const u8, ...) -> isize { 209 | -1 210 | } 211 | 212 | #[no_mangle] 213 | unsafe extern "C" fn fflush(_stream: *const u8) -> i32 { 214 | -1 215 | } 216 | 217 | #[no_mangle] 218 | unsafe extern "C" fn abort() { 219 | loop { llvm_asm!("" :::: "volatile"); } 220 | } 221 | 222 | #[no_mangle] 223 | unsafe extern "C" fn malloc(size: usize) -> *mut u8 { 224 | use alloc::alloc::{alloc, Layout}; 225 | 226 | let size = size + 4; 227 | 228 | let data = alloc(Layout::from_size_align_unchecked(size, 4)); 229 | *(data as *mut usize) = size; 230 | 231 | data.offset(4) 232 | } 233 | 234 | #[no_mangle] 235 | unsafe extern "C" fn free(data: *mut u8) { 236 | use alloc::alloc::{dealloc, Layout}; 237 | 238 | let base = data.sub(4); 239 | let size = *(base as *mut usize); 240 | 241 | dealloc(base, Layout::from_size_align_unchecked(size, 4)); 242 | } 243 | 244 | #[no_mangle] 245 | unsafe extern "C" fn getenv(_name: *const u8) -> *const u8 { 246 | core::ptr::null() 247 | } 248 | 249 | #[no_mangle] 250 | unsafe extern "C" fn __assert_func(_: *const u8, _: i32, _: *const u8, _: *const u8) {} 251 | 252 | #[no_mangle] 253 | static _impure_ptr: [usize; 0] = []; 254 | } 255 | -------------------------------------------------------------------------------- /psp/src/screenshot.rs: -------------------------------------------------------------------------------- 1 | use core::{ptr, ffi::c_void}; 2 | use crate::sys::{self, DisplayPixelFormat}; 3 | use crate::{SCREEN_WIDTH, SCREEN_HEIGHT}; 4 | 5 | // RGBA 6 | const BYTES_PER_PIXEL: usize = 4; 7 | 8 | const NUM_PIXELS: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize; 9 | 10 | #[repr(C, packed)] 11 | struct BmpHeader { 12 | pub file_type: [u8; 2], 13 | pub file_size: u32, 14 | pub reserved_1: u16, 15 | pub reserved_2: u16, 16 | pub image_data_start: u32, 17 | pub dib_header_size: u32, 18 | pub image_width: u32, 19 | pub image_height: u32, 20 | pub color_planes: u16, 21 | pub bpp: u16, 22 | pub compression: u32, 23 | pub image_data_len: u32, 24 | pub print_resolution_x: u32, 25 | pub print_resolution_y: u32, 26 | pub palette_color_count: u32, 27 | pub important_colors: u32, 28 | } 29 | 30 | impl BmpHeader { 31 | const BYTES: usize = core::mem::size_of::(); 32 | 33 | fn to_bytes(self) -> [u8; Self::BYTES] { 34 | unsafe { 35 | core::mem::transmute(self) 36 | } 37 | } 38 | } 39 | 40 | fn rgba_to_bgra(rgba: u32) -> u32 { 41 | // 0xAABBGGRR -> 0xAARRGGBB 42 | 43 | core::intrinsics::bswap(rgba << 8 | rgba >> 24) 44 | } 45 | 46 | fn rgb565_to_bgra(rgb565: u16) -> u32 { 47 | let rgb565 = rgb565 as u32; 48 | 49 | // bbbb bggg gggr rrrr -> 0xffRRGGBB 50 | ((rgb565 & 0x1f) << 16) * 0x100 / 0x20 51 | | ((rgb565 & 0x7e0) << 3) * 0x100 / 0x40 52 | | ((rgb565 & 0xf800) >> 11) * 0x100 / 0x20 53 | | 0xff00_0000 54 | } 55 | 56 | fn rgba5551_to_bgra(rgba5551: u16) -> u32 { 57 | let rgba5551 = rgba5551 as u32; 58 | 59 | // abbb bbgg gggr rrrr -> 0xAARRGGBB 60 | ((rgba5551 & 0x1f) << 16) * 0x100 / 0x20 61 | | ((rgba5551 & 0x3e0) << 3) * 0x100 / 0x20 62 | | ((rgba5551 & 0x7c00) >> 10) * 0x100 / 0x20 63 | | ((rgba5551 & 0x8000) >> 15) * 0xff00_0000 64 | } 65 | 66 | fn rgba4444_to_bgra(rgba4444: u16) -> u32 { 67 | let rgba4444 = rgba4444 as u32; 68 | 69 | // aaaa bbbb gggg rrrr -> 0xAARRGGBB 70 | ((rgba4444 & 0x000f) << 16) * 0x100 / 0x10 71 | | ((rgba4444 & 0x00f0) << 4) * 0x100 / 0x10 72 | | ((rgba4444 & 0x0f00) >> 8) * 0x100 / 0x10 73 | | ((rgba4444 & 0xf000) << 12) * 0x100 / 0x10 74 | } 75 | 76 | /// Take a screenshot, returning a raw ARGB (big-endian) array. 77 | pub fn screenshot_argb_be() -> alloc::vec::Vec { 78 | let mut screenshot_buffer = alloc::vec![0; NUM_PIXELS]; 79 | let mut buffer_width: usize = 0; 80 | let mut pixel_format = DisplayPixelFormat::Psm5650; 81 | let mut top_addr: *mut c_void = ptr::null_mut(); 82 | 83 | unsafe { 84 | sys::sceDisplayGetFrameBuf( 85 | &mut top_addr, 86 | &mut buffer_width, 87 | &mut pixel_format, 88 | sys::DisplaySetBufSync::Immediate, 89 | ); 90 | } 91 | 92 | // http://uofw.github.io/upspd/docs/hardware/PSPTEK.htm#memmap 93 | 94 | // If this is a kernel address... 95 | if top_addr as u32 & 0x80000000 != 0 { 96 | // Set the kernel cache-through bit. 97 | top_addr = (top_addr as u32 | 0xA0000000) as _; 98 | } else { 99 | // Else set the regular cache-through bit. 100 | top_addr = (top_addr as u32 | 0x40000000) as _; 101 | } 102 | 103 | for x in 0..SCREEN_WIDTH { 104 | for y in 0..SCREEN_HEIGHT { 105 | // BGRA is reversed ARGB. We do this for little-endian based copying. 106 | let bgra = match pixel_format { 107 | sys::DisplayPixelFormat::Psm8888 => { 108 | let rgba = unsafe { 109 | *(top_addr as *mut u32).add(x as usize + y as usize * buffer_width) 110 | }; 111 | 112 | rgba_to_bgra(rgba) 113 | }, 114 | 115 | sys::DisplayPixelFormat::Psm5650 => { 116 | let rgb565 = unsafe { 117 | *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width) 118 | }; 119 | 120 | rgb565_to_bgra(rgb565) 121 | } 122 | 123 | sys::DisplayPixelFormat::Psm5551 => { 124 | let rgba5551 = unsafe { 125 | *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width) 126 | }; 127 | 128 | rgba5551_to_bgra(rgba5551) 129 | } 130 | 131 | sys::DisplayPixelFormat::Psm4444 => { 132 | let rgba4444 = unsafe { 133 | *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width) 134 | }; 135 | 136 | rgba4444_to_bgra(rgba4444) 137 | } 138 | }; 139 | 140 | // Display buffer is flipped upside down. 141 | let y_inv = SCREEN_HEIGHT - y - 1; 142 | screenshot_buffer[x as usize + y_inv as usize * SCREEN_WIDTH as usize] = bgra; 143 | } 144 | } 145 | 146 | screenshot_buffer 147 | } 148 | 149 | /// Take a screenshot, returning a valid bitmap file. 150 | pub fn screenshot_bmp() -> alloc::vec::Vec { 151 | let mut screenshot_buffer = alloc::vec![0; BmpHeader::BYTES + NUM_PIXELS * BYTES_PER_PIXEL]; 152 | 153 | let payload = screenshot_argb_be(); 154 | 155 | let bmp_header = BmpHeader { 156 | file_type: *b"BM", 157 | file_size: BmpHeader::BYTES as u32 + payload.len() as u32 * 4, 158 | reserved_1: 0, 159 | reserved_2: 0, 160 | image_data_start: BmpHeader::BYTES as u32, 161 | dib_header_size: 40, 162 | image_width: SCREEN_WIDTH as u32, 163 | image_height: SCREEN_HEIGHT as u32, 164 | color_planes: 1, 165 | bpp: 32, 166 | compression: 0, 167 | image_data_len: payload.len() as u32 * 4, 168 | print_resolution_x: 2835, // 72 DPI 169 | print_resolution_y: 2835, // 72 DPI 170 | palette_color_count: 0, 171 | important_colors: 0 172 | }; 173 | 174 | screenshot_buffer[0..BmpHeader::BYTES].copy_from_slice(&bmp_header.to_bytes()); 175 | 176 | unsafe { 177 | core::ptr::copy_nonoverlapping( 178 | &payload[0] as *const _ as _, 179 | &mut screenshot_buffer[BmpHeader::BYTES] as *mut u8, 180 | NUM_PIXELS * BYTES_PER_PIXEL, 181 | ); 182 | } 183 | 184 | screenshot_buffer 185 | } 186 | -------------------------------------------------------------------------------- /psp/src/sys/atrac.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | use crate::eabi::i5; 3 | 4 | #[repr(C)] 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct Atrac3BufferInfo { 7 | pub puc_write_position_first_buf: *mut u8, 8 | pub ui_writable_byte_first_buf: u32, 9 | pub ui_min_write_byte_first_buf: u32, 10 | pub ui_read_position_first_buf: u32, 11 | pub puc_write_position_second_buf: *mut u8, 12 | pub ui_writable_byte_second_buf: u32, 13 | pub ui_min_write_byte_second_buf: u32, 14 | pub ui_read_position_second_buf: u32, 15 | } 16 | 17 | psp_extern! { 18 | #![name = "sceAtrac3plus"] 19 | #![flags = 0x0009] 20 | #![version = (0x00, 0x00)] 21 | 22 | #[psp(0x780F88D1)] 23 | pub fn sceAtracGetAtracID(ui_codec_type: u32) -> i32; 24 | 25 | #[psp(0x7A20E7AF)] 26 | /// Creates a new Atrac ID from the specified data 27 | /// 28 | /// # Parameters 29 | /// 30 | /// - `buf`: the buffer holding the atrac3 data, including the RIFF/WAVE header. 31 | /// - `bufsize`: the size of the buffer pointed by buf 32 | /// 33 | /// # Return Value 34 | /// 35 | /// the new atrac ID, or < 0 on error 36 | pub fn sceAtracSetDataAndGetID( 37 | buf: *mut c_void, 38 | bufsize: usize, 39 | ) -> i32; 40 | 41 | #[psp(0x6A8C3CD5, i5)] 42 | /// Decode a frame of data. 43 | /// 44 | /// # Parameters 45 | /// 46 | /// - `atrac_id`: the atrac ID 47 | /// - `out_samples`: pointer to a buffer that receives the decoded data of the current frame 48 | /// - `out_n`: pointer to a integer that receives the number of audio samples of the decoded frame 49 | /// - `out_end`: pointer to a integer that receives a boolean value indicating if the decoded frame is the last one 50 | /// - `out_remain_frame`: pointer to a integer that receives either -1 if all at3 data is already on memory, 51 | /// or the remaining (not decoded yet) frames at memory if not all at3 data is on memory 52 | /// 53 | /// 54 | /// # Return Value 55 | /// 56 | /// < 0 on error, otherwise 0 57 | pub fn sceAtracDecodeData( 58 | atrac_id: i32, 59 | out_samples: *mut u16, 60 | out_n: *mut i32, 61 | out_end: *mut i32, 62 | out_remain_frame: *mut i32, 63 | ) -> i32; 64 | 65 | #[psp(0x9AE849A7)] 66 | /// Gets the remaining (not decoded) number of frames 67 | /// 68 | /// # Parameters 69 | /// 70 | /// - `atrac_id`: the atrac ID 71 | /// - `out_remain_frame`: pointer to a integer that receives either -1 if all at3 data is already on memory, 72 | /// or the remaining (not decoded yet) frames at memory if not all at3 data is on memory 73 | /// 74 | /// # Return Value 75 | /// 76 | /// < 0 on error, otherwise 0 77 | pub fn sceAtracGetRemainFrame( 78 | atrac_id: i32, 79 | out_remain_frame: *mut i32, 80 | ) -> i32; 81 | 82 | #[psp(0x5D268707)] 83 | /// # Parameters 84 | /// 85 | /// - `atrac_id`: the atrac ID 86 | /// - `write_pointer`: Pointer to where to read the atrac data 87 | /// - `available_bytes`: Number of bytes available at the writePointer location 88 | /// - `read_offset`: Offset where to seek into the atrac file before reading 89 | /// 90 | /// # Return Value 91 | /// 92 | /// < 0 on error, otherwise 0 93 | pub fn sceAtracGetStreamDataInfo( 94 | atrac_id: i32, 95 | write_pointer: *mut *mut u8, 96 | available_bytes: *mut u32, 97 | read_offset: *mut u32, 98 | ) -> i32; 99 | 100 | #[psp(0x7DB31251)] 101 | /// # Parameters 102 | /// 103 | /// - `atrac_id`: the atrac ID 104 | /// - `bytes_to_add`: Number of bytes read into location given by sceAtracGetStreamDataInfo(). 105 | /// 106 | /// # Return Value 107 | /// 108 | /// < 0 on error, otherwise 0 109 | pub fn sceAtracAddStreamData( 110 | atrac_id: i32, 111 | bytes_to_add: u32, 112 | ) -> i32; 113 | 114 | #[psp(0xA554A158)] 115 | /// Gets the bitrate. 116 | /// 117 | /// # Parameters 118 | /// 119 | /// - `atrac_id`: the atracID 120 | /// - `out_bitrate`: pointer to a integer that receives the bitrate in kbps 121 | /// 122 | /// # Return Value 123 | /// 124 | /// < 0 on error, otherwise 0 125 | pub fn sceAtracGetBitrate( 126 | atrac_id: i32, 127 | out_bitrate: *mut i32, 128 | ) -> i32; 129 | 130 | #[psp(0x868120B5)] 131 | /// Sets the number of loops for this atrac ID 132 | /// 133 | /// # Parameters 134 | /// 135 | /// - `atrac_id`: the atracID 136 | /// - `nloops`: the number of loops to set 137 | /// 138 | /// # Return Value 139 | /// 140 | /// < 0 on error, otherwise 0 141 | pub fn sceAtracSetLoopNum( 142 | atrac_id: i32, 143 | nloops: i32, 144 | ) -> i32; 145 | 146 | #[psp(0x61EB33F5)] 147 | /// It releases an atrac ID 148 | /// 149 | /// # Parameters 150 | /// 151 | /// - `atrac_id`: the atrac ID to release 152 | /// 153 | /// # Return Value 154 | /// 155 | /// < 0 on error 156 | pub fn sceAtracReleaseAtracID(atrac_id: i32) -> i32; 157 | 158 | #[psp(0x36FAABFB)] 159 | /// Gets the number of samples of the next frame to be decoded. 160 | /// 161 | /// # Parameters 162 | /// 163 | /// - `atrac_id`: the atrac ID 164 | /// - `out_n`: pointer to receives the number of samples of the next frame. 165 | /// 166 | /// # Return Value 167 | /// 168 | /// < 0 on error, otherwise 0 169 | /// 170 | pub fn sceAtracGetNextSample( 171 | atrac_id: i32, 172 | out_n: *mut i32, 173 | ) -> i32; 174 | 175 | #[psp(0xD6A5F2F7)] 176 | /// Gets the maximum number of samples of the atrac3 stream. 177 | /// 178 | /// # Parameters 179 | /// 180 | /// - `atrac_id`: the atrac ID 181 | /// - `out_max`: pointer to a integer that receives the maximum number of samples. 182 | /// 183 | /// # Return Value 184 | /// 185 | /// < 0 on error, otherwise 0 186 | /// 187 | pub fn sceAtracGetMaxSample( 188 | atrac_id: i32, 189 | out_max: *mut i32, 190 | ) -> i32; 191 | 192 | #[psp(0xCA3CA3D2)] 193 | pub fn sceAtracGetBufferInfoForReseting( 194 | atrac_id: i32, 195 | ui_sample: u32, 196 | pbuffer_info: *mut Atrac3BufferInfo, 197 | ) -> i32; 198 | 199 | #[psp(0x31668BAA)] 200 | pub fn sceAtracGetChannel( 201 | atrac_id: i32, 202 | pui_channel: *mut u32, 203 | ) -> i32; 204 | 205 | #[psp(0xE88F759B)] 206 | pub fn sceAtracGetInternalErrorInfo( 207 | atrac_id: i32, 208 | pi_result: *mut i32, 209 | ) -> i32; 210 | 211 | #[psp(0xFAA4F89B)] 212 | pub fn sceAtracGetLoopStatus( 213 | atrac_id: i32, 214 | pi_loop_num: *mut i32, 215 | pui_loop_status: *mut u32, 216 | ) -> i32; 217 | 218 | #[psp(0xE23E3A35)] 219 | pub fn sceAtracGetNextDecodePosition( 220 | atrac_id: i32, 221 | pui_sample_position: *mut u32, 222 | ) -> i32; 223 | 224 | #[psp(0x83E85EA0)] 225 | pub fn sceAtracGetSecondBufferInfo( 226 | atrac_id: i32, 227 | pui_position: *mut u32, 228 | pui_data_byte: *mut u32, 229 | ) -> i32; 230 | 231 | #[psp(0xA2BBA8BE)] 232 | pub fn sceAtracGetSoundSample( 233 | atrac_id: i32, 234 | pi_end_sample: *mut i32, 235 | pi_loop_start_sample: *mut i32, 236 | pi_loop_end_sample: *mut i32, 237 | ) -> i32; 238 | 239 | #[psp(0x644E5607)] 240 | pub fn sceAtracResetPlayPosition( 241 | atrac_id: i32, 242 | ui_sample: u32, 243 | ui_write_byte_first_buf: u32, 244 | ui_write_byte_second_buf: u32, 245 | ) -> i32; 246 | 247 | #[psp(0x0E2A73AB)] 248 | pub fn sceAtracSetData( 249 | atrac_id: i32, 250 | puc_buffer_addr: *mut u8, 251 | ui_buffer_byte: u32, 252 | ) -> i32; 253 | 254 | #[psp(0x3F6E26B5)] 255 | pub fn sceAtracSetHalfwayBuffer( 256 | atrac_id: i32, 257 | puc_buffer_addr: *mut u8, 258 | ui_read_byte: u32, 259 | ui_buffer_byte: u32, 260 | ) -> i32; 261 | 262 | #[psp(0x0FAE370E)] 263 | pub fn sceAtracSetHalfwayBufferAndGetID( 264 | puc_buffer_addr: *mut u8, 265 | ui_read_byte: u32, 266 | ui_buffer_byte: u32, 267 | ) -> i32; 268 | 269 | #[psp(0x83BF7AFD)] 270 | pub fn sceAtracSetSecondBuffer( 271 | atrac_id: i32, 272 | puc_second_buffer_addr: *mut u8, 273 | ui_second_buffer_byte: u32, 274 | ) -> i32; 275 | } 276 | -------------------------------------------------------------------------------- /psp/src/sys/codec.rs: -------------------------------------------------------------------------------- 1 | psp_extern! { 2 | #![name = "sceVideocodec"] 3 | #![flags = 0x4001] 4 | #![version = (0x00, 0x11)] 5 | 6 | #[psp(0xC01EC829)] 7 | pub fn sceVideocodecOpen( 8 | buffer: *mut u32, 9 | type_: i32, 10 | ) -> i32; 11 | 12 | #[psp(0x2D31F5B1)] 13 | pub fn sceVideocodecGetEDRAM( 14 | buffer: *mut u32, 15 | type_: i32, 16 | ) -> i32; 17 | 18 | #[psp(0x17099F0A)] 19 | pub fn sceVideocodecInit( 20 | buffer: *mut u32, 21 | type_: i32, 22 | ) -> i32; 23 | 24 | #[psp(0xDBA273FA)] 25 | pub fn sceVideocodecDecode( 26 | buffer: *mut u32, 27 | type_: i32, 28 | ) -> i32; 29 | 30 | #[psp(0x4F160BF4)] 31 | pub fn sceVideocodecReleaseEDRAM(buffer: *mut u32) -> i32; 32 | } 33 | 34 | pub enum AudioCodec { 35 | At3Plus = 0x00001000, 36 | At3 = 0x00001001, 37 | Mp3 = 0x00001002, 38 | Aac = 0x00001003, 39 | } 40 | 41 | psp_extern! { 42 | #![name = "sceAudiocodec"] 43 | #![flags = 0x4009] 44 | #![version = (0x00, 0x00)] 45 | 46 | #[psp(0x9D3F790C)] 47 | pub fn sceAudiocodecCheckNeedMem( 48 | buffer: *mut u32, 49 | type_: i32, 50 | ) -> i32; 51 | 52 | #[psp(0x5B37EB1D)] 53 | pub fn sceAudiocodecInit( 54 | buffer: *mut u32, 55 | type_: i32, 56 | ) -> i32; 57 | 58 | #[psp(0x70A703F8)] 59 | pub fn sceAudiocodecDecode( 60 | buffer: *mut u32, 61 | type_: i32, 62 | ) -> i32; 63 | 64 | #[psp(0x3A20A200)] 65 | pub fn sceAudiocodecGetEDRAM( 66 | buffer: *mut u32, 67 | type_: i32, 68 | ) -> i32; 69 | 70 | #[psp(0x29681260)] 71 | pub fn sceAudiocodecReleaseEDRAM(buffer: *mut u32) -> i32; 72 | } 73 | -------------------------------------------------------------------------------- /psp/src/sys/ctrl.rs: -------------------------------------------------------------------------------- 1 | bitflags::bitflags! { 2 | /// Enumeration for the digital controller buttons. 3 | /// 4 | /// # Note 5 | /// 6 | /// Home, Note, Screen, VolUp, VolDown, Disc, WlanUp, Remote, and MS can only be 7 | /// read in kernel mode. 8 | #[derive(Default)] 9 | #[repr(transparent)] 10 | pub struct CtrlButtons: u32 { 11 | /// Select button. 12 | const SELECT = 0x000001; 13 | /// Start button. 14 | const START = 0x000008; 15 | /// Up D-Pad button. 16 | const UP = 0x000010; 17 | /// Right D-Pad button. 18 | const RIGHT = 0x000020; 19 | /// Down D-Pad button. 20 | const DOWN = 0x000040; 21 | /// Left D-Pad button. 22 | const LEFT = 0x000080; 23 | /// Left trigger. 24 | const LTRIGGER = 0x000100; 25 | /// Right trigger. 26 | const RTRIGGER = 0x000200; 27 | /// Triangle button. 28 | const TRIANGLE = 0x001000; 29 | /// Circle button. 30 | const CIRCLE = 0x002000; 31 | /// Cross button. 32 | const CROSS = 0x004000; 33 | /// Square button. 34 | const SQUARE = 0x008000; 35 | /// Home button. In user mode this bit is set if the exit dialog is visible. 36 | const HOME = 0x010000; 37 | /// Hold button. 38 | const HOLD = 0x020000; 39 | /// Music Note button. 40 | const NOTE = 0x800000; 41 | /// Screen button. 42 | const SCREEN = 0x400000; 43 | /// Volume up button. 44 | const VOL_UP = 0x100000; 45 | /// Volume down button. 46 | const VOL_DOWN = 0x200000; 47 | /// Wlan switch up. 48 | const WLAN_UP = 0x040000; 49 | /// Remote hold position. 50 | const REMOTE = 0x080000; 51 | /// Disc present. 52 | const DISC = 0x1000000; 53 | /// Memory stick present. 54 | const MEM_STICK = 0x2000000; 55 | } 56 | } 57 | 58 | /// Controller mode. 59 | #[repr(u32)] 60 | pub enum CtrlMode { 61 | /// Digital. 62 | Digital = 0, 63 | /// Analog. 64 | Analog 65 | } 66 | 67 | #[repr(C)] 68 | #[derive(Debug, Clone, Copy, Default)] 69 | /// Returned controller data 70 | pub struct SceCtrlData { 71 | /// The current read frame. 72 | pub timestamp: u32, 73 | /// Bit mask containing zero or more of `CtrlButtons`. 74 | pub buttons: CtrlButtons, 75 | /// Analogue stick, X axis. 76 | pub lx: u8, 77 | /// Analogue stick, Y axis. 78 | pub ly: u8, 79 | /// Reserved. 80 | pub rsrv: [u8; 6], 81 | } 82 | 83 | #[repr(C)] 84 | #[derive(Debug, Clone, Copy, Default)] 85 | pub struct SceCtrlLatch { 86 | pub ui_make: u32, 87 | pub ui_break: u32, 88 | pub ui_press: u32, 89 | pub ui_release: u32, 90 | } 91 | 92 | psp_extern! { 93 | #![name = "sceCtrl"] 94 | #![flags = 0x4001] 95 | #![version = (0, 0)] 96 | 97 | #[psp(0x6A2774F3)] 98 | /// Set the controller cycle setting. 99 | /// 100 | /// # Parameters 101 | /// 102 | /// - `cycle`: Cycle. Normally set to 0. 103 | /// 104 | /// # Return value 105 | /// 106 | /// The previous cycle setting. 107 | pub fn sceCtrlSetSamplingCycle(cycle: i32) -> i32; 108 | 109 | #[psp(0x02BAAD91)] 110 | /// Get the controller current cycle setting. 111 | /// 112 | /// # Parameters 113 | /// 114 | /// - `pcycle`: Return value. 115 | /// 116 | /// # Return value 117 | /// 118 | /// 0 119 | pub fn sceCtrlGetSamplingCycle(pcycle: *mut i32) -> i32; 120 | 121 | #[psp(0x1F4011E6)] 122 | /// Set the controller mode. 123 | /// 124 | /// # Parameters 125 | /// 126 | /// - `mode`: One of `CtrlMode`. 127 | /// 128 | /// # Return Value 129 | /// 130 | /// The previous mode. 131 | pub fn sceCtrlSetSamplingMode(mode: CtrlMode) -> i32; 132 | 133 | #[psp(0xDA6B76A1)] 134 | /// Get the current controller mode. 135 | /// 136 | /// # Parameters 137 | /// 138 | /// - `pmode`: Return value. 139 | /// 140 | /// # Return value 141 | /// 142 | /// 0 143 | pub fn sceCtrlGetSamplingMode(pmode: *mut i32) -> i32; 144 | 145 | #[psp(0x3A622550)] 146 | pub fn sceCtrlPeekBufferPositive(pad_data: *mut SceCtrlData, count: i32) -> i32; 147 | 148 | #[psp(0xC152080A)] 149 | pub fn sceCtrlPeekBufferNegative(pad_data: *mut SceCtrlData, count: i32) -> i32; 150 | 151 | #[psp(0x1F803938)] 152 | /// Read buffer positive 153 | /// 154 | /// # Parameters 155 | /// 156 | /// - `pad_data`: Pointer to a `SceCtrlData` structure used to hold the returned pad data. 157 | /// - `count`: Number of `SceCtrlData` buffers to read. 158 | pub fn sceCtrlReadBufferPositive(pad_data: *mut SceCtrlData, count: i32) -> i32; 159 | 160 | #[psp(0x60B81F86)] 161 | pub fn sceCtrlReadBufferNegative(pad_data: *mut SceCtrlData, count: i32) -> i32; 162 | 163 | #[psp(0xB1D0E5CD)] 164 | pub fn sceCtrlPeekLatch(latch_data: *mut SceCtrlLatch) -> i32; 165 | 166 | #[psp(0x0B588501)] 167 | pub fn sceCtrlReadLatch(latch_data: *mut SceCtrlLatch) -> i32; 168 | 169 | #[psp(0xA7144800)] 170 | /// Set analog threshold relating to the idle timer. 171 | /// 172 | /// # Parameters 173 | /// 174 | /// - `idlereset`: Movement needed by the analog to reset the idle timer. 175 | /// - `idleback`: Movement needed by the analog to bring the PSP back from 176 | /// an idle state. 177 | /// 178 | /// Set to -1 for analog to not cancel idle timer. 179 | /// 180 | /// Set to 0 for idle timer to be cancelled even if the analog is not moved. 181 | /// 182 | /// Set between 1-128 to specify the movement on either axis needed by the analog 183 | /// to fire the event. 184 | /// 185 | /// # Return value 186 | /// 187 | /// < 0 on error. 188 | pub fn sceCtrlSetIdleCancelThreshold(idlereset: i32, idleback: i32) -> i32; 189 | 190 | #[psp(0x687660FA)] 191 | /// Get the idle threshold values. 192 | /// 193 | /// # Parameters 194 | /// 195 | /// - `idlereset`: Movement needed by the analog to reset the idle timer. 196 | /// - `idleback`: Movement needed by the analog to bring the PSP back from 197 | /// an idle state. 198 | /// 199 | /// # Return value 200 | /// 201 | /// < 0 on error. 202 | pub fn sceCtrlGetIdleCancelThreshold(idlereset: *mut i32, idleback: *mut i32) -> i32; 203 | } 204 | -------------------------------------------------------------------------------- /psp/src/sys/debugfont.bin: -------------------------------------------------------------------------------- 1 | (((((|(|((x8P< L d`T$X@ @  @T88T||@ 8DdTLD8|8D@ |8D@0@D8 0($| |<@@D80 u32; 56 | 57 | #[psp(0xDEA197D4)] 58 | /// Get display mode 59 | /// 60 | /// # Parameters 61 | /// 62 | /// - `pmode`: Pointer to an integer to receive the current mode. 63 | /// - `pwidth`: Pointer to an integer to receive the current width. 64 | /// - `pheight`: Pointer to an integer to receive the current height. 65 | /// 66 | /// # Return value 67 | /// 68 | /// 0 on success 69 | pub fn sceDisplayGetMode(pmode: *mut i32, pwidth: *mut i32, pheight: *mut i32) -> i32; 70 | 71 | #[psp(0x289D82FE)] 72 | /// Display set framebuffer 73 | /// 74 | /// # Parameters 75 | /// 76 | /// - `top_addr`: Address of start of framebuffer 77 | /// - `buffer_width`: Buffer width (must be power of 2) 78 | /// - `pixel_format`: One of `PspDisplayPixelFormats`. 79 | /// - `sync`: One of `PspDisplaySetBufSync` 80 | /// 81 | /// # Return value 82 | /// 83 | /// 0 on success 84 | pub fn sceDisplaySetFrameBuf( 85 | top_addr: *const u8, 86 | buffer_width: usize, 87 | pixel_format: DisplayPixelFormat, 88 | sync: DisplaySetBufSync, 89 | ) -> u32; 90 | 91 | #[psp(0xEEDA2E54)] 92 | /// Get display framebuffer information 93 | /// 94 | /// # Parameters 95 | /// 96 | /// - `top_addr`: Pointer to void* to receive address of start of framebuffer 97 | /// - `buffer_width`: Pointer to usize to receive buffer width (must be power of 2) 98 | /// - `pixelformat`: Pointer to receive DisplayPixelFormat. 99 | /// - `sync`: One of `DisplaySetBufSync` 100 | pub fn sceDisplayGetFrameBuf( 101 | top_addr: *mut *mut c_void, 102 | buffer_width: *mut usize, 103 | pixel_format: *mut DisplayPixelFormat, 104 | sync: DisplaySetBufSync, 105 | ) -> i32; 106 | 107 | #[psp(0x9C6EAAD7)] 108 | /// Number of vertical blank pulses up to now 109 | pub fn sceDisplayGetVcount() -> u32; 110 | 111 | #[psp(0x36CDFADE)] 112 | /// Wait for vertical blank 113 | pub fn sceDisplayWaitVblank() -> i32; 114 | 115 | #[psp(0x8EB9EC49)] 116 | /// Wait for vertical blank with callback 117 | pub fn sceDisplayWaitVblankCB() -> i32; 118 | 119 | #[psp(0x984C27E7)] 120 | /// Wait for vertical blank start 121 | pub fn sceDisplayWaitVblankStart() -> i32; 122 | 123 | #[psp(0x46F186C3)] 124 | /// Wait for vertical blank start with callback 125 | pub fn sceDisplayWaitVblankStartCB() -> i32; 126 | 127 | #[psp(0x210EAB3A)] 128 | /// Get accumulated HSYNC count 129 | pub fn sceDisplayGetAccumulatedHcount() -> i32; 130 | 131 | #[psp(0x773DD3A3)] 132 | /// Get current HSYNC count 133 | pub fn sceDisplayGetCurrentHcount() -> i32; 134 | 135 | #[psp(0xDBA6C4C4)] 136 | /// Get number of frames per second 137 | pub fn sceDisplayGetFramePerSec() -> f32; 138 | 139 | #[psp(0xB4F378FA)] 140 | /// Get whether or not frame buffer is being displayed 141 | pub fn sceDisplayIsForeground() -> i32; 142 | 143 | #[psp(0x4D4E10EC)] 144 | /// Test whether vblank is active 145 | pub fn sceDisplayIsVblank() -> i32; 146 | } 147 | -------------------------------------------------------------------------------- /psp/src/sys/hprm.rs: -------------------------------------------------------------------------------- 1 | //! Headphone Remote 2 | 3 | bitflags::bitflags! { 4 | #[repr(transparent)] 5 | pub struct Key: u32 { 6 | const PLAY_PAUSE = 0x1; 7 | const FORWARD = 0x4; 8 | const BACK = 0x8; 9 | const VOL_UP = 0x10; 10 | const VOL_DOWN = 0x20; 11 | const HOLD = 0x80; 12 | } 13 | } 14 | 15 | psp_extern! { 16 | #![name = "sceHprm"] 17 | #![flags = 0x4001] 18 | #![version = (0x00, 0x00)] 19 | 20 | #[psp(0x1910B327)] 21 | /// Peek at the current being pressed on the remote. 22 | /// 23 | /// # Parameters 24 | /// 25 | /// - `key`: Pointer to receive the key bitmap, should be an instance of ::Key 26 | /// 27 | /// # Return Value 28 | /// 29 | /// < 0 on error 30 | pub fn sceHprmPeekCurrentKey(key: *mut Key) -> i32; 31 | 32 | #[psp(0x2BCEC83E)] 33 | /// Peek at the current latch data. 34 | /// 35 | /// # Parameters 36 | /// 37 | /// - `latch`: Pointer a to a 4 dword array to contain the latch data. 38 | /// 39 | /// # Return Value 40 | /// 41 | /// < 0 on error. 42 | pub fn sceHprmPeekLatch(latch: *mut [u32;4]) -> i32; 43 | 44 | #[psp(0x40D2F9F0)] 45 | /// Read the current latch data. 46 | /// 47 | /// # Parameters 48 | /// 49 | /// - `latch`: Pointer a to a 4 dword array to contain the latch data. 50 | /// 51 | /// # Return Value 52 | /// 53 | /// < 0 on error. 54 | pub fn sceHprmReadLatch(latch: *mut [u32;4]) -> i32; 55 | 56 | #[psp(0x7E69EDA4)] 57 | /// Determines whether the headphones are plugged in. 58 | /// 59 | /// # Return Value 60 | /// 61 | /// 1 if the headphones are plugged in, else 0. 62 | pub fn sceHprmIsHeadphoneExist() -> i32; 63 | 64 | #[psp(0x208DB1BD)] 65 | /// Determines whether the remote is plugged in. 66 | /// 67 | /// # Return Value 68 | /// 69 | /// 1 if the remote is plugged in, else 0. 70 | pub fn sceHprmIsRemoteExist() -> i32; 71 | 72 | #[psp(0x219C58F1)] 73 | /// Determines whether the microphone is plugged in. 74 | /// 75 | /// # Return Value 76 | /// 77 | /// 1 if the microphone is plugged in, else 0. 78 | pub fn sceHprmIsMicrophoneExist() -> i32; 79 | } 80 | -------------------------------------------------------------------------------- /psp/src/sys/jpeg.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | psp_extern! { 4 | #![name = "sceJpeg"] 5 | #![flags = 0x0009] 6 | #![version = (0x00, 0x00)] 7 | 8 | #[psp(0xAC9E70E6)] 9 | /// Inits the MJpeg library 10 | /// 11 | /// # Return Value 12 | /// 13 | /// 0 on success, < 0 on error 14 | pub fn sceJpegInitMJpeg() -> i32; 15 | 16 | #[psp(0x7D2F3D7F)] 17 | /// Finishes the MJpeg library 18 | /// 19 | /// # Return Value 20 | /// 21 | /// 0 on success, < 0 on error 22 | pub fn sceJpegFinishMJpeg() -> i32; 23 | 24 | #[psp(0x9D47469C)] 25 | /// Creates the decoder context. 26 | /// 27 | /// # Parameters 28 | /// 29 | /// - `width`: The width of the frame 30 | /// - `height`: The height of the frame 31 | /// 32 | /// # Return Value 33 | /// 34 | /// 0 on success, < 0 on error 35 | pub fn sceJpegCreateMJpeg(width: i32, height: i32) -> i32; 36 | 37 | #[psp(0x48B602B7)] 38 | /// Deletes the current decoder context. 39 | /// 40 | /// # Return Value 41 | /// 42 | /// 0 on success, < 0 on error 43 | pub fn sceJpegDeleteMJpeg() -> i32; 44 | 45 | #[psp(0x04B93CEF)] 46 | /// Decodes a mjpeg frame. 47 | /// 48 | /// # Parameters 49 | /// 50 | /// - `jpeg_buf`: the buffer with the mjpeg frame 51 | /// - `size`: size of the buffer pointed by `jpeg_buf` 52 | /// - `rgba`: buffer where the decoded data in RGBA format will be stored. 53 | /// It should have a size of (width * height * 4). 54 | /// - `unk`: Unknown, pass 0 55 | /// 56 | /// # Return Value 57 | /// 58 | /// (width * 65536) + height on success, < 0 on error 59 | pub fn sceJpegDecodeMJpeg( 60 | jpeg_buf: *mut u8, 61 | size: usize, 62 | rgba: *mut c_void, 63 | unk: u32, 64 | ) -> i32; 65 | } 66 | -------------------------------------------------------------------------------- /psp/src/sys/macros.rs: -------------------------------------------------------------------------------- 1 | /// Generate a zero-sized sentinel value. 2 | /// 3 | /// Used to mark the top of a section, e.g. `.sceStub.text.ThreadManForUser`. 4 | #[cfg(target_os = "psp")] 5 | macro_rules! sentinel { 6 | ($name:ident, $section:expr) => { 7 | #[link_section = $section] 8 | #[no_mangle] 9 | static $name: () = (); 10 | } 11 | } 12 | 13 | #[cfg(target_os = "psp")] 14 | macro_rules! count { 15 | ($single:ident) => { 1 }; 16 | ($first:ident, $($rest:ident),*) => { 1 + count!($($rest),*) }; 17 | } 18 | 19 | /// Generate a PSP function stub. 20 | /// 21 | /// This generates an assembly stub with the given section and name. 22 | #[cfg(target_os = "psp")] 23 | macro_rules! stub { 24 | ($name:ident, $section:expr) => { 25 | global_asm!(concat!( 26 | " 27 | .section ", $section, ", \"ax\", @progbits 28 | .global ", stringify!($name), " 29 | ", stringify!($name), ": 30 | jr $ra 31 | " 32 | )); 33 | } 34 | } 35 | 36 | /// Generate a PSP function NID. 37 | /// 38 | /// This macro is split from `psp_extern!` to allow for `concat!`-based 39 | /// generation. If you try to generate a NID like so... 40 | /// 41 | /// ```ignore 42 | /// #[link_section = concat!(".foo", ".bar")] 43 | /// static FOO: u32 = 123; 44 | /// ``` 45 | /// 46 | /// ... you will receive an error. However, calling this macro in place works 47 | /// fine: 48 | /// 49 | /// ```ignore 50 | /// nid!(FOO, concat!(".foo", ".bar"), 123); 51 | /// ``` 52 | #[cfg(target_os = "psp")] 53 | macro_rules! nid { 54 | ($name:ident, $section:expr, $value:expr) => { 55 | #[no_mangle] 56 | #[link_section = $section] 57 | #[used] 58 | static $name: u32 = $value; 59 | } 60 | } 61 | 62 | /// Calculate the padded length for a library name. 63 | /// 64 | /// The name is padded on the end with zeroes. Must be at least one and a 65 | /// multiple of 4. 66 | #[cfg(target_os = "psp")] 67 | pub const fn lib_name_bytes_len(name: &str) -> usize { 68 | let name_len = name.as_bytes().len(); 69 | return name_len + (4 - name_len % 4); 70 | } 71 | 72 | /// Convert a library name to a byte array. 73 | /// 74 | /// This is intended to be used with `lib_name_bytes_len`. 75 | #[cfg(target_os = "psp")] 76 | pub const fn lib_name_bytes(name: &str) -> [u8; T] { 77 | let mut buf = [0; T]; 78 | 79 | let name_bytes = name.as_bytes(); 80 | let mut i = 0; 81 | 82 | while i < name_bytes.len() { 83 | buf[i] = name_bytes[i]; 84 | i += 1; 85 | } 86 | 87 | buf 88 | } 89 | 90 | /// A complex macro used to define and link a PSP system library. 91 | macro_rules! psp_extern { 92 | // Generate body with default ABI. 93 | (__BODY $name:ident ($($arg:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => { 94 | expr! { 95 | extern { 96 | fn [< __ $name _stub >]($($arg : $arg_ty),*) $(-> $ret)?; 97 | } 98 | 99 | [< __ $name _stub >] ($($arg),*) 100 | } 101 | }; 102 | 103 | // Generate body with an ABI mapper 104 | (__BODY $abi:ident $name:ident ($($arg:ident : $arg_ty:ty),*) $(-> $ret:ty)?) => {{ 105 | expr! { 106 | extern { 107 | fn [< __ $name _stub >]($($arg : $arg_ty),*) $(-> $ret)?; 108 | } 109 | 110 | // The transmutes here are for newtypes that fit into a single 111 | // register. 112 | core::mem::transmute($abi( 113 | $(core::mem::transmute($arg)),*, 114 | core::mem::transmute([< __ $name _stub >] as usize), 115 | )) 116 | } 117 | }}; 118 | 119 | ( 120 | #![name = $lib_name:expr] 121 | #![flags = $lib_flags:expr] 122 | #![version = ($lib_major_version:expr, $lib_minor_version:expr)] 123 | 124 | $( 125 | #[psp($nid:expr $(, $abi:ident)?)] 126 | $(#[$attr:meta])* 127 | pub fn $name:ident($($arg:ident : $arg_ty:ty),* $(,)?) 128 | $(-> $ret:ty)?; 129 | )* 130 | ) => { 131 | item! { 132 | // Just passed to the linker, not used anywhere else. 133 | #[cfg(target_os = "psp")] 134 | #[allow(non_snake_case)] 135 | mod [< __ $lib_name _mod >] { 136 | #[link_section = ".rodata.sceResident"] 137 | #[no_mangle] 138 | #[used] 139 | static [< __ $lib_name _RESIDENT >] : [u8; $crate::sys::macros::lib_name_bytes_len($lib_name)] = $crate::sys::macros::lib_name_bytes($lib_name); 140 | 141 | #[link_section = ".lib.stub"] 142 | #[no_mangle] 143 | #[used] 144 | static [< __ $lib_name _STUB >] : $crate::sys::SceStubLibraryEntry = $crate::sys::SceStubLibraryEntry { 145 | name: expr! { & [< __ $lib_name _RESIDENT >] [0] }, 146 | version: [$lib_minor_version, $lib_major_version], 147 | flags: $lib_flags, 148 | len: 5, 149 | v_stub_count: 0, 150 | stub_count: count!($($name),*), 151 | nid_table: & [< __ $lib_name _NID_START >] as *const () as *const _, 152 | stub_table: & [< __ $lib_name _STUB_START >] as *const () as *const _, 153 | }; 154 | 155 | sentinel!( 156 | [< __ $lib_name _NID_START >], 157 | concat!(".rodata.sceNid.", $lib_name) 158 | ); 159 | 160 | sentinel!( 161 | [< __ $lib_name _STUB_START >], 162 | concat!(".sceStub.text.", $lib_name) 163 | ); 164 | 165 | $( 166 | stub!( 167 | [< __ $name _stub >], 168 | concat!( 169 | ".sceStub.text.", $lib_name, 170 | ".", stringify!($name) 171 | ) 172 | ); 173 | 174 | nid!( 175 | [< __ $name _NID >], 176 | concat!( 177 | ".rodata.sceNid.", $lib_name, 178 | ".", stringify!($name) 179 | ), 180 | $nid 181 | ); 182 | )* 183 | } 184 | } 185 | 186 | $( 187 | $(#[$attr])* 188 | #[allow(non_snake_case)] 189 | #[no_mangle] 190 | pub unsafe extern fn $name($($arg : $arg_ty),*) $(-> $ret)? { 191 | #[cfg(target_os = "psp")] 192 | { 193 | psp_extern!( 194 | __BODY $($abi)? 195 | $name($($arg : $arg_ty),*) $(-> $ret)? 196 | ) 197 | } 198 | 199 | #[cfg(not(target_os = "psp"))] 200 | { 201 | // Get rid of warnings 202 | $(let _arg = $arg;)* 203 | $(let _abi = $abi;)? 204 | 205 | panic!("tried to call PSP system function on non-PSP target"); 206 | } 207 | } 208 | )* 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /psp/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! PSP OS System API 2 | //! 3 | //! The names of functions and types beginning with `sce` or `Sce` were found by 4 | //! reverse engineering various PSP games and OS versions. 5 | //! 6 | //! - `sceXYZ`: Sony API 7 | //! - `sceKernelXYZ`: Interface to the PSP OS kernel 8 | //! - `sceCtrlXYZ`: Button control API 9 | //! - `sceDisplayXYZ`: Display API 10 | //! - `sceGeXYZ`: Interface to the graphics chip (Graphics Engine) 11 | //! - `sceUsb`: USB API 12 | //! - `sceUsbCam`: USB camera 13 | //! - `scePower`: Power API 14 | //! - `sceWlan`: Wireless network API 15 | //! - `sceRtc`: Real time clock API 16 | //! - `sceIo`: File I/O API 17 | //! - `sceAudio`: Audio API 18 | //! - `sceAtrac`: Sony ATRAC3 Codec API 19 | //! - `sceJpeg`: JPEG decoding API 20 | //! - `sceUmd`: UMD Drive API 21 | //! - `sceMpeg`: MPEG codec API 22 | //! - `sceHprm`: Headphone Remote API (headphone accessory with controls) 23 | //! - `sceGu`: Graphics API (Similar to OpenGL) 24 | //! - `sceGum`: Matrix utility functions 25 | //! - `sceMp3`: MP3 decoder API 26 | //! - `sceRegistry`: PSP OS Registry API 27 | //! - `sceOpenPSID`: Console identification API (unique to every console) 28 | //! - `sceUtility`: Various utilities such as msg dialogs and savedata 29 | 30 | use core::{mem, ptr}; 31 | 32 | #[macro_use] 33 | mod macros; 34 | 35 | mod ctrl; 36 | pub use ctrl::*; 37 | 38 | mod display; 39 | pub use display::*; 40 | 41 | mod ge; 42 | pub use ge::*; 43 | 44 | mod kernel; 45 | pub use kernel::*; 46 | 47 | mod usb; 48 | pub use usb::*; 49 | 50 | mod power; 51 | pub use power::*; 52 | 53 | mod wlan; 54 | pub use wlan::*; 55 | 56 | mod rtc; 57 | pub use rtc::*; 58 | 59 | mod io; 60 | pub use io::*; 61 | 62 | mod audio; 63 | pub use audio::*; 64 | 65 | mod atrac; 66 | pub use atrac::*; 67 | 68 | mod jpeg; 69 | pub use jpeg::*; 70 | 71 | mod umd; 72 | pub use umd::*; 73 | 74 | mod mpeg; 75 | pub use mpeg::*; 76 | 77 | mod hprm; 78 | pub use hprm::*; 79 | 80 | mod gu; 81 | pub use gu::*; 82 | 83 | mod gum; 84 | pub use gum::*; 85 | 86 | mod types; 87 | pub use types::*; 88 | 89 | mod mp3; 90 | pub use mp3::*; 91 | 92 | mod registry; 93 | pub use registry::*; 94 | 95 | mod openpsid; 96 | pub use openpsid::*; 97 | 98 | mod utility; 99 | pub use utility::*; 100 | 101 | mod net; 102 | pub use net::*; 103 | 104 | mod font; 105 | pub use font::*; 106 | 107 | // These are not found (likely because this was tested in user mode on a PSP-2000). 108 | // pub mod sircs; 109 | // pub mod codec; 110 | // TODO: Add kernel module support to this crate. 111 | // pub mod nand; 112 | 113 | pub mod vfpu_context; 114 | 115 | use core::ffi::c_void; 116 | 117 | // http://uofw.github.io/uofw/structSceStubLibraryEntryTable.html 118 | #[repr(C)] 119 | pub struct SceStubLibraryEntry { 120 | pub name: *const u8, 121 | pub version: [u8; 2], 122 | pub flags: u16, 123 | pub len: u8, 124 | pub v_stub_count: u8, 125 | pub stub_count: u16, 126 | pub nid_table: *const u32, 127 | pub stub_table: *const c_void, 128 | } 129 | 130 | unsafe impl Sync for SceStubLibraryEntry {} 131 | 132 | #[repr(u16)] 133 | pub enum ModuleInfoAttr { 134 | User = 0, 135 | NoStop = 0x0001, 136 | SingleLoad = 0x0002, 137 | SingleStart = 0x0004, 138 | Kernel = 0x1000, 139 | } 140 | 141 | #[repr(C, packed)] 142 | pub struct SceModuleInfo { 143 | // TODO: Change this type to `ModuleInfoAttr`. This is a breaking change. 144 | pub mod_attribute: u16, 145 | pub mod_version: [u8; 2], 146 | pub mod_name: [u8; 27], 147 | pub terminal: u8, 148 | pub gp_value: *const u8, 149 | pub ent_top: *const u8, 150 | pub ent_end: *const u8, 151 | pub stub_top: *const u8, 152 | pub stub_end: *const u8, 153 | } 154 | 155 | unsafe impl Sync for SceModuleInfo {} 156 | 157 | impl SceModuleInfo { 158 | #[doc(hidden)] 159 | pub const fn name(s: &str) -> [u8; 27] { 160 | let bytes = s.as_bytes(); 161 | let mut result = [0; 27]; 162 | 163 | let mut i = 0; 164 | while i < bytes.len() { 165 | result[i] = bytes[i]; 166 | 167 | i += 1; 168 | } 169 | 170 | result 171 | } 172 | } 173 | 174 | #[repr(C, packed)] 175 | pub struct SceLibraryEntry { 176 | pub name: *const u8, 177 | pub version: (u8, u8), 178 | pub attribute: SceLibAttr, 179 | pub entry_len: u8, 180 | pub var_count: u8, 181 | pub func_count: u16, 182 | pub entry_table: *const SceLibraryEntryTable, 183 | } 184 | 185 | unsafe impl Sync for SceLibraryEntry {} 186 | 187 | bitflags::bitflags! { 188 | // https://github.com/uofw/uofw/blob/f099b78dc0937df4e7346e2e417b63f471f8a3af/include/loadcore.h#L152 189 | pub struct SceLibAttr: u16 { 190 | const SCE_LIB_NO_SPECIAL_ATTR = 0; 191 | const SCE_LIB_AUTO_EXPORT = 0x1; 192 | const SCE_LIB_WEAK_EXPORT = 0x2; 193 | const SCE_LIB_NOLINK_EXPORT = 0x4; 194 | const SCE_LIB_WEAK_IMPORT = 0x8; 195 | const SCE_LIB_SYSCALL_EXPORT = 0x4000; 196 | const SCE_LIB_IS_SYSLIB = 0x8000; 197 | } 198 | } 199 | 200 | pub struct SceLibraryEntryTable { 201 | pub module_start_nid: u32, 202 | pub module_info_nid: u32, 203 | pub module_start: unsafe extern "C" fn(isize, *const *const u8) -> isize, 204 | pub module_info: *const SceModuleInfo, 205 | } 206 | 207 | unsafe impl Sync for SceLibraryEntryTable {} 208 | 209 | /// Event which has occurred in the memory stick ejection callback, passed in 210 | /// `arg2`. 211 | pub enum MsCbEvent { 212 | Inserted = 1, 213 | Ejected = 2, 214 | } 215 | 216 | /// Returns whether a memory stick is current inserted 217 | /// 218 | /// # Return Value 219 | /// 220 | /// 1 if memory stick inserted, 0 if not or if < 0 on error 221 | #[inline(always)] 222 | #[allow(non_snake_case)] 223 | pub unsafe fn MScmIsMediumInserted() -> i32 { 224 | let mut status: i32 = 0; 225 | 226 | let ret = io::sceIoDevctl( 227 | b"mscmhc0:\0" as _, 228 | 0x02025806, 229 | ptr::null_mut(), 230 | 0, 231 | &mut status as *mut _ as _, 232 | mem::size_of::() as i32, 233 | ); 234 | 235 | if ret < 0 { 236 | ret 237 | } else if status != 1 { 238 | 0 239 | } else { 240 | 1 241 | } 242 | } 243 | 244 | /// Registers a memory stick ejection callback. 245 | /// 246 | /// See `MsCbEvent`. 247 | /// 248 | /// # Parameters 249 | /// 250 | /// - `cbid`: The uid of an allocated callback 251 | /// 252 | /// # Return Value 253 | /// 254 | /// 0 on success, < 0 on error 255 | #[inline(always)] 256 | #[allow(non_snake_case)] 257 | pub unsafe fn MScmRegisterMSInsertEjectCallback(mut cbid: SceUid) -> i32 { 258 | sceIoDevctl( 259 | b"fatms0:\0" as _, 260 | 0x02415821, 261 | &mut cbid as *mut _ as _, 262 | mem::size_of::() as i32, 263 | ptr::null_mut(), 264 | 0, 265 | ) 266 | } 267 | 268 | /// Unregister a memory stick ejection callback 269 | /// 270 | /// # Parameters 271 | /// 272 | /// - `cbid`: The uid of an allocated callback 273 | /// 274 | /// # Return Value 275 | /// 276 | /// 0 on success, < 0 on error 277 | #[inline(always)] 278 | #[allow(non_snake_case)] 279 | pub unsafe fn MScmUnregisterMSInsertEjectCallback(mut cbid: SceUid) -> i32 { 280 | sceIoDevctl( 281 | b"fatms0:\0" as _, 282 | 0x02415822, 283 | &mut cbid as *mut _ as _, 284 | mem::size_of::() as i32, 285 | ptr::null_mut(), 286 | 0, 287 | ) 288 | } 289 | -------------------------------------------------------------------------------- /psp/src/sys/mp3.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | /// A structure used for initializing a handle in `sceMp3ReserveMp3Handle`. 4 | #[repr(C)] 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct SceMp3InitArg { 7 | /// Stream start position 8 | pub mp3_stream_start: u32, 9 | /// Unknown - set to 0 10 | pub unk1: u32, 11 | /// Stream end position 12 | pub mp3_stream_end: u32, 13 | /// Unknown - set to 0 14 | pub unk2: u32, 15 | /// Pointer to a buffer to contain raw mp3 stream data (+1472 bytes workspace) 16 | pub mp3_buf: *mut c_void, 17 | /// Size of the `mp3_buf` buffer (must be >= 8192) 18 | pub mp3_buf_size: i32, 19 | /// Pointer to output buffer where decoded PCM samples will be written. 20 | pub pcm_buf: *mut c_void, 21 | /// Size of `pcm_buf` buffer (must be >= 9216) 22 | pub pcm_buf_size: i32, 23 | } 24 | 25 | #[derive(Copy, Clone, Debug)] 26 | #[repr(transparent)] 27 | pub struct Handle(pub i32); 28 | 29 | psp_extern! { 30 | #![name = "sceMp3"] 31 | #![flags = 0x0009] 32 | #![version = (0x00, 0x11)] 33 | 34 | #[psp(0x07EC321A)] 35 | /// # Parameters 36 | /// 37 | /// - `args`: Pointer to `SceMp3InitArg` structure 38 | /// 39 | /// # Return Value 40 | /// 41 | /// Raw MP3 handle on success, < 0 on error. Construct a `Handle` instance 42 | /// from this value to use the other functions in this module. 43 | // TODO: Investigate adding `Result` support to `psp_extern!`. 44 | pub fn sceMp3ReserveMp3Handle(args: *mut SceMp3InitArg) -> i32; 45 | 46 | #[psp(0xF5478233)] 47 | /// # Parameters 48 | /// 49 | /// - `handle`: MP3 handle 50 | /// 51 | /// # Return Value 52 | /// 53 | /// 0 if success, < 0 on error. 54 | pub fn sceMp3ReleaseMp3Handle(handle: Handle) -> i32; 55 | 56 | #[psp(0x35750070)] 57 | /// # Return Value 58 | /// 59 | /// 0 if success, < 0 on error. 60 | pub fn sceMp3InitResource() -> i32; 61 | 62 | #[psp(0x3C2FA058)] 63 | /// # Return Value 64 | /// 65 | /// 0 if success, < 0 on error. 66 | pub fn sceMp3TermResource() -> i32; 67 | 68 | #[psp(0x44E07129)] 69 | /// # Parameters 70 | /// 71 | /// - `handle`: MP3 handle 72 | /// 73 | /// # Return Value 74 | /// 75 | /// 0 if success, < 0 on error. 76 | pub fn sceMp3Init(handle: Handle) -> i32; 77 | 78 | #[psp(0xD021C0FB)] 79 | /// # Parameters 80 | /// 81 | /// - `handle`: MP3 handle 82 | /// - `dst`: Pointer to destination pcm samples buffer 83 | /// 84 | /// # Return Value 85 | /// 86 | /// number of bytes in decoded pcm buffer, < 0 on error. 87 | pub fn sceMp3Decode(handle: Handle, dst: *mut *mut i16) -> i32; 88 | 89 | #[psp(0xA703FE0F)] 90 | /// # Parameters 91 | /// 92 | /// - `handle`: MP3 handle 93 | /// - `dst`: Pointer to stream data buffer 94 | /// - `to_write`: Space remaining in stream data buffer 95 | /// - `src_pos`: Position in source stream to start reading from 96 | /// 97 | /// # Return Value 98 | /// 99 | /// 0 if success, < 0 on error. 100 | pub fn sceMp3GetInfoToAddStreamData( 101 | handle: Handle, 102 | dst: *mut *mut u8, 103 | to_write: *mut i32, 104 | src_pos: *mut i32, 105 | ) -> i32; 106 | 107 | #[psp(0x0DB149F4)] 108 | /// # Parameters 109 | /// 110 | /// - `handle`: MP3 handle 111 | /// - `size`: number of bytes added to the stream data buffer 112 | /// 113 | /// # Return Value 114 | /// 115 | /// 0 if success, < 0 on error. 116 | pub fn sceMp3NotifyAddStreamData(handle: Handle, size: i32) -> i32; 117 | 118 | #[psp(0xD0A56296)] 119 | /// # Parameters 120 | /// 121 | /// - `handle`: MP3 handle 122 | /// 123 | /// # Return Value 124 | /// 125 | /// 1 if more stream data is needed, < 0 on error. 126 | pub fn sceMp3CheckStreamDataNeeded(handle: Handle) -> i32; 127 | 128 | #[psp(0x3CEF484F)] 129 | /// # Parameters 130 | /// 131 | /// - `handle`: MP3 handle 132 | /// - `loop_`: Number of loops 133 | /// 134 | /// # Return Value 135 | /// 136 | /// 0 if success, < 0 on error. 137 | pub fn sceMp3SetLoopNum(handle: Handle, loop_: i32) -> i32; 138 | 139 | #[psp(0xD8F54A51)] 140 | /// # Parameters 141 | /// 142 | /// - `handle`: MP3 handle 143 | /// 144 | /// # Return Value 145 | /// 146 | /// Number of loops 147 | pub fn sceMp3GetLoopNum(handle: Handle) -> i32; 148 | 149 | #[psp(0x354D27EA)] 150 | /// # Parameters 151 | /// 152 | /// - `handle`: MP3 handle 153 | /// 154 | /// # Return Value 155 | /// 156 | /// Number of decoded samples 157 | pub fn sceMp3GetSumDecodedSample(handle: Handle) -> i32; 158 | 159 | #[psp(0x87C263D1)] 160 | /// # Parameters 161 | /// 162 | /// - `handle`: MP3 handle 163 | /// 164 | /// # Return Value 165 | /// 166 | /// Number of max samples to output 167 | pub fn sceMp3GetMaxOutputSample(handle: Handle) -> i32; 168 | 169 | #[psp(0x8F450998)] 170 | /// # Parameters 171 | /// 172 | /// - `handle`: MP3 handle 173 | /// 174 | /// # Return Value 175 | /// 176 | /// Sampling rate of the mp3 177 | pub fn sceMp3GetSamplingRate(handle: Handle) -> i32; 178 | 179 | #[psp(0x87677E40)] 180 | /// # Parameters 181 | /// 182 | /// - `handle`: MP3 handle 183 | /// 184 | /// # Return Value 185 | /// 186 | /// Bitrate of the mp3 187 | pub fn sceMp3GetBitRate(handle: Handle) -> i32; 188 | 189 | #[psp(0x7F696782)] 190 | /// # Parameters 191 | /// 192 | /// - `handle`: MP3 handle 193 | /// 194 | /// # Return Value 195 | /// 196 | /// Number of channels of the mp3 197 | pub fn sceMp3GetMp3ChannelNum(handle: Handle) -> i32; 198 | 199 | #[psp(0x2A368661)] 200 | /// # Parameters 201 | /// 202 | /// - `handle`: MP3 handle 203 | /// 204 | /// # Return Value 205 | /// 206 | /// < 0 on error 207 | pub fn sceMp3ResetPlayPosition(handle: Handle) -> i32; 208 | } 209 | -------------------------------------------------------------------------------- /psp/src/sys/nand.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | psp_extern! { 4 | #![name = "sceNand_driver"] 5 | #![flags = 0x0001] 6 | #![version = (0x00, 0x00)] 7 | 8 | #[psp(0x84EE5D76)] 9 | pub fn sceNandSetWriteProtect(protect_flag: i32) -> i32; 10 | 11 | #[psp(0xAE4438C7)] 12 | pub fn sceNandLock(write_flag: i32) -> i32; 13 | 14 | #[psp(0x41FFA822)] 15 | pub fn sceNandUnlock(); 16 | 17 | #[psp(0xE41A11DE)] 18 | pub fn sceNandReadStatus() -> i32; 19 | 20 | #[psp(0x7AF7B77A)] 21 | pub fn sceNandReset(flag: i32) -> i32; 22 | 23 | #[psp(0xFCDF7610)] 24 | pub fn sceNandReadId(buf: *mut c_void, size: usize) -> i32; 25 | 26 | #[psp(0x89BDCA08)] 27 | pub fn sceNandReadPages( 28 | ppn: u32, 29 | buf: *mut c_void, 30 | buf2: *mut c_void, 31 | count: u32, 32 | ) -> i32; 33 | 34 | #[psp(0xCE9843E6)] 35 | pub fn sceNandGetPageSize() -> i32; 36 | 37 | #[psp(0xB07C41D4)] 38 | pub fn sceNandGetPagesPerBlock() -> i32; 39 | 40 | #[psp(0xC1376222)] 41 | pub fn sceNandGetTotalBlocks() -> i32; 42 | 43 | #[psp(0xC32EA051)] 44 | pub fn sceNandReadBlockWithRetry( 45 | ppn: u32, 46 | buf: *mut c_void, 47 | buf2: *mut c_void, 48 | ) -> i32; 49 | 50 | #[psp(0x01F09203)] 51 | pub fn sceNandIsBadBlock(ppn: u32) -> i32; 52 | } 53 | -------------------------------------------------------------------------------- /psp/src/sys/openpsid.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Debug, Copy, Clone)] 3 | pub struct OpenPSID { 4 | pub data: [u8; 16usize], 5 | } 6 | 7 | psp_extern! { 8 | #![name = "sceOpenPSID"] 9 | #![flags = 0x4001] 10 | #![version = (0x00, 0x11)] 11 | 12 | #[psp(0xC69BEBCE)] 13 | pub fn sceOpenPSIDGetOpenPSID(openpsid: *mut OpenPSID) -> i32; 14 | } 15 | -------------------------------------------------------------------------------- /psp/src/sys/sircs.rs: -------------------------------------------------------------------------------- 1 | /// Sony Integrated Remote Control System Library. 2 | /// 3 | /// This module contains the imports for the kernel's remote control routines. 4 | 5 | #[repr(C)] 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct SircsData { 8 | pub type_: u8, 9 | pub cmd: u8, 10 | pub dev: u16, 11 | } 12 | 13 | psp_extern! { 14 | #![name = "sceSircs"] 15 | #![flags = 0x4001] 16 | #![version = (0x00, 0x00)] 17 | 18 | #[psp(0x71EEF62D)] 19 | pub fn sceSircsSend(sd: *mut SircsData, count: i32) -> i32; 20 | } 21 | -------------------------------------------------------------------------------- /psp/src/sys/types.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Debug, Copy, Clone)] 3 | pub struct ScePspSRect { 4 | pub x: i16, 5 | pub y: i16, 6 | pub w: i16, 7 | pub h: i16, 8 | } 9 | 10 | #[repr(C)] 11 | #[derive(Debug, Copy, Clone)] 12 | pub struct ScePspIRect { 13 | pub x: i32, 14 | pub y: i32, 15 | pub w: i32, 16 | pub h: i32, 17 | } 18 | 19 | #[repr(C)] 20 | #[derive(Debug, Copy, Clone)] 21 | pub struct ScePspL64Rect { 22 | pub x: u64, 23 | pub y: u64, 24 | pub w: u64, 25 | pub h: u64, 26 | } 27 | 28 | #[repr(C)] 29 | #[derive(Debug, Copy, Clone)] 30 | pub struct ScePspFRect { 31 | pub x: f32, 32 | pub y: f32, 33 | pub w: f32, 34 | pub h: f32, 35 | } 36 | 37 | #[repr(C)] 38 | #[derive(Debug, Copy, Clone)] 39 | pub struct ScePspSVector2 { 40 | pub x: i16, 41 | pub y: i16, 42 | } 43 | 44 | #[repr(C)] 45 | #[derive(Debug, Copy, Clone)] 46 | pub struct ScePspIVector2 { 47 | pub x: i32, 48 | pub y: i32, 49 | } 50 | 51 | #[repr(C)] 52 | #[derive(Debug, Copy, Clone)] 53 | pub struct ScePspL64Vector2 { 54 | pub x: u64, 55 | pub y: u64, 56 | } 57 | 58 | #[repr(C)] 59 | #[derive(Debug, Copy, Clone)] 60 | pub struct ScePspFVector2 { 61 | pub x: f32, 62 | pub y: f32, 63 | } 64 | 65 | #[repr(C)] 66 | #[derive(Copy, Clone)] 67 | pub union ScePspVector2 { 68 | pub fv: ScePspFVector2, 69 | pub iv: ScePspIVector2, 70 | pub f: [f32; 2usize], 71 | pub i: [i32; 2usize], 72 | } 73 | 74 | #[repr(C)] 75 | #[derive(Debug, Copy, Clone)] 76 | pub struct ScePspSVector3 { 77 | pub x: i16, 78 | pub y: i16, 79 | pub z: i16, 80 | } 81 | 82 | #[repr(C)] 83 | #[derive(Debug, Copy, Clone)] 84 | pub struct ScePspIVector3 { 85 | pub x: i32, 86 | pub y: i32, 87 | pub z: i32, 88 | } 89 | 90 | #[repr(C)] 91 | #[derive(Debug, Copy, Clone)] 92 | pub struct ScePspL64Vector3 { 93 | pub x: u64, 94 | pub y: u64, 95 | pub z: u64, 96 | } 97 | 98 | // This is not annotated as aligned in PSPSDK. Maybe the `gum` operations that 99 | // take this struct should load it as 3 components (X, Y, Z) rather than a quad 100 | // load at once? 101 | #[repr(C, align(16))] 102 | #[derive(Debug, Copy, Clone)] 103 | pub struct ScePspFVector3 { 104 | pub x: f32, 105 | pub y: f32, 106 | pub z: f32, 107 | } 108 | 109 | #[repr(C)] 110 | #[derive(Copy, Clone)] 111 | pub union ScePspVector3 { 112 | pub fv: ScePspFVector3, 113 | pub iv: ScePspIVector3, 114 | pub f: [f32; 3usize], 115 | pub i: [i32; 3usize], 116 | } 117 | 118 | #[repr(C)] 119 | #[derive(Debug, Copy, Clone)] 120 | pub struct ScePspSVector4 { 121 | pub x: i16, 122 | pub y: i16, 123 | pub z: i16, 124 | pub w: i16, 125 | } 126 | 127 | #[repr(C)] 128 | #[derive(Debug, Copy, Clone)] 129 | pub struct ScePspIVector4 { 130 | pub x: i32, 131 | pub y: i32, 132 | pub z: i32, 133 | pub w: i32, 134 | } 135 | 136 | #[repr(C)] 137 | #[derive(Debug, Copy, Clone)] 138 | pub struct ScePspL64Vector4 { 139 | pub x: u64, 140 | pub y: u64, 141 | pub z: u64, 142 | pub w: u64, 143 | } 144 | 145 | #[repr(C, align(16))] 146 | #[derive(Debug, Copy, Clone)] 147 | pub struct ScePspFVector4 { 148 | pub x: f32, 149 | pub y: f32, 150 | pub z: f32, 151 | pub w: f32, 152 | } 153 | 154 | #[repr(C)] 155 | #[derive(Debug, Copy, Clone)] 156 | pub struct ScePspFVector4Unaligned { 157 | pub x: f32, 158 | pub y: f32, 159 | pub z: f32, 160 | pub w: f32, 161 | } 162 | 163 | #[repr(C, align(16))] 164 | #[derive(Copy, Clone)] 165 | pub union ScePspVector4 { 166 | pub fv: ScePspFVector4, 167 | pub iv: ScePspIVector4, 168 | pub qw: u128, 169 | pub f: [f32; 4usize], 170 | pub i: [i32; 4usize], 171 | } 172 | 173 | #[repr(C)] 174 | #[derive(Debug, Copy, Clone)] 175 | pub struct ScePspIMatrix2 { 176 | pub x: ScePspIVector2, 177 | pub y: ScePspIVector2, 178 | } 179 | 180 | #[repr(C)] 181 | #[derive(Debug, Copy, Clone)] 182 | pub struct ScePspFMatrix2 { 183 | pub x: ScePspFVector2, 184 | pub y: ScePspFVector2, 185 | } 186 | 187 | #[repr(C)] 188 | #[derive(Copy, Clone)] 189 | pub union ScePspMatrix2 { 190 | pub fm: ScePspFMatrix2, 191 | pub im: ScePspIMatrix2, 192 | pub fv: [ScePspFVector2; 2usize], 193 | pub iv: [ScePspIVector2; 2usize], 194 | pub v: [ScePspVector2; 2usize], 195 | pub f: [[f32; 2usize]; 2usize], 196 | pub i: [[i32; 2usize]; 2usize], 197 | } 198 | 199 | #[repr(C)] 200 | #[derive(Debug, Copy, Clone)] 201 | pub struct ScePspIMatrix3 { 202 | pub x: ScePspIVector3, 203 | pub y: ScePspIVector3, 204 | pub z: ScePspIVector3, 205 | } 206 | 207 | #[repr(C)] 208 | #[derive(Debug, Copy, Clone)] 209 | pub struct ScePspFMatrix3 { 210 | pub x: ScePspFVector3, 211 | pub y: ScePspFVector3, 212 | pub z: ScePspFVector3, 213 | } 214 | 215 | #[repr(C)] 216 | #[derive(Copy, Clone)] 217 | pub union ScePspMatrix3 { 218 | pub fm: ScePspFMatrix3, 219 | pub im: ScePspIMatrix3, 220 | pub fv: [ScePspFVector3; 3usize], 221 | pub iv: [ScePspIVector3; 3usize], 222 | pub v: [ScePspVector3; 3usize], 223 | pub f: [[f32; 3usize]; 3usize], 224 | pub i: [[i32; 3usize]; 3usize], 225 | } 226 | 227 | #[repr(C, align(16))] 228 | #[derive(Debug, Copy, Clone)] 229 | pub struct ScePspIMatrix4 { 230 | pub x: ScePspIVector4, 231 | pub y: ScePspIVector4, 232 | pub z: ScePspIVector4, 233 | pub w: ScePspIVector4, 234 | } 235 | 236 | #[repr(C)] 237 | #[derive(Debug, Copy, Clone)] 238 | pub struct ScePspIMatrix4Unaligned { 239 | pub x: ScePspIVector4, 240 | pub y: ScePspIVector4, 241 | pub z: ScePspIVector4, 242 | pub w: ScePspIVector4, 243 | } 244 | 245 | #[repr(C, align(16))] 246 | #[derive(Debug, Copy, Clone)] 247 | pub struct ScePspFMatrix4 { 248 | pub x: ScePspFVector4, 249 | pub y: ScePspFVector4, 250 | pub z: ScePspFVector4, 251 | pub w: ScePspFVector4, 252 | } 253 | 254 | #[repr(C)] 255 | #[derive(Debug, Copy, Clone)] 256 | pub struct ScePspFMatrix4Unaligned { 257 | pub x: ScePspFVector4, 258 | pub y: ScePspFVector4, 259 | pub z: ScePspFVector4, 260 | pub w: ScePspFVector4, 261 | } 262 | 263 | #[repr(C)] 264 | #[derive(Copy, Clone)] 265 | pub union ScePspMatrix4 { 266 | pub fm: ScePspFMatrix4, 267 | pub im: ScePspIMatrix4, 268 | pub fv: [ScePspFVector4; 4usize], 269 | pub iv: [ScePspIVector4; 4usize], 270 | pub v: [ScePspVector4; 4usize], 271 | pub f: [[f32; 4usize]; 4usize], 272 | pub i: [[i32; 4usize]; 4usize], 273 | } 274 | -------------------------------------------------------------------------------- /psp/src/sys/umd.rs: -------------------------------------------------------------------------------- 1 | /// UMD Info 2 | #[repr(C)] 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct UmdInfo { 5 | /// This should hold the size of this struct. 6 | pub size: u32, 7 | pub type_: UmdType, 8 | } 9 | 10 | /// UMD type 11 | #[repr(u32)] 12 | #[derive(Debug, Copy, Clone)] 13 | pub enum UmdType { 14 | Game = 0x10, 15 | Video = 0x20, 16 | Audio = 0x40, 17 | } 18 | 19 | bitflags::bitflags! { 20 | /// UMD drive state 21 | #[repr(transparent)] 22 | pub struct UmdStateFlags: i32 { 23 | const NOT_PRESENT = 0x01; 24 | const PRESENT = 0x02; 25 | const CHANGED = 0x04; 26 | const INITING = 0x08; 27 | const INITED = 0x10; 28 | const READY = 0x20; 29 | } 30 | } 31 | 32 | /// Callback type 33 | pub type UmdCallback = fn(unknown: i32, event: i32) -> i32; 34 | 35 | psp_extern! { 36 | #![name = "sceUmdUser"] 37 | #![flags = 0x4001] 38 | #![version = (0x00, 0x11)] 39 | 40 | #[psp(0x46EBB729)] 41 | /// Check whether there is a disc in the UMD drive 42 | /// 43 | /// # Return Value 44 | /// 45 | /// 0 if no disc present, anything else indicates a disc is inserted. 46 | pub fn sceUmdCheckMedium() -> i32; 47 | 48 | #[psp(0x340B7686)] 49 | /// Get the disc info 50 | /// 51 | /// # Parameters 52 | /// 53 | /// - `info`: An out pointer to a `UmdInfo` instance. 54 | /// 55 | /// # Return Value 56 | /// 57 | /// < 0 on error 58 | pub fn sceUmdGetDiscInfo(info: *mut UmdInfo) -> i32; 59 | 60 | #[psp(0xC6183D47)] 61 | /// Activates the UMD drive 62 | /// 63 | /// # Parameters 64 | /// 65 | /// - `unit`: The unit to initialise (probably). Should be set to 1. 66 | /// - `drive`: A prefix string for the fs device to mount the UMD on (e.g. "disc0:") 67 | /// 68 | /// # Return Value 69 | /// 70 | /// < 0 on error 71 | pub fn sceUmdActivate(unit: i32, drive: *const u8) -> i32; 72 | 73 | #[psp(0xE83742BA)] 74 | /// Deativates the UMD drive 75 | /// 76 | /// # Parameters 77 | /// 78 | /// - `unit`: The unit to initialise (probably). Should be set to 1. 79 | /// - `drive`: A prefix string for the fs device to mount the UMD on (e.g. "disc0:") 80 | /// 81 | /// # Return Value 82 | /// 83 | /// < 0 on error 84 | pub fn sceUmdDeactivate(unit: i32, drive: *const u8) -> i32; 85 | 86 | #[psp(0x8EF08FCE)] 87 | /// Wait for the UMD drive to reach a certain state 88 | /// 89 | /// # Parameters 90 | /// 91 | /// - `state`: UMD state(s) to wait for 92 | /// 93 | /// # Return Value 94 | /// 95 | /// < 0 on error 96 | pub fn sceUmdWaitDriveStat(state: UmdStateFlags) -> i32; 97 | 98 | #[psp(0x56202973)] 99 | /// Wait for the UMD drive to reach a certain state 100 | /// 101 | /// # Parameters 102 | /// 103 | /// - `state`: UMD state(s) to wait for 104 | /// 105 | /// # Parameters 106 | /// 107 | /// - `timeout`: Timeout value in microseconds 108 | /// 109 | /// # Return Value 110 | /// 111 | /// < 0 on error 112 | pub fn sceUmdWaitDriveStatWithTimer( 113 | state: UmdStateFlags, 114 | timeout: u32, 115 | ) -> i32; 116 | 117 | #[psp(0x4A9E5E29)] 118 | /// Wait for the UMD drive to reach a certain state (plus callback) 119 | /// 120 | /// # Parameters 121 | /// 122 | /// - `state`: UMD state(s) to wait for 123 | /// 124 | /// # Parameters 125 | /// 126 | /// - `timeout`: Timeout value in microseconds 127 | /// 128 | /// # Return Value 129 | /// 130 | /// < 0 on error 131 | pub fn sceUmdWaitDriveStatCB( 132 | state: UmdStateFlags, 133 | timeout: u32, 134 | ) -> i32; 135 | 136 | #[psp(0x6AF9B50A)] 137 | /// Cancel a `sceUmdWait*` call 138 | /// 139 | /// # Return Value 140 | /// 141 | /// < 0 on error 142 | pub fn sceUmdCancelWaitDriveStat() -> i32; 143 | 144 | #[psp(0x6B4A146C)] 145 | /// Get (poll) the current state of the UMD drive 146 | /// 147 | /// # Return Value 148 | /// 149 | /// < 0 on error, one or more of `UmdStateFlags` on success 150 | pub fn sceUmdGetDriveStat() -> i32; 151 | 152 | #[psp(0x20628E6F)] 153 | /// Get the error code associated with a failed event 154 | /// 155 | /// # Return Value 156 | /// 157 | /// < 0 on error, the error code on success 158 | pub fn sceUmdGetErrorStat() -> i32; 159 | 160 | #[psp(0xAEE7404D)] 161 | /// Register a callback for the UMD drive 162 | /// 163 | /// # Parameters 164 | /// 165 | /// - `cbid`: A callback ID created from `sceKernelCreateCallback`. 166 | /// Callback should be of type `UmdCallback`. 167 | /// 168 | /// # Return Value 169 | /// 170 | /// < 0 on error 171 | pub fn sceUmdRegisterUMDCallBack(cbid: i32) -> i32; 172 | 173 | #[psp(0xBD2BDE07)] 174 | /// Un-register a callback for the UMD drive 175 | /// 176 | /// # Parameters 177 | /// 178 | /// - `cbid`: A callback ID created from `sceKernelCreateCallback` 179 | /// 180 | /// # Return Value 181 | /// 182 | /// < 0 on error 183 | pub fn sceUmdUnRegisterUMDCallBack(cbid: i32) -> i32; 184 | 185 | #[psp(0xCBE9F02A)] 186 | /// Permit UMD disc being replaced 187 | /// 188 | /// # Return Value 189 | /// 190 | /// < 0 on error 191 | pub fn sceUmdReplacePermit() -> i32; 192 | 193 | #[psp(0x87533940)] 194 | /// Prohibit UMD disc being replaced 195 | /// 196 | /// # Return Value 197 | /// 198 | /// < 0 on error 199 | pub fn sceUmdReplaceProhibit() -> i32; 200 | } 201 | -------------------------------------------------------------------------------- /psp/src/sys/vfpu_context.rs: -------------------------------------------------------------------------------- 1 | //! An internal library to manage VFPU register contexts. 2 | //! 3 | //! This is similar (but not identical) to the pspvfpu library from PSPSDK. 4 | 5 | use crate::sys::{ScePspFMatrix4, ScePspFVector4}; 6 | 7 | const NUM_MATRICES: usize = 8; 8 | 9 | bitflags::bitflags! { 10 | #[repr(transparent)] 11 | pub struct MatrixSet: u8 { 12 | const VMAT0 = 0b0000_0001; 13 | const VMAT1 = 0b0000_0010; 14 | const VMAT2 = 0b0000_0100; 15 | const VMAT3 = 0b0000_1000; 16 | const VMAT4 = 0b0001_0000; 17 | const VMAT5 = 0b0010_0000; 18 | const VMAT6 = 0b0100_0000; 19 | const VMAT7 = 0b1000_0000; 20 | } 21 | } 22 | 23 | #[repr(C, align(16))] 24 | pub struct Context { 25 | matrices: [ScePspFMatrix4; NUM_MATRICES], 26 | saved: MatrixSet, 27 | } 28 | 29 | impl Context { 30 | pub fn new() -> Self { 31 | unsafe { 32 | use crate::sys::{self, ThreadAttributes}; 33 | 34 | // TODO: Handle errors. 35 | sys::sceKernelChangeCurrentThreadAttr(0, ThreadAttributes::VFPU); 36 | } 37 | 38 | let zero_vector = ScePspFVector4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 }; 39 | let zero_matrix = ScePspFMatrix4 { 40 | x: zero_vector, 41 | y: zero_vector, 42 | z: zero_vector, 43 | w: zero_vector, 44 | }; 45 | 46 | let matrices = [ 47 | zero_matrix, 48 | zero_matrix, 49 | zero_matrix, 50 | zero_matrix, 51 | 52 | zero_matrix, 53 | zero_matrix, 54 | zero_matrix, 55 | zero_matrix, 56 | ]; 57 | 58 | Self { 59 | matrices, 60 | saved: MatrixSet::empty(), 61 | } 62 | } 63 | 64 | fn restore(&mut self, matrix_idx: u8) { 65 | macro_rules! restore { 66 | ($restore_addr:expr, $c0:ident, $c1:ident, $c2:ident, $c3:ident) => { 67 | vfpu_asm! { 68 | lv_q $c0, t0; 69 | lv_q $c1, 16(t0); 70 | lv_q $c2, 32(t0); 71 | lv_q $c3, 48(t0); 72 | 73 | : : "{$8}"($restore_addr) : "memory" : "volatile" 74 | } 75 | } 76 | } 77 | 78 | let idx = matrix_idx as usize; 79 | 80 | unsafe { 81 | match matrix_idx { 82 | 0 => restore!(&self.matrices[idx], C000, C010, C020, C030), 83 | 1 => restore!(&self.matrices[idx], C100, C110, C120, C130), 84 | 2 => restore!(&self.matrices[idx], C200, C210, C220, C230), 85 | 3 => restore!(&self.matrices[idx], C300, C310, C320, C330), 86 | 4 => restore!(&self.matrices[idx], C400, C410, C420, C430), 87 | 5 => restore!(&self.matrices[idx], C500, C510, C520, C530), 88 | 6 => restore!(&self.matrices[idx], C600, C610, C620, C630), 89 | 7 => restore!(&self.matrices[idx], C700, C710, C720, C730), 90 | _ => core::intrinsics::unreachable(), 91 | } 92 | 93 | self.saved &= !MatrixSet::from_bits_unchecked(1 << matrix_idx); 94 | } 95 | } 96 | 97 | fn save(&mut self, matrix_idx: u8) { 98 | macro_rules! save { 99 | ($save_addr:expr, $c0:ident, $c1:ident, $c2:ident, $c3:ident) => { 100 | vfpu_asm! { 101 | sv_q $c0, t0; 102 | sv_q $c1, 16(t0); 103 | sv_q $c2, 32(t0); 104 | sv_q $c3, 48(t0); 105 | 106 | : : "{$8}"($save_addr) : "memory" : "volatile" 107 | } 108 | } 109 | } 110 | 111 | let idx = matrix_idx as usize; 112 | 113 | unsafe { 114 | match matrix_idx { 115 | 0 => save!(&mut self.matrices[idx], C000, C010, C020, C030), 116 | 1 => save!(&mut self.matrices[idx], C100, C110, C120, C130), 117 | 2 => save!(&mut self.matrices[idx], C200, C210, C220, C230), 118 | 3 => save!(&mut self.matrices[idx], C300, C310, C320, C330), 119 | 4 => save!(&mut self.matrices[idx], C400, C410, C420, C430), 120 | 5 => save!(&mut self.matrices[idx], C500, C510, C520, C530), 121 | 6 => save!(&mut self.matrices[idx], C600, C610, C620, C630), 122 | 7 => save!(&mut self.matrices[idx], C700, C710, C720, C730), 123 | _ => core::intrinsics::unreachable(), 124 | } 125 | 126 | self.saved |= MatrixSet::from_bits_unchecked(1 << matrix_idx); 127 | } 128 | } 129 | 130 | pub unsafe fn prepare(&mut self, in_out: MatrixSet, clobber: MatrixSet) { 131 | for i in 0..8 { 132 | let matrix = MatrixSet::from_bits_unchecked(1 << i); 133 | 134 | if in_out.intersects(matrix) && self.saved.intersects(matrix) { 135 | self.restore(i); 136 | } else if clobber.intersects(matrix) && !self.saved.intersects(matrix) { 137 | self.save(i); 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /psp/src/sys/wlan.rs: -------------------------------------------------------------------------------- 1 | psp_extern! { 2 | #![name = "sceWlanDrv"] 3 | #![flags = 0x4001] 4 | #![version = (0x00, 0x00)] 5 | 6 | #[psp(0x93440B11)] 7 | /// Determine if the wlan device is currently powered on 8 | /// 9 | /// # Return Value 10 | /// 11 | /// 0 if off, 1 if on 12 | pub fn sceWlanDevIsPowerOn() -> i32; 13 | 14 | #[psp(0xD7763699)] 15 | /// Determine the state of the Wlan power switch 16 | /// 17 | /// # Return Value 18 | /// 19 | /// 0 if off, 1 if on 20 | pub fn sceWlanGetSwitchState() -> i32; 21 | 22 | #[psp(0x0C622081)] 23 | /// Get the Ethernet Address of the wlan controller 24 | /// 25 | /// # Parameters 26 | /// 27 | /// - `ether_addr`: pointer to an output buffer of u8 (NOTE: it only writes 28 | /// to 6 bytes, but requests 8 so pass it 8 bytes just in case) 29 | /// 30 | /// # Return Value 31 | /// 32 | /// 0 on success, < 0 on error 33 | pub fn sceWlanGetEtherAddr(ether_addr: *mut u8) -> i32; 34 | } 35 | 36 | psp_extern! { 37 | #![name = "sceWlanDrv_lib"] 38 | #![flags = 0x4001] 39 | #![version = (0x00, 0x00)] 40 | 41 | #[psp(0x482CAE9A)] 42 | /// Attach to the wlan device 43 | /// 44 | /// # Return Value 45 | /// 46 | /// 0 on success, < 0 on error. 47 | pub fn sceWlanDevAttach() -> i32; 48 | 49 | #[psp(0xC9A8CAB7)] 50 | /// Detach from the wlan device 51 | /// 52 | /// # Return Value 53 | /// 54 | /// 0 on success, < 0 on error 55 | pub fn sceWlanDevDetach() -> i32; 56 | } 57 | -------------------------------------------------------------------------------- /psp/src/test_runner.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::{self, SceUid}; 2 | use core::ffi::c_void; 3 | 4 | pub const OUTPUT_FILENAME: &str = "psp_output_file.log"; 5 | pub const OUTPUT_FIFO: &str = "psp_output_pipe.fifo"; 6 | 7 | pub const STARTING_TOKEN: &str = "STARTING_TESTS"; 8 | pub const SUCCESS_TOKEN: &str = "FINAL_SUCCESS"; 9 | pub const FAILURE_TOKEN: &str = "FINAL_FAILURE"; 10 | 11 | use alloc::format; 12 | use alloc::vec::Vec; 13 | use core::fmt::Arguments; 14 | 15 | pub struct TestRunner<'a> { 16 | mode: TestRunnerMode, 17 | failure: bool, 18 | failures: Vec<&'a str> 19 | } 20 | 21 | enum TestRunnerMode { 22 | FIFO(SceUid), 23 | FILE(SceUid), 24 | Dprintln, 25 | } 26 | 27 | impl<'a> TestRunner<'a> { 28 | pub fn new_fifo_runner() -> Self { 29 | let fd = get_test_output_pipe(); 30 | Self { 31 | mode: TestRunnerMode::FIFO(fd), 32 | failure: false, 33 | failures: Vec::new(), 34 | } 35 | } 36 | 37 | pub fn new_file_runner() -> Self { 38 | let fd = get_test_output_file(); 39 | Self { 40 | mode: TestRunnerMode::FILE(fd), 41 | failure: false, 42 | failures: Vec::new(), 43 | } 44 | } 45 | 46 | pub fn new_dprintln_runner() -> Self { 47 | Self { 48 | mode: TestRunnerMode::Dprintln, 49 | failure: false, 50 | failures: Vec::new(), 51 | } 52 | } 53 | 54 | 55 | pub fn run(&mut self, f: F) { 56 | f(self) 57 | } 58 | 59 | pub fn start_run(&self) { 60 | self.write_args(format_args!("\n\n{}\n", STARTING_TOKEN)); 61 | } 62 | 63 | pub fn finish_run(self) { 64 | if self.failure { 65 | self.write_args(format_args!("Failing tests: {:?}\n", self.failures)); 66 | self.write_args(format_args!("{}\n", FAILURE_TOKEN)); 67 | } else { 68 | self.write_args(format_args!("{}\n", SUCCESS_TOKEN)); 69 | } 70 | self.quit(); 71 | } 72 | 73 | pub fn check_fns_do_not_panic(&self, tests: &[(&str, &dyn Fn())]) { 74 | for (testcase_name, f) in tests { 75 | f(); 76 | self.pass(testcase_name, ""); 77 | } 78 | } 79 | 80 | pub fn check(&mut self, testcase_name: &'a str, l: T, r: T) 81 | where 82 | T: core::fmt::Debug + PartialEq, 83 | { 84 | if l == r { 85 | self.pass(testcase_name, &format!("{:?} == {:?}", l, r)); 86 | } else { 87 | self.fail(testcase_name, &format!("{:?} != {:?}", l, r)); 88 | } 89 | } 90 | 91 | pub fn check_large_collection(&mut self, testcase_name: &'a str, l: &[T], r: &[T]) 92 | where 93 | T: core::fmt::Debug + PartialEq + Eq, 94 | { 95 | if l.iter().eq(r.iter()) { 96 | self.pass(testcase_name, "Equal!"); 97 | } else { 98 | if l.len() != r.len() { 99 | self.dbg( 100 | testcase_name, 101 | &format!("Lengths differ! {} != {}", l.len(), r.len()), 102 | ); 103 | } 104 | 105 | let mut i = 0; 106 | for (li, ri) in l.iter().zip(r.iter()) { 107 | if li != ri { 108 | self.dbg( 109 | testcase_name, 110 | &format!("Differ on item {}: {:?} != {:?}", i, li, ri), 111 | ); 112 | break; 113 | } 114 | i += 1; 115 | } 116 | 117 | self.fail( 118 | testcase_name, 119 | "Collections were not equal!", 120 | ); 121 | } 122 | } 123 | 124 | pub fn check_silent(&mut self, testcase_name: &'a str, l: T, r: T) 125 | where 126 | T: core::fmt::Debug + PartialEq, 127 | { 128 | if l == r { 129 | self.pass(testcase_name, "Equal."); 130 | } else { 131 | self.fail(testcase_name, "Not equal!"); 132 | } 133 | } 134 | 135 | pub fn check_list(&mut self, val_pairs: &[(&'a str, T, T)]) 136 | where 137 | T: core::fmt::Debug + PartialEq, 138 | { 139 | for (testcase_name, l, r) in val_pairs { 140 | self.check(testcase_name, l, r) 141 | } 142 | } 143 | 144 | pub fn _check_return_values(&mut self, val_pairs: &[(&'a str, &dyn Fn() -> T, T)]) 145 | where 146 | T: core::fmt::Debug + PartialEq + Eq + Clone, 147 | { 148 | self.check_list( 149 | &val_pairs 150 | .iter() 151 | .map(|(testcase_name, f, v)| (*testcase_name, f(), v.clone())) 152 | .collect::>(), 153 | ) 154 | } 155 | 156 | pub fn pass(&self, testcase_name: &str, msg: &str) { 157 | self.write_args(format_args!("[PASS]: ({}) {}\n", testcase_name, msg)); 158 | } 159 | 160 | pub fn dbg(&self, testcase_name: &str, msg: &str) { 161 | self.write_args(format_args!("[NOTE]: ({}) {}\n", testcase_name, msg)); 162 | } 163 | 164 | pub fn fail(&mut self, testcase_name: &'a str, msg: &str) { 165 | self.failure = true; 166 | self.failures.push(testcase_name); 167 | self.write_args(format_args!("[FAIL]: ({}) {}\n", testcase_name, msg)); 168 | } 169 | 170 | pub fn write_args(&self, args: Arguments) { 171 | match self.mode { 172 | TestRunnerMode::FILE(fd) | TestRunnerMode::FIFO(fd) => { 173 | write_to_psp_output_fd(fd, &format!("{}", args)); 174 | } 175 | TestRunnerMode::Dprintln => { 176 | crate::dprintln!("{}", args); 177 | } 178 | 179 | } 180 | } 181 | 182 | fn quit(self) { 183 | match self.mode { 184 | TestRunnerMode::FILE(fd) | TestRunnerMode::FIFO(fd) => { 185 | close_psp_file(fd); 186 | quit_game(); 187 | } 188 | TestRunnerMode::Dprintln => { 189 | loop {} 190 | } 191 | } 192 | } 193 | } 194 | 195 | fn get_test_output_pipe() -> SceUid { 196 | unsafe { 197 | let fd = sys::sceIoOpen( 198 | psp_filename(OUTPUT_FIFO), 199 | sys::IoOpenFlags::APPEND | sys::IoOpenFlags::WR_ONLY, 200 | 0o777, 201 | ); 202 | if fd.0 < 0 { 203 | panic!( 204 | "Unable to open pipe for output! \ 205 | You must create it yourself with `mkfifo`." 206 | ); 207 | } 208 | return fd; 209 | } 210 | } 211 | 212 | fn get_test_output_file() -> SceUid { 213 | unsafe { 214 | let fd = sys::sceIoOpen( 215 | psp_filename(OUTPUT_FILENAME), 216 | sys::IoOpenFlags::TRUNC | sys::IoOpenFlags::CREAT | sys::IoOpenFlags::RD_WR, 217 | 0o777, 218 | ); 219 | if fd.0 < 0 { 220 | panic!("Unable to open file \"{}\" for output!", OUTPUT_FILENAME); 221 | } 222 | return fd; 223 | } 224 | } 225 | 226 | fn psp_filename(filename: &str) -> *const u8 { 227 | format!("host0:/{}\0", filename).as_bytes().as_ptr() 228 | } 229 | 230 | fn write_to_psp_output_fd(fd: SceUid, msg: &str) { 231 | unsafe { 232 | sys::sceIoWrite( 233 | fd, 234 | msg.as_bytes().as_ptr() as *const u8 as *const c_void, 235 | msg.len(), 236 | ); 237 | } 238 | } 239 | 240 | fn close_psp_file(fd: SceUid) { 241 | unsafe { 242 | sys::sceIoClose(fd); 243 | } 244 | } 245 | 246 | fn quit_game() { 247 | unsafe { 248 | sys::sceKernelExitGame(); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /psp/src/vram_alloc.rs: -------------------------------------------------------------------------------- 1 | use crate::sys::TexturePixelFormat; 2 | use crate::sys::{sceGeEdramGetAddr, sceGeEdramGetSize}; 3 | use core::mem::size_of; 4 | use core::ptr::null_mut; 5 | 6 | type VramAllocator = SimpleVramAllocator; 7 | 8 | #[derive(Debug)] 9 | pub struct VramAllocatorInUseError {} 10 | 11 | static mut VRAM_ALLOCATOR: VramAllocatorSingleton = VramAllocatorSingleton { 12 | alloc: Some(VramAllocator::new()), 13 | }; 14 | 15 | pub fn get_vram_allocator() -> Result { 16 | let opt_alloc = unsafe { VRAM_ALLOCATOR.get_vram_alloc() }; 17 | opt_alloc.ok_or(VramAllocatorInUseError {}) 18 | } 19 | 20 | pub struct VramAllocatorSingleton { 21 | alloc: Option, 22 | } 23 | 24 | impl VramAllocatorSingleton { 25 | pub fn get_vram_alloc(&mut self) -> Option { 26 | self.alloc.take() 27 | } 28 | } 29 | 30 | pub struct VramMemChunk { 31 | start: u32, 32 | len: u32, 33 | } 34 | 35 | impl VramMemChunk { 36 | fn new(start: u32, len: u32) -> Self { 37 | Self { start, len } 38 | } 39 | 40 | pub fn as_mut_ptr_from_zero(&self) -> *mut u8 { 41 | unsafe { vram_start_addr_zero().add(self.start as usize) } 42 | } 43 | 44 | pub fn as_mut_ptr_direct_to_vram(&self) -> *mut u8 { 45 | unsafe { vram_start_addr_direct().add(self.start as usize) } 46 | } 47 | 48 | pub fn len(&self) -> u32 { 49 | self.len 50 | } 51 | } 52 | 53 | // A dead-simple VRAM bump allocator. 54 | // TODO: pin? 55 | #[derive(Debug)] 56 | pub struct SimpleVramAllocator { 57 | offset: u32, 58 | } 59 | 60 | impl SimpleVramAllocator { 61 | const fn new() -> Self { 62 | Self { offset: 0 } 63 | } 64 | 65 | // TODO: return a Result instead of panicking 66 | pub fn alloc(&mut self, size: u32) -> VramMemChunk { 67 | let old_offset = self.offset; 68 | self.offset += size; 69 | 70 | if self.offset > self.total_mem() { 71 | panic!("Total VRAM size exceeded!"); 72 | } 73 | 74 | VramMemChunk::new(old_offset, size) 75 | } 76 | 77 | // TODO: ensure 16-bit alignment? 78 | pub fn alloc_sized(&mut self, count: u32) -> VramMemChunk { 79 | let size = size_of::() as u32; 80 | self.alloc(count * size) 81 | } 82 | 83 | pub fn alloc_texture_pixels( 84 | &mut self, 85 | width: u32, 86 | height: u32, 87 | psm: TexturePixelFormat, 88 | ) -> VramMemChunk { 89 | let size = get_memory_size(width, height, psm); 90 | self.alloc(size) 91 | } 92 | 93 | // TODO: write, or write_volatile? 94 | // TODO: result instead of unwrap? 95 | // TODO: Keep track of the allocated chunk 96 | // TODO: determine unsafety of this 97 | pub unsafe fn move_to_vram(&mut self, obj: T) -> &mut T { 98 | let chunk = self.alloc_sized::(1); 99 | let ptr = chunk.as_mut_ptr_direct_to_vram() as *mut T; 100 | ptr.write(obj); 101 | ptr.as_mut().unwrap() 102 | } 103 | 104 | fn total_mem(&self) -> u32 { 105 | total_vram_size() 106 | } 107 | } 108 | 109 | fn total_vram_size() -> u32 { 110 | unsafe { sceGeEdramGetSize() } 111 | } 112 | 113 | // NOTE: VRAM actually starts at 0x4000000, as returned by sceGeEdramGetAddr. 114 | // The Gu functions take that into account, and start their pointer 115 | // indices at 0. See GE_EDRAM_ADDRESS in gu.rs for that offset being used. 116 | fn vram_start_addr_zero() -> *mut u8 { 117 | null_mut() 118 | } 119 | 120 | fn vram_start_addr_direct() -> *mut u8 { 121 | unsafe { sceGeEdramGetAddr() } 122 | } 123 | 124 | fn get_memory_size(width: u32, height: u32, psm: TexturePixelFormat) -> u32 { 125 | match psm { 126 | TexturePixelFormat::PsmT4 => (width * height) >> 1, 127 | TexturePixelFormat::PsmT8 => width * height, 128 | 129 | TexturePixelFormat::Psm5650 130 | | TexturePixelFormat::Psm5551 131 | | TexturePixelFormat::Psm4444 132 | | TexturePixelFormat::PsmT16 => 2 * width * height, 133 | 134 | TexturePixelFormat::Psm8888 | TexturePixelFormat::PsmT32 => 4 * width * height, 135 | 136 | _ => unimplemented!(), 137 | } 138 | } 139 | --------------------------------------------------------------------------------