├── .gitignore ├── exports.yml ├── libpng └── VITABUILD ├── CMakeLists.txt ├── README.md ├── LICENSE.txt └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /exports.yml: -------------------------------------------------------------------------------- 1 | pngshot: 2 | attributes: 0 3 | version: 4 | major: 1 5 | minor: 0 6 | main: 7 | start: module_start 8 | stop: module_stop 9 | -------------------------------------------------------------------------------- /libpng/VITABUILD: -------------------------------------------------------------------------------- 1 | pkgname=libpng 2 | pkgver=1.6.29 3 | pkgrel=1 4 | url="http://www.libpng.org/pub/png/libpng.html" 5 | source=("http://download.sourceforge.net/libpng/libpng-${pkgver}.tar.gz") 6 | sha256sums=('e30bf36cd5882e017c23a5c6a79a9aa1a744dd5841bb45ff7035ec6e3b3096b8') 7 | 8 | build() { 9 | cd $pkgname-$pkgver 10 | export CPPFLAGS="-DPNG_NO_CONVERT_tIME=1 -DPNG_NO_SETJMP=1 -DPNG_NO_SIMPLIFIED_READ=1 -DPNG_NO_SIMPLIFIED_WRITE=1 -DPNG_NO_SIMPLIFIED_WRITE_STDIO=1 -DPNG_NO_STDIO=1" 11 | ./configure --host=arm-vita-eabi --prefix=$prefix --disable-shared --enable-static --enable-arm-neon 12 | make 13 | } 14 | 15 | package () { 16 | cd $pkgname-$pkgver 17 | make DESTDIR=$pkgdir install 18 | } 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) 4 | if(DEFINED ENV{VITASDK}) 5 | set(CMAKE_TOOLCHAIN_FILE "$ENV{VITASDK}/share/vita.toolchain.cmake" CACHE PATH "toolchain file") 6 | else() 7 | message(FATAL_ERROR "Please define VITASDK to point to your SDK path!") 8 | endif() 9 | endif() 10 | 11 | project(pngshot) 12 | include("${VITASDK}/share/vita.cmake" REQUIRED) 13 | 14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-q -Wall -O3 -nostdlib -std=c99 -fno-strict-aliasing") 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions") 16 | 17 | add_executable(pngshot 18 | main.c 19 | ) 20 | 21 | target_link_libraries(pngshot 22 | png16 23 | z 24 | k 25 | m 26 | gcc 27 | taihen_stub 28 | ScePaf_stub_weak 29 | SceSysmem_stub 30 | ) 31 | 32 | vita_create_self(pngshot.suprx pngshot CONFIG exports.yml UNSAFE) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pngshot 2 | 3 | pngshot is a plugin to make screenshots great again. 4 | 5 | ## Features 6 | 7 | * Takes screenshots in png format 8 | * No watermark 9 | * Take screenshots in any app 10 | 11 | ## Installation 12 | 13 | Download from the [Releases section](https://github.com/xyzz/pngshot/releases). 14 | 15 | Copy `pngshot.suprx` to `ur0:tai` and add `ur0:tai/pngshot.suprx` below `*main` in `ur0:tai/config.txt`. 16 | 17 | Note that this was only tested with retail SceShell. If, for some reason, you have some weird modifications done to your SceShell, this plugin will probably crash your Vita. 18 | 19 | ## Usage 20 | 21 | Press PS button + Start to take a screenshot. You can access screenshots with the Photos app, or from `ux0:picture/SCREENSHOT`. 22 | 23 | ## Additional notes 24 | 25 | To compile this plugin from source, you need a custom build of libpng (the one in vdpm will not work). Check out `libpng` directory for a working `VITABUILD`. 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 xyz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | int __errno = 0; 10 | void abort(void) { 11 | __builtin_trap(); 12 | } 13 | 14 | void *malloc(size_t sz) { 15 | return sce_paf_private_malloc(sz); 16 | } 17 | 18 | void free(void *p) { 19 | sce_paf_private_free(p); 20 | } 21 | 22 | ////// 23 | 24 | tai_hook_ref_t watermark_hook, encode_hook, encode_type2_hook, reg_hook; 25 | 26 | int get_key_int(const char *dir, const char *file, int *out) { 27 | if (strcmp(dir, "/CONFIG/PHOTO") == 0 && strcmp(file, "debug_screenshot") == 0) { 28 | *out = 1; 29 | return 0; 30 | } 31 | 32 | return TAI_CONTINUE(int, reg_hook, dir, file, out); 33 | } 34 | 35 | int place_watermark_hook() { 36 | return 0; 37 | } 38 | 39 | int encode_screenshot(void **ss_arg1, unsigned unk) { 40 | // set format to 2 = "raw" but we will hook and change that later 41 | ((int*)(*ss_arg1))[8/4] = 2; 42 | 43 | return TAI_CONTINUE(int, encode_hook, ss_arg1, unk); 44 | } 45 | 46 | typedef struct picture_t picture_t; 47 | 48 | typedef struct { 49 | int width; 50 | int height; 51 | } dimensions_t; 52 | 53 | typedef struct { 54 | void *field_0; 55 | void *field_4; 56 | void *field_8; 57 | int (*get_type)(picture_t *); 58 | int (*get_dimensions)(dimensions_t *, picture_t *); 59 | void *field_14; 60 | unsigned (*get_pixel)(picture_t *, int x, int y); 61 | void *field_1c; 62 | void *field_20; 63 | } picture_vtable_t; 64 | 65 | struct picture_t { 66 | picture_vtable_t *vptr; 67 | }; 68 | 69 | typedef struct encode_t encode_t; 70 | 71 | typedef struct { 72 | void *field_0; 73 | void *field_4; 74 | int (*is_buffer_init)(encode_t *); 75 | int (*append)(encode_t *, const void *buffer, size_t sz); 76 | void *field_10; 77 | void *field_14; 78 | void *field_18; 79 | void *field_1c; 80 | void *field_20; 81 | } encode_vtable_t; 82 | 83 | struct encode_t { 84 | encode_vtable_t *vptr; 85 | } __attribute__((packed)); 86 | 87 | typedef struct { 88 | picture_t *picture; 89 | encode_t *encode; 90 | void *field_8; 91 | } actual_encode_args_t; 92 | 93 | typedef struct { 94 | int (*func)(); 95 | unsigned field_4; 96 | } unk_obj_t; 97 | 98 | size_t g_png_size; 99 | 100 | void write_func(png_structp png_ptr, png_bytep data, png_size_t length) { 101 | g_png_size += length; 102 | actual_encode_args_t *args = png_get_io_ptr(png_ptr); 103 | args->encode->vptr->append(args->encode, data, length); 104 | } 105 | 106 | enum { 107 | ENCODE_ERROR1 = 0x80103001, 108 | ENCODE_ERROR = 0x80103002, 109 | }; 110 | 111 | int encode_type2(actual_encode_args_t *args) { 112 | int ret = 0; 113 | unsigned *pixels = NULL; 114 | png_structp png_ptr = NULL; 115 | png_infop info_ptr = NULL; 116 | dimensions_t wh = {0}; 117 | 118 | encode_t *encode = args->encode; 119 | picture_t *picture = args->picture; 120 | 121 | if (!encode || !encode->vptr->is_buffer_init(encode) || !picture || !picture->vptr->get_type(picture)) 122 | return ENCODE_ERROR; 123 | 124 | g_png_size = 0; 125 | picture->vptr->get_dimensions(&wh, picture); 126 | 127 | png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 128 | if (!png_ptr) 129 | goto cleanup; 130 | 131 | info_ptr = png_create_info_struct(png_ptr); 132 | if (!info_ptr) 133 | goto cleanup; 134 | 135 | png_set_check_for_invalid_index(png_ptr, 0); 136 | 137 | png_set_IHDR(png_ptr, info_ptr, wh.width, wh.height, 8, 138 | PNG_COLOR_TYPE_RGBA, 139 | PNG_INTERLACE_NONE, 140 | PNG_COMPRESSION_TYPE_DEFAULT, 141 | PNG_FILTER_TYPE_DEFAULT); 142 | 143 | png_set_write_fn(png_ptr, args, write_func, NULL); 144 | 145 | pixels = sce_paf_private_malloc(wh.width * 4); 146 | 147 | png_write_info(png_ptr, info_ptr); 148 | 149 | for (int i = 0; i < wh.height; ++i) { 150 | // haven't reversed what this does, just copied from assembly 151 | unk_obj_t *unk = args->field_8; 152 | if (unk && unk->func && unk->func(unk->field_4)) { 153 | ret = ENCODE_ERROR1; 154 | goto cleanup; 155 | } 156 | 157 | for (int j = 0; j < wh.width; ++j) 158 | pixels[j] = picture->vptr->get_pixel(picture, j, i) | 0xFF000000u; 159 | 160 | png_write_row(png_ptr, (void*)pixels); 161 | } 162 | 163 | png_write_end(png_ptr, info_ptr); 164 | 165 | cleanup: 166 | if (png_ptr || info_ptr) 167 | png_destroy_write_struct(&png_ptr, &info_ptr); 168 | 169 | if (pixels) 170 | sce_paf_private_free(pixels); 171 | 172 | if (ret < 0) 173 | return ret; 174 | 175 | return g_png_size; 176 | } 177 | 178 | int module_start() { 179 | tai_module_info_t info = {0}; 180 | info.size = sizeof(info); 181 | taiGetModuleInfo(TAI_MAIN_MODULE, &info); 182 | 183 | if (info.module_nid == 0x0552F692) { // 3.60 retail 184 | // disable watermark 185 | taiHookFunctionOffset(&watermark_hook, info.modid, 0, 0x247e00, 1, place_watermark_hook); 186 | 187 | // enable type=2 screenshot encoding 188 | taiHookFunctionOffset(&encode_hook, info.modid, 0, 0x365f46, 1, encode_screenshot); 189 | 190 | // replace type=2 encoding with our png implementation 191 | taiHookFunctionOffset(&encode_type2_hook, info.modid, 0, 0x36bd22, 1, encode_type2); 192 | 193 | // change branch for 0x34560004 (screenshot disable) to 0x34560003 (screenshot enable) 194 | int value = 0x3b; 195 | taiInjectData(info.modid, 0, 0x248840, &value, 2); 196 | 197 | // change extension from jpg to png 198 | const char *path = "ur0:temp/screenshot/capture.png"; 199 | taiInjectData(info.modid, 0, 0x5148b8, path, strlen(path) + 1); 200 | } else if (info.module_nid == 0xEAB89D5C) //3.60 Testkit 201 | { 202 | // disable watermark 203 | taiHookFunctionOffset(&watermark_hook, info.modid, 0, 0x240234, 1, place_watermark_hook); 204 | 205 | // enable type=2 screenshot encoding 206 | taiHookFunctionOffset(&encode_hook, info.modid, 0, 0x35c98e, 1, encode_screenshot); 207 | 208 | // replace type=2 encoding with our png implementation 209 | taiHookFunctionOffset(&encode_type2_hook, info.modid, 0, 0x36276a, 1, encode_type2); 210 | 211 | // change branch for 0x34560004 (screenshot disable) to 0x34560003 (screenshot enable) 212 | int value = 0x3b; 213 | taiInjectData(info.modid, 0, 0x240C74, &value, 2); 214 | 215 | // change extension from jpg to png 216 | const char *path = "ur0:temp/screenshot/capture.png"; 217 | taiInjectData(info.modid, 0, 0x508B18, path, strlen(path) + 1); 218 | }else if (info.module_nid == 0x5549BF1F || 219 | info.module_nid == 0x34B4D82E || 220 | info.module_nid == 0x12DAC0F3) { // 3.65/3.67/3.68 retail 221 | // disable watermark 222 | taiHookFunctionOffset(&watermark_hook, info.modid, 0, 0x247e9c, 1, place_watermark_hook); 223 | 224 | // enable type=2 screenshot encoding 225 | taiHookFunctionOffset(&encode_hook, info.modid, 0, 0x36638a, 1, encode_screenshot); 226 | 227 | // replace type=2 encoding with our png implementation 228 | taiHookFunctionOffset(&encode_type2_hook, info.modid, 0, 0x36c166, 1, encode_type2); 229 | 230 | // change branch for 0x34560004 (screenshot disable) to 0x34560003 (screenshot enable) 231 | int value = 0x3b; 232 | taiInjectData(info.modid, 0, 0x2488dc, &value, 2); 233 | 234 | // change extension from jpg to png 235 | const char *path = "ur0:temp/screenshot/capture.png"; 236 | taiInjectData(info.modid, 0, 0x514df8, path, strlen(path) + 1); 237 | } 238 | 239 | return 0; 240 | } 241 | 242 | int module_stop() { 243 | return 0; 244 | } 245 | --------------------------------------------------------------------------------