├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets └── background.png ├── pkgi.c ├── pkgi.h ├── pkgi_aes128.c ├── pkgi_aes128.h ├── pkgi_config.c ├── pkgi_config.h ├── pkgi_db.c ├── pkgi_db.h ├── pkgi_dialog.c ├── pkgi_dialog.h ├── pkgi_download.c ├── pkgi_download.h ├── pkgi_menu.c ├── pkgi_menu.h ├── pkgi_sha256.c ├── pkgi_sha256.h ├── pkgi_simulator.c ├── pkgi_style.h ├── pkgi_utils.h ├── pkgi_vita.c ├── pkgi_zrif.c ├── pkgi_zrif.h ├── puff.c ├── puff.h ├── sce_sys ├── icon0.png └── livearea │ └── contents │ ├── bg.png │ ├── startup.png │ └── template.xml └── simulator ├── pkgi.sln ├── pkgi.vcxproj └── pkgi.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | simulator/.vs 2 | simulator/*.pkg 3 | simulator/*.user 4 | simulator/*.otf 5 | simulator/x64 6 | simulator/app 7 | simulator/pkgi 8 | 9 | CMakeFiles 10 | CMakeCache.txt 11 | Makefile 12 | *.cmake 13 | 14 | *.sfo 15 | *.vpk 16 | *.velf 17 | pkgi 18 | eboot.bin 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git: 2 | depth: 1 3 | 4 | os: linux 5 | language: c 6 | dist: trusty 7 | sudo: false 8 | 9 | install: 10 | - cd ${TRAVIS_BUILD_DIR} 11 | - curl -L "https://github.com/vitasdk/autobuilds/releases/download/master-linux-v589/vitasdk-x86_64-linux-gnu-2017-11-13_19-11-13.tar.bz2" | tar xj 12 | - for name in libvita2d libpng zlib; do curl -L "http://dl.vitasdk.org/${name}.tar.xz" | tar -xJC vitasdk/arm-vita-eabi; done 13 | 14 | script: 15 | - export VITASDK=${TRAVIS_BUILD_DIR}/vitasdk 16 | - export PATH=${VITASDK}/bin:${PATH} 17 | - cmake -DCMAKE_BUILD_TYPE=Release . 18 | - cmake --build . 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(pkgi) 12 | 13 | include("${VITASDK}/share/vita.cmake" REQUIRED) 14 | 15 | set(VITA_APP_NAME "PKGi") 16 | set(VITA_TITLEID "PKGI00000") 17 | set(VITA_VERSION "00.05") 18 | 19 | option(PKGI_ENABLE_LOGGING "enables debug logging over udp multicast" OFF) 20 | 21 | if(PKGI_ENABLE_LOGGING) 22 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPKGI_ENABLE_LOGGING") 23 | endif() 24 | 25 | if(NOT $ENV{PKGI_REFRESH_URL} STREQUAL "") 26 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPKGI_REFRESH_URL=\\\"$ENV{PKGI_REFRESH_URL}\\\"") 27 | endif() 28 | 29 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPKGI_VERSION=\\\"${VITA_VERSION}\\\" -D_GNU_SOURCE -g -Wall -Wextra -Werror -fvisibility=hidden") 30 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto") 31 | 32 | set(VITA_MKSFOEX_FLAGS "${VITA_MKSFOEX_FLAGS} -d PARENTAL_LEVEL=1") 33 | 34 | function(add_assets target) 35 | set(result) 36 | foreach(in_f ${ARGN}) 37 | set(out_f "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${in_f}.o") 38 | get_filename_component(out_dir ${out_f} DIRECTORY) 39 | add_custom_command(OUTPUT ${out_f} 40 | COMMAND ${CMAKE_COMMAND} -E make_directory ${out_dir} 41 | COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${in_f} 42 | DEPENDS ${in_f} 43 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 44 | COMMENT "Using ${in_f}" 45 | VERBATIM 46 | ) 47 | list(APPEND result ${out_f}) 48 | endforeach() 49 | set(${target} "${result}" PARENT_SCOPE) 50 | endfunction() 51 | 52 | add_assets(assets assets/background.png) 53 | 54 | add_executable(pkgi 55 | ${assets} 56 | pkgi.c 57 | pkgi_aes128.c 58 | pkgi_config.c 59 | pkgi_db.c 60 | pkgi_dialog.c 61 | pkgi_download.c 62 | pkgi_menu.c 63 | pkgi_sha256.c 64 | pkgi_vita.c 65 | pkgi_zrif.c 66 | puff.c 67 | ) 68 | 69 | target_link_libraries(pkgi 70 | vita2d 71 | png 72 | z 73 | m 74 | SceAppMgr_stub 75 | SceAppUtil_stub 76 | SceCommonDialog_stub 77 | SceCtrl_stub 78 | SceDisplay_stub 79 | SceGxm_stub 80 | SceHttp_stub 81 | SceNet_stub 82 | SceNetCtl_stub 83 | ScePgf_stub 84 | ScePower_stub 85 | ScePromoterUtil_stub 86 | SceShellSvc_stub 87 | SceSsl_stub 88 | SceSysmodule_stub 89 | ) 90 | 91 | vita_create_self(eboot.bin pkgi UNSAFE) 92 | 93 | vita_create_vpk(${PROJECT_NAME}.vpk ${VITA_TITLEID} eboot.bin 94 | VERSION ${VITA_VERSION} 95 | NAME ${VITA_APP_NAME} 96 | FILE sce_sys/icon0.png sce_sys/icon0.png 97 | sce_sys/livearea/contents/bg.png sce_sys/livearea/contents/bg.png 98 | sce_sys/livearea/contents/startup.png sce_sys/livearea/contents/startup.png 99 | sce_sys/livearea/contents/template.xml sce_sys/livearea/contents/template.xml 100 | ) 101 | 102 | add_custom_target(send 103 | COMMAND curl -T eboot.bin ftp://"$ENV{PSVITAIP}":1337/ux0:/app/${VITA_TITLEID}/ 104 | DEPENDS eboot.bin 105 | ) 106 | 107 | add_custom_target(copy 108 | COMMAND cp eboot.bin ${PSVITADRIVE}/app/${VITA_TITLEID}/eboot.bin 109 | DEPENDS eboot.bin 110 | ) 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pkgi 2 | 3 | [![Travis CI Build Status][img_travis]][pkgi_travis] [![Downloads][img_downloads]][pkgi_downloads] [![Release][img_latest]][pkgi_latest] [![License][img_license]][pkgi_license] 4 | 5 | pkgi allows to install original pkg files on your Vita. 6 | 7 | This homebrew allows to download & unpack pkg file directly on Vita together with your [NoNpDrm][] fake license. 8 | 9 | # Features 10 | 11 | * **easy** way to see list of available downloads, including searching, filter & sorting. 12 | * **standalone**, no PC required, everything happens directly on Vita. 13 | * **automatic** download and unpack, just choose an item, and it will be installed, including bubble in live area. 14 | * **resumes** interrupted download, you can stop download at any time, switch applications, and come back to download 15 | from place it stopped. 16 | 17 | Current limitations: 18 | * **no support for DLC or PSM**. 19 | * **no queuing** up multiple downloads. 20 | * **no background downloads** - if application is closed or Vita is put in sleep then download will stop. 21 | 22 | # Download 23 | 24 | Get latest version as [vpk file here][pkgi_latest]. 25 | 26 | # Setup instructions 27 | 28 | You need to create `ux0:pkgi/pkgi.txt` file that will contain items available for installation. This file is in very 29 | simple CSV format where each line means one item in the list: 30 | 31 | contentid,flags,name,name2,zrif,url,size,checksum 32 | 33 | where: 34 | 35 | * *contentid* is full content id of item, for example: `UP2120-PCSE00747_00-TOWERFALLVITA000`. 36 | * *flags* is currently unused number, set it to 0. 37 | * *name* is arbitrary UTF-8 string to display for name. 38 | * *name2* is currently unused alternative name, leave it empty. 39 | * *zrif* is NoNpDrm created fake license in zRIF format, it must match contentid. 40 | * *url* is http url where to download PKG, pkg content id must match the specified contentid. 41 | * *size* is size of pkg in bytes, or 0 if not known. 42 | * *checksum* is sha256 digest of pkg as 32 hex bytes to make sure pkg is not tampered with. Leave empty to skip the check. 43 | 44 | Name cannot contain newlines or commas. 45 | 46 | To avoid downloading pkg file over network, you can place it in `ux0:pkgi` folder. Keep the name of file same as in http url, 47 | or rename it with same name as contentid. pkgi will first check if pkg file can be read locally, and only if it is missing 48 | then pkgi will download it from http url. 49 | 50 | # Usage 51 | 52 | Using application is pretty straight forward. Select item you want to install and press X. To sort/filter/search press triangle. 53 | It will open context menu. Press triangle again to confirm choice(s) you make in menu. Or press O to cancel any changes you did. 54 | 55 | Press left or right shoulder button to move page up or down. 56 | 57 | # Q&A 58 | 59 | 1. Where to get zRIF string? 60 | 61 | You must use [NoNpDrm][] plugin to dump existing games you have. Plugin will generate rif file with fake license. 62 | Then you can use either [web page][zrif_online_converter] or [make_key][pkg_dec] to convert rif file to zRIF string. 63 | 64 | 2. Where to get pkg URL? 65 | 66 | You can use [PSDLE][] to find pkg URL for games you own. Then either use original URL, or host the file on your own server. 67 | 68 | 3. Where to remove interrupted/failed downloads to free up the space? 69 | 70 | In `ux0:pkgi` folder - each download will be in separate folder by its title id. Simply delete the folder & resume file. 71 | 72 | 4. Download speed is too slow! 73 | 74 | Typically you should see speeds ~1-2 MB/s. This is normal for Vita hardware. Of course it also depends on WiFi router you 75 | have and WiFi signal strength. But sometimes speed will drop down to only few hundred KB/s. This happens for pkg files that 76 | contains many small files or many folders. Creating a new file or a new folder takes extra time which slows down the download. 77 | 78 | # Building 79 | 80 | You need to have [Vita SDK][vitasdk] with [libvita2d][] installed. 81 | 82 | Run `cmake .` to create debug build, or `cmake -DCMAKE_BUILD_TYPE=Release .` to create optimized release build. 83 | 84 | After than run `make` to create vpk file. You can set environment variable `PSVITAIP` (before running cmake) to IP address of 85 | Vita, that will allow to use `make send` for sending eboot.bin file directly to `ux0:app/PKGI00000` folder. 86 | 87 | To enable debugging logging pass `-DPKGI_ENABLE_LOGGING=ON` argument to cmake. Then application will send debug messages to 88 | UDP multicast address 239.255.0.100:30000. To receive them you can use [socat][] on your PC: 89 | 90 | $ socat udp4-recv:30000,ip-add-membership=239.255.0.100:0.0.0.0 - 91 | 92 | For easer debugging on Windows you can build pkgi in "simulator" mode - use Visual Studio 2017 solution from simulator folder. 93 | 94 | # License 95 | 96 | This is free and unencumbered software released into the public domain. 97 | 98 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a 99 | compiled binary, for any purpose, commercial or non-commercial, and by any means. 100 | 101 | puff.h and puff.c files are under [zlib][] license. 102 | 103 | [NoNpDrm]: https://github.com/TheOfficialFloW/NoNpDrm 104 | [zrif_online_converter]: https://rawgit.com/mmozeiko/pkg2zip/online/zrif.html 105 | [pkg_dec]: https://github.com/weaknespase/PkgDecrypt 106 | [pkg_releases]: https://github.com/mmozeiko/pkgi/releases 107 | [vitasdk]: https://vitasdk.org/ 108 | [libvita2d]: https://github.com/xerpi/libvita2d 109 | [PSDLE]: https://repod.github.io/psdle/ 110 | [socat]: http://www.dest-unreach.org/socat/ 111 | [zlib]: https://www.zlib.net/zlib_license.html 112 | [pkgi_travis]: https://travis-ci.org/mmozeiko/pkgi/ 113 | [pkgi_downloads]: https://github.com/mmozeiko/pkgi/releases 114 | [pkgi_latest]: https://github.com/mmozeiko/pkgi/releases/latest 115 | [pkgi_license]: https://github.com/mmozeiko/pkgi/blob/master/LICENSE 116 | [img_travis]: https://api.travis-ci.org/mmozeiko/pkgi.svg?branch=master 117 | [img_downloads]: https://img.shields.io/github/downloads/mmozeiko/pkgi/total.svg?maxAge=3600 118 | [img_latest]: https://img.shields.io/github/release/mmozeiko/pkgi.svg?maxAge=3600 119 | [img_license]: https://img.shields.io/github/license/mmozeiko/pkgi.svg?maxAge=2592000 120 | -------------------------------------------------------------------------------- /assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmozeiko/pkgi/3c988705e494bc173284873fd21ad234c75d24e3/assets/background.png -------------------------------------------------------------------------------- /pkgi.c: -------------------------------------------------------------------------------- 1 | #include "pkgi.h" 2 | #include "pkgi_db.h" 3 | #include "pkgi_zrif.h" 4 | #include "pkgi_menu.h" 5 | #include "pkgi_config.h" 6 | #include "pkgi_dialog.h" 7 | #include "pkgi_download.h" 8 | #include "pkgi_utils.h" 9 | #include "pkgi_style.h" 10 | 11 | #include 12 | 13 | #define PKGI_UPDATE_URL "https://api.github.com/repos/mmozeiko/pkgi/releases/latest" 14 | 15 | typedef enum { 16 | StateError, 17 | StateRefreshing, 18 | StateUpdateDone, 19 | StateMain, 20 | } State; 21 | 22 | static State state; 23 | 24 | static uint32_t first_item; 25 | static uint32_t selected_item; 26 | 27 | static int search_active; 28 | 29 | static char refresh_url[256]; 30 | 31 | static Config config; 32 | static Config config_temp; 33 | 34 | static int font_height; 35 | static int avail_height; 36 | static int bottom_y; 37 | 38 | static char search_text[256]; 39 | static char error_state[256]; 40 | 41 | static const char* pkgi_get_ok_str(void) 42 | { 43 | return pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_X : PKGI_UTF8_O; 44 | } 45 | 46 | static const char* pkgi_get_cancel_str(void) 47 | { 48 | return pkgi_cancel_button() == PKGI_BUTTON_O ? PKGI_UTF8_O : PKGI_UTF8_X; 49 | } 50 | 51 | static void pkgi_refresh_thread(void) 52 | { 53 | LOG("starting update"); 54 | const char* url = refresh_url; 55 | #ifdef PKGI_REFRESH_URL 56 | if (url[0] == 0) 57 | { 58 | url = PKGI_REFRESH_URL; 59 | } 60 | #endif 61 | if (pkgi_db_update(url, error_state, sizeof(error_state))) 62 | { 63 | first_item = 0; 64 | selected_item = 0; 65 | state = StateUpdateDone; 66 | } 67 | else 68 | { 69 | state = StateError; 70 | } 71 | } 72 | 73 | static int install(const char* content) 74 | { 75 | LOG("installing..."); 76 | pkgi_dialog_start_progress("Installing", "Please wait...", -1); 77 | 78 | char titleid[10]; 79 | pkgi_memcpy(titleid, content + 7, 9); 80 | titleid[9] = 0; 81 | 82 | pkgi_dialog_allow_close(0); 83 | int ok = pkgi_install(titleid); 84 | pkgi_dialog_allow_close(1); 85 | 86 | if (!ok) 87 | { 88 | pkgi_dialog_error("installation failed"); 89 | return 0; 90 | } 91 | 92 | LOG("install succeeded"); 93 | 94 | return 1; 95 | } 96 | 97 | static void pkgi_download_thread(void) 98 | { 99 | DbItem* item = pkgi_db_get(selected_item); 100 | 101 | LOG("decoding zRIF"); 102 | 103 | uint8_t rif[PKGI_RIF_SIZE]; 104 | char message[256]; 105 | if (item->zrif == NULL || pkgi_zrif_decode(item->zrif, rif, message, sizeof(message))) 106 | { 107 | // short delay to allow download dialog to animate smoothly 108 | pkgi_sleep(300); 109 | 110 | pkgi_lock_process(); 111 | if (pkgi_download(item->content, item->url, item->zrif == NULL ? NULL : rif, item->digest) && install(item->content)) 112 | { 113 | pkgi_snprintf(message, sizeof(message), "Successfully installed %s", item->name); 114 | pkgi_dialog_message(message); 115 | LOG("download completed!"); 116 | } 117 | pkgi_unlock_process(); 118 | 119 | if (pkgi_dialog_is_cancelled()) 120 | { 121 | pkgi_dialog_close(); 122 | } 123 | } 124 | else 125 | { 126 | pkgi_dialog_error(message); 127 | } 128 | 129 | item->presence = PresenceUnknown; 130 | state = StateMain; 131 | } 132 | 133 | static uint32_t friendly_size(uint64_t size) 134 | { 135 | if (size > 10ULL * 1000 * 1024 * 1024) 136 | { 137 | return (uint32_t)(size / (1024 * 1024 * 1024)); 138 | } 139 | else if (size > 10 * 1000 * 1024) 140 | { 141 | return (uint32_t)(size / (1024 * 1024)); 142 | } 143 | else if (size > 10 * 1000) 144 | { 145 | return (uint32_t)(size / 1024); 146 | } 147 | else 148 | { 149 | return (uint32_t)size; 150 | } 151 | } 152 | 153 | static const char* friendly_size_str(uint64_t size) 154 | { 155 | if (size > 10ULL * 1000 * 1024 * 1024) 156 | { 157 | return "GB"; 158 | } 159 | else if (size > 10 * 1000 * 1024) 160 | { 161 | return "MB"; 162 | } 163 | else if (size > 10 * 1000) 164 | { 165 | return "KB"; 166 | } 167 | else 168 | { 169 | return "B"; 170 | } 171 | } 172 | 173 | int pkgi_check_free_space(uint64_t size) 174 | { 175 | uint64_t free = pkgi_get_free_space(); 176 | if (size > free + 1024 * 1024) 177 | { 178 | char error[256]; 179 | pkgi_snprintf(error, sizeof(error), "pkg requires %u %s free space, but only %u %s available", 180 | friendly_size(size), friendly_size_str(size), 181 | friendly_size(free), friendly_size_str(free) 182 | ); 183 | 184 | pkgi_dialog_error(error); 185 | return 0; 186 | } 187 | 188 | return 1; 189 | } 190 | 191 | static void pkgi_friendly_size(char* text, uint32_t textlen, int64_t size) 192 | { 193 | if (size <= 0) 194 | { 195 | text[0] = 0; 196 | } 197 | else if (size < 1000LL) 198 | { 199 | pkgi_snprintf(text, textlen, "%u " PKGI_UTF8_B, (uint32_t)size); 200 | } 201 | else if (size < 1000LL * 1000) 202 | { 203 | pkgi_snprintf(text, textlen, "%.2f " PKGI_UTF8_KB, size / 1024.f); 204 | } 205 | else if (size < 1000LL * 1000 * 1000) 206 | { 207 | pkgi_snprintf(text, textlen, "%.2f " PKGI_UTF8_MB, size / 1024.f / 1024.f); 208 | } 209 | else 210 | { 211 | pkgi_snprintf(text, textlen, "%.2f " PKGI_UTF8_GB, size / 1024.f / 1024.f / 1024.f); 212 | } 213 | } 214 | 215 | static void pkgi_do_main(pkgi_input* input) 216 | { 217 | int col_titleid = 0; 218 | int col_region = col_titleid + pkgi_text_width("PCSE00000") + PKGI_MAIN_COLUMN_PADDING; 219 | int col_installed = col_region + pkgi_text_width("USA") + PKGI_MAIN_COLUMN_PADDING; 220 | int col_name = col_installed + pkgi_text_width(PKGI_UTF8_INSTALLED) + PKGI_MAIN_COLUMN_PADDING; 221 | 222 | uint32_t db_count = pkgi_db_count(); 223 | 224 | if (input) 225 | { 226 | if (input->active & PKGI_BUTTON_UP) 227 | { 228 | if (selected_item == first_item && first_item > 0) 229 | { 230 | first_item--; 231 | selected_item = first_item; 232 | } 233 | else if (selected_item > 0) 234 | { 235 | selected_item--; 236 | } 237 | else if (selected_item == 0) 238 | { 239 | selected_item = db_count - 1; 240 | uint32_t max_items = avail_height / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 241 | first_item = db_count > max_items ? db_count - max_items - 1 : 0; 242 | } 243 | } 244 | 245 | if (input->active & PKGI_BUTTON_DOWN) 246 | { 247 | uint32_t max_items = avail_height / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 248 | if (selected_item == db_count - 1) 249 | { 250 | selected_item = first_item = 0; 251 | } 252 | else if (selected_item == first_item + max_items) 253 | { 254 | first_item++; 255 | selected_item++; 256 | } 257 | else 258 | { 259 | selected_item++; 260 | } 261 | } 262 | 263 | if (input && (input->active & PKGI_BUTTON_LT)) 264 | { 265 | uint32_t max_items = avail_height / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 266 | if (first_item < max_items) 267 | { 268 | first_item = 0; 269 | } 270 | else 271 | { 272 | first_item -= max_items; 273 | } 274 | if (selected_item < max_items) 275 | { 276 | selected_item = 0; 277 | } 278 | else 279 | { 280 | selected_item -= max_items; 281 | } 282 | } 283 | 284 | if (input && (input->active & PKGI_BUTTON_RT)) 285 | { 286 | uint32_t max_items = avail_height / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 287 | if (first_item + max_items < db_count - 1) 288 | { 289 | first_item += max_items; 290 | selected_item += max_items; 291 | if (selected_item >= db_count) 292 | { 293 | selected_item = db_count - 1; 294 | } 295 | } 296 | } 297 | } 298 | 299 | int y = font_height + PKGI_MAIN_HLINE_EXTRA; 300 | int line_height = font_height + PKGI_MAIN_ROW_PADDING; 301 | for (uint32_t i = first_item; i < db_count; i++) 302 | { 303 | DbItem* item = pkgi_db_get(i); 304 | 305 | if (i == selected_item) 306 | { 307 | pkgi_draw_rect(0, y, VITA_WIDTH, font_height + PKGI_MAIN_ROW_PADDING - 1, PKGI_COLOR_SELECTED_BACKGROUND); 308 | } 309 | uint32_t color = PKGI_COLOR_TEXT; 310 | 311 | char titleid[10]; 312 | pkgi_memcpy(titleid, item->content + 7, 9); 313 | titleid[9] = 0; 314 | 315 | if (item->presence == PresenceUnknown) 316 | { 317 | item->presence = pkgi_is_incomplete(titleid) ? PresenceIncomplete : pkgi_is_installed(titleid) ? PresenceInstalled : PresenceMissing; 318 | } 319 | 320 | char size_str[64]; 321 | pkgi_friendly_size(size_str, sizeof(size_str), item->size); 322 | int sizew = pkgi_text_width(size_str); 323 | 324 | pkgi_clip_set(0, y, VITA_WIDTH, line_height); 325 | pkgi_draw_text(col_titleid, y, color, titleid); 326 | const char* region; 327 | switch (pkgi_get_region(item->content)) 328 | { 329 | case RegionASA: region = "ASA"; break; 330 | case RegionEUR: region = "EUR"; break; 331 | case RegionJPN: region = "JPN"; break; 332 | case RegionUSA: region = "USA"; break; 333 | default: region = "???"; break; 334 | } 335 | pkgi_draw_text(col_region, y, color, region); 336 | if (item->presence == PresenceIncomplete) 337 | { 338 | pkgi_draw_text(col_installed, y, color, PKGI_UTF8_PARTIAL); 339 | } 340 | else if (item->presence == PresenceInstalled) 341 | { 342 | pkgi_draw_text(col_installed, y, color, PKGI_UTF8_INSTALLED); 343 | } 344 | pkgi_draw_text(VITA_WIDTH - PKGI_MAIN_SCROLL_WIDTH - PKGI_MAIN_SCROLL_PADDING - sizew, y, color, size_str); 345 | pkgi_clip_remove(); 346 | 347 | pkgi_clip_set(col_name, y, VITA_WIDTH - PKGI_MAIN_SCROLL_WIDTH - PKGI_MAIN_SCROLL_PADDING - PKGI_MAIN_COLUMN_PADDING - sizew - col_name, line_height); 348 | pkgi_draw_text(col_name, y, color, item->name); 349 | pkgi_clip_remove(); 350 | 351 | y += font_height + PKGI_MAIN_ROW_PADDING; 352 | if (y > VITA_HEIGHT - (font_height + PKGI_MAIN_HLINE_EXTRA)) 353 | { 354 | break; 355 | } 356 | else if (y + font_height > VITA_HEIGHT - (font_height + PKGI_MAIN_HLINE_EXTRA)) 357 | { 358 | line_height = (VITA_HEIGHT - (font_height + PKGI_MAIN_HLINE_EXTRA)) - (y + 1); 359 | if (line_height < PKGI_MAIN_ROW_PADDING) 360 | { 361 | break; 362 | } 363 | } 364 | } 365 | 366 | if (db_count == 0) 367 | { 368 | const char* text = "No items!"; 369 | 370 | int w = pkgi_text_width(text); 371 | pkgi_draw_text((VITA_WIDTH - w) / 2, VITA_HEIGHT / 2, PKGI_COLOR_TEXT, text); 372 | } 373 | 374 | // scroll-bar 375 | if (db_count != 0) 376 | { 377 | uint32_t max_items = (avail_height + font_height + PKGI_MAIN_ROW_PADDING - 1) / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 378 | if (max_items < db_count) 379 | { 380 | uint32_t min_height = PKGI_MAIN_SCROLL_MIN_HEIGHT; 381 | uint32_t height = max_items * avail_height / db_count; 382 | uint32_t start = first_item * (avail_height - (height < min_height ? min_height : 0)) / db_count; 383 | height = max32(height, min_height); 384 | pkgi_draw_rect(VITA_WIDTH - PKGI_MAIN_SCROLL_WIDTH - 1, font_height + PKGI_MAIN_HLINE_EXTRA + start, PKGI_MAIN_SCROLL_WIDTH, height, PKGI_COLOR_SCROLL_BAR); 385 | } 386 | } 387 | 388 | if (input && (input->pressed & pkgi_ok_button())) 389 | { 390 | input->pressed &= ~pkgi_ok_button(); 391 | 392 | DbItem* item = pkgi_db_get(selected_item); 393 | 394 | if (item->presence == PresenceInstalled) 395 | { 396 | LOG("[%.9s] %s - alreay installed", item->content + 7, item->name); 397 | pkgi_dialog_error("Already installed"); 398 | } 399 | else if (item->presence == PresenceIncomplete || (item->presence == PresenceMissing && pkgi_check_free_space(item->size))) 400 | { 401 | LOG("[%.9s] %s - starting to install", item->content + 7, item->name); 402 | pkgi_dialog_start_progress("Downloading", "Preparing...", 0); 403 | pkgi_start_thread("download_thread", &pkgi_download_thread); 404 | } 405 | } 406 | else if (input && (input->pressed & PKGI_BUTTON_T)) 407 | { 408 | input->pressed &= ~PKGI_BUTTON_T; 409 | 410 | config_temp = config; 411 | int allow_refresh = refresh_url[0] != 0; 412 | #ifdef PKGI_REFRESH_URL 413 | allow_refresh = 1; 414 | #endif 415 | pkgi_menu_start(search_active, &config, allow_refresh); 416 | } 417 | } 418 | 419 | static void pkgi_do_refresh(void) 420 | { 421 | char text[256]; 422 | 423 | uint32_t updated; 424 | uint32_t total; 425 | pkgi_db_get_update_status(&updated, &total); 426 | 427 | if (total == 0) 428 | { 429 | pkgi_snprintf(text, sizeof(text), "Refreshing... %.2f KB", (uint32_t)updated / 1024.f); 430 | } 431 | else 432 | { 433 | pkgi_snprintf(text, sizeof(text), "Refreshing... %u%%", updated * 100U / total); 434 | } 435 | 436 | int w = pkgi_text_width(text); 437 | pkgi_draw_text((VITA_WIDTH - w) / 2, VITA_HEIGHT / 2, PKGI_COLOR_TEXT, text); 438 | } 439 | 440 | static void pkgi_do_head(void) 441 | { 442 | const char* version = PKGI_VERSION; 443 | 444 | char title[256]; 445 | pkgi_snprintf(title, sizeof(title), "PKGi v%s", version + 1); 446 | pkgi_draw_text(0, 0, PKGI_COLOR_TEXT_HEAD, title); 447 | 448 | pkgi_draw_rect(0, font_height, VITA_WIDTH, PKGI_MAIN_HLINE_HEIGHT, PKGI_COLOR_HLINE); 449 | 450 | int rightw; 451 | if (pkgi_battery_present()) 452 | { 453 | char battery[256]; 454 | pkgi_snprintf(battery, sizeof(battery), "Battery: %u%%", pkgi_bettery_get_level()); 455 | 456 | uint32_t color; 457 | if (pkgi_battery_is_low()) 458 | { 459 | color = PKGI_COLOR_BATTERY_LOW; 460 | } 461 | else if (pkgi_battery_is_charging()) 462 | { 463 | color = PKGI_COLOR_BATTERY_CHARGING; 464 | } 465 | else 466 | { 467 | color = PKGI_COLOR_TEXT_HEAD; 468 | } 469 | 470 | rightw = pkgi_text_width(battery); 471 | pkgi_draw_text(VITA_WIDTH - PKGI_MAIN_HLINE_EXTRA - rightw, 0, color, battery); 472 | } 473 | else 474 | { 475 | rightw = 0; 476 | } 477 | 478 | if (search_active) 479 | { 480 | char text[256]; 481 | int left = pkgi_text_width(search_text) + PKGI_MAIN_TEXT_PADDING; 482 | int right = rightw + PKGI_MAIN_TEXT_PADDING; 483 | 484 | pkgi_snprintf(text, sizeof(text), ">> %s <<", search_text); 485 | 486 | pkgi_clip_set(left, 0, VITA_WIDTH - right - left, font_height + PKGI_MAIN_HLINE_EXTRA); 487 | pkgi_draw_text((VITA_WIDTH - pkgi_text_width(text)) / 2, 0, PKGI_COLOR_TEXT_TAIL, text); 488 | pkgi_clip_remove(); 489 | } 490 | } 491 | 492 | static void pkgi_do_tail(void) 493 | { 494 | pkgi_draw_rect(0, bottom_y, VITA_WIDTH, PKGI_MAIN_HLINE_HEIGHT, PKGI_COLOR_HLINE); 495 | 496 | uint32_t count = pkgi_db_count(); 497 | uint32_t total = pkgi_db_total(); 498 | 499 | char text[256]; 500 | if (count == total) 501 | { 502 | pkgi_snprintf(text, sizeof(text), "Count: %u", count); 503 | } 504 | else 505 | { 506 | pkgi_snprintf(text, sizeof(text), "Count: %u (%u)", count, total); 507 | } 508 | pkgi_draw_text(0, bottom_y, PKGI_COLOR_TEXT_TAIL, text); 509 | 510 | char size[64]; 511 | pkgi_friendly_size(size, sizeof(size), pkgi_get_free_space()); 512 | 513 | char free[64]; 514 | pkgi_snprintf(free, sizeof(free), "Free: %s", size); 515 | 516 | int rightw = pkgi_text_width(free); 517 | pkgi_draw_text(VITA_WIDTH - PKGI_MAIN_HLINE_EXTRA - rightw, bottom_y, PKGI_COLOR_TEXT_TAIL, free); 518 | 519 | int left = pkgi_text_width(text) + PKGI_MAIN_TEXT_PADDING; 520 | int right = rightw + PKGI_MAIN_TEXT_PADDING; 521 | 522 | if (pkgi_menu_is_open()) 523 | { 524 | pkgi_snprintf(text, sizeof(text), "%s select " PKGI_UTF8_T " close %s cancel", pkgi_get_ok_str(), pkgi_get_cancel_str()); 525 | } 526 | else 527 | { 528 | pkgi_snprintf(text, sizeof(text), "%s install " PKGI_UTF8_T " menu", pkgi_get_ok_str()); 529 | } 530 | 531 | pkgi_clip_set(left, bottom_y, VITA_WIDTH - right - left, VITA_HEIGHT - bottom_y); 532 | pkgi_draw_text((VITA_WIDTH - pkgi_text_width(text)) / 2, bottom_y, PKGI_COLOR_TEXT_TAIL, text); 533 | pkgi_clip_remove(); 534 | } 535 | 536 | static void pkgi_do_error(void) 537 | { 538 | pkgi_draw_text((VITA_WIDTH - pkgi_text_width(error_state)) / 2, VITA_HEIGHT / 2, PKGI_COLOR_TEXT_ERROR, error_state); 539 | } 540 | 541 | static void reposition(void) 542 | { 543 | uint32_t count = pkgi_db_count(); 544 | if (first_item + selected_item < count) 545 | { 546 | return; 547 | } 548 | 549 | uint32_t max_items = (avail_height + font_height + PKGI_MAIN_ROW_PADDING - 1) / (font_height + PKGI_MAIN_ROW_PADDING) - 1; 550 | if (count > max_items) 551 | { 552 | uint32_t delta = selected_item - first_item; 553 | first_item = count - max_items; 554 | selected_item = first_item + delta; 555 | } 556 | else 557 | { 558 | first_item = 0; 559 | selected_item = 0; 560 | } 561 | } 562 | 563 | static void pkgi_check_for_update(void) 564 | { 565 | LOG("checking latest pkgi version at %s", PKGI_UPDATE_URL); 566 | 567 | pkgi_http* http = pkgi_http_get(PKGI_UPDATE_URL, NULL, 0); 568 | if (http) 569 | { 570 | char buffer[8 << 10]; 571 | uint32_t size = 0; 572 | 573 | while (size < sizeof(buffer) - 1) 574 | { 575 | int read = pkgi_http_read(http, buffer + size, sizeof(buffer) - 1 - size); 576 | if (read < 0) 577 | { 578 | size = 0; 579 | break; 580 | } 581 | else if (read == 0) 582 | { 583 | break; 584 | } 585 | size += read; 586 | } 587 | 588 | if (size != 0) 589 | { 590 | LOG("received %u bytes", size); 591 | } 592 | buffer[size] = 0; 593 | 594 | static const char find[] = "\"name\":\"pkgi v"; 595 | const char* start = pkgi_strstr(buffer, find); 596 | if (start != NULL) 597 | { 598 | LOG("found name"); 599 | start += sizeof(find) - 1; 600 | 601 | char* end = pkgi_strstr(start, "\""); 602 | if (end != NULL) 603 | { 604 | *end = 0; 605 | LOG("latest version is %s", start); 606 | 607 | const char* current = PKGI_VERSION; 608 | if (current[0] == '0') 609 | { 610 | current++; 611 | } 612 | 613 | if (pkgi_stricmp(current, start) != 0) 614 | { 615 | LOG("new version available"); 616 | 617 | char text[256]; 618 | pkgi_snprintf(text, sizeof(text), "New pkgi version v%s available!", start); 619 | pkgi_dialog_message(text); 620 | } 621 | } 622 | else 623 | { 624 | LOG("no end of name found"); 625 | } 626 | } 627 | else 628 | { 629 | LOG("no name found"); 630 | } 631 | 632 | pkgi_http_close(http); 633 | } 634 | else 635 | { 636 | LOG("http request to %s failed", PKGI_UPDATE_URL); 637 | } 638 | } 639 | 640 | int main() 641 | { 642 | pkgi_start(); 643 | LOG("started"); 644 | 645 | pkgi_load_config(&config, refresh_url, sizeof(refresh_url)); 646 | pkgi_dialog_init(); 647 | 648 | font_height = pkgi_text_height("M"); 649 | avail_height = VITA_HEIGHT - 2 * (font_height + PKGI_MAIN_HLINE_EXTRA); 650 | bottom_y = VITA_HEIGHT - font_height - PKGI_MAIN_ROW_PADDING; 651 | 652 | if (pkgi_is_unsafe_mode()) 653 | { 654 | state = StateRefreshing; 655 | pkgi_start_thread("refresh_thread", &pkgi_refresh_thread); 656 | } 657 | else 658 | { 659 | state = StateError; 660 | pkgi_snprintf(error_state, sizeof(error_state), "pkgi requires unsafe enabled in Henkaku settings!"); 661 | } 662 | 663 | pkgi_texture background = pkgi_load_png(background); 664 | 665 | pkgi_input input; 666 | while (pkgi_update(&input)) 667 | { 668 | pkgi_draw_texture(background, 0, 0); 669 | 670 | if (state == StateUpdateDone) 671 | { 672 | if (!config.no_version_check) 673 | { 674 | pkgi_start_thread("update_thread", &pkgi_check_for_update); 675 | } 676 | 677 | pkgi_db_configure(NULL, &config); 678 | state = StateMain; 679 | } 680 | 681 | pkgi_do_head(); 682 | switch (state) 683 | { 684 | case StateError: 685 | pkgi_do_error(); 686 | break; 687 | 688 | case StateRefreshing: 689 | pkgi_do_refresh(); 690 | break; 691 | 692 | case StateMain: 693 | pkgi_do_main(pkgi_dialog_is_open() || pkgi_menu_is_open() ? NULL : &input); 694 | break; 695 | 696 | case StateUpdateDone: 697 | // never happens, just to shut up the compiler 698 | break; 699 | } 700 | 701 | pkgi_do_tail(); 702 | 703 | if (pkgi_dialog_is_open()) 704 | { 705 | pkgi_do_dialog(&input); 706 | } 707 | 708 | if (pkgi_dialog_input_update()) 709 | { 710 | search_active = 1; 711 | pkgi_dialog_input_get_text(search_text, sizeof(search_text)); 712 | pkgi_db_configure(search_text, &config); 713 | reposition(); 714 | } 715 | 716 | if (pkgi_menu_is_open()) 717 | { 718 | if (pkgi_do_menu(&input)) 719 | { 720 | Config new_config; 721 | pkgi_menu_get(&new_config); 722 | if (config_temp.sort != new_config.sort || 723 | config_temp.order != new_config.order || 724 | config_temp.filter != new_config.filter) 725 | { 726 | config_temp = new_config; 727 | pkgi_db_configure(search_active ? search_text : NULL, &config_temp); 728 | reposition(); 729 | } 730 | } 731 | else 732 | { 733 | MenuResult mres = pkgi_menu_result(); 734 | if (mres == MenuResultSearch) 735 | { 736 | pkgi_dialog_input_text("Search", search_text); 737 | } 738 | else if (mres == MenuResultSearchClear) 739 | { 740 | search_active = 0; 741 | search_text[0] = 0; 742 | pkgi_db_configure(NULL, &config); 743 | } 744 | else if (mres == MenuResultCancel) 745 | { 746 | if (config_temp.sort != config.sort || config_temp.order != config.order || config_temp.filter != config.filter) 747 | { 748 | pkgi_db_configure(search_active ? search_text : NULL, &config); 749 | reposition(); 750 | } 751 | } 752 | else if (mres == MenuResultAccept) 753 | { 754 | pkgi_menu_get(&config); 755 | pkgi_save_config(&config, refresh_url); 756 | } 757 | else if (mres == MenuResultRefresh) 758 | { 759 | state = StateRefreshing; 760 | pkgi_start_thread("refresh_thread", &pkgi_refresh_thread); 761 | } 762 | } 763 | } 764 | 765 | pkgi_swap(); 766 | } 767 | 768 | LOG("finished"); 769 | pkgi_end(); 770 | } 771 | -------------------------------------------------------------------------------- /pkgi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // values compatible with psp2/ctrl.h header 7 | #define PKGI_BUTTON_SELECT 0x00000001 8 | #define PKGI_BUTTON_START 0x00000008 9 | 10 | #define PKGI_BUTTON_UP 0x00000010 11 | #define PKGI_BUTTON_RIGHT 0x00000020 12 | #define PKGI_BUTTON_DOWN 0x00000040 13 | #define PKGI_BUTTON_LEFT 0x00000080 14 | 15 | #define PKGI_BUTTON_LT 0x00000100 16 | #define PKGI_BUTTON_RT 0x00000200 17 | 18 | #define PKGI_BUTTON_X 0x00004000 // cross 19 | #define PKGI_BUTTON_O 0x00002000 // circle 20 | #define PKGI_BUTTON_T 0x00001000 // triangle 21 | #define PKGI_BUTTON_S 0x00008000 // square 22 | 23 | #define PKGI_UNUSED(x) (void)(x) 24 | 25 | typedef struct pkgi_input { 26 | uint64_t delta; // microseconds from previous frame 27 | 28 | uint32_t pressed; // button pressed in last frame 29 | uint32_t down; // button is currently down 30 | uint32_t active; // button is pressed in last frame, or held down for a long time (10 frames) 31 | } pkgi_input; 32 | 33 | #define PKGI_COUNTOF(arr) (sizeof(arr)/sizeof(0[arr])) 34 | 35 | #ifdef PKGI_ENABLE_LOGGING 36 | #define LOG(msg, ...) pkgi_log(msg, ## __VA_ARGS__) 37 | #else 38 | #define LOG(...) 39 | #endif 40 | 41 | void pkgi_log(const char* msg, ...); 42 | 43 | int pkgi_snprintf(char* buffer, uint32_t size, const char* msg, ...); 44 | void pkgi_vsnprintf(char* buffer, uint32_t size, const char* msg, va_list args); 45 | char* pkgi_strstr(const char* str, const char* sub); 46 | int pkgi_stricontains(const char* str, const char* sub); 47 | int pkgi_stricmp(const char* a, const char* b); 48 | void pkgi_strncpy(char* dst, uint32_t size, const char* src); 49 | char* pkgi_strrchr(const char* str, char ch); 50 | void pkgi_memcpy(void* dst, const void* src, uint32_t size); 51 | void pkgi_memmove(void* dst, const void* src, uint32_t size); 52 | int pkgi_memequ(const void* a, const void* b, uint32_t size); 53 | 54 | int pkgi_is_unsafe_mode(void); 55 | 56 | int pkgi_ok_button(void); 57 | int pkgi_cancel_button(void); 58 | 59 | void pkgi_start(void); 60 | int pkgi_update(pkgi_input* input); 61 | void pkgi_swap(void); 62 | void pkgi_end(void); 63 | 64 | int pkgi_battery_present(); 65 | int pkgi_bettery_get_level(); 66 | int pkgi_battery_is_low(); 67 | int pkgi_battery_is_charging(); 68 | 69 | uint64_t pkgi_get_free_space(void); 70 | const char* pkgi_get_config_folder(void); 71 | const char* pkgi_get_temp_folder(void); 72 | const char* pkgi_get_app_folder(void); 73 | int pkgi_is_incomplete(const char* titleid); 74 | int pkgi_is_installed(const char* titleid); 75 | int pkgi_install(const char* titleid); 76 | 77 | uint32_t pkgi_time_msec(); 78 | 79 | typedef void pkgi_thread_entry(void); 80 | void pkgi_start_thread(const char* name, pkgi_thread_entry* start); 81 | void pkgi_sleep(uint32_t msec); 82 | 83 | int pkgi_load(const char* name, void* data, uint32_t max); 84 | int pkgi_save(const char* name, const void* data, uint32_t size); 85 | 86 | void pkgi_lock_process(void); 87 | void pkgi_unlock_process(void); 88 | 89 | void pkgi_dialog_lock(void); 90 | void pkgi_dialog_unlock(void); 91 | 92 | void pkgi_dialog_input_text(const char* title, const char* text); 93 | int pkgi_dialog_input_update(void); 94 | void pkgi_dialog_input_get_text(char* text, uint32_t size); 95 | 96 | int pkgi_check_free_space(uint64_t http_length); 97 | 98 | typedef struct pkgi_http pkgi_http; 99 | 100 | pkgi_http* pkgi_http_get(const char* url, const char* content, uint64_t offset); 101 | int pkgi_http_response_length(pkgi_http* http, int64_t* length); 102 | int pkgi_http_read(pkgi_http* http, void* buffer, uint32_t size); 103 | void pkgi_http_close(pkgi_http* http); 104 | 105 | int pkgi_mkdirs(char* path); 106 | void pkgi_rm(const char* file); 107 | int64_t pkgi_get_size(const char* path); 108 | 109 | // creates file (if it exists, truncates size to 0) 110 | void* pkgi_create(const char* path); 111 | // open existing file in read/write, fails if file does not exist 112 | void* pkgi_openrw(const char* path); 113 | // open file for writing, next write will append data to end of it 114 | void* pkgi_append(const char* path); 115 | 116 | void pkgi_close(void* f); 117 | 118 | int pkgi_read(void* f, void* buffer, uint32_t size); 119 | int pkgi_write(void* f, const void* buffer, uint32_t size); 120 | 121 | // UI stuff 122 | typedef void* pkgi_texture; 123 | #ifdef _MSC_VER 124 | #define pkgi_load_png(name) \ 125 | pkgi_load_png_raw(# name ## ".png", 0) 126 | #else 127 | #define pkgi_load_png(name) \ 128 | ({ extern uint8_t _binary_assets_##name##_png_start; \ 129 | extern uint8_t _binary_assets_##name##_png_end; \ 130 | pkgi_load_png_raw((void*)&_binary_assets_##name##_png_start, \ 131 | (uint32_t)(&_binary_assets_##name##_png_end - &_binary_assets_##name##_png_start)); \ 132 | }) 133 | #endif 134 | 135 | pkgi_texture pkgi_load_png_raw(const void* data, uint32_t size); 136 | void pkgi_draw_texture(pkgi_texture texture, int x, int y); 137 | 138 | void pkgi_clip_set(int x, int y, int w, int h); 139 | void pkgi_clip_remove(void); 140 | void pkgi_draw_rect(int x, int y, int w, int h, uint32_t color); 141 | void pkgi_draw_text(int x, int y, uint32_t color, const char* text); 142 | int pkgi_text_width(const char* text); 143 | int pkgi_text_height(const char* text); 144 | -------------------------------------------------------------------------------- /pkgi_aes128.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_utils.h" 4 | 5 | typedef struct { 6 | uint32_t key[4*11] GCC_ALIGN(16); 7 | #if __ARM_NEON__ 8 | uint8_t bskey[16*8*9] GCC_ALIGN(16); 9 | #endif 10 | } aes128_ctx; 11 | 12 | #define AES_BLOCK_SIZE 16 13 | 14 | void aes128_init(aes128_ctx* ctx, const uint8_t* key); 15 | void aes128_encrypt(const aes128_ctx* ctx, const uint8_t* input, uint8_t* output); 16 | 17 | void aes128_ctr_init(aes128_ctx* ctx, const uint8_t* key); 18 | void aes128_ctr(const aes128_ctx* ctx, const uint8_t* iv, uint64_t offset, uint8_t* buffer, uint32_t size); 19 | -------------------------------------------------------------------------------- /pkgi_config.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_config.h" 2 | #include "pkgi.h" 3 | 4 | static char* skipnonws(char* text, char* end) 5 | { 6 | while (text < end && *text != ' ' && *text != '\n' && *text != '\r') 7 | { 8 | text++; 9 | } 10 | return text; 11 | } 12 | 13 | static char* skipws(char* text, char* end) 14 | { 15 | while (text < end && (*text == ' ' || *text == '\n' || *text == '\r')) 16 | { 17 | text++; 18 | } 19 | return text; 20 | } 21 | 22 | static DbSort parse_sort(const char* value, DbSort sort) 23 | { 24 | if (pkgi_stricmp(value, "title") == 0) 25 | { 26 | return SortByTitle; 27 | } 28 | else if (pkgi_stricmp(value, "region") == 0) 29 | { 30 | return SortByRegion; 31 | } 32 | else if (pkgi_stricmp(value, "name") == 0) 33 | { 34 | return SortByName; 35 | } 36 | else if (pkgi_stricmp(value, "size") == 0) 37 | { 38 | return SortBySize; 39 | } 40 | else 41 | { 42 | return sort; 43 | } 44 | } 45 | 46 | static DbSortOrder parse_order(const char* value, DbSortOrder order) 47 | { 48 | if (pkgi_stricmp(value, "asc") == 0) 49 | { 50 | return SortAscending; 51 | } 52 | else if (pkgi_stricmp(value, "desc") == 0) 53 | { 54 | return SortDescending; 55 | } 56 | else 57 | { 58 | return order; 59 | } 60 | } 61 | 62 | static DbSortOrder parse_filter(char* value, uint32_t filter) 63 | { 64 | uint32_t result = 0; 65 | 66 | char* start = value; 67 | for (;;) 68 | { 69 | char ch = *value; 70 | if (ch == 0 || ch == ',') 71 | { 72 | *value = 0; 73 | if (pkgi_stricmp(start, "ASA") == 0) 74 | { 75 | result |= DbFilterRegionASA; 76 | } 77 | else if (pkgi_stricmp(start, "EUR") == 0) 78 | { 79 | result |= DbFilterRegionEUR; 80 | } 81 | else if (pkgi_stricmp(start, "JPN") == 0) 82 | { 83 | result |= DbFilterRegionJPN; 84 | } 85 | else if (pkgi_stricmp(start, "USA") == 0) 86 | { 87 | result |= DbFilterRegionUSA; 88 | } 89 | else 90 | { 91 | return filter; 92 | } 93 | if (ch == 0) 94 | { 95 | break; 96 | } 97 | value++; 98 | start = value; 99 | } 100 | else 101 | { 102 | value++; 103 | } 104 | } 105 | 106 | return result; 107 | } 108 | 109 | void pkgi_load_config(Config* config, char* refresh_url, uint32_t refresh_len) 110 | { 111 | refresh_url[0] = 0; 112 | config->sort = SortByName; 113 | config->order = SortAscending; 114 | config->filter = DbFilterAll; 115 | config->no_version_check = 0; 116 | 117 | char data[4096]; 118 | char path[256]; 119 | pkgi_snprintf(path, sizeof(path), "%s/config.txt", pkgi_get_config_folder()); 120 | LOG("config location: %s", path); 121 | 122 | int loaded = pkgi_load(path, data, sizeof(data) - 1); 123 | if (loaded > 0) 124 | { 125 | data[loaded] = '\n'; 126 | 127 | LOG("config.txt loaded, parsing"); 128 | char* text = data; 129 | char* end = data + loaded + 1; 130 | 131 | if (loaded > 3 && (uint8_t)text[0] == 0xef && (uint8_t)text[1] == 0xbb && (uint8_t)text[2] == 0xbf) 132 | { 133 | text += 3; 134 | } 135 | 136 | while (text < end) 137 | { 138 | char* key = text; 139 | 140 | text = skipnonws(text, end); 141 | if (text == end) break; 142 | 143 | *text++ = 0; 144 | 145 | text = skipws(text, end); 146 | if (text == end) break; 147 | 148 | char* value = text; 149 | 150 | text = skipnonws(text, end); 151 | if (text == end) break; 152 | 153 | *text++ = 0; 154 | 155 | text = skipws(text, end); 156 | 157 | if (pkgi_stricmp(key, "url") == 0) 158 | { 159 | pkgi_strncpy(refresh_url, refresh_len, value); 160 | } 161 | else if (pkgi_stricmp(key, "sort") == 0) 162 | { 163 | config->sort = parse_sort(value, SortByName); 164 | } 165 | else if (pkgi_stricmp(key, "order") == 0) 166 | { 167 | config->order = parse_order(value, SortAscending); 168 | } 169 | else if (pkgi_stricmp(key, "filter") == 0) 170 | { 171 | config->filter = parse_filter(value, DbFilterAll); 172 | } 173 | else if (pkgi_stricmp(key, "no_version_check") == 0) 174 | { 175 | config->no_version_check = 1; 176 | } 177 | } 178 | } 179 | else 180 | { 181 | LOG("config.txt cannot be loaded, using default values"); 182 | } 183 | } 184 | 185 | static const char* sort_str(DbSort sort) 186 | { 187 | switch (sort) 188 | { 189 | case SortByTitle: return "title"; 190 | case SortByRegion: return "region"; 191 | case SortByName: return "name"; 192 | case SortBySize: return "size"; 193 | default: return ""; 194 | } 195 | } 196 | 197 | static const char* order_str(DbSortOrder order) 198 | { 199 | switch (order) 200 | { 201 | case SortAscending: return "asc"; 202 | case SortDescending: return "desc"; 203 | default: return ""; 204 | } 205 | } 206 | 207 | void pkgi_save_config(const Config* config, const char* update_url) 208 | { 209 | char data[4096]; 210 | int len = 0; 211 | if (update_url && update_url[0] != 0) 212 | { 213 | len += pkgi_snprintf(data + len, sizeof(data) - len, "url %s\n", update_url); 214 | } 215 | len += pkgi_snprintf(data + len, sizeof(data) - len, "sort %s\n", sort_str(config->sort)); 216 | len += pkgi_snprintf(data + len, sizeof(data) - len, "order %s\n", order_str(config->order)); 217 | len += pkgi_snprintf(data + len, sizeof(data) - len, "filter "); 218 | const char* sep = ""; 219 | if (config->filter & DbFilterRegionASA) 220 | { 221 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sASA", sep); 222 | sep = ","; 223 | } 224 | if (config->filter & DbFilterRegionEUR) 225 | { 226 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sEUR", sep); 227 | sep = ","; 228 | } 229 | if (config->filter & DbFilterRegionJPN) 230 | { 231 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sJPN", sep); 232 | sep = ","; 233 | } 234 | if (config->filter & DbFilterRegionUSA) 235 | { 236 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sUSA", sep); 237 | sep = ","; 238 | } 239 | len += pkgi_snprintf(data + len, sizeof(data) - len, "\n"); 240 | 241 | if (config->no_version_check) 242 | { 243 | len += pkgi_snprintf(data + len, sizeof(data) - len, "no_version_check 1\n"); 244 | } 245 | 246 | char path[256]; 247 | pkgi_snprintf(path, sizeof(path), "%s/config.txt", pkgi_get_config_folder()); 248 | 249 | if (pkgi_save(path, data, len)) 250 | { 251 | LOG("saved config.txt"); 252 | } 253 | else 254 | { 255 | LOG("cannot save config.txt"); 256 | } 257 | } -------------------------------------------------------------------------------- /pkgi_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_db.h" 4 | 5 | typedef struct Config { 6 | DbSort sort; 7 | DbSortOrder order; 8 | uint32_t filter; 9 | int no_version_check; 10 | } Config; 11 | 12 | void pkgi_load_config(Config* config, char* update_url, uint32_t update_len); 13 | void pkgi_save_config(const Config* config, const char* update_url); 14 | -------------------------------------------------------------------------------- /pkgi_db.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_db.h" 2 | #include "pkgi_config.h" 3 | #include "pkgi_utils.h" 4 | #include "pkgi_sha256.h" 5 | #include "pkgi.h" 6 | 7 | #include 8 | 9 | #define MAX_DB_SIZE (4*1024*1024) 10 | #define MAX_DB_ITEMS 8192 11 | 12 | static char db_data[MAX_DB_SIZE]; 13 | static uint32_t db_total; 14 | static uint32_t db_size; 15 | 16 | static DbItem db[MAX_DB_ITEMS]; 17 | static uint32_t db_count; 18 | 19 | static DbItem* db_item[MAX_DB_SIZE]; 20 | static uint32_t db_item_count; 21 | 22 | static int64_t pkgi_strtoll(const char* str) 23 | { 24 | int64_t res = 0; 25 | const char* s = str; 26 | if (*s && *s == '-') 27 | { 28 | s++; 29 | } 30 | while (*s) 31 | { 32 | res = res * 10 + (*s - '0'); 33 | s++; 34 | } 35 | 36 | return str[0] == '-' ? -res : res; 37 | } 38 | 39 | static uint8_t hexvalue(char ch) 40 | { 41 | if (ch >= '0' && ch <= '9') 42 | { 43 | return ch - '0'; 44 | } 45 | else if (ch >= 'a' && ch <= 'f') 46 | { 47 | return ch - 'a' + 10; 48 | } 49 | else if (ch >= 'A' && ch <= 'F') 50 | { 51 | return ch - 'A' + 10; 52 | } 53 | return 0; 54 | } 55 | 56 | static uint8_t* pkgi_hexbytes(const char* digest, uint32_t length) 57 | { 58 | uint8_t* result = (uint8_t*)digest; 59 | 60 | for (uint32_t i = 0; i < length; i++) 61 | { 62 | char ch1 = digest[2 * i]; 63 | char ch2 = digest[2 * i + 1]; 64 | if (ch1 == 0 || ch2 == 0) 65 | { 66 | return NULL; 67 | } 68 | 69 | result[i] = hexvalue(ch1) * 16 + hexvalue(ch2); 70 | } 71 | 72 | return result; 73 | } 74 | 75 | int pkgi_db_update(const char* update_url, char* error, uint32_t error_size) 76 | { 77 | db_total = 0; 78 | db_size = 0; 79 | db_count = 0; 80 | db_item_count = 0; 81 | 82 | char path[256]; 83 | pkgi_snprintf(path, sizeof(path), "%s/pkgi.txt", pkgi_get_config_folder()); 84 | 85 | LOG("loading update from %s", path); 86 | int loaded = pkgi_load(path, db_data, sizeof(db_data) - 1); 87 | if (loaded > 0) 88 | { 89 | db_size = loaded; 90 | } 91 | else if (update_url[0] != 0) 92 | { 93 | LOG("loading update from %s", update_url); 94 | 95 | pkgi_http* http = pkgi_http_get(update_url, NULL, 0); 96 | if (!http) 97 | { 98 | pkgi_snprintf(error, error_size, "failed to download list"); 99 | return 0; 100 | } 101 | else 102 | { 103 | int64_t length; 104 | if (!pkgi_http_response_length(http, &length)) 105 | { 106 | pkgi_snprintf(error, error_size, "failed to download list"); 107 | } 108 | else 109 | { 110 | if (length > (int64_t)sizeof(db_data) - 1) 111 | { 112 | pkgi_snprintf(error, sizeof(error_size), "list is too large... check for newer pkgi version!"); 113 | } 114 | else if (length != 0) 115 | { 116 | db_total = (uint32_t)length; 117 | } 118 | 119 | error[0] = 0; 120 | 121 | for (;;) 122 | { 123 | uint32_t want = (uint32_t)min64(1 << 16, sizeof(db_data) - 1 - db_size); 124 | int read = pkgi_http_read(http, db_data + db_size, want); 125 | if (read == 0) 126 | { 127 | break; 128 | } 129 | else if (read < 0) 130 | { 131 | pkgi_snprintf(error, sizeof(error_size), "HTTP error 0x%08x", read); 132 | db_size = 0; 133 | break; 134 | } 135 | db_size += read; 136 | } 137 | 138 | if (error[0] == 0 && db_size == 0) 139 | { 140 | pkgi_snprintf(error, sizeof(error_size), "list is empty... check for newer pkgi version!"); 141 | } 142 | } 143 | 144 | pkgi_http_close(http); 145 | 146 | if (db_size == 0) 147 | { 148 | return 0; 149 | } 150 | } 151 | } 152 | else 153 | { 154 | pkgi_snprintf(error, error_size, "ERROR: pkgi.txt file missing or bad config.txt file?"); 155 | return 0; 156 | } 157 | 158 | LOG("parsing items"); 159 | 160 | db_data[db_size] = '\n'; 161 | char* ptr = db_data; 162 | char* end = db_data + db_size + 1; 163 | 164 | if (db_size > 3 && (uint8_t)ptr[0] == 0xef && (uint8_t)ptr[1] == 0xbb && (uint8_t)ptr[2] == 0xbf) 165 | { 166 | ptr += 3; 167 | } 168 | 169 | while (ptr < end && *ptr) 170 | { 171 | const char* content = ptr; 172 | while (ptr < end && *ptr != ',') 173 | { 174 | ptr++; 175 | } 176 | if (ptr == end) 177 | { 178 | break; 179 | } 180 | *ptr++ = 0; 181 | 182 | const char* flags = ptr; 183 | while (ptr < end && *ptr != ',') 184 | { 185 | ptr++; 186 | } 187 | if (ptr == end) 188 | { 189 | break; 190 | } 191 | *ptr++ = 0; 192 | 193 | const char* name = ptr; 194 | while (ptr < end && *ptr != ',') 195 | { 196 | ptr++; 197 | } 198 | if (ptr == end) 199 | { 200 | break; 201 | } 202 | *ptr++ = 0; 203 | 204 | const char* name_org = ptr; 205 | while (ptr < end && *ptr != ',') 206 | { 207 | ptr++; 208 | } 209 | if (ptr == end) 210 | { 211 | break; 212 | } 213 | *ptr++ = 0; 214 | 215 | const char* zrif = ptr; 216 | while (ptr < end && *ptr != ',') 217 | { 218 | ptr++; 219 | } 220 | if (ptr == end) 221 | { 222 | break; 223 | } 224 | *ptr++ = 0; 225 | 226 | const char* url = ptr; 227 | while (ptr < end && *ptr != ',') 228 | { 229 | ptr++; 230 | } 231 | if (ptr == end) 232 | { 233 | break; 234 | } 235 | *ptr++ = 0; 236 | 237 | const char* size = ptr; 238 | while (ptr < end && *ptr != ',') 239 | { 240 | ptr++; 241 | } 242 | if (ptr == end) 243 | { 244 | break; 245 | } 246 | *ptr++ = 0; 247 | 248 | const char* digest = ptr; 249 | while (ptr < end && *ptr != '\n' && *ptr != '\r') 250 | { 251 | ptr++; 252 | } 253 | if (ptr == end) 254 | { 255 | break; 256 | } 257 | *ptr++ = 0; 258 | 259 | if (*ptr == '\n') 260 | { 261 | ptr++; 262 | } 263 | 264 | db[db_count].content = content; 265 | db[db_count].flags = (uint32_t)pkgi_strtoll(flags); 266 | db[db_count].name = name; 267 | db[db_count].name_org = name_org[0] == 0 ? name : name_org; 268 | db[db_count].zrif = zrif[0] == 0 ? NULL : zrif; 269 | db[db_count].url = url; 270 | db[db_count].size = pkgi_strtoll(size); 271 | db[db_count].digest = pkgi_hexbytes(digest, SHA256_DIGEST_SIZE); 272 | db_item[db_count] = db + db_count; 273 | db_count++; 274 | 275 | if (db_count == MAX_DB_ITEMS) 276 | { 277 | break; 278 | } 279 | 280 | if (ptr < end && *ptr == '\r') 281 | { 282 | ptr++; 283 | } 284 | } 285 | 286 | db_item_count = db_count; 287 | 288 | LOG("finished parsing, %u total items", db_count); 289 | return 1; 290 | } 291 | 292 | static void swap(uint32_t a, uint32_t b) 293 | { 294 | DbItem* temp = db_item[a]; 295 | db_item[a] = db_item[b]; 296 | db_item[b] = temp; 297 | } 298 | 299 | static int matches(GameRegion region, uint32_t filter) 300 | { 301 | return (region == RegionASA && (filter & DbFilterRegionASA)) 302 | || (region == RegionEUR && (filter & DbFilterRegionEUR)) 303 | || (region == RegionJPN && (filter & DbFilterRegionJPN)) 304 | || (region == RegionUSA && (filter & DbFilterRegionUSA)) 305 | || (region == RegionUnknown); 306 | } 307 | 308 | static int lower(const DbItem* a, const DbItem* b, DbSort sort, DbSortOrder order, uint32_t filter) 309 | { 310 | GameRegion reg_a = pkgi_get_region(a->content); 311 | GameRegion reg_b = pkgi_get_region(b->content); 312 | 313 | int cmp = 0; 314 | if (sort == SortByTitle) 315 | { 316 | cmp = pkgi_stricmp(a->content + 7, b->content + 7) < 0; 317 | } 318 | else if (sort == SortByRegion) 319 | { 320 | cmp = reg_a == reg_b ? pkgi_stricmp(a->content + 7, b->content + 7) < 0 : reg_a < reg_b; 321 | } 322 | else if (sort == SortByName) 323 | { 324 | cmp = pkgi_stricmp(a->name, b->name) < 0; 325 | } 326 | else if (sort == SortBySize) 327 | { 328 | cmp = a->size < b->size; 329 | } 330 | 331 | int matches_a = matches(reg_a, filter); 332 | int matches_b = matches(reg_b, filter); 333 | 334 | if (matches_a == matches_b) 335 | { 336 | return order == SortAscending ? cmp : !cmp; 337 | } 338 | else if (matches_a) 339 | { 340 | return 1; 341 | } 342 | else 343 | { 344 | return 0; 345 | } 346 | } 347 | 348 | static void heapify(uint32_t n, uint32_t index, DbSort sort, DbSortOrder order, uint32_t filter) 349 | { 350 | uint32_t largest = index; 351 | uint32_t left = 2 * index + 1; 352 | uint32_t right = 2 * index + 2; 353 | 354 | if (left < n && lower(db_item[largest], db_item[left], sort, order, filter)) 355 | { 356 | largest = left; 357 | } 358 | 359 | if (right < n && lower(db_item[largest], db_item[right], sort, order, filter)) 360 | { 361 | largest = right; 362 | } 363 | 364 | if (largest != index) 365 | { 366 | swap(index, largest); 367 | heapify(n, largest, sort, order, filter); 368 | } 369 | } 370 | 371 | void pkgi_db_configure(const char* search, const Config* config) 372 | { 373 | uint32_t search_count; 374 | if (!search) 375 | { 376 | search_count = db_count; 377 | } 378 | else 379 | { 380 | uint32_t write = 0; 381 | for (uint32_t read = 0; read < db_count; read++) 382 | { 383 | if (pkgi_stricontains(db_item[read]->name, search)) 384 | { 385 | if (write < read) 386 | { 387 | swap(read, write); 388 | } 389 | write++; 390 | } 391 | } 392 | search_count = write; 393 | } 394 | 395 | if (search_count == 0) 396 | { 397 | db_item_count = 0; 398 | return; 399 | } 400 | 401 | for (int i = search_count / 2 - 1; i >= 0; i--) 402 | { 403 | heapify(search_count, i, config->sort, config->order, config->filter); 404 | } 405 | 406 | for (int i = search_count - 1; i >= 0; i--) 407 | { 408 | swap(i, 0); 409 | heapify(i, 0, config->sort, config->order, config->filter); 410 | } 411 | 412 | if (config->filter == DbFilterAll) 413 | { 414 | db_item_count = search_count; 415 | } 416 | else 417 | { 418 | uint32_t low = 0; 419 | uint32_t high = search_count - 1; 420 | while (low <= high) 421 | { 422 | // this never overflows because of MAX_DB_ITEMS 423 | uint32_t middle = (low + high) / 2; 424 | 425 | GameRegion region = pkgi_get_region(db_item[middle]->content); 426 | if (matches(region, config->filter)) 427 | { 428 | low = middle + 1; 429 | } 430 | else 431 | { 432 | if (middle == 0) 433 | { 434 | break; 435 | } 436 | high = middle - 1; 437 | } 438 | } 439 | db_item_count = low; 440 | } 441 | } 442 | 443 | void pkgi_db_get_update_status(uint32_t* updated, uint32_t* total) 444 | { 445 | *updated = db_size; 446 | *total = db_total; 447 | } 448 | 449 | uint32_t pkgi_db_count(void) 450 | { 451 | return db_item_count; 452 | } 453 | 454 | uint32_t pkgi_db_total(void) 455 | { 456 | return db_count; 457 | } 458 | 459 | DbItem* pkgi_db_get(uint32_t index) 460 | { 461 | return index < db_item_count ? db_item[index] : NULL; 462 | } 463 | 464 | GameRegion pkgi_get_region(const char* content) 465 | { 466 | uint32_t first = get32le((uint8_t*)content + 7); 467 | 468 | #define ID(a, b, c, d) (uint32_t)(((uint8_t)(d) << 24) | ((uint8_t)(c) << 16) | ((uint8_t)(b) << 8) | ((uint8_t)(a))) 469 | 470 | switch (first) 471 | { 472 | case ID('V', 'C', 'A', 'S'): 473 | case ID('P', 'C', 'S', 'H'): 474 | case ID('V', 'L', 'A', 'S'): 475 | case ID('P', 'C', 'S', 'D'): 476 | return RegionASA; 477 | 478 | case ID('P', 'C', 'S', 'F'): 479 | case ID('P', 'C', 'S', 'B'): 480 | return RegionEUR; 481 | 482 | case ID('P', 'C', 'S', 'C'): 483 | case ID('V', 'C', 'J', 'S'): 484 | case ID('P', 'C', 'S', 'G'): 485 | case ID('V', 'L', 'J', 'S'): 486 | case ID('V', 'L', 'J', 'M'): 487 | return RegionJPN; 488 | 489 | case ID('P', 'C', 'S', 'E'): 490 | case ID('P', 'C', 'S', 'A'): 491 | return RegionUSA; 492 | 493 | default: 494 | return RegionUnknown; 495 | } 496 | 497 | #undef ID 498 | } -------------------------------------------------------------------------------- /pkgi_db.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef enum { 6 | PresenceUnknown, 7 | PresenceIncomplete, 8 | PresenceInstalled, 9 | PresenceMissing, 10 | } DbPresence; 11 | 12 | typedef enum { 13 | SortByTitle, 14 | SortByRegion, 15 | SortByName, 16 | SortBySize, 17 | } DbSort; 18 | 19 | typedef enum { 20 | SortAscending, 21 | SortDescending, 22 | } DbSortOrder; 23 | 24 | typedef enum { 25 | DbFilterRegionASA = 0x01, 26 | DbFilterRegionEUR = 0x02, 27 | DbFilterRegionJPN = 0x04, 28 | DbFilterRegionUSA = 0x08, 29 | 30 | // TODO: implement these two 31 | DbFilterInstalled = 0x10, 32 | DbFilterMissing = 0x20, 33 | 34 | DbFilterAllRegions = DbFilterRegionUSA | DbFilterRegionEUR | DbFilterRegionJPN | DbFilterRegionASA, 35 | DbFilterAll = DbFilterAllRegions | DbFilterInstalled | DbFilterMissing, 36 | } DbFilter; 37 | 38 | typedef struct { 39 | DbPresence presence; 40 | const char* content; 41 | uint32_t flags; 42 | const char* name; 43 | const char* name_org; 44 | const char* zrif; 45 | const char* url; 46 | const uint8_t* digest; 47 | int64_t size; 48 | } DbItem; 49 | 50 | 51 | typedef enum { 52 | RegionASA, 53 | RegionEUR, 54 | RegionJPN, 55 | RegionUSA, 56 | RegionUnknown, 57 | } GameRegion; 58 | 59 | typedef struct Config Config; 60 | 61 | int pkgi_db_update(const char* update_url, char* error, uint32_t error_size); 62 | void pkgi_db_get_update_status(uint32_t* updated, uint32_t* total); 63 | 64 | void pkgi_db_configure(const char* search, const Config* config); 65 | 66 | uint32_t pkgi_db_count(void); 67 | uint32_t pkgi_db_total(void); 68 | DbItem* pkgi_db_get(uint32_t index); 69 | 70 | GameRegion pkgi_get_region(const char* content); 71 | -------------------------------------------------------------------------------- /pkgi_dialog.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_dialog.h" 2 | #include "pkgi_style.h" 3 | #include "pkgi_utils.h" 4 | #include "pkgi.h" 5 | 6 | typedef enum { 7 | DialogNone, 8 | DialogMessage, 9 | DialogError, 10 | DialogProgress, 11 | } DialogType; 12 | 13 | static DialogType dialog_type; 14 | static char dialog_title[256]; 15 | static char dialog_text[256]; 16 | static char dialog_extra[256]; 17 | static char dialog_eta[256]; 18 | static float dialog_progress; 19 | static int dialog_allow_close; 20 | static int dialog_cancelled; 21 | 22 | static int32_t dialog_width; 23 | static int32_t dialog_height; 24 | static int32_t dialog_delta; 25 | 26 | void pkgi_dialog_init(void) 27 | { 28 | dialog_type = DialogNone; 29 | dialog_allow_close = 1; 30 | } 31 | 32 | int pkgi_dialog_is_open(void) 33 | { 34 | return dialog_type != DialogNone; 35 | } 36 | 37 | int pkgi_dialog_is_cancelled(void) 38 | { 39 | return dialog_cancelled; 40 | } 41 | 42 | void pkgi_dialog_allow_close(int allow) 43 | { 44 | pkgi_dialog_lock(); 45 | dialog_allow_close = allow; 46 | pkgi_dialog_unlock(); 47 | } 48 | 49 | void pkgi_dialog_message(const char* text) 50 | { 51 | pkgi_dialog_lock(); 52 | 53 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 54 | dialog_title[0] = 0; 55 | dialog_extra[0] = 0; 56 | dialog_eta[0] = 0; 57 | 58 | dialog_cancelled = 0; 59 | dialog_type = DialogMessage; 60 | dialog_delta = 1; 61 | 62 | pkgi_dialog_unlock(); 63 | } 64 | 65 | void pkgi_dialog_error(const char* text) 66 | { 67 | pkgi_dialog_lock(); 68 | 69 | pkgi_strncpy(dialog_title, sizeof(dialog_title), "ERROR"); 70 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 71 | dialog_extra[0] = 0; 72 | dialog_eta[0] = 0; 73 | 74 | dialog_cancelled = 0; 75 | dialog_type = DialogError; 76 | dialog_delta = 1; 77 | 78 | pkgi_dialog_unlock(); 79 | } 80 | 81 | void pkgi_dialog_start_progress(const char* title, const char* text, float progress) 82 | { 83 | pkgi_dialog_lock(); 84 | 85 | pkgi_strncpy(dialog_title, sizeof(dialog_title), title); 86 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 87 | dialog_extra[0] = 0; 88 | dialog_eta[0] = 0; 89 | 90 | dialog_progress = progress; 91 | dialog_cancelled = 0; 92 | dialog_type = DialogProgress; 93 | dialog_delta = 1; 94 | 95 | pkgi_dialog_unlock(); 96 | } 97 | 98 | void pkgi_dialog_set_progress_title(const char* title) 99 | { 100 | pkgi_dialog_lock(); 101 | pkgi_strncpy(dialog_title, sizeof(dialog_title), title); 102 | pkgi_dialog_unlock(); 103 | } 104 | 105 | void pkgi_dialog_update_progress(const char* text, const char* extra, const char* eta, float progress) 106 | { 107 | pkgi_dialog_lock(); 108 | 109 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 110 | pkgi_strncpy(dialog_extra, sizeof(dialog_extra), extra ? extra : ""); 111 | pkgi_strncpy(dialog_eta, sizeof(dialog_eta), eta ? eta : ""); 112 | 113 | dialog_progress = progress; 114 | 115 | pkgi_dialog_unlock(); 116 | } 117 | 118 | void pkgi_dialog_close(void) 119 | { 120 | dialog_delta = -1; 121 | } 122 | 123 | void pkgi_do_dialog(pkgi_input* input) 124 | { 125 | pkgi_dialog_lock(); 126 | 127 | if (dialog_allow_close) 128 | { 129 | if ((dialog_type == DialogMessage || dialog_type == DialogError) && (input->pressed & pkgi_ok_button())) 130 | { 131 | dialog_delta = -1; 132 | } 133 | else if (dialog_type == DialogProgress && (input->pressed & pkgi_cancel_button())) 134 | { 135 | dialog_cancelled = 1; 136 | } 137 | } 138 | 139 | if (dialog_delta != 0) 140 | { 141 | dialog_width += dialog_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED / 1000000); 142 | dialog_height += dialog_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED / 1000000); 143 | 144 | if (dialog_delta < 0 && (dialog_width <= 0 || dialog_height <= 0)) 145 | { 146 | dialog_type = DialogNone; 147 | dialog_text[0] = 0; 148 | dialog_extra[0] = 0; 149 | dialog_eta[0] = 0; 150 | 151 | dialog_width = 0; 152 | dialog_height = 0; 153 | dialog_delta = 0; 154 | pkgi_dialog_unlock(); 155 | return; 156 | } 157 | else if (dialog_delta > 0) 158 | { 159 | if (dialog_width >= PKGI_DIALOG_WIDTH && dialog_height >= PKGI_DIALOG_HEIGHT) 160 | { 161 | dialog_delta = 0; 162 | } 163 | dialog_width = min32(dialog_width, PKGI_DIALOG_WIDTH); 164 | dialog_height = min32(dialog_height, PKGI_DIALOG_HEIGHT); 165 | } 166 | } 167 | 168 | DialogType local_type = dialog_type; 169 | char local_title[256]; 170 | char local_text[256]; 171 | char local_extra[256]; 172 | char local_eta[256]; 173 | float local_progress = dialog_progress; 174 | int local_allow_close = dialog_allow_close; 175 | int32_t local_width = dialog_width; 176 | int32_t local_height = dialog_height; 177 | 178 | pkgi_strncpy(local_title, sizeof(local_title), dialog_title); 179 | pkgi_strncpy(local_text, sizeof(local_text), dialog_text); 180 | pkgi_strncpy(local_extra, sizeof(local_extra), dialog_extra); 181 | pkgi_strncpy(local_eta, sizeof(local_eta), dialog_eta); 182 | 183 | pkgi_dialog_unlock(); 184 | 185 | if (local_width != 0 && local_height != 0) 186 | { 187 | pkgi_draw_rect((VITA_WIDTH - local_width) / 2, (VITA_HEIGHT - local_height) / 2, local_width, local_height, PKGI_COLOR_MENU_BACKGROUND); 188 | } 189 | 190 | if (local_width != PKGI_DIALOG_WIDTH || local_height != PKGI_DIALOG_HEIGHT) 191 | { 192 | return; 193 | } 194 | 195 | int font_height = pkgi_text_height("M"); 196 | 197 | int w = VITA_WIDTH - 2 * PKGI_DIALOG_HMARGIN; 198 | int h = VITA_HEIGHT - 2 * PKGI_DIALOG_VMARGIN; 199 | 200 | if (local_title[0]) 201 | { 202 | uint32_t color; 203 | if (local_type == DialogError) 204 | { 205 | color = PKGI_COLOR_TEXT_ERROR; 206 | } 207 | else 208 | { 209 | color = PKGI_COLOR_TEXT_DIALOG; 210 | } 211 | 212 | int width = pkgi_text_width(local_title); 213 | if (width > w + 2 * PKGI_DIALOG_PADDING) 214 | { 215 | pkgi_clip_set(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_DIALOG_VMARGIN + PKGI_DIALOG_PADDING, w - 2 * PKGI_DIALOG_PADDING, h - 2 * PKGI_DIALOG_PADDING); 216 | pkgi_draw_text((VITA_WIDTH - width) / 2, PKGI_DIALOG_VMARGIN + font_height, color, local_title); 217 | pkgi_clip_remove(); 218 | } 219 | else 220 | { 221 | pkgi_draw_text((VITA_WIDTH - width) / 2, PKGI_DIALOG_VMARGIN + font_height, color, local_title); 222 | } 223 | } 224 | 225 | if (local_type == DialogProgress) 226 | { 227 | int extraw = pkgi_text_width(local_extra); 228 | 229 | int availw = VITA_WIDTH - 2 * (PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING) - (extraw ? extraw + 10 : 10); 230 | pkgi_clip_set(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2 - font_height - PKGI_DIALOG_PROCESS_BAR_PADDING, availw, font_height + 2); 231 | pkgi_draw_text(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2 - font_height - PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_COLOR_TEXT_DIALOG, local_text); 232 | pkgi_clip_remove(); 233 | 234 | if (local_extra[0]) 235 | { 236 | pkgi_draw_text(PKGI_DIALOG_HMARGIN + w - (PKGI_DIALOG_PADDING + extraw), VITA_HEIGHT / 2 - font_height - PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_COLOR_TEXT_DIALOG, local_extra); 237 | } 238 | 239 | if (local_progress < 0) 240 | { 241 | uint32_t avail = w - 2 * PKGI_DIALOG_PADDING; 242 | 243 | uint32_t start = (pkgi_time_msec() / 2) % (avail + PKGI_DIALOG_PROCESS_BAR_CHUNK); 244 | uint32_t end = start < PKGI_DIALOG_PROCESS_BAR_CHUNK ? start : start + PKGI_DIALOG_PROCESS_BAR_CHUNK > avail + PKGI_DIALOG_PROCESS_BAR_CHUNK ? avail : start; 245 | start = start < PKGI_DIALOG_PROCESS_BAR_CHUNK ? 0 : start - PKGI_DIALOG_PROCESS_BAR_CHUNK; 246 | 247 | pkgi_draw_rect(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2, avail, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BACKGROUND); 248 | pkgi_draw_rect(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING + start, VITA_HEIGHT / 2, end - start, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BAR); 249 | } 250 | else 251 | { 252 | pkgi_draw_rect(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2, w - 2 * PKGI_DIALOG_PADDING, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BACKGROUND); 253 | pkgi_draw_rect(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2, (int)((w - 2 * PKGI_DIALOG_PADDING) * local_progress), PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BAR); 254 | 255 | char percent[256]; 256 | pkgi_snprintf(percent, sizeof(percent), "%.0f%%", local_progress * 100.f); 257 | 258 | int percentw = pkgi_text_width(percent); 259 | pkgi_draw_text((VITA_WIDTH - percentw) / 2, VITA_HEIGHT / 2 + PKGI_DIALOG_PROCESS_BAR_HEIGHT + PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_COLOR_TEXT_DIALOG, percent); 260 | } 261 | 262 | if (local_eta[0]) 263 | { 264 | pkgi_draw_text(PKGI_DIALOG_HMARGIN + w - (PKGI_DIALOG_PADDING + pkgi_text_width(local_eta)), VITA_HEIGHT / 2 + PKGI_DIALOG_PROCESS_BAR_HEIGHT + PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_COLOR_TEXT_DIALOG, local_eta); 265 | } 266 | 267 | if (local_allow_close) 268 | { 269 | char text[256]; 270 | pkgi_snprintf(text, sizeof(text), "press %s to cancel", pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_O : PKGI_UTF8_X); 271 | pkgi_draw_text((VITA_WIDTH - pkgi_text_width(text)) / 2, PKGI_DIALOG_VMARGIN + h - 2 * font_height, PKGI_COLOR_TEXT_DIALOG, text); 272 | } 273 | } 274 | else 275 | { 276 | uint32_t color; 277 | if (local_type == DialogMessage) 278 | { 279 | color = PKGI_COLOR_TEXT_DIALOG; 280 | } 281 | else // local_type == DialogError 282 | { 283 | color = PKGI_COLOR_TEXT_ERROR; 284 | } 285 | 286 | int textw = pkgi_text_width(local_text); 287 | if (textw > w + 2 * PKGI_DIALOG_PADDING) 288 | { 289 | pkgi_clip_set(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_DIALOG_VMARGIN + PKGI_DIALOG_PADDING, w - 2 * PKGI_DIALOG_PADDING, h - 2 * PKGI_DIALOG_PADDING); 290 | pkgi_draw_text(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, VITA_HEIGHT / 2 - font_height / 2, color, local_text); 291 | pkgi_clip_remove(); 292 | } 293 | else 294 | { 295 | pkgi_draw_text((VITA_WIDTH - textw) / 2, VITA_HEIGHT / 2 - font_height / 2, color, local_text); 296 | } 297 | 298 | if (local_allow_close) 299 | { 300 | char text[256]; 301 | pkgi_snprintf(text, sizeof(text), "press %s to close", pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_X : PKGI_UTF8_O); 302 | pkgi_draw_text((VITA_WIDTH - pkgi_text_width(text)) / 2, PKGI_DIALOG_VMARGIN + h - 2 * font_height, PKGI_COLOR_TEXT_DIALOG, text); 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /pkgi_dialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct pkgi_input pkgi_input; 4 | 5 | void pkgi_dialog_init(void); 6 | 7 | int pkgi_dialog_is_open(void); 8 | int pkgi_dialog_is_cancelled(void); 9 | void pkgi_dialog_allow_close(int allow); 10 | void pkgi_dialog_message(const char* text); 11 | void pkgi_dialog_error(const char* text); 12 | 13 | void pkgi_dialog_start_progress(const char* title, const char* text, float progress); 14 | void pkgi_dialog_set_progress_title(const char* title); 15 | void pkgi_dialog_update_progress(const char* text, const char* extra, const char* eta, float progress); 16 | 17 | void pkgi_dialog_close(void); 18 | 19 | void pkgi_do_dialog(pkgi_input* input); 20 | -------------------------------------------------------------------------------- /pkgi_download.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_download.h" 2 | #include "pkgi_dialog.h" 3 | #include "pkgi.h" 4 | #include "pkgi_utils.h" 5 | #include "pkgi_aes128.h" 6 | #include "pkgi_sha256.h" 7 | 8 | #include 9 | 10 | #define PKG_HEADER_SIZE 192 11 | #define PKG_HEADER_EXT_SIZE 64 12 | #define PKG_TAIL_SIZE 480 13 | 14 | static const uint8_t pkg_psp_key[] = { 0x07, 0xf2, 0xc6, 0x82, 0x90, 0xb5, 0x0d, 0x2c, 0x33, 0x81, 0x8d, 0x70, 0x9b, 0x60, 0xe6, 0x2b }; 15 | static const uint8_t pkg_vita_2[] = { 0xe3, 0x1a, 0x70, 0xc9, 0xce, 0x1d, 0xd7, 0x2b, 0xf3, 0xc0, 0x62, 0x29, 0x63, 0xf2, 0xec, 0xcb }; 16 | static const uint8_t pkg_vita_3[] = { 0x42, 0x3a, 0xca, 0x3a, 0x2b, 0xd5, 0x64, 0x9f, 0x96, 0x86, 0xab, 0xad, 0x6f, 0xd8, 0x80, 0x1f }; 17 | static const uint8_t pkg_vita_4[] = { 0xaf, 0x07, 0xfd, 0x59, 0x65, 0x25, 0x27, 0xba, 0xf1, 0x33, 0x89, 0x66, 0x8b, 0x17, 0xd9, 0xea }; 18 | 19 | // temporary unpack folder ux0:pkgi/TITLE 20 | static char root[256]; 21 | 22 | static char resume_file[256]; 23 | 24 | static pkgi_http* http; 25 | static const char* download_content; 26 | static const char* download_url; 27 | static int download_resume; 28 | 29 | static uint64_t initial_offset; // where http download resumes 30 | static uint64_t download_offset; // pkg absolute offset 31 | static uint64_t download_size; // pkg total size (from http request) 32 | 33 | static uint8_t iv[AES_BLOCK_SIZE]; 34 | static aes128_ctx aes; 35 | static sha256_ctx sha; 36 | 37 | static void* item_file; // current file handle 38 | static char item_name[256]; // current file name 39 | static char item_path[256]; // current file path 40 | static int item_index; // current item 41 | 42 | // head.bin contents, kept in memory while downloading 43 | static uint8_t head[4 * 1024 * 1024]; 44 | static uint32_t head_size; 45 | 46 | // temporary buffer for downloads 47 | static uint8_t down[64 * 1024]; 48 | 49 | // pkg header 50 | static uint32_t meta_offset; 51 | static uint32_t meta_count; 52 | static uint32_t index_count; 53 | static uint64_t total_size; 54 | static uint64_t enc_offset; 55 | static uint64_t enc_size; 56 | static uint32_t index_size; 57 | 58 | // encrypted files 59 | static uint64_t encrypted_base; // offset in pkg where it starts 60 | static uint64_t encrypted_offset; // offset from beginning of file 61 | static uint64_t decrypted_size; // size that's left to write into decrypted file 62 | 63 | // UI stuff 64 | static char dialog_extra[256]; 65 | static char dialog_eta[256]; 66 | static uint32_t info_start; 67 | static uint32_t info_update; 68 | 69 | static void calculate_eta(uint32_t speed) 70 | { 71 | uint64_t seconds = (download_size - download_offset) / speed; 72 | if (seconds < 60) 73 | { 74 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "ETA: %us", (uint32_t)seconds); 75 | } 76 | else if (seconds < 3600) 77 | { 78 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "ETA: %um %02us", (uint32_t)(seconds / 60), (uint32_t)(seconds % 60)); 79 | } 80 | else 81 | { 82 | uint32_t hours = (uint32_t)(seconds / 3600); 83 | uint32_t minutes = (uint32_t)((seconds - hours * 3600) / 60); 84 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "ETA: %uh %02um", hours, minutes); 85 | } 86 | } 87 | 88 | static void update_progress(void) 89 | { 90 | uint32_t info_now = pkgi_time_msec(); 91 | if (info_now >= info_update) 92 | { 93 | char text[256]; 94 | if (item_index < 0) 95 | { 96 | pkgi_snprintf(text, sizeof(text), "%s", item_name); 97 | } 98 | else 99 | { 100 | pkgi_snprintf(text, sizeof(text), "[%u/%u] %s", item_index, index_count, item_name); 101 | } 102 | 103 | if (download_resume) 104 | { 105 | // if resuming download, then there is no "download speed" 106 | dialog_extra[0] = 0; 107 | } 108 | else 109 | { 110 | // report download speed 111 | uint32_t speed = (uint32_t)(((download_offset - initial_offset) * 1000) / (info_now - info_start)); 112 | if (speed > 10 * 1000 * 1024) 113 | { 114 | pkgi_snprintf(dialog_extra, sizeof(dialog_extra), "%u MB/s", speed / 1024 / 1024); 115 | } 116 | else if (speed > 1000) 117 | { 118 | pkgi_snprintf(dialog_extra, sizeof(dialog_extra), "%u KB/s", speed / 1024); 119 | } 120 | 121 | if (speed != 0) 122 | { 123 | // report ETA 124 | calculate_eta(speed); 125 | } 126 | } 127 | 128 | float percent; 129 | if (download_resume) 130 | { 131 | // if resuming, then we may not know download size yet, use total_size from pkg header 132 | percent = total_size ? (float)((double)download_offset / total_size) : 0.f; 133 | } 134 | else 135 | { 136 | // when downloading use content length from http response as download size 137 | percent = download_size ? (float)((double)download_offset / download_size) : 0.f; 138 | } 139 | 140 | pkgi_dialog_update_progress(text, dialog_extra, dialog_eta, percent); 141 | info_update = info_now + 500; 142 | } 143 | } 144 | 145 | static void download_start(void) 146 | { 147 | LOG("resuming pkg download from %llu offset", download_offset); 148 | download_resume = 0; 149 | info_update = pkgi_time_msec() + 1000; 150 | pkgi_dialog_set_progress_title("Downloading"); 151 | } 152 | 153 | static int download_data(uint8_t* buffer, uint32_t size, int encrypted, int save) 154 | { 155 | if (pkgi_dialog_is_cancelled()) 156 | { 157 | pkgi_save(resume_file, &sha, sizeof(sha)); 158 | return 0; 159 | } 160 | 161 | update_progress(); 162 | 163 | if (download_resume) 164 | { 165 | int read = pkgi_read(item_file, buffer, size); 166 | if (read < 0) 167 | { 168 | char error[256]; 169 | pkgi_snprintf(error, sizeof(error), "failed to read file %s", item_path); 170 | pkgi_dialog_error(error); 171 | return -1; 172 | } 173 | 174 | if (read != 0) 175 | { 176 | // this is only for non-encrypted files (head/tail) when resuming 177 | download_offset += read; 178 | return read; 179 | } 180 | 181 | // not more data to read, need to start actual download 182 | download_start(); 183 | } 184 | 185 | if (!http) 186 | { 187 | initial_offset = download_offset; 188 | LOG("requesting %s @ %llu", download_url, download_offset); 189 | http = pkgi_http_get(download_url, download_content, download_offset); 190 | if (!http) 191 | { 192 | pkgi_dialog_error("cannot send HTTP request"); 193 | return 0; 194 | } 195 | 196 | int64_t http_length; 197 | if (!pkgi_http_response_length(http, &http_length)) 198 | { 199 | pkgi_dialog_error("HTTP request failed"); 200 | return 0; 201 | } 202 | if (http_length < 0) 203 | { 204 | pkgi_dialog_error("HTTP response has unknown length"); 205 | return 0; 206 | } 207 | 208 | download_size = http_length + download_offset; 209 | 210 | if (!pkgi_check_free_space(http_length)) 211 | { 212 | return 0; 213 | } 214 | 215 | LOG("http response length = %lld, total pkg size = %llu", http_length, download_size); 216 | info_start = pkgi_time_msec(); 217 | info_update = pkgi_time_msec() + 500; 218 | } 219 | 220 | int read = pkgi_http_read(http, buffer, size); 221 | if (read < 0) 222 | { 223 | char error[256]; 224 | pkgi_snprintf(error, sizeof(error), "HTTP download error 0x%08x", read); 225 | pkgi_dialog_error(error); 226 | pkgi_save(resume_file, &sha, sizeof(sha)); 227 | return -1; 228 | } 229 | else if (read == 0) 230 | { 231 | pkgi_dialog_error("HTTP connection closed"); 232 | pkgi_save(resume_file, &sha, sizeof(sha)); 233 | return -1; 234 | } 235 | download_offset += read; 236 | 237 | sha256_update(&sha, buffer, read); 238 | 239 | if (encrypted) 240 | { 241 | aes128_ctr(&aes, iv, encrypted_base + encrypted_offset, buffer, read); 242 | encrypted_offset += read; 243 | } 244 | 245 | if (save) 246 | { 247 | uint32_t write; 248 | if (encrypted) 249 | { 250 | write = (uint32_t)min64(decrypted_size, read); 251 | decrypted_size -= write; 252 | } 253 | else 254 | { 255 | write = read; 256 | } 257 | 258 | if (!pkgi_write(item_file, buffer, write)) 259 | { 260 | char error[256]; 261 | pkgi_snprintf(error, sizeof(error), "failed to write to %s", item_path); 262 | pkgi_dialog_error(error); 263 | return -1; 264 | } 265 | } 266 | 267 | return read; 268 | } 269 | 270 | // this includes creating of all the parent folders necessary to actually create file 271 | static int create_file(void) 272 | { 273 | char folder[256]; 274 | pkgi_strncpy(folder, sizeof(folder), item_path); 275 | char* last = pkgi_strrchr(folder, '/'); 276 | *last = 0; 277 | 278 | if (!pkgi_mkdirs(folder)) 279 | { 280 | char error[256]; 281 | pkgi_snprintf(error, sizeof(error), "cannot create folder %s", folder); 282 | pkgi_dialog_error(error); 283 | return 1; 284 | } 285 | 286 | LOG("creating %s file", item_name); 287 | item_file = pkgi_create(item_path); 288 | if (!item_file) 289 | { 290 | char error[256]; 291 | pkgi_snprintf(error, sizeof(error), "cannot create file %s", item_name); 292 | pkgi_dialog_error(error); 293 | return 0; 294 | } 295 | 296 | return 1; 297 | } 298 | 299 | static int download_head(const uint8_t* rif) 300 | { 301 | LOG("downloading pkg head"); 302 | 303 | pkgi_strncpy(item_name, sizeof(item_name), "Preparing..."); 304 | pkgi_snprintf(item_path, sizeof(item_path), "%s/sce_sys/package/head.bin", root); 305 | 306 | int result = 0; 307 | 308 | if (download_resume) 309 | { 310 | item_file = pkgi_openrw(item_path); 311 | if (item_file) 312 | { 313 | LOG("trying to resume %s file", item_name); 314 | } 315 | else 316 | { 317 | download_start(); 318 | } 319 | } 320 | 321 | if (!download_resume) 322 | { 323 | if (!create_file()) 324 | { 325 | char error[256]; 326 | pkgi_snprintf(error, sizeof(error), "cannot create %s", item_path); 327 | pkgi_dialog_error(error); 328 | goto bail; 329 | } 330 | } 331 | 332 | head_size = PKG_HEADER_SIZE + PKG_HEADER_EXT_SIZE; 333 | uint32_t head_offset = 0; 334 | while (head_offset != head_size) 335 | { 336 | int size = download_data(head + head_offset, head_size - head_offset, 0, 1); 337 | if (size <= 0) 338 | { 339 | goto bail; 340 | } 341 | head_offset += size; 342 | } 343 | 344 | if (get32be(head) != 0x7f504b47 || get32be(head + PKG_HEADER_SIZE) != 0x7F657874) 345 | { 346 | pkgi_dialog_error("wrong pkg header"); 347 | goto bail; 348 | } 349 | 350 | if (rif && !pkgi_memequ(rif + 0x10, head + 0x30, 0x30)) 351 | { 352 | pkgi_dialog_error("zRIF content id doesn't match pkg"); 353 | goto bail; 354 | } 355 | 356 | meta_offset = get32be(head + 8); 357 | meta_count = get32be(head + 12); 358 | index_count = get32be(head + 20); 359 | total_size = get64be(head + 24); 360 | enc_offset = get64be(head + 32); 361 | enc_size = get64be(head + 40); 362 | LOG("meta_offset=%u meta_count=%u index_count=%u total_size=%llu enc_offset=%llu enc_size=%llu", 363 | meta_offset, meta_count, index_count, total_size, enc_offset, enc_size); 364 | 365 | if (enc_offset > sizeof(head)) 366 | { 367 | LOG("pkg file head is too large"); 368 | pkgi_dialog_error("pkg is not supported, head.bin is too big"); 369 | goto bail; 370 | } 371 | 372 | pkgi_memcpy(iv, head + 0x70, sizeof(iv)); 373 | 374 | uint8_t key[AES_BLOCK_SIZE]; 375 | int key_type = head[0xe7] & 7; 376 | if (key_type == 1) 377 | { 378 | pkgi_memcpy(key, pkg_psp_key, sizeof(key)); 379 | } 380 | else if (key_type == 2) 381 | { 382 | aes128_ctx ctx; 383 | aes128_init(&ctx, pkg_vita_2); 384 | aes128_encrypt(&ctx, iv, key); 385 | } 386 | else if (key_type == 3) 387 | { 388 | aes128_ctx ctx; 389 | aes128_init(&ctx, pkg_vita_3); 390 | aes128_encrypt(&ctx, iv, key); 391 | } 392 | else if (key_type == 4) 393 | { 394 | aes128_ctx ctx; 395 | aes128_init(&ctx, pkg_vita_4); 396 | aes128_encrypt(&ctx, iv, key); 397 | } 398 | 399 | aes128_ctr_init(&aes, key); 400 | 401 | uint32_t target_size = (uint32_t)enc_offset; 402 | while (head_size != target_size) 403 | { 404 | int size = download_data(head + head_size, target_size - head_size, 0, 1); 405 | if (size <= 0) 406 | { 407 | goto bail; 408 | } 409 | head_size += size; 410 | } 411 | 412 | index_size = 0; 413 | 414 | uint32_t index_offset = 1; 415 | uint32_t offset = meta_offset; 416 | for (uint32_t i = 0; i < meta_count; i++) 417 | { 418 | if (offset + 16 >= enc_offset) 419 | { 420 | pkgi_dialog_error("pkg file is too small or corrupted"); 421 | goto bail; 422 | } 423 | 424 | uint32_t type = get32be(head + offset + 0); 425 | uint32_t size = get32be(head + offset + 4); 426 | 427 | if (type == 2) 428 | { 429 | uint32_t content_type = get32be(head + offset + 8); 430 | if (content_type != 21) 431 | { 432 | pkgi_dialog_error("pkg is not a main package"); 433 | goto bail; 434 | } 435 | } 436 | else if (type == 13) 437 | { 438 | index_offset = get32be(head + offset + 8); 439 | index_size = get32be(head + offset + 12); 440 | } 441 | offset += 8 + size; 442 | } 443 | 444 | if (index_offset != 0 || index_size == 0) 445 | { 446 | pkgi_dialog_error("pkg is missing encrypted file index"); 447 | goto bail; 448 | } 449 | 450 | target_size = (uint32_t)(enc_offset + index_size); 451 | if (target_size > sizeof(head)) 452 | { 453 | LOG("pkg file head is too large"); 454 | pkgi_dialog_error("pkg is not supported, head.bin is too big"); 455 | goto bail; 456 | } 457 | 458 | while (head_size != target_size) 459 | { 460 | int size = download_data(head + head_size, target_size - head_size, 0, 1); 461 | if (size <= 0) 462 | { 463 | goto bail; 464 | } 465 | head_size += size; 466 | } 467 | 468 | LOG("head.bin downloaded"); 469 | result = 1; 470 | 471 | bail: 472 | if (item_file) 473 | { 474 | pkgi_close(item_file); 475 | item_file = NULL; 476 | } 477 | 478 | return result; 479 | } 480 | 481 | static int download_files(void) 482 | { 483 | LOG("downloading encrypted files"); 484 | 485 | int result = 0; 486 | 487 | for (uint32_t index = 0; index < index_count; index++) 488 | { 489 | uint8_t item[32]; 490 | pkgi_memcpy(item, head + enc_offset + sizeof(item) * index, sizeof(item)); 491 | aes128_ctr(&aes, iv, sizeof(item) * index, item, sizeof(item)); 492 | 493 | uint32_t name_offset = get32be(item + 0); 494 | uint32_t name_size = get32be(item + 4); 495 | uint64_t item_offset = get64be(item + 8); 496 | uint64_t item_size = get64be(item + 16); 497 | uint8_t type = item[27]; 498 | 499 | if (name_size > sizeof(item_name) - 1 || enc_offset + name_offset + name_size > total_size) 500 | { 501 | pkgi_dialog_error("pkg file is too small or corrupted"); 502 | goto bail; 503 | } 504 | 505 | pkgi_memcpy(item_name, head + enc_offset + name_offset, name_size); 506 | aes128_ctr(&aes, iv, name_offset, (uint8_t*)item_name, name_size); 507 | item_name[name_size] = 0; 508 | 509 | LOG("[%u/%u] %s item_offset=%llu item_size=%llu type=%u", item_index + 1, index_count, item_name, item_offset, item_size, type); 510 | 511 | if (type == 4 || type == 18) 512 | { 513 | continue; 514 | } 515 | 516 | pkgi_snprintf(item_path, sizeof(item_path), "%s/%s", root, item_name); 517 | 518 | uint64_t encrypted_size = (item_size + AES_BLOCK_SIZE - 1) & ~((uint64_t)AES_BLOCK_SIZE - 1); 519 | decrypted_size = item_size; 520 | encrypted_base = item_offset; 521 | encrypted_offset = 0; 522 | item_index = index; 523 | 524 | if (download_resume) 525 | { 526 | if (pkgi_dialog_is_cancelled()) 527 | { 528 | goto bail; 529 | } 530 | 531 | int64_t current_size = pkgi_get_size(item_path); 532 | if (current_size < 0) 533 | { 534 | LOG("file does not exist %s", item_path); 535 | download_start(); 536 | } 537 | else if ((uint64_t)current_size != decrypted_size) 538 | { 539 | LOG("downloaded %llu, total %llu, resuming %s", (uint64_t)current_size, decrypted_size, item_path); 540 | item_file = pkgi_append(item_path); 541 | if (!item_file) 542 | { 543 | char error[256]; 544 | pkgi_snprintf(error, sizeof(error), "cannot append to %s", item_name); 545 | pkgi_dialog_error(error); 546 | goto bail; 547 | } 548 | encrypted_offset = (uint64_t)current_size; 549 | decrypted_size -= current_size; 550 | download_offset += current_size; 551 | download_start(); 552 | } 553 | else 554 | { 555 | LOG("file fully downloaded %s", item_name); 556 | download_offset += encrypted_size; 557 | update_progress(); 558 | continue; 559 | } 560 | } 561 | 562 | // if we are starting to download file from scratch 563 | if (!download_resume && !item_file) 564 | { 565 | if (!create_file()) 566 | { 567 | char error[256]; 568 | pkgi_snprintf(error, sizeof(error), "cannot create %s", item_name); 569 | pkgi_dialog_error(error); 570 | goto bail; 571 | } 572 | } 573 | 574 | if (enc_offset + item_offset + encrypted_offset != download_offset) 575 | { 576 | pkgi_dialog_error("pkg is not supported, files are in wrong order"); 577 | goto bail; 578 | } 579 | 580 | if (enc_offset + item_offset + item_size > total_size) 581 | { 582 | pkgi_dialog_error("pkg file is too small or corrupted"); 583 | goto bail; 584 | } 585 | 586 | while (encrypted_offset != encrypted_size) 587 | { 588 | uint32_t read = (uint32_t)min64(sizeof(down), encrypted_size - encrypted_offset); 589 | int size = download_data(down, read, 1, 1); 590 | if (size <= 0) 591 | { 592 | goto bail; 593 | } 594 | } 595 | 596 | pkgi_close(item_file); 597 | item_file = NULL; 598 | } 599 | 600 | item_index = -1; 601 | 602 | LOG("all files decrypted"); 603 | result = 1; 604 | 605 | bail: 606 | if (item_file) 607 | { 608 | pkgi_close(item_file); 609 | item_file = NULL; 610 | } 611 | return result; 612 | } 613 | 614 | static int download_tail(void) 615 | { 616 | LOG("downloading tail.bin"); 617 | 618 | int result = 0; 619 | 620 | pkgi_strncpy(item_name, sizeof(item_name), "Finishing..."); 621 | pkgi_snprintf(item_path, sizeof(item_path), "%s/sce_sys/package/tail.bin", root); 622 | 623 | if (download_resume) 624 | { 625 | download_start(); 626 | } 627 | 628 | if (!create_file()) 629 | { 630 | char error[256]; 631 | pkgi_snprintf(error, sizeof(error), "cannot create %s", item_path); 632 | pkgi_dialog_error(error); 633 | goto bail; 634 | } 635 | 636 | uint64_t tail_offset = enc_offset + enc_size; 637 | while (download_offset < tail_offset) 638 | { 639 | uint32_t read = (uint32_t)min64(sizeof(down), tail_offset - download_offset); 640 | int size = download_data(down, read, 0, 0); 641 | if (size <= 0) 642 | { 643 | goto bail; 644 | } 645 | } 646 | 647 | while (download_offset != total_size) 648 | { 649 | uint32_t read = (uint32_t)min64(sizeof(down), total_size - download_offset); 650 | int size = download_data(down, read, 0, 1); 651 | if (size <= 0) 652 | { 653 | goto bail; 654 | } 655 | } 656 | 657 | LOG("tail.bin downloaded"); 658 | result = 1; 659 | 660 | bail: 661 | if (item_file != NULL) 662 | { 663 | pkgi_close(item_file); 664 | item_file = NULL; 665 | } 666 | return result; 667 | } 668 | 669 | static int check_integrity(const uint8_t* digest) 670 | { 671 | if (!digest) 672 | { 673 | LOG("no integrity provided, skipping check"); 674 | return 1; 675 | } 676 | 677 | uint8_t check[SHA256_DIGEST_SIZE]; 678 | sha256_finish(&sha, check); 679 | 680 | LOG("checking integrity of pkg"); 681 | if (!pkgi_memequ(digest, check, SHA256_DIGEST_SIZE)) 682 | { 683 | LOG("pkg integrity is wrong, removing head.bin & resume data"); 684 | 685 | char path[256]; 686 | pkgi_snprintf(path, sizeof(path), "%s/sce_sys/package/head.bin", root); 687 | pkgi_rm(path); 688 | 689 | pkgi_dialog_error("pkg integrity failed, try downloading again"); 690 | return 0; 691 | } 692 | 693 | LOG("pkg integrity check succeeded"); 694 | return 1; 695 | } 696 | 697 | static int create_rif(const uint8_t* rif) 698 | { 699 | LOG("creating work.bin"); 700 | pkgi_dialog_update_progress("Creating work.bin", NULL, NULL, 1.f); 701 | 702 | char path[256]; 703 | pkgi_snprintf(path, sizeof(path), "%s/sce_sys/package/work.bin", root); 704 | 705 | if (!pkgi_save(path, rif, PKGI_RIF_SIZE)) 706 | { 707 | char error[256]; 708 | pkgi_snprintf(error, sizeof(error), "cannot save rif to %s", path); 709 | pkgi_dialog_error(error); 710 | return 0; 711 | } 712 | 713 | LOG("work.bin created"); 714 | return 1; 715 | } 716 | 717 | int pkgi_download(const char* content, const char* url, const uint8_t* rif, const uint8_t* digest) 718 | { 719 | int result = 0; 720 | 721 | pkgi_snprintf(root, sizeof(root), "%s/%.9s", pkgi_get_temp_folder(), content + 7); 722 | LOG("temp installation folder: %s", root); 723 | 724 | pkgi_snprintf(resume_file, sizeof(resume_file), "%s/%.9s.resume", pkgi_get_temp_folder(), content + 7); 725 | if (pkgi_load(resume_file, &sha, sizeof(sha)) == sizeof(sha)) 726 | { 727 | LOG("resume file exists, trying to resume"); 728 | pkgi_dialog_set_progress_title("Resuming"); 729 | download_resume = 1; 730 | } 731 | else 732 | { 733 | LOG("cannot load resume file, starting download from scratch"); 734 | pkgi_dialog_set_progress_title("Downloading"); 735 | download_resume = 0; 736 | sha256_init(&sha); 737 | } 738 | 739 | http = NULL; 740 | item_file = NULL; 741 | item_index = -1; 742 | download_size = 0; 743 | download_offset = 0; 744 | download_content = content; 745 | download_url = url; 746 | 747 | dialog_extra[0] = 0; 748 | dialog_eta[0] = 0; 749 | info_start = pkgi_time_msec(); 750 | info_update = info_start + 1000; 751 | 752 | if (!download_head(rif)) goto finish; 753 | if (!download_files()) goto finish; 754 | if (!download_tail()) goto finish; 755 | if (!check_integrity(digest)) goto finish; 756 | if (rif) 757 | { 758 | if (!create_rif(rif)) goto finish; 759 | } 760 | 761 | pkgi_rm(resume_file); 762 | result = 1; 763 | 764 | finish: 765 | if (http) 766 | { 767 | pkgi_http_close(http); 768 | } 769 | 770 | return result; 771 | } 772 | -------------------------------------------------------------------------------- /pkgi_download.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PKGI_RIF_SIZE 512 6 | 7 | int pkgi_download(const char* content, const char* url, const uint8_t* rif, const uint8_t* digest); 8 | -------------------------------------------------------------------------------- /pkgi_menu.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_menu.h" 2 | #include "pkgi_config.h" 3 | #include "pkgi_style.h" 4 | #include "pkgi.h" 5 | 6 | static int menu_search_clear; 7 | 8 | static Config menu_config; 9 | static uint32_t menu_selected; 10 | static int menu_allow_refresh; 11 | 12 | static MenuResult menu_result; 13 | 14 | static int32_t menu_width; 15 | static int32_t menu_delta; 16 | 17 | typedef enum { 18 | MenuSearch, 19 | MenuSearchClear, 20 | MenuText, 21 | MenuSort, 22 | MenuFilter, 23 | MenuRefresh, 24 | } MenuType; 25 | 26 | typedef struct { 27 | MenuType type; 28 | const char* text; 29 | uint32_t value; 30 | } MenuEntry; 31 | 32 | static const MenuEntry menu_entries[] = 33 | { 34 | { MenuSearch, "Search...", 0 }, 35 | { MenuSearchClear, PKGI_UTF8_CLEAR " clear", 0 }, 36 | 37 | { MenuText, "Sort by:", 0 }, 38 | { MenuSort, "Title", SortByTitle}, 39 | { MenuSort, "Region", SortByRegion }, 40 | { MenuSort, "Name", SortByName }, 41 | { MenuSort, "Size", SortBySize }, 42 | 43 | { MenuText, "Regions:", 0 }, 44 | { MenuFilter, "Asia", DbFilterRegionASA }, 45 | { MenuFilter, "Europe", DbFilterRegionEUR }, 46 | { MenuFilter, "Japan", DbFilterRegionJPN }, 47 | { MenuFilter, "USA", DbFilterRegionUSA }, 48 | 49 | { MenuRefresh, "Refresh...", 0 }, 50 | }; 51 | 52 | int pkgi_menu_is_open(void) 53 | { 54 | return menu_width != 0; 55 | } 56 | 57 | MenuResult pkgi_menu_result() 58 | { 59 | return menu_result; 60 | } 61 | 62 | void pkgi_menu_get(Config* config) 63 | { 64 | *config = menu_config; 65 | } 66 | 67 | void pkgi_menu_start(int search_clear, const Config* config, int allow_refresh) 68 | { 69 | menu_search_clear = search_clear; 70 | menu_width = 1; 71 | menu_delta = 1; 72 | menu_config = *config; 73 | menu_allow_refresh = allow_refresh; 74 | } 75 | 76 | int pkgi_do_menu(pkgi_input* input) 77 | { 78 | if (menu_delta != 0) 79 | { 80 | menu_width += menu_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED/2 / 1000000); 81 | 82 | if (menu_delta < 0 && menu_width <= 0) 83 | { 84 | menu_width = 0; 85 | menu_delta = 0; 86 | return 0; 87 | } 88 | else if (menu_delta > 0 && menu_width >= PKGI_MENU_WIDTH) 89 | { 90 | menu_width = PKGI_MENU_WIDTH; 91 | menu_delta = 0; 92 | } 93 | } 94 | 95 | if (menu_width != 0) 96 | { 97 | pkgi_draw_rect(VITA_WIDTH - menu_width, 0, menu_width, VITA_HEIGHT, PKGI_COLOR_MENU_BACKGROUND); 98 | } 99 | 100 | if (input->active & PKGI_BUTTON_UP) 101 | { 102 | do { 103 | if (menu_selected == 0) 104 | { 105 | menu_selected = PKGI_COUNTOF(menu_entries) - 1; 106 | } 107 | else 108 | { 109 | menu_selected--; 110 | } 111 | } while (menu_entries[menu_selected].type == MenuText 112 | || (menu_entries[menu_selected].type == MenuSearchClear && !menu_search_clear) 113 | || (menu_entries[menu_selected].type == MenuRefresh && !menu_allow_refresh)); 114 | } 115 | 116 | if (input->active & PKGI_BUTTON_DOWN) 117 | { 118 | do { 119 | if (menu_selected == PKGI_COUNTOF(menu_entries) - 1) 120 | { 121 | menu_selected = 0; 122 | } 123 | else 124 | { 125 | menu_selected++; 126 | } 127 | } while (menu_entries[menu_selected].type == MenuText 128 | || (menu_entries[menu_selected].type == MenuSearchClear && !menu_search_clear) 129 | || (menu_entries[menu_selected].type == MenuRefresh && !menu_allow_refresh)); 130 | } 131 | 132 | if (input->pressed & pkgi_cancel_button()) 133 | { 134 | menu_result = MenuResultCancel; 135 | menu_delta = -1; 136 | return 1; 137 | } 138 | else if (input->pressed & PKGI_BUTTON_T) 139 | { 140 | menu_result = MenuResultAccept; 141 | menu_delta = -1; 142 | return 1; 143 | } 144 | else if (input->pressed & pkgi_ok_button()) 145 | { 146 | MenuType type = menu_entries[menu_selected].type; 147 | if (type == MenuSearch) 148 | { 149 | menu_result = MenuResultSearch; 150 | menu_delta = -1; 151 | return 1; 152 | } 153 | if (type == MenuSearchClear) 154 | { 155 | menu_selected--; 156 | menu_result = MenuResultSearchClear; 157 | menu_delta = -1; 158 | return 1; 159 | } 160 | else if (type == MenuRefresh) 161 | { 162 | menu_result = MenuResultRefresh; 163 | menu_delta = -1; 164 | return 1; 165 | } 166 | else if (type == MenuSort) 167 | { 168 | DbSort value = (DbSort)menu_entries[menu_selected].value; 169 | if (menu_config.sort == value) 170 | { 171 | menu_config.order = menu_config.order == SortAscending ? SortDescending : SortAscending; 172 | } 173 | else 174 | { 175 | menu_config.sort = value; 176 | } 177 | } 178 | else if (type == MenuFilter) 179 | { 180 | menu_config.filter ^= menu_entries[menu_selected].value; 181 | } 182 | } 183 | 184 | if (menu_width != PKGI_MENU_WIDTH) 185 | { 186 | return 1; 187 | } 188 | 189 | int font_height = pkgi_text_height("M"); 190 | 191 | int y = PKGI_MENU_TOP_PADDING; 192 | for (uint32_t i = 0; i < PKGI_COUNTOF(menu_entries); i++) 193 | { 194 | const MenuEntry* entry = menu_entries + i; 195 | 196 | MenuType type = entry->type; 197 | if (type == MenuText) 198 | { 199 | y += font_height; 200 | } 201 | else if (type == MenuSearchClear && !menu_search_clear) 202 | { 203 | continue; 204 | } 205 | else if (type == MenuRefresh) 206 | { 207 | if (!menu_allow_refresh) 208 | { 209 | continue; 210 | } 211 | y += font_height; 212 | } 213 | 214 | uint32_t color = menu_selected == i ? PKGI_COLOR_TEXT_MENU_SELECTED : PKGI_COLOR_TEXT_MENU; 215 | 216 | int x = VITA_WIDTH - PKGI_MENU_WIDTH + PKGI_MENU_LEFT_PADDING; 217 | 218 | char text[64]; 219 | if (type == MenuSearch || type == MenuSearchClear || type == MenuText || type == MenuRefresh) 220 | { 221 | pkgi_strncpy(text, sizeof(text), entry->text); 222 | } 223 | else if (type == MenuSort) 224 | { 225 | if (menu_config.sort == (DbSort)entry->value) 226 | { 227 | pkgi_snprintf(text, sizeof(text), "%s %s", 228 | menu_config.order == SortAscending ? PKGI_UTF8_SORT_ASC : PKGI_UTF8_SORT_DESC, 229 | entry->text); 230 | } 231 | else 232 | { 233 | x += pkgi_text_width(PKGI_UTF8_SORT_ASC " "); 234 | pkgi_strncpy(text, sizeof(text), entry->text); 235 | } 236 | } 237 | else if (type == MenuFilter) 238 | { 239 | pkgi_snprintf(text, sizeof(text), "%s %s", 240 | menu_config.filter & entry->value ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF, 241 | entry->text); 242 | } 243 | pkgi_draw_text(x, y, color, text); 244 | 245 | y += font_height; 246 | } 247 | 248 | return 1; 249 | } 250 | -------------------------------------------------------------------------------- /pkgi_menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_db.h" 4 | 5 | typedef struct pkgi_input pkgi_input; 6 | 7 | typedef enum { 8 | MenuResultSearch, 9 | MenuResultSearchClear, 10 | MenuResultAccept, 11 | MenuResultCancel, 12 | MenuResultRefresh, 13 | } MenuResult; 14 | 15 | int pkgi_menu_is_open(void); 16 | void pkgi_menu_get(Config* config); 17 | MenuResult pkgi_menu_result(void); 18 | 19 | void pkgi_menu_start(int search_clear, const Config* config, int allow_update); 20 | 21 | int pkgi_do_menu(pkgi_input* input); 22 | -------------------------------------------------------------------------------- /pkgi_sha256.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_sha256.h" 2 | #include "pkgi.h" // just for memcpy 3 | 4 | #if __ARM_NEON__ 5 | 6 | #include 7 | 8 | // Optimized SHA-256 Neon implementation is based on following whitepaper from Intel: 9 | // "Fast SHA-256 Implementations on Intel(R) Architecture Processors" 10 | // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/sha-256-implementations-paper.pdf 11 | 12 | // It is ~2x faster on PlayStation Vita - ~23 MB/s 13 | 14 | #endif 15 | 16 | static const uint32_t sha256_K[64] GCC_ALIGN(16) = 17 | { 18 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 19 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 20 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 21 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 22 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 23 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 24 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 25 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 26 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 27 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 28 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 29 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 30 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 31 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 32 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 33 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, 34 | }; 35 | 36 | static inline uint32_t Ch(uint32_t x, uint32_t y, uint32_t z) 37 | { 38 | return z ^ (x & (y ^ z)); 39 | } 40 | 41 | static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) 42 | { 43 | return ((x | y) & z) | (x & y); 44 | } 45 | 46 | static inline uint32_t Sigma0(uint32_t x) 47 | { 48 | return ror32(x, 2) ^ ror32(x, 13) ^ ror32(x, 22); 49 | } 50 | 51 | static inline uint32_t Sigma1(uint32_t x) 52 | { 53 | return ror32(x, 6) ^ ror32(x, 11) ^ ror32(x, 25); 54 | } 55 | 56 | static inline uint32_t Gamma0(uint32_t x) 57 | { 58 | return ror32(x, 7) ^ ror32(x, 18) ^ (x >> 3); 59 | } 60 | 61 | static inline uint32_t Gamma1(uint32_t x) 62 | { 63 | return ror32(x, 17) ^ ror32(x, 19) ^ (x >> 10); 64 | } 65 | 66 | #define ROUND(tmp, a, b, c, d, e, f, g, h) do { \ 67 | uint32_t t = tmp; \ 68 | t += h + Sigma1(e) + Ch(e, f, g); \ 69 | d += t; \ 70 | t += Sigma0(a) + Maj(a, b, c); \ 71 | h = g; \ 72 | g = f; \ 73 | f = e; \ 74 | e = d; \ 75 | d = c; \ 76 | c = b; \ 77 | b = a; \ 78 | a = t; \ 79 | } while (0) 80 | 81 | #if __ARM_NEON__ 82 | 83 | #define ROUNDx4(x, n, a, b, c, d, e, f, g, h) do { \ 84 | uint32x4_t tmp; \ 85 | uint32_t arr[4]; \ 86 | tmp = vld1q_u32(sha256_K + n); \ 87 | tmp = vaddq_u32(tmp, x); \ 88 | vst1q_u32(arr, tmp); \ 89 | ROUND(arr[0], a, b, c, d, e, f, g, h); \ 90 | ROUND(arr[1], a, b, c, d, e, f, g, h); \ 91 | ROUND(arr[2], a, b, c, d, e, f, g, h); \ 92 | ROUND(arr[3], a, b, c, d, e, f, g, h); \ 93 | } while (0) 94 | 95 | #define PREPARE_NEXT() do { \ 96 | uint32x4_t q0, q1, q2, q3, q4, q5; \ 97 | uint32x2_t d0, d1, d2, d3, d4, d5, d6; \ 98 | \ 99 | q0 = vextq_u32(x2, x3, 1); \ 100 | q0 = vaddq_u32(q0, x0); \ 101 | \ 102 | q1 = vextq_u32(x0, x1, 1); \ 103 | q2 = vshrq_n_u32(q1, 7); \ 104 | q3 = vshlq_n_u32(q1, 32-7); \ 105 | q4 = vshrq_n_u32(q1, 18); \ 106 | q5 = vshlq_n_u32(q1, 32-18); \ 107 | q1 = vshrq_n_u32(q1, 3); \ 108 | q1 = veorq_u32(q1, q2); \ 109 | q2 = veorq_u32(q3, q4); \ 110 | q1 = veorq_u32(q1, q2); \ 111 | q1 = veorq_u32(q1, q5); \ 112 | \ 113 | d0 = vget_high_u32(x3); \ 114 | d1 = vshr_n_u32(d0, 17); \ 115 | d2 = vshl_n_u32(d0, 32-17); \ 116 | d3 = vshr_n_u32(d0, 19); \ 117 | d4 = vshl_n_u32(d0, 32-19); \ 118 | d5 = vshr_n_u32(d0, 10); \ 119 | d0 = veor_u32(d1, d2); \ 120 | d1 = veor_u32(d3, d4); \ 121 | d0 = veor_u32(d0, d1); \ 122 | d6 = veor_u32(d0, d5); \ 123 | \ 124 | d0 = vget_low_u32(q0); \ 125 | d1 = vget_low_u32(q1); \ 126 | d0 = vadd_u32(d0, d6); \ 127 | d0 = vadd_u32(d0, d1); \ 128 | \ 129 | d1 = vshr_n_u32(d0, 17); \ 130 | d2 = vshl_n_u32(d0, 32-17); \ 131 | d3 = vshr_n_u32(d0, 19); \ 132 | d4 = vshl_n_u32(d0, 32-19); \ 133 | d5 = vshr_n_u32(d0, 10); \ 134 | d0 = veor_u32(d1, d2); \ 135 | d1 = veor_u32(d3, d4); \ 136 | d0 = veor_u32(d0, d1); \ 137 | d0 = veor_u32(d0, d5); \ 138 | \ 139 | q0 = vaddq_u32(q0, q1); \ 140 | q1 = vcombine_u32(d6, d0); \ 141 | q0 = vaddq_u32(q0, q1); \ 142 | \ 143 | x0 = x1; \ 144 | x1 = x2; \ 145 | x2 = x3; \ 146 | x3 = q0; \ 147 | } while (0) 148 | 149 | static void sha256_process(uint32_t* state, const uint8_t* buffer, uint32_t blocks) 150 | { 151 | for (uint32_t i = 0; i < blocks; i++) 152 | { 153 | uint32_t a = state[0]; 154 | uint32_t b = state[1]; 155 | uint32_t c = state[2]; 156 | uint32_t d = state[3]; 157 | uint32_t e = state[4]; 158 | uint32_t f = state[5]; 159 | uint32_t g = state[6]; 160 | uint32_t h = state[7]; 161 | 162 | uint32x4_t x0 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(buffer + 0*16))); 163 | uint32x4_t x1 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(buffer + 1*16))); 164 | uint32x4_t x2 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(buffer + 2*16))); 165 | uint32x4_t x3 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(buffer + 3*16))); 166 | buffer += 64; 167 | 168 | // rounds [0..47] 169 | for (uint32_t r = 0; r < 48; r += 16) 170 | { 171 | ROUNDx4(x0, r + 0, a, b, c, d, e, f, g, h); 172 | PREPARE_NEXT(); 173 | 174 | ROUNDx4(x0, r + 4, a, b, c, d, e, f, g, h); 175 | PREPARE_NEXT(); 176 | 177 | ROUNDx4(x0, r + 8, a, b, c, d, e, f, g, h); 178 | PREPARE_NEXT(); 179 | 180 | ROUNDx4(x0, r + 12, a, b, c, d, e, f, g, h); 181 | PREPARE_NEXT(); 182 | } 183 | 184 | // rounds [48..63] 185 | ROUNDx4(x0, 48, a, b, c, d, e, f, g, h); 186 | ROUNDx4(x1, 52, a, b, c, d, e, f, g, h); 187 | ROUNDx4(x2, 56, a, b, c, d, e, f, g, h); 188 | ROUNDx4(x3, 60, a, b, c, d, e, f, g, h); 189 | 190 | state[0] += a; 191 | state[1] += b; 192 | state[2] += c; 193 | state[3] += d; 194 | state[4] += e; 195 | state[5] += f; 196 | state[6] += g; 197 | state[7] += h; 198 | } 199 | } 200 | 201 | #else 202 | 203 | static void sha256_process(uint32_t* state, const uint8_t* buffer, uint32_t blocks) 204 | { 205 | for (uint32_t i = 0; i < blocks; i++) 206 | { 207 | uint32_t w[64]; 208 | for (uint32_t r = 0; r < 16; r++) 209 | { 210 | w[r] = get32be(buffer + 4 * r); 211 | } 212 | for (uint32_t r = 16; r < 64; r++) 213 | { 214 | w[r] = Gamma1(w[r - 2]) + Gamma0(w[r - 15]) + w[r - 7] + w[r - 16]; 215 | } 216 | buffer += SHA256_BLOCK_SIZE; 217 | 218 | uint32_t a = state[0]; 219 | uint32_t b = state[1]; 220 | uint32_t c = state[2]; 221 | uint32_t d = state[3]; 222 | uint32_t e = state[4]; 223 | uint32_t f = state[5]; 224 | uint32_t g = state[6]; 225 | uint32_t h = state[7]; 226 | 227 | for (uint32_t r = 0; r < 64; r++) 228 | { 229 | ROUND(sha256_K[r] + w[r], a, b, c, d, e, f, g, h); 230 | } 231 | 232 | state[0] += a; 233 | state[1] += b; 234 | state[2] += c; 235 | state[3] += d; 236 | state[4] += e; 237 | state[5] += f; 238 | state[6] += g; 239 | state[7] += h; 240 | } 241 | } 242 | 243 | #endif 244 | 245 | void sha256_init(sha256_ctx* ctx) 246 | { 247 | ctx->count = 0; 248 | ctx->state[0] = 0x6a09e667; 249 | ctx->state[1] = 0xbb67ae85; 250 | ctx->state[2] = 0x3c6ef372; 251 | ctx->state[3] = 0xa54ff53a; 252 | ctx->state[4] = 0x510e527f; 253 | ctx->state[5] = 0x9b05688c; 254 | ctx->state[6] = 0x1f83d9ab; 255 | ctx->state[7] = 0x5be0cd19; 256 | } 257 | 258 | void sha256_update(sha256_ctx* ctx, const uint8_t* buffer, uint32_t size) 259 | { 260 | if (size == 0) 261 | { 262 | return; 263 | } 264 | 265 | uint32_t left = ctx->count % SHA256_BLOCK_SIZE; 266 | uint32_t fill = SHA256_BLOCK_SIZE - left; 267 | ctx->count += size; 268 | 269 | if (left && size >= fill) 270 | { 271 | pkgi_memcpy(ctx->buffer + left, buffer, fill); 272 | sha256_process(ctx->state, ctx->buffer, 1); 273 | buffer += fill; 274 | size -= fill; 275 | left = 0; 276 | } 277 | 278 | uint32_t full = size / SHA256_BLOCK_SIZE; 279 | if (full != 0) 280 | { 281 | sha256_process(ctx->state, buffer, full); 282 | uint32_t used = full * SHA256_BLOCK_SIZE; 283 | buffer += used; 284 | size -= used; 285 | } 286 | 287 | pkgi_memcpy(ctx->buffer + left, buffer, size); 288 | } 289 | 290 | void sha256_finish(sha256_ctx* ctx, uint8_t* digest) 291 | { 292 | static const uint8_t padding[SHA256_BLOCK_SIZE] = { 0x80 }; 293 | 294 | uint8_t bits[8]; 295 | set64be(bits, ctx->count * 8); 296 | 297 | uint32_t last = ctx->count % SHA256_BLOCK_SIZE; 298 | uint32_t pad = (last < SHA256_BLOCK_SIZE - 8) ? (SHA256_BLOCK_SIZE - 8 - last) : (2 * SHA256_BLOCK_SIZE - 8 - last); 299 | 300 | sha256_update(ctx, padding, pad); 301 | sha256_update(ctx, bits, sizeof(bits)); 302 | 303 | for (uint32_t i = 0; i < 8; i++) 304 | { 305 | set32be(digest + 4 * i, ctx->state[i]); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /pkgi_sha256.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_utils.h" 4 | 5 | #define SHA256_BLOCK_SIZE 64 6 | #define SHA256_DIGEST_SIZE 32 7 | 8 | typedef struct { 9 | uint8_t buffer[SHA256_BLOCK_SIZE] GCC_ALIGN(16); 10 | uint32_t state[8]; 11 | uint64_t count; 12 | } sha256_ctx; 13 | 14 | void sha256_init(sha256_ctx* ctx); 15 | void sha256_update(sha256_ctx* ctx, const uint8_t* buffer, uint32_t size); 16 | void sha256_finish(sha256_ctx* ctx, uint8_t* digest); 17 | -------------------------------------------------------------------------------- /pkgi_simulator.c: -------------------------------------------------------------------------------- 1 | #include "pkgi.h" 2 | #include "pkgi_style.h" 3 | 4 | #define INITGUID 5 | #define COBJMACROS 6 | #define WIN32_LEAN_AND_MEAN 7 | #define NOMINMAX 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #pragma comment (lib, "shlwapi.lib") 19 | #pragma comment (lib, "dwmapi.lib") 20 | #pragma comment (lib, "wininet.lib") 21 | #pragma comment (lib, "windowscodecs.lib") 22 | 23 | #define Assert(cond) do { if (!(cond)) __debugbreak(); } while (0) 24 | 25 | static HINTERNET g_inet; 26 | 27 | static HWND g_hwnd; 28 | static HDC g_dc; 29 | static HDC g_memdc; 30 | static HBRUSH g_brush; 31 | static HPEN g_pen; 32 | 33 | static LARGE_INTEGER g_time_freq; 34 | static LARGE_INTEGER g_time; 35 | 36 | static CRITICAL_SECTION g_dialog_lock; 37 | 38 | static char g_input_text[256]; 39 | static int g_input_text_active; 40 | 41 | static uint32_t g_button_frame_count; 42 | 43 | struct pkgi_http 44 | { 45 | HANDLE handle; 46 | uint64_t size; 47 | uint64_t offset; 48 | 49 | HINTERNET conn; 50 | }; 51 | 52 | static pkgi_http g_http[4]; 53 | 54 | #define PKGI_FOLDER "pkgi" 55 | #define PKGI_APP_FOLDER "app" 56 | 57 | #define PKGI_DIALOG_MESSAGE 1 58 | #define PKGI_DIALOG_ERROR 2 59 | #define PKGI_DIALOG_PROGRESS 3 60 | 61 | #define GDI_COLOR(c) RGB((c)&0xff, (c>>8)&0xff, (c>>16)&0xff) 62 | 63 | static LRESULT CALLBACK pkgi_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) 64 | { 65 | switch (msg) 66 | { 67 | case WM_DESTROY: 68 | PostQuitMessage(0); 69 | break; 70 | 71 | case WM_ERASEBKGND: 72 | return 1; 73 | 74 | case WM_PAINT: 75 | ValidateRect(hwnd, NULL); 76 | return 0; 77 | } 78 | 79 | return DefWindowProcW(hwnd, msg, wparam, lparam); 80 | } 81 | 82 | void pkgi_log(const char* msg, ...) 83 | { 84 | va_list args; 85 | va_start(args, msg); 86 | vprintf(msg, args); 87 | printf("\n"); 88 | va_end(args); 89 | } 90 | 91 | int pkgi_snprintf(char* buffer, uint32_t size, const char* msg, ...) 92 | { 93 | va_list args; 94 | va_start(args, msg); 95 | int len = vsnprintf(buffer, size - 1, msg, args); 96 | va_end(args); 97 | buffer[len] = 0; 98 | return len; 99 | } 100 | 101 | void pkgi_vsnprintf(char* buffer, uint32_t size, const char* msg, va_list args) 102 | { 103 | int len = vsnprintf(buffer, size - 1, msg, args); 104 | buffer[len] = 0; 105 | } 106 | 107 | char* pkgi_strstr(const char* str, const char* sub) 108 | { 109 | return StrStrA(str, sub); 110 | } 111 | 112 | int pkgi_stricontains(const char* str, const char* sub) 113 | { 114 | return StrStrIA(str, sub) != NULL; 115 | } 116 | 117 | int pkgi_stricmp(const char* a, const char* b) 118 | { 119 | return stricmp(a, b); 120 | } 121 | 122 | void pkgi_strncpy(char* dst, uint32_t size, const char* src) 123 | { 124 | strncpy(dst, src, size); 125 | } 126 | 127 | char* pkgi_strrchr(const char* str, char ch) 128 | { 129 | return strrchr(str, ch); 130 | } 131 | 132 | void pkgi_memcpy(void* dst, const void* src, uint32_t size) 133 | { 134 | memcpy(dst, src, size); 135 | } 136 | 137 | void pkgi_memmove(void* dst, const void* src, uint32_t size) 138 | { 139 | memmove(dst, src, size); 140 | } 141 | 142 | int pkgi_memequ(const void* a, const void* b, uint32_t size) 143 | { 144 | return memcmp(a, b, size) == 0; 145 | } 146 | 147 | int pkgi_is_unsafe_mode(void) 148 | { 149 | return 1; 150 | } 151 | 152 | int pkgi_ok_button(void) 153 | { 154 | return PKGI_BUTTON_X; 155 | } 156 | 157 | int pkgi_cancel_button(void) 158 | { 159 | return PKGI_BUTTON_O; 160 | } 161 | 162 | void pkgi_dialog_lock(void) 163 | { 164 | EnterCriticalSection(&g_dialog_lock); 165 | } 166 | 167 | void pkgi_dialog_unlock(void) 168 | { 169 | LeaveCriticalSection(&g_dialog_lock); 170 | } 171 | 172 | void pkgi_dialog_input_text(const char* title, const char* text) 173 | { 174 | printf("%s (default=%s): ", title, text); 175 | g_input_text_active = 1; 176 | } 177 | 178 | int pkgi_dialog_input_update(void) 179 | { 180 | if (g_input_text_active) 181 | { 182 | gets_s(g_input_text, sizeof(g_input_text)); 183 | g_input_text_active = 0; 184 | return g_input_text[0] != 0; 185 | } 186 | return 0; 187 | } 188 | 189 | void pkgi_dialog_input_get_text(char* text, uint32_t size) 190 | { 191 | strncpy(text, g_input_text, size); 192 | } 193 | 194 | void pkgi_start(void) 195 | { 196 | InitializeCriticalSection(&g_dialog_lock); 197 | 198 | HRESULT hr = CoInitializeEx(NULL, 0); 199 | Assert(SUCCEEDED(hr)); 200 | 201 | g_inet = InternetOpenW(L"pkgi", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); 202 | Assert(g_inet); 203 | 204 | WNDCLASSEXW wc = 205 | { 206 | .cbSize = sizeof(wc), 207 | .style = CS_OWNDC, 208 | .lpfnWndProc = pkgi_window_proc, 209 | .lpszClassName = L"pkg_simulator", 210 | }; 211 | 212 | ATOM atom = RegisterClassExW(&wc); 213 | Assert(atom); 214 | 215 | RECT r = { 0, 0, VITA_WIDTH, VITA_HEIGHT }; 216 | 217 | DWORD style = WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX); 218 | DWORD exstyle = WS_EX_APPWINDOW; 219 | BOOL ok = AdjustWindowRectEx(&r, style, FALSE, exstyle); 220 | Assert(ok); 221 | 222 | g_hwnd = CreateWindowExW( 223 | exstyle, wc.lpszClassName, L"pkgi simulator", style | WS_VISIBLE, 224 | CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, 225 | NULL, NULL, NULL, NULL); 226 | Assert(g_hwnd); 227 | 228 | g_dc = GetDC(g_hwnd); 229 | Assert(g_dc); 230 | 231 | BITMAPINFO bmi = 232 | { 233 | .bmiHeader = 234 | { 235 | .biSize = sizeof(bmi.bmiHeader), 236 | .biWidth = VITA_WIDTH, 237 | .biHeight = -VITA_HEIGHT, 238 | .biPlanes = 1, 239 | .biBitCount = 32, 240 | .biCompression = BI_RGB, 241 | }, 242 | }; 243 | 244 | g_memdc = CreateCompatibleDC(g_dc); 245 | Assert(g_memdc); 246 | 247 | void* bits = NULL; 248 | HBITMAP bmp = CreateDIBSection(g_memdc, &bmi, 0, &bits, NULL, 0); 249 | Assert(bmp); 250 | 251 | SelectObject(g_memdc, bmp); 252 | 253 | AddFontResourceW(L"ltn0.otf"); 254 | 255 | LOGFONTW log = 256 | { 257 | .lfHeight = -18, 258 | .lfWeight = FW_NORMAL, 259 | .lfOutPrecision = OUT_TT_ONLY_PRECIS, 260 | .lfQuality = CLEARTYPE_QUALITY, 261 | .lfFaceName = L"SCE Rodin Cattleya LATIN", 262 | }; 263 | 264 | HFONT font = CreateFontIndirectW(&log); 265 | Assert(font); 266 | 267 | g_brush = GetStockObject(DC_BRUSH); 268 | g_pen = GetStockObject(DC_PEN); 269 | SelectObject(g_memdc, font); 270 | SelectObject(g_memdc, g_brush); 271 | SelectObject(g_memdc, g_pen); 272 | SetBkMode(g_memdc, TRANSPARENT); 273 | 274 | QueryPerformanceFrequency(&g_time_freq); 275 | QueryPerformanceCounter(&g_time); 276 | } 277 | 278 | int pkgi_update(pkgi_input* input) 279 | { 280 | MSG msg; 281 | while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 282 | { 283 | if (msg.message == WM_QUIT) 284 | { 285 | return 0; 286 | } 287 | TranslateMessage(&msg); 288 | DispatchMessageW(&msg); 289 | } 290 | 291 | uint32_t previous = input->down; 292 | 293 | input->down = 0; 294 | if (GetActiveWindow() == g_hwnd) 295 | { 296 | if (GetAsyncKeyState(VK_UP) >> 15) input->down |= PKGI_BUTTON_UP; 297 | if (GetAsyncKeyState(VK_DOWN) >> 15) input->down |= PKGI_BUTTON_DOWN; 298 | if (GetAsyncKeyState(VK_LEFT) >> 15) input->down |= PKGI_BUTTON_LEFT; 299 | if (GetAsyncKeyState(VK_RIGHT) >> 15) input->down |= PKGI_BUTTON_RIGHT; 300 | if (GetAsyncKeyState(VK_END) >> 15) input->down |= PKGI_BUTTON_X; 301 | if (GetAsyncKeyState(VK_NEXT) >> 15) input->down |= PKGI_BUTTON_O; 302 | if (GetAsyncKeyState(VK_HOME) >> 15) input->down |= PKGI_BUTTON_T; 303 | if (GetAsyncKeyState(VK_DELETE) >> 15) input->down |= PKGI_BUTTON_S; 304 | if (GetAsyncKeyState(VK_RETURN) >> 15) input->down |= PKGI_BUTTON_START; 305 | if (GetAsyncKeyState(VK_SPACE) >> 15) input->down |= PKGI_BUTTON_SELECT; 306 | if (GetAsyncKeyState(VK_OEM_6) >> 15) input->down |= PKGI_BUTTON_RT; // [ 307 | if (GetAsyncKeyState(VK_OEM_4) >> 15) input->down |= PKGI_BUTTON_LT; // ] 308 | } 309 | 310 | input->pressed = input->down & ~previous; 311 | input->active = input->pressed; 312 | 313 | if (input->down == previous) 314 | { 315 | if (g_button_frame_count >= 10) 316 | { 317 | input->active = input->down; 318 | } 319 | g_button_frame_count++; 320 | } 321 | else 322 | { 323 | g_button_frame_count = 0; 324 | } 325 | 326 | LARGE_INTEGER time; 327 | QueryPerformanceCounter(&time); 328 | input->delta = 1000000 * (time.QuadPart - g_time.QuadPart) / g_time_freq.QuadPart; 329 | g_time = time; 330 | 331 | return 1; 332 | } 333 | 334 | void pkgi_swap(void) 335 | { 336 | BitBlt(g_dc, 0, 0, VITA_WIDTH, VITA_HEIGHT, g_memdc, 0, 0, SRCCOPY); 337 | DwmFlush(); 338 | } 339 | 340 | void pkgi_end(void) 341 | { 342 | InternetCloseHandle(g_inet); 343 | DeleteCriticalSection(&g_dialog_lock); 344 | } 345 | 346 | int pkgi_battery_present() 347 | { 348 | return 1; 349 | } 350 | 351 | int pkgi_bettery_get_level() 352 | { 353 | return 66; 354 | } 355 | 356 | int pkgi_battery_is_low() 357 | { 358 | return 0; 359 | } 360 | 361 | int pkgi_battery_is_charging() 362 | { 363 | return 0; 364 | } 365 | 366 | uint64_t pkgi_get_free_space(void) 367 | { 368 | ULARGE_INTEGER available; 369 | BOOL ok = GetDiskFreeSpaceExW(NULL, &available, NULL, NULL); 370 | Assert(ok); 371 | return available.QuadPart; 372 | } 373 | 374 | const char* pkgi_get_config_folder(void) 375 | { 376 | return PKGI_FOLDER; 377 | } 378 | 379 | const char* pkgi_get_temp_folder(void) 380 | { 381 | return PKGI_FOLDER; 382 | } 383 | 384 | const char* pkgi_get_app_folder(void) 385 | { 386 | return PKGI_APP_FOLDER; 387 | } 388 | 389 | int pkgi_is_incomplete(const char* titleid) 390 | { 391 | char path[256]; 392 | pkgi_snprintf(path, sizeof(path), "%s/%s.resume", pkgi_get_temp_folder(), titleid); 393 | 394 | WCHAR wpath[MAX_PATH]; 395 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 396 | 397 | DWORD attrib = GetFileAttributesW(wpath); 398 | if (attrib == INVALID_FILE_ATTRIBUTES) 399 | { 400 | DWORD err = GetLastError(); 401 | if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) 402 | { 403 | return 0; 404 | } 405 | Assert(0); 406 | } 407 | 408 | return !!(attrib & FILE_ATTRIBUTE_ARCHIVE); 409 | } 410 | 411 | int pkgi_is_installed(const char* titleid) 412 | { 413 | char path[256]; 414 | pkgi_snprintf(path, sizeof(path), "%s/%s", pkgi_get_app_folder(), titleid); 415 | 416 | WCHAR wpath[MAX_PATH]; 417 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 418 | 419 | DWORD attrib = GetFileAttributesW(wpath); 420 | if (attrib == INVALID_FILE_ATTRIBUTES) 421 | { 422 | DWORD err = GetLastError(); 423 | if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) 424 | { 425 | return 0; 426 | } 427 | Assert(0); 428 | } 429 | 430 | return !!(attrib & FILE_ATTRIBUTE_DIRECTORY); 431 | } 432 | 433 | int pkgi_install(const char* titleid) 434 | { 435 | Sleep(2000); 436 | 437 | CreateDirectoryW(L"app", NULL); 438 | 439 | char src[256]; 440 | pkgi_snprintf(src, sizeof(src), "%s/%.9s", pkgi_get_temp_folder(), titleid); 441 | char dst[256]; 442 | pkgi_snprintf(dst, sizeof(dst), "%s/%.9s", pkgi_get_app_folder(), titleid); 443 | 444 | WCHAR wsrc[MAX_PATH]; 445 | MultiByteToWideChar(CP_UTF8, 0, src, -1, wsrc, MAX_PATH); 446 | WCHAR wdst[MAX_PATH]; 447 | MultiByteToWideChar(CP_UTF8, 0, dst, -1, wdst, MAX_PATH); 448 | 449 | return MoveFileW(wsrc, wdst) == FALSE ? 0 : 1; 450 | } 451 | 452 | uint32_t pkgi_time_msec() 453 | { 454 | return GetTickCount(); 455 | } 456 | 457 | static DWORD WINAPI pkgi_win32_thread(void* arg) 458 | { 459 | pkgi_thread_entry* start = arg; 460 | start(); 461 | return 0; 462 | } 463 | 464 | void pkgi_start_thread(const char* name, pkgi_thread_entry* start) 465 | { 466 | PKGI_UNUSED(name); 467 | HANDLE h = CreateThread(NULL, 0, &pkgi_win32_thread, start, 0, NULL); 468 | Assert(h); 469 | } 470 | 471 | void pkgi_sleep(uint32_t msec) 472 | { 473 | Sleep(msec); 474 | } 475 | 476 | int pkgi_load(const char* name, void* data, uint32_t max) 477 | { 478 | WCHAR wname[MAX_PATH]; 479 | MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH); 480 | 481 | HANDLE f = CreateFileW(wname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 482 | if (f == INVALID_HANDLE_VALUE) 483 | { 484 | return -1; 485 | } 486 | 487 | DWORD read; 488 | BOOL ok = ReadFile(f, data, max, &read, NULL); 489 | CloseHandle(f); 490 | 491 | return ok ? read : -1; 492 | } 493 | 494 | int pkgi_save(const char* name, const void* data, uint32_t size) 495 | { 496 | WCHAR wname[MAX_PATH]; 497 | MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH); 498 | 499 | HANDLE f = CreateFileW(wname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); 500 | if (f == INVALID_HANDLE_VALUE) 501 | { 502 | return 0; 503 | } 504 | 505 | DWORD written; 506 | BOOL ok = WriteFile(f, data, size, &written, NULL); 507 | CloseHandle(f); 508 | 509 | return ok && written == size; 510 | } 511 | 512 | void pkgi_lock_process(void) 513 | { 514 | } 515 | 516 | void pkgi_unlock_process(void) 517 | { 518 | } 519 | 520 | pkgi_texture pkgi_load_png_raw(const void* data, uint32_t size) 521 | { 522 | (void)size; 523 | 524 | HBITMAP bmp = NULL; 525 | 526 | WCHAR exepath[MAX_PATH]; 527 | GetModuleFileNameW(NULL, exepath, MAX_PATH); 528 | WCHAR* last = StrRChrIW(exepath, NULL, L'\\'); 529 | *last = 0; 530 | 531 | WCHAR path[MAX_PATH]; 532 | int cnt = wsprintfW(path, L"%s\\..\\..\\..\\assets\\", exepath); 533 | 534 | MultiByteToWideChar(CP_UTF8, 0, data, -1, path + cnt, MAX_PATH - cnt); 535 | 536 | HRESULT hr; 537 | 538 | IWICImagingFactory* factory; 539 | hr = CoCreateInstance(&CLSID_WICImagingFactory, 0, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, (LPVOID*)&factory); 540 | if (SUCCEEDED(hr)) 541 | { 542 | IWICBitmapDecoder* decoder; 543 | hr = IWICImagingFactory_CreateDecoderFromFilename(factory, path, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder); 544 | if (SUCCEEDED(hr)) 545 | { 546 | IWICBitmapFrameDecode* frame; 547 | hr = IWICBitmapDecoder_GetFrame(decoder, 0, &frame); 548 | if (SUCCEEDED(hr)) 549 | { 550 | IWICFormatConverter* converter; 551 | hr = IWICImagingFactory_CreateFormatConverter(factory, &converter); 552 | if (SUCCEEDED(hr)) 553 | { 554 | hr = IWICFormatConverter_Initialize(converter, (IWICBitmapSource*)frame, &GUID_WICPixelFormat32bppBGRA, 555 | WICBitmapDitherTypeNone, 0, 0.0, WICBitmapPaletteTypeCustom); 556 | if (SUCCEEDED(hr)) 557 | { 558 | UINT width, height; 559 | hr = IWICFormatConverter_GetSize(converter, &width, &height); 560 | if (SUCCEEDED(hr)) 561 | { 562 | BITMAPINFO bmi = 563 | { 564 | .bmiHeader = 565 | { 566 | .biSize = sizeof(bmi.bmiHeader), 567 | .biWidth = width, 568 | .biHeight = -(LONG)height, 569 | .biPlanes = 1, 570 | .biBitCount = 32, 571 | .biCompression = BI_RGB, 572 | }, 573 | }; 574 | 575 | HDC dc = GetDC(NULL); 576 | Assert(dc); 577 | 578 | void* bits; 579 | bmp = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); 580 | Assert(bmp); 581 | 582 | ReleaseDC(NULL, dc); 583 | 584 | hr = IWICFormatConverter_CopyPixels(converter, 0, width * 4, width * height * 4, bits); 585 | Assert(SUCCEEDED(hr)); 586 | } 587 | } 588 | IWICFormatConverter_Release(converter); 589 | } 590 | IWICBitmapFrameDecode_Release(frame); 591 | } 592 | IWICBitmapDecoder_Release(decoder); 593 | } 594 | IWICImagingFactory_Release(factory); 595 | } 596 | 597 | Assert(bmp); 598 | 599 | return (pkgi_texture)bmp; 600 | } 601 | 602 | void pkgi_draw_texture(pkgi_texture texture, int x, int y) 603 | { 604 | HBITMAP bmp = (HBITMAP)texture; 605 | BITMAP bitmap; 606 | GetObjectW(bmp, sizeof(bitmap), &bitmap); 607 | 608 | HDC dc = CreateCompatibleDC(NULL); 609 | SelectObject(dc, bmp); 610 | 611 | BitBlt(g_memdc, x, y, bitmap.bmWidth, bitmap.bmHeight, dc, 0, 0, SRCCOPY); 612 | 613 | DeleteObject(dc); 614 | } 615 | 616 | void pkgi_clip_set(int x, int y, int w, int h) 617 | { 618 | SelectClipRgn(g_memdc, NULL); 619 | IntersectClipRect(g_memdc, x, y, x + w, y + h); 620 | } 621 | 622 | void pkgi_clip_remove(void) 623 | { 624 | SelectClipRgn(g_memdc, NULL); 625 | } 626 | 627 | void pkgi_draw_rect(int x, int y, int w, int h, uint32_t color) 628 | { 629 | RECT rect = { x, y, x + w, y + h }; 630 | 631 | SetDCBrushColor(g_memdc, GDI_COLOR(color)); 632 | FillRect(g_memdc, &rect, g_brush); 633 | } 634 | 635 | void pkgi_draw_text(int x, int y, uint32_t color, const char* text) 636 | { 637 | WCHAR wtext[1024]; 638 | int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext)); 639 | 640 | SetTextColor(g_memdc, GDI_COLOR(color)); 641 | ExtTextOutW(g_memdc, x, y, 0, NULL, wtext, wlen, NULL); 642 | } 643 | 644 | int pkgi_text_width(const char* text) 645 | { 646 | WCHAR wtext[1024]; 647 | int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext)); 648 | 649 | RECT r = { 0, 0, 65536, 65536 }; 650 | DrawTextW(g_memdc, wtext, wlen, &r, DT_CALCRECT | DT_NOCLIP); 651 | 652 | return r.right - r.left; 653 | } 654 | 655 | int pkgi_text_height(const char* text) 656 | { 657 | WCHAR wtext[1024]; 658 | int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext)); 659 | 660 | RECT r = { 0, 0, 65536, 65536 }; 661 | DrawTextW(g_memdc, wtext, wlen, &r, DT_CALCRECT | DT_NOCLIP); 662 | 663 | return r.bottom - r.top; 664 | } 665 | 666 | pkgi_http* pkgi_http_get(const char* url, const char* content, uint64_t offset) 667 | { 668 | pkgi_http* http = NULL; 669 | for (size_t i = 0; i < 4; i++) 670 | { 671 | if (g_http[i].handle == NULL && g_http[i].conn == NULL) 672 | { 673 | http = g_http + i; 674 | break; 675 | } 676 | } 677 | 678 | if (http == NULL) 679 | { 680 | LOG("too many simultaneous http requests"); 681 | return NULL; 682 | } 683 | 684 | HANDLE handle = INVALID_HANDLE_VALUE; 685 | if (content) 686 | { 687 | char path[MAX_PATH]; 688 | strcpy(path, pkgi_get_temp_folder()); 689 | strcat(path, strrchr(url, '/')); 690 | 691 | WCHAR wpath[MAX_PATH]; 692 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 693 | 694 | handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 695 | if (handle == INVALID_HANDLE_VALUE) 696 | { 697 | LOG("%s not found, trying shorter path", path); 698 | pkgi_snprintf(path, sizeof(path), "%s/%s.pkg", pkgi_get_temp_folder(), content); 699 | 700 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 701 | handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 702 | } 703 | } 704 | 705 | if (handle == INVALID_HANDLE_VALUE) 706 | { 707 | WCHAR headers[256]; 708 | if (offset != 0) 709 | { 710 | wsprintfW(headers, L"Range: bytes=%I64u-", offset); 711 | } 712 | 713 | WCHAR wurl[MAX_PATH]; 714 | MultiByteToWideChar(CP_UTF8, 0, url, -1, wurl, MAX_PATH); 715 | 716 | DWORD flags = INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_SECURE; 717 | HINTERNET conn = InternetOpenUrlW(g_inet, wurl, offset ? headers : NULL, (DWORD)-1, flags, 0); 718 | if (conn) 719 | { 720 | http->conn = conn; 721 | } 722 | else 723 | { 724 | http = NULL; 725 | } 726 | } 727 | else 728 | { 729 | LARGE_INTEGER size; 730 | BOOL ok = GetFileSizeEx(handle, &size); 731 | Assert(ok); 732 | 733 | if (offset != 0) 734 | { 735 | LARGE_INTEGER seek = { .QuadPart = offset }; 736 | ok = SetFilePointerEx(handle, seek, NULL, FILE_BEGIN); 737 | Assert(ok); 738 | } 739 | 740 | http->handle = handle; 741 | http->size = size.QuadPart - offset; 742 | http->offset = 0; 743 | } 744 | 745 | Sleep(300); 746 | 747 | return http; 748 | } 749 | 750 | int pkgi_http_response_length(pkgi_http* http, int64_t* length) 751 | { 752 | if (http->conn) 753 | { 754 | DWORD status; 755 | DWORD status_len = sizeof(status); 756 | if (HttpQueryInfoA(http->conn, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &status_len, NULL)) 757 | { 758 | LOG("http status code = %d", status); 759 | 760 | if (status == 200 || status == 206) 761 | { 762 | char str[64]; 763 | DWORD len = sizeof(str); 764 | if (HttpQueryInfoA(http->conn, HTTP_QUERY_CONTENT_LENGTH, str, &len, NULL)) 765 | { 766 | sscanf(str, "%I64d", length); 767 | LOG("http response length = %lld", length); 768 | return 1; 769 | } 770 | else 771 | { 772 | if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND) 773 | { 774 | LOG("http response has no content length (or chunked encoding)"); 775 | *length = 0; 776 | return 1; 777 | } 778 | 779 | LOG("error retrieving content length response header"); 780 | return 0; 781 | } 782 | } 783 | 784 | return 0; 785 | } 786 | else 787 | { 788 | LOG("cannot get http status code"); 789 | return 0; 790 | } 791 | } 792 | else 793 | { 794 | *length = (int64_t)http->size; 795 | return 1; 796 | } 797 | } 798 | 799 | int pkgi_http_read(pkgi_http* http, void* buffer, uint32_t size) 800 | { 801 | DWORD read; 802 | 803 | if (http->conn) 804 | { 805 | if (!InternetReadFile(http->conn, buffer, size, &read)) 806 | { 807 | return -(int)GetLastError(); 808 | } 809 | return read; 810 | } 811 | else 812 | { 813 | if (size > http->size - http->offset) 814 | { 815 | size = (uint32_t)(http->size - http->offset); 816 | } 817 | 818 | //if (size > 1024) 819 | //{ 820 | // uint32_t temp = 1024 + (rand()%102400); 821 | // if (temp < size) 822 | // { 823 | // size = temp; 824 | // } 825 | //} 826 | 827 | BOOL ok = ReadFile(http->handle, buffer, size, &read, NULL); 828 | if (!ok) 829 | { 830 | return -1; 831 | } 832 | http->offset += read; 833 | 834 | Sleep(1); 835 | } 836 | 837 | return read; 838 | } 839 | 840 | void pkgi_http_close(pkgi_http* http) 841 | { 842 | if (http->conn) 843 | { 844 | InternetCloseHandle(http->conn); 845 | http->conn = NULL; 846 | } 847 | else 848 | { 849 | CloseHandle(http->handle); 850 | http->handle = NULL; 851 | } 852 | } 853 | 854 | int pkgi_mkdirs(char* path) 855 | { 856 | WCHAR wpath[MAX_PATH]; 857 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 858 | 859 | WCHAR* ptr = wpath; 860 | while (*ptr) 861 | { 862 | while (*ptr && *ptr != L'/') 863 | { 864 | ptr++; 865 | } 866 | WCHAR last = *ptr; 867 | *ptr = 0; 868 | BOOL ok = CreateDirectoryW(wpath, NULL); 869 | if (!ok && GetLastError() != ERROR_ALREADY_EXISTS) 870 | { 871 | return 0; 872 | } 873 | if (last == 0) 874 | { 875 | break; 876 | } 877 | *ptr++ = last; 878 | } 879 | 880 | // Sleep(10); 881 | return 1; 882 | } 883 | 884 | void pkgi_rm(const char* file) 885 | { 886 | WCHAR wpath[MAX_PATH]; 887 | MultiByteToWideChar(CP_UTF8, 0, file, -1, wpath, MAX_PATH); 888 | 889 | if (!DeleteFileW(wpath)) 890 | { 891 | LOG("cannot delete %s file", file); 892 | } 893 | } 894 | 895 | int64_t pkgi_get_size(const char* path) 896 | { 897 | WCHAR wpath[MAX_PATH]; 898 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 899 | 900 | WIN32_FILE_ATTRIBUTE_DATA data; 901 | if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &data)) 902 | { 903 | Sleep(10); 904 | return ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow; 905 | } 906 | 907 | return -1; 908 | } 909 | 910 | void* pkgi_create(const char* path) 911 | { 912 | WCHAR wpath[MAX_PATH]; 913 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 914 | 915 | HANDLE f = CreateFileW(wpath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 916 | if (f == INVALID_HANDLE_VALUE) 917 | { 918 | return NULL; 919 | } 920 | 921 | return f; 922 | } 923 | 924 | void* pkgi_append(const char* path) 925 | { 926 | WCHAR wpath[MAX_PATH]; 927 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 928 | 929 | HANDLE f = CreateFileW(wpath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 930 | if (f == INVALID_HANDLE_VALUE) 931 | { 932 | return NULL; 933 | } 934 | 935 | SetFilePointer(f, 0, NULL, FILE_END); 936 | 937 | return f; 938 | } 939 | 940 | void*pkgi_openrw(const char* path) 941 | { 942 | WCHAR wpath[MAX_PATH]; 943 | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); 944 | 945 | HANDLE f = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 946 | if (f == INVALID_HANDLE_VALUE) 947 | { 948 | return NULL; 949 | } 950 | 951 | return f; 952 | } 953 | 954 | int pkgi_read(void* f, void* buffer, uint32_t size) 955 | { 956 | DWORD read; 957 | BOOL ok = ReadFile(f, buffer, size, &read, NULL); 958 | return ok ? read : -1; 959 | } 960 | 961 | int pkgi_write(void* f, const void* buffer, uint32_t size) 962 | { 963 | DWORD written; 964 | BOOL ok = WriteFile(f, buffer, size, &written, NULL); 965 | return ok && written == size; 966 | } 967 | 968 | void pkgi_close(void* f) 969 | { 970 | CloseHandle(f); 971 | } 972 | -------------------------------------------------------------------------------- /pkgi_style.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VITA_WIDTH 960 4 | #define VITA_HEIGHT 544 5 | 6 | #define PKGI_COLOR(r,g,b) ((r)|((g)<<8)|((b)<<16)) 7 | 8 | #define PKGI_UTF8_X "\xe2\x95\xb3" // 0x2573 9 | #define PKGI_UTF8_O "\xe2\x97\x8b" // 0x25cb 10 | #define PKGI_UTF8_T "\xe2\x96\xb3" // 0x25b3 11 | #define PKGI_UTF8_S "\xe2\x96\xa1" // 0x25a1 12 | 13 | #define PKGI_UTF8_INSTALLED "\xe2\x97\x8f" // 0x25cf 14 | #define PKGI_UTF8_PARTIAL "\xe2\x97\x8b" // 0x25cb 15 | 16 | #define PKGI_UTF8_B "B" 17 | #define PKGI_UTF8_KB "K" // "\xe3\x8e\x85" // 0x3385 18 | #define PKGI_UTF8_MB "M" // "\xe3\x8e\x86" // 0x3386 19 | #define PKGI_UTF8_GB "G" // "\xe3\x8e\x87" // 0x3387 20 | 21 | #define PKGI_UTF8_CLEAR "\xc3\x97" // 0x00d7 22 | 23 | #define PKGI_UTF8_SORT_ASC "\xe2\x96\xb2" // 0x25b2 24 | #define PKGI_UTF8_SORT_DESC "\xe2\x96\xbc" // 0x25bc 25 | 26 | #define PKGI_UTF8_CHECK_ON "\xe2\x97\x8f" // 0x25cf 27 | #define PKGI_UTF8_CHECK_OFF "\xe2\x97\x8b" // 0x25cb 28 | 29 | #define PKGI_COLOR_DIALOG_BACKGROUND PKGI_COLOR(48, 48, 48) 30 | #define PKGI_COLOR_MENU_BACKGROUND PKGI_COLOR(48, 48, 48) 31 | #define PKGI_COLOR_TEXT_MENU PKGI_COLOR(255, 255, 255) 32 | #define PKGI_COLOR_TEXT_MENU_SELECTED PKGI_COLOR(0, 255, 0) 33 | #define PKGI_COLOR_TEXT PKGI_COLOR(255, 255, 255) 34 | #define PKGI_COLOR_TEXT_HEAD PKGI_COLOR(255, 255, 255) 35 | #define PKGI_COLOR_TEXT_TAIL PKGI_COLOR(255, 255, 255) 36 | #define PKGI_COLOR_TEXT_DIALOG PKGI_COLOR(255, 255, 255) 37 | #define PKGI_COLOR_TEXT_ERROR PKGI_COLOR(255, 50, 50) 38 | #define PKGI_COLOR_HLINE PKGI_COLOR(200, 200, 200) 39 | #define PKGI_COLOR_SCROLL_BAR PKGI_COLOR(255, 255, 255) 40 | #define PKGI_COLOR_BATTERY_LOW PKGI_COLOR(255, 50, 50) 41 | #define PKGI_COLOR_BATTERY_CHARGING PKGI_COLOR(50, 255, 50) 42 | #define PKGI_COLOR_SELECTED_BACKGROUND PKGI_COLOR(100, 100, 100) 43 | #define PKGI_COLOR_PROGRESS_BACKGROUND PKGI_COLOR(128, 128, 128) 44 | #define PKGI_COLOR_PROGRESS_BAR PKGI_COLOR(128, 255, 0) 45 | 46 | #define PKGI_ANIMATION_SPEED 4000 // px/second 47 | 48 | #define PKGI_MAIN_COLUMN_PADDING 10 49 | #define PKGI_MAIN_HLINE_EXTRA 5 50 | #define PKGI_MAIN_ROW_PADDING 2 51 | #define PKGI_MAIN_HLINE_HEIGHT 2 52 | #define PKGI_MAIN_TEXT_PADDING 5 53 | #define PKGI_MAIN_SCROLL_WIDTH 2 54 | #define PKGI_MAIN_SCROLL_PADDING 2 55 | #define PKGI_MAIN_SCROLL_MIN_HEIGHT 50 56 | 57 | #define PKGI_DIALOG_HMARGIN 100 58 | #define PKGI_DIALOG_VMARGIN 150 59 | #define PKGI_DIALOG_PADDING 30 60 | #define PKGI_DIALOG_WIDTH (VITA_WIDTH - 2*PKGI_DIALOG_HMARGIN) 61 | #define PKGI_DIALOG_HEIGHT (VITA_HEIGHT - 2*PKGI_DIALOG_VMARGIN) 62 | 63 | #define PKGI_DIALOG_PROCESS_BAR_HEIGHT 10 64 | #define PKGI_DIALOG_PROCESS_BAR_PADDING 10 65 | #define PKGI_DIALOG_PROCESS_BAR_CHUNK 200 66 | 67 | #define PKGI_MENU_WIDTH 150 68 | #define PKGI_MENU_LEFT_PADDING 20 69 | #define PKGI_MENU_TOP_PADDING 50 70 | -------------------------------------------------------------------------------- /pkgi_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef _MSC_VER 6 | #define GCC_ALIGN(n) 7 | #else 8 | #define GCC_ALIGN(n) __attribute__((aligned(n))) 9 | #endif 10 | 11 | static inline uint8_t byte32(uint32_t x, int n) 12 | { 13 | return (uint8_t)(x >> (8 * n)); 14 | } 15 | 16 | static inline uint32_t min32(uint32_t a, uint32_t b) 17 | { 18 | return a < b ? a : b; 19 | } 20 | 21 | static inline uint64_t min64(uint64_t a, uint64_t b) 22 | { 23 | return a < b ? a : b; 24 | } 25 | 26 | static inline uint32_t max32(uint32_t a, uint32_t b) 27 | { 28 | return a > b ? a : b; 29 | } 30 | 31 | static inline uint64_t max64(uint64_t a, uint64_t b) 32 | { 33 | return a > b ? a : b; 34 | } 35 | 36 | static inline uint32_t ror32(uint32_t x, int n) 37 | { 38 | return (x >> n) | (x << (32 - n)); 39 | } 40 | 41 | static inline uint16_t get16le(const uint8_t* bytes) 42 | { 43 | return (bytes[0]) | (bytes[1] << 8); 44 | } 45 | 46 | static inline uint32_t get32le(const uint8_t* bytes) 47 | { 48 | return (bytes[0]) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); 49 | } 50 | 51 | static inline uint64_t get64le(const uint8_t* bytes) 52 | { 53 | return (uint64_t)bytes[0] 54 | | ((uint64_t)bytes[1] << 8) 55 | | ((uint64_t)bytes[2] << 16) 56 | | ((uint64_t)bytes[3] << 24) 57 | | ((uint64_t)bytes[4] << 32) 58 | | ((uint64_t)bytes[5] << 40) 59 | | ((uint64_t)bytes[6] << 48) 60 | | ((uint64_t)bytes[7] << 56); 61 | } 62 | 63 | static inline uint16_t get16be(const uint8_t* bytes) 64 | { 65 | return (bytes[1]) | (bytes[0] << 8); 66 | } 67 | 68 | static inline uint32_t get32be(const uint8_t* bytes) 69 | { 70 | return (bytes[3]) | (bytes[2] << 8) | (bytes[1] << 16) | (bytes[0] << 24); 71 | } 72 | 73 | static inline uint64_t get64be(const uint8_t* bytes) 74 | { 75 | return (uint64_t)bytes[7] 76 | | ((uint64_t)bytes[6] << 8) 77 | | ((uint64_t)bytes[5] << 16) 78 | | ((uint64_t)bytes[4] << 24) 79 | | ((uint64_t)bytes[3] << 32) 80 | | ((uint64_t)bytes[2] << 40) 81 | | ((uint64_t)bytes[1] << 48) 82 | | ((uint64_t)bytes[0] << 56); 83 | } 84 | 85 | static inline void set16le(uint8_t* bytes, uint16_t x) 86 | { 87 | bytes[0] = (uint8_t)x; 88 | bytes[1] = (uint8_t)(x >> 8); 89 | } 90 | 91 | static inline void set32le(uint8_t* bytes, uint32_t x) 92 | { 93 | bytes[0] = (uint8_t)x; 94 | bytes[1] = (uint8_t)(x >> 8); 95 | bytes[2] = (uint8_t)(x >> 16); 96 | bytes[3] = (uint8_t)(x >> 24); 97 | } 98 | 99 | static inline void set64le(uint8_t* bytes, uint64_t x) 100 | { 101 | bytes[0] = (uint8_t)x; 102 | bytes[1] = (uint8_t)(x >> 8); 103 | bytes[2] = (uint8_t)(x >> 16); 104 | bytes[3] = (uint8_t)(x >> 24); 105 | bytes[4] = (uint8_t)(x >> 32); 106 | bytes[5] = (uint8_t)(x >> 40); 107 | bytes[6] = (uint8_t)(x >> 48); 108 | bytes[7] = (uint8_t)(x >> 56); 109 | } 110 | 111 | static inline void set16be(uint8_t* bytes, uint16_t x) 112 | { 113 | bytes[0] = (uint8_t)(x >> 8); 114 | bytes[1] = (uint8_t)x; 115 | } 116 | 117 | static inline void set32be(uint8_t* bytes, uint32_t x) 118 | { 119 | bytes[0] = (uint8_t)(x >> 24); 120 | bytes[1] = (uint8_t)(x >> 16); 121 | bytes[2] = (uint8_t)(x >> 8); 122 | bytes[3] = (uint8_t)x; 123 | } 124 | 125 | static inline void set64be(uint8_t* bytes, uint64_t x) 126 | { 127 | bytes[0] = (uint8_t)(x >> 56); 128 | bytes[1] = (uint8_t)(x >> 48); 129 | bytes[2] = (uint8_t)(x >> 40); 130 | bytes[3] = (uint8_t)(x >> 32); 131 | bytes[4] = (uint8_t)(x >> 24); 132 | bytes[5] = (uint8_t)(x >> 16); 133 | bytes[6] = (uint8_t)(x >> 8); 134 | bytes[7] = (uint8_t)x; 135 | } 136 | -------------------------------------------------------------------------------- /pkgi_vita.c: -------------------------------------------------------------------------------- 1 | #include "pkgi.h" 2 | #include "pkgi_style.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | static vita2d_pgf* g_font; 30 | 31 | static SceKernelLwMutexWork g_dialog_lock; 32 | static volatile int g_power_lock; 33 | 34 | static int g_ok_button; 35 | static int g_cancel_button; 36 | static uint32_t g_button_frame_count; 37 | 38 | static SceUInt64 g_time; 39 | 40 | static char g_pkgi_folder[32]; 41 | 42 | #ifdef PKGI_ENABLE_LOGGING 43 | static int g_log_socket; 44 | #endif 45 | 46 | #define ANALOG_CENTER 128 47 | #define ANALOG_THRESHOLD 64 48 | #define ANALOG_SENSITIVITY 16 49 | 50 | #define VITA_COLOR(c) RGBA8((c)&0xff, (c>>8)&0xff, (c>>16)&0xff, 255) 51 | 52 | #define PKGI_ERRNO_EEXIST (int)(0x80010000 + SCE_NET_EEXIST) 53 | 54 | #define PKGI_USER_AGENT "libhttp/3.65 (PS Vita)" 55 | 56 | #ifdef PKGI_ENABLE_LOGGING 57 | void pkgi_log(const char* msg, ...) 58 | { 59 | char buffer[512]; 60 | 61 | va_list args; 62 | va_start(args, msg); 63 | // TODO: why sceClibVsnprintf doesn't work here? 64 | int len = vsnprintf(buffer, sizeof(buffer) - 1, msg, args); 65 | va_end(args); 66 | buffer[len] = '\n'; 67 | 68 | sceNetSend(g_log_socket, buffer, len + 1, 0); 69 | // sceKernelDelayThread(10); 70 | } 71 | #endif 72 | 73 | int pkgi_snprintf(char* buffer, uint32_t size, const char* msg, ...) 74 | { 75 | va_list args; 76 | va_start(args, msg); 77 | // TODO: why sceClibVsnprintf doesn't work here? 78 | int len = vsnprintf(buffer, size - 1, msg, args); 79 | va_end(args); 80 | buffer[len] = 0; 81 | return len; 82 | } 83 | 84 | void pkgi_vsnprintf(char* buffer, uint32_t size, const char* msg, va_list args) 85 | { 86 | // TODO: why sceClibVsnprintf doesn't work here? 87 | int len = vsnprintf(buffer, size - 1, msg, args); 88 | buffer[len] = 0; 89 | } 90 | 91 | char* pkgi_strstr(const char* str, const char* sub) 92 | { 93 | return strstr(str, sub); 94 | } 95 | 96 | int pkgi_stricontains(const char* str, const char* sub) 97 | { 98 | return strcasestr(str, sub) != NULL; 99 | } 100 | 101 | int pkgi_stricmp(const char* a, const char* b) 102 | { 103 | return strcasecmp(a, b); 104 | } 105 | 106 | void pkgi_strncpy(char* dst, uint32_t size, const char* src) 107 | { 108 | sceClibStrncpy(dst, src, size); 109 | } 110 | 111 | char* pkgi_strrchr(const char* str, char ch) 112 | { 113 | return strrchr(str, ch); 114 | } 115 | 116 | void pkgi_memcpy(void* dst, const void* src, uint32_t size) 117 | { 118 | sceClibMemcpy(dst, src, size); 119 | } 120 | 121 | void pkgi_memmove(void* dst, const void* src, uint32_t size) 122 | { 123 | sceClibMemmove(dst, src, size); 124 | } 125 | 126 | int pkgi_memequ(const void* a, const void* b, uint32_t size) 127 | { 128 | return memcmp(a, b, size) == 0; 129 | } 130 | 131 | static void pkgi_start_debug_log(void) 132 | { 133 | #ifdef PKGI_ENABLE_LOGGING 134 | g_log_socket = sceNetSocket("log_socket", SCE_NET_AF_INET, SCE_NET_SOCK_DGRAM, SCE_NET_IPPROTO_UDP); 135 | 136 | SceNetSockaddrIn addr = 137 | { 138 | .sin_family = SCE_NET_AF_INET, 139 | .sin_port = sceNetHtons(30000), 140 | }; 141 | sceNetInetPton(SCE_NET_AF_INET, "239.255.0.100", &addr.sin_addr); 142 | 143 | sceNetConnect(g_log_socket, (SceNetSockaddr*)&addr, sizeof(addr)); 144 | LOG("debug logging socket initialized"); 145 | #endif 146 | } 147 | 148 | static void pkgi_stop_debug_log(void) 149 | { 150 | #ifdef PKGI_ENABLE_LOGGING 151 | sceNetSocketClose(g_log_socket); 152 | #endif 153 | } 154 | 155 | // TODO: this is from VitaShell 156 | // no idea why, but this seems to be required for promoter utility functions to work 157 | static void pkgi_load_sce_paf() 158 | { 159 | uint32_t args[] = { 160 | 0x00400000, 161 | 0x0000ea60, 162 | 0x00040000, 163 | 0x00000000, 164 | 0x00000001, 165 | 0x00000000, 166 | }; 167 | 168 | uint32_t result = 0xDEADBEEF; 169 | 170 | uint32_t buffer[4] = {}; 171 | buffer[1] = (uint32_t)&result; 172 | 173 | sceSysmoduleLoadModuleInternalWithArg(SCE_SYSMODULE_INTERNAL_PAF, sizeof(args), args, buffer); 174 | } 175 | 176 | int pkgi_is_unsafe_mode(void) 177 | { 178 | return sceIoDevctl("ux0:", 0x3001, NULL, 0, NULL, 0) != (int)0x80010030; 179 | } 180 | 181 | int pkgi_ok_button(void) 182 | { 183 | return g_ok_button; 184 | } 185 | 186 | int pkgi_cancel_button(void) 187 | { 188 | return g_cancel_button; 189 | } 190 | 191 | static int pkgi_power_thread(SceSize args, void *argp) 192 | { 193 | PKGI_UNUSED(args); 194 | PKGI_UNUSED(argp); 195 | for (;;) 196 | { 197 | int lock; 198 | __atomic_load(&g_power_lock, &lock, __ATOMIC_SEQ_CST); 199 | if (lock > 0) 200 | { 201 | sceKernelPowerTick(SCE_KERNEL_POWER_TICK_DISABLE_AUTO_SUSPEND); 202 | } 203 | 204 | sceKernelDelayThread(10 * 1000 * 1000); 205 | } 206 | return 0; 207 | } 208 | 209 | void pkgi_dialog_lock(void) 210 | { 211 | int res = sceKernelLockLwMutex(&g_dialog_lock, 1, NULL); 212 | if (res < 0) 213 | { 214 | LOG("dialog unlock failed error=0x%08x", res); 215 | } 216 | } 217 | 218 | void pkgi_dialog_unlock(void) 219 | { 220 | int res = sceKernelUnlockLwMutex(&g_dialog_lock, 1); 221 | if (res < 0) 222 | { 223 | LOG("dialog lock failed error=0x%08x", res); 224 | } 225 | } 226 | 227 | static int g_ime_active; 228 | 229 | static uint16_t g_ime_title[SCE_IME_DIALOG_MAX_TITLE_LENGTH]; 230 | static uint16_t g_ime_text[SCE_IME_DIALOG_MAX_TEXT_LENGTH]; 231 | static uint16_t g_ime_input[SCE_IME_DIALOG_MAX_TEXT_LENGTH + 1]; 232 | 233 | static int convert_to_utf16(const char* utf8, uint16_t* utf16, uint32_t available) 234 | { 235 | int count = 0; 236 | while (*utf8) 237 | { 238 | uint8_t ch = (uint8_t)*utf8++; 239 | uint32_t code; 240 | uint32_t extra; 241 | 242 | if (ch < 0x80) 243 | { 244 | code = ch; 245 | extra = 0; 246 | } 247 | else if ((ch & 0xe0) == 0xc0) 248 | { 249 | code = ch & 31; 250 | extra = 1; 251 | } 252 | else if ((ch & 0xf0) == 0xe0) 253 | { 254 | code = ch & 15; 255 | extra = 2; 256 | } 257 | else 258 | { 259 | // TODO: this assumes there won't be invalid utf8 codepoints 260 | code = ch & 7; 261 | extra = 3; 262 | } 263 | 264 | for (uint32_t i=0; i= 0xe000) 275 | { 276 | if (available < 1) return count; 277 | utf16[count++] = (uint16_t)code; 278 | available--; 279 | } 280 | else // surrogate pair 281 | { 282 | if (available < 2) return count; 283 | code -= 0x10000; 284 | utf16[count++] = 0xd800 | (code >> 10); 285 | utf16[count++] = 0xdc00 | (code & 0x3ff); 286 | available -= 2; 287 | } 288 | } 289 | return count; 290 | } 291 | 292 | static int convert_from_utf16(const uint16_t* utf16, char* utf8, uint32_t size) 293 | { 294 | int count = 0; 295 | while (*utf16) 296 | { 297 | uint32_t code; 298 | uint16_t ch = *utf16++; 299 | if (ch < 0xd800 || ch >= 0xe000) 300 | { 301 | code = ch; 302 | } 303 | else // surrogate pair 304 | { 305 | uint16_t ch2 = *utf16++; 306 | if (ch < 0xdc00 || ch > 0xe000 || ch2 < 0xd800 || ch2 > 0xdc00) 307 | { 308 | return count; 309 | } 310 | code = 0x10000 + ((ch & 0x03FF) << 10) + (ch2 & 0x03FF); 311 | } 312 | 313 | if (code < 0x80) 314 | { 315 | if (size < 1) return count; 316 | utf8[count++] = (char)code; 317 | size--; 318 | } 319 | else if (code < 0x800) 320 | { 321 | if (size < 2) return count; 322 | utf8[count++] = (char)(0xc0 | (code >> 6)); 323 | utf8[count++] = (char)(0x80 | (code & 0x3f)); 324 | size -= 2; 325 | } 326 | else if (code < 0x10000) 327 | { 328 | if (size < 3) return count; 329 | utf8[count++] = (char)(0xe0 | (code >> 12)); 330 | utf8[count++] = (char)(0x80 | ((code >> 6) & 0x3f)); 331 | utf8[count++] = (char)(0x80 | (code & 0x3f)); 332 | size -= 3; 333 | } 334 | else 335 | { 336 | if (size < 4) return count; 337 | utf8[count++] = (char)(0xf0 | (code >> 18)); 338 | utf8[count++] = (char)(0x80 | ((code >> 12) & 0x3f)); 339 | utf8[count++] = (char)(0x80 | ((code >> 6) & 0x3f)); 340 | utf8[count++] = (char)(0x80 | (code & 0x3f)); 341 | size -= 4; 342 | } 343 | } 344 | return count; 345 | } 346 | 347 | void pkgi_dialog_input_text(const char* title, const char* text) 348 | { 349 | SceImeDialogParam param; 350 | sceImeDialogParamInit(¶m); 351 | 352 | int title_len = convert_to_utf16(title, g_ime_title, PKGI_COUNTOF(g_ime_title) - 1); 353 | int text_len = convert_to_utf16(text, g_ime_text, PKGI_COUNTOF(g_ime_text) - 1); 354 | g_ime_title[title_len] = 0; 355 | g_ime_text[text_len] = 0; 356 | 357 | param.supportedLanguages = 0x0001FFFF; 358 | param.languagesForced = SCE_TRUE; 359 | param.type = SCE_IME_TYPE_DEFAULT; 360 | param.option = 0; 361 | param.title = g_ime_title; 362 | param.maxTextLength = 128; 363 | param.initialText = g_ime_text; 364 | param.inputTextBuffer = g_ime_input; 365 | 366 | int res = sceImeDialogInit(¶m); 367 | if (res < 0) 368 | { 369 | LOG("sceImeDialogInit failed, error 0x%08x", res); 370 | } 371 | else 372 | { 373 | g_ime_active = 1; 374 | } 375 | } 376 | 377 | int pkgi_dialog_input_update(void) 378 | { 379 | if (!g_ime_active) 380 | { 381 | return 0; 382 | } 383 | 384 | SceCommonDialogStatus status = sceImeDialogGetStatus(); 385 | if (status == SCE_COMMON_DIALOG_STATUS_FINISHED) 386 | { 387 | SceImeDialogResult result = { 0 }; 388 | sceImeDialogGetResult(&result); 389 | 390 | g_ime_active = 0; 391 | sceImeDialogTerm(); 392 | 393 | if (result.button == SCE_IME_DIALOG_BUTTON_ENTER) 394 | { 395 | return 1; 396 | } 397 | } 398 | 399 | return 0; 400 | } 401 | 402 | void pkgi_dialog_input_get_text(char* text, uint32_t size) 403 | { 404 | int count = convert_from_utf16(g_ime_input, text, size - 1); 405 | text[count] = 0; 406 | } 407 | 408 | void pkgi_start(void) 409 | { 410 | pkgi_load_sce_paf(); 411 | sceSysmoduleLoadModuleInternal(SCE_SYSMODULE_INTERNAL_PROMOTER_UTIL); 412 | sceSysmoduleLoadModule(SCE_SYSMODULE_NET); 413 | sceSysmoduleLoadModule(SCE_SYSMODULE_HTTP); 414 | sceSysmoduleLoadModule(SCE_SYSMODULE_SSL); 415 | 416 | static uint8_t netmem[1024 * 1024]; 417 | SceNetInitParam net = 418 | { 419 | .memory = netmem, 420 | .size = sizeof(netmem), 421 | }; 422 | 423 | sceNetInit(&net); 424 | sceNetCtlInit(); 425 | 426 | pkgi_start_debug_log(); 427 | 428 | LOG("initializing SSL"); 429 | sceSslInit(1024 * 1024); 430 | LOG("initializing HTTP"); 431 | sceHttpInit(1024 * 1024); 432 | LOG("network initialized"); 433 | 434 | sceHttpsDisableOption(SCE_HTTPS_FLAG_SERVER_VERIFY); 435 | 436 | sceKernelCreateLwMutex(&g_dialog_lock, "dialog_lock", 2, 0, NULL); 437 | 438 | scePowerSetArmClockFrequency(444); 439 | 440 | sceShellUtilInitEvents(0); 441 | sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_USB_CONNECTION); 442 | 443 | SceAppUtilInitParam init = { 0 }; 444 | SceAppUtilBootParam boot = { 0 }; 445 | sceAppUtilInit(&init, &boot); 446 | 447 | SceCommonDialogConfigParam config; 448 | sceCommonDialogConfigParamInit(&config); 449 | sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, (int*)&config.language); 450 | sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_ENTER_BUTTON, (int*)&config.enterButtonAssign); 451 | sceCommonDialogSetConfigParam(&config); 452 | 453 | if (config.enterButtonAssign == SCE_SYSTEM_PARAM_ENTER_BUTTON_CIRCLE) 454 | { 455 | g_ok_button = PKGI_BUTTON_O; 456 | g_cancel_button = PKGI_BUTTON_X; 457 | } 458 | else 459 | { 460 | g_ok_button = PKGI_BUTTON_X; 461 | g_cancel_button = PKGI_BUTTON_O; 462 | } 463 | 464 | SceIoStat stat; 465 | if (sceIoGetstat("ur0:pkgi", &stat) >= 0 && SCE_S_ISDIR(stat.st_mode)) 466 | { 467 | pkgi_strncpy(g_pkgi_folder, sizeof(g_pkgi_folder), "ur0:pkgi"); 468 | } 469 | else 470 | { 471 | pkgi_strncpy(g_pkgi_folder, sizeof(g_pkgi_folder), "ux0:pkgi"); 472 | } 473 | 474 | if (scePromoterUtilityInit() < 0) 475 | { 476 | LOG("cannot initialize promoter utility"); 477 | } 478 | 479 | sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG); 480 | 481 | g_power_lock = 0; 482 | SceUID power_thread = sceKernelCreateThread("power_thread", &pkgi_power_thread, 0x10000100, 0x40000, 0, 0, NULL); 483 | if (power_thread >= 0) 484 | { 485 | sceKernelStartThread(power_thread, 0, NULL); 486 | } 487 | 488 | vita2d_init_advanced(4 * 1024 * 1024); 489 | g_font = vita2d_load_default_pgf(); 490 | 491 | g_time = sceKernelGetProcessTimeWide(); 492 | } 493 | 494 | int pkgi_update(pkgi_input* input) 495 | { 496 | SceCtrlData pad = { 0 }; 497 | sceCtrlPeekBufferPositive(0, &pad, 1); 498 | 499 | uint32_t previous = input->down; 500 | 501 | input->down = pad.buttons; 502 | if (pad.lx < ANALOG_CENTER - ANALOG_THRESHOLD) input->down |= PKGI_BUTTON_LEFT; 503 | if (pad.lx > ANALOG_CENTER + ANALOG_THRESHOLD) input->down |= PKGI_BUTTON_RIGHT; 504 | if (pad.ly < ANALOG_CENTER - ANALOG_THRESHOLD) input->down |= PKGI_BUTTON_UP; 505 | if (pad.ly > ANALOG_CENTER + ANALOG_THRESHOLD) input->down |= PKGI_BUTTON_DOWN; 506 | 507 | input->pressed = input->down & ~previous; 508 | input->active = input->pressed; 509 | 510 | if (input->down == previous) 511 | { 512 | if (g_button_frame_count >= 10) 513 | { 514 | input->active = input->down; 515 | } 516 | g_button_frame_count++; 517 | } 518 | else 519 | { 520 | g_button_frame_count = 0; 521 | } 522 | 523 | vita2d_start_drawing(); 524 | 525 | uint64_t time = sceKernelGetProcessTimeWide(); 526 | input->delta = time - g_time; 527 | g_time = time; 528 | 529 | return 1; 530 | } 531 | 532 | void pkgi_swap(void) 533 | { 534 | // LOG("vita2d pool free space = %u KB", vita2d_pool_free_space() / 1024); 535 | 536 | vita2d_end_drawing(); 537 | vita2d_common_dialog_update(); 538 | vita2d_swap_buffers(); 539 | sceDisplayWaitVblankStart(); 540 | } 541 | 542 | void pkgi_end(void) 543 | { 544 | pkgi_stop_debug_log(); 545 | 546 | vita2d_fini(); 547 | vita2d_free_pgf(g_font); 548 | 549 | scePromoterUtilityExit(); 550 | 551 | sceAppUtilShutdown(); 552 | 553 | sceKernelDeleteLwMutex(&g_dialog_lock); 554 | 555 | sceHttpTerm(); 556 | //sceSslTerm(); 557 | sceNetCtlTerm(); 558 | sceNetTerm(); 559 | 560 | sceSysmoduleUnloadModule(SCE_SYSMODULE_SSL); 561 | sceSysmoduleUnloadModule(SCE_SYSMODULE_HTTP); 562 | sceSysmoduleUnloadModule(SCE_SYSMODULE_NET); 563 | sceSysmoduleUnloadModuleInternal(SCE_SYSMODULE_INTERNAL_PROMOTER_UTIL); 564 | 565 | sceKernelExitProcess(0); 566 | } 567 | 568 | int pkgi_battery_present() 569 | { 570 | return sceKernelGetModel() == SCE_KERNEL_MODEL_VITA; 571 | } 572 | 573 | int pkgi_bettery_get_level() 574 | { 575 | return scePowerGetBatteryLifePercent(); 576 | } 577 | 578 | int pkgi_battery_is_low() 579 | { 580 | return scePowerIsLowBattery() && !scePowerIsBatteryCharging(); 581 | } 582 | 583 | int pkgi_battery_is_charging() 584 | { 585 | return scePowerIsBatteryCharging(); 586 | } 587 | 588 | uint64_t pkgi_get_free_space(void) 589 | { 590 | if (pkgi_is_unsafe_mode()) 591 | { 592 | SceIoDevInfo info = { 0 }; 593 | sceIoDevctl("ux0:", 0x3001, NULL, 0, &info, sizeof(info)); 594 | return info.free_size; 595 | } 596 | else 597 | { 598 | uint64_t free, max; 599 | sceAppMgrGetDevInfo("ux0:", &max, &free); 600 | return free; 601 | } 602 | } 603 | 604 | const char* pkgi_get_config_folder(void) 605 | { 606 | return g_pkgi_folder; 607 | } 608 | 609 | const char* pkgi_get_temp_folder(void) 610 | { 611 | return "ux0:pkgi"; 612 | } 613 | 614 | const char* pkgi_get_app_folder(void) 615 | { 616 | return "ux0:app"; 617 | } 618 | 619 | int pkgi_is_incomplete(const char* titleid) 620 | { 621 | char path[256]; 622 | pkgi_snprintf(path, sizeof(path), "%s/%s.resume", pkgi_get_temp_folder(), titleid); 623 | 624 | SceIoStat stat; 625 | int res = sceIoGetstat(path, &stat); 626 | return res == 0; 627 | } 628 | 629 | int pkgi_is_installed(const char* titleid) 630 | { 631 | int ret = -1; 632 | LOG("calling scePromoterUtilityCheckExist on %s", titleid); 633 | int res = scePromoterUtilityCheckExist(titleid, &ret); 634 | LOG("res=%d ret=%d", res, ret); 635 | return res == 0; 636 | } 637 | 638 | int pkgi_install(const char* titleid) 639 | { 640 | char path[128]; 641 | snprintf(path, sizeof(path), "%s/%s", pkgi_get_temp_folder(), titleid); 642 | 643 | LOG("calling scePromoterUtilityPromotePkgWithRif on %s", path); 644 | int res = scePromoterUtilityPromotePkgWithRif(path, 1); 645 | if (res == 0) 646 | { 647 | LOG("scePromoterUtilityPromotePkgWithRif succeeded"); 648 | } 649 | else 650 | { 651 | LOG("scePromoterUtilityPromotePkgWithRif failed"); 652 | } 653 | return res == 0; 654 | } 655 | 656 | uint32_t pkgi_time_msec() 657 | { 658 | return sceKernelGetProcessTimeLow() / 1000; 659 | } 660 | 661 | static int pkgi_vita_thread(SceSize args, void* argp) 662 | { 663 | PKGI_UNUSED(args); 664 | pkgi_thread_entry* start = *((pkgi_thread_entry**)argp); 665 | start(); 666 | return sceKernelExitDeleteThread(0); 667 | } 668 | 669 | void pkgi_start_thread(const char* name, pkgi_thread_entry* start) 670 | { 671 | SceUID id = sceKernelCreateThread(name, &pkgi_vita_thread, 0x40, 1024*1024, 0, 0, NULL); 672 | if (id < 0) 673 | { 674 | LOG("failed to start %s thread", name); 675 | } 676 | else 677 | { 678 | sceKernelStartThread(id, sizeof(start), &start); 679 | } 680 | } 681 | 682 | void pkgi_sleep(uint32_t msec) 683 | { 684 | sceKernelDelayThread(msec * 1000); 685 | } 686 | 687 | int pkgi_load(const char* name, void* data, uint32_t max) 688 | { 689 | SceUID fd = sceIoOpen(name, SCE_O_RDONLY, 0777); 690 | if (fd < 0) 691 | { 692 | return -1; 693 | } 694 | 695 | char* data8 = data; 696 | 697 | int total = 0; 698 | while (max != 0) 699 | { 700 | int read = sceIoRead(fd, data8 + total, max); 701 | if (read < 0) 702 | { 703 | total = -1; 704 | break; 705 | } 706 | else if (read == 0) 707 | { 708 | break; 709 | } 710 | total += read; 711 | max -= read; 712 | } 713 | 714 | sceIoClose(fd); 715 | return total; 716 | } 717 | 718 | int pkgi_save(const char* name, const void* data, uint32_t size) 719 | { 720 | SceUID fd = sceIoOpen(name, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777); 721 | if (fd < 0) 722 | { 723 | return 0; 724 | } 725 | 726 | int ret = 1; 727 | const char* data8 = data; 728 | while (size != 0) 729 | { 730 | int written = sceIoWrite(fd, data8, size); 731 | if (written <= 0) 732 | { 733 | ret = 0; 734 | break; 735 | } 736 | data8 += written; 737 | size -= written; 738 | } 739 | 740 | sceIoClose(fd); 741 | return ret; 742 | } 743 | 744 | void pkgi_lock_process(void) 745 | { 746 | if (__atomic_fetch_add(&g_power_lock, 1, __ATOMIC_SEQ_CST) == 0) 747 | { 748 | LOG("locking shell functionality"); 749 | if (sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN) < 0) 750 | { 751 | LOG("sceShellUtilLock failed"); 752 | } 753 | } 754 | } 755 | 756 | void pkgi_unlock_process(void) 757 | { 758 | if (__atomic_sub_fetch(&g_power_lock, 1, __ATOMIC_SEQ_CST) == 0) 759 | { 760 | LOG("unlocking shell functionality"); 761 | if (sceShellUtilUnlock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN) < 0) 762 | { 763 | LOG("sceShellUtilUnlock failed"); 764 | } 765 | } 766 | } 767 | 768 | pkgi_texture pkgi_load_png_raw(const void* data, uint32_t size) 769 | { 770 | (void)size; 771 | vita2d_texture* tex = vita2d_load_PNG_buffer((const char*)data); 772 | if (!tex) 773 | { 774 | LOG("failed to load texture"); 775 | } 776 | return tex; 777 | } 778 | 779 | void pkgi_draw_texture(pkgi_texture texture, int x, int y) 780 | { 781 | vita2d_texture* tex = texture; 782 | vita2d_draw_texture(tex, (float)x, (float)y); 783 | } 784 | 785 | void pkgi_clip_set(int x, int y, int w, int h) 786 | { 787 | vita2d_enable_clipping(); 788 | vita2d_set_clip_rectangle(x, y, x + w - 1, y + h - 1); 789 | } 790 | 791 | void pkgi_clip_remove(void) 792 | { 793 | vita2d_disable_clipping(); 794 | } 795 | 796 | void pkgi_draw_rect(int x, int y, int w, int h, uint32_t color) 797 | { 798 | vita2d_draw_rectangle((float)x, (float)y, (float)w, (float)h, VITA_COLOR(color)); 799 | } 800 | 801 | void pkgi_draw_text(int x, int y, uint32_t color, const char* text) 802 | { 803 | vita2d_pgf_draw_text(g_font, x, y + 20, VITA_COLOR(color), 1.f, text); 804 | } 805 | 806 | int pkgi_text_width(const char* text) 807 | { 808 | return vita2d_pgf_text_width(g_font, 1.f, text); 809 | } 810 | 811 | int pkgi_text_height(const char* text) 812 | { 813 | PKGI_UNUSED(text); 814 | // return vita2d_pgf_text_height(g_font, 1.f, text); 815 | return 23; 816 | } 817 | 818 | #define USE_LOCAL 0 819 | 820 | struct pkgi_http 821 | { 822 | int used; 823 | int local; 824 | 825 | SceUID fd; 826 | uint64_t size; 827 | uint64_t offset; 828 | 829 | int tmpl; 830 | int conn; 831 | int req; 832 | }; 833 | 834 | static pkgi_http g_http[4]; 835 | 836 | pkgi_http* pkgi_http_get(const char* url, const char* content, uint64_t offset) 837 | { 838 | LOG("http get"); 839 | 840 | pkgi_http* http = NULL; 841 | for (size_t i = 0; i < 4; i++) 842 | { 843 | if (g_http[i].used == 0) 844 | { 845 | http = g_http + i; 846 | break; 847 | } 848 | } 849 | 850 | if (!http) 851 | { 852 | LOG("too many simultaneous http requests"); 853 | return NULL; 854 | } 855 | 856 | pkgi_http* result = NULL; 857 | 858 | char path[256]; 859 | 860 | if (content) 861 | { 862 | strcpy(path, pkgi_get_temp_folder()); 863 | strcat(path, strrchr(url, '/')); 864 | 865 | http->fd = sceIoOpen(path, SCE_O_RDONLY, 0777); 866 | if (http->fd < 0) 867 | { 868 | LOG("%s not found, trying shorter path", path); 869 | pkgi_snprintf(path, sizeof(path), "%s/%s.pkg", pkgi_get_temp_folder(), content); 870 | } 871 | 872 | http->fd = sceIoOpen(path, SCE_O_RDONLY, 0777); 873 | } 874 | else 875 | { 876 | http->fd = -1; 877 | } 878 | 879 | if (http->fd >= 0) 880 | { 881 | LOG("%s found, using it", path); 882 | 883 | SceIoStat stat; 884 | if (sceIoGetstatByFd(http->fd, &stat) < 0) 885 | { 886 | LOG("cannot get size of file %s", path); 887 | sceIoClose(http->fd); 888 | return NULL; 889 | } 890 | 891 | http->used = 1; 892 | http->local = 1; 893 | http->offset = 0; 894 | http->size = stat.st_size; 895 | 896 | result = http; 897 | } 898 | else 899 | { 900 | if (content) 901 | { 902 | LOG("%s not found, downloading url", path); 903 | } 904 | 905 | int tmpl = -1; 906 | int conn = -1; 907 | int req = -1; 908 | 909 | LOG("starting http GET request for %s", url); 910 | 911 | if ((tmpl = sceHttpCreateTemplate(PKGI_USER_AGENT, SCE_HTTP_VERSION_1_1, SCE_TRUE)) < 0) 912 | { 913 | LOG("sceHttpCreateTemplate failed: 0x%08x", tmpl); 914 | goto bail; 915 | } 916 | // sceHttpSetRecvTimeOut(tmpl, 10 * 1000 * 1000); 917 | 918 | if ((conn = sceHttpCreateConnectionWithURL(tmpl, url, SCE_FALSE)) < 0) 919 | { 920 | LOG("sceHttpCreateConnectionWithURL failed: 0x%08x", conn); 921 | goto bail; 922 | } 923 | 924 | if ((req = sceHttpCreateRequestWithURL(conn, SCE_HTTP_METHOD_GET, url, 0)) < 0) 925 | { 926 | LOG("sceHttpCreateRequestWithURL failed: 0x%08x", req); 927 | goto bail; 928 | } 929 | 930 | int err; 931 | 932 | if (offset != 0) 933 | { 934 | char range[64]; 935 | pkgi_snprintf(range, sizeof(range), "bytes=%llu-", offset); 936 | if ((err = sceHttpAddRequestHeader(req, "Range", range, SCE_HTTP_HEADER_ADD)) < 0) 937 | { 938 | LOG("sceHttpAddRequestHeader failed: 0x%08x", err); 939 | goto bail; 940 | } 941 | } 942 | 943 | if ((err = sceHttpSendRequest(req, NULL, 0)) < 0) 944 | { 945 | LOG("sceHttpSendRequest failed: 0x%08x", err); 946 | goto bail; 947 | } 948 | 949 | http->used = 1; 950 | http->local = 0; 951 | http->tmpl = tmpl; 952 | http->conn = conn; 953 | http->req = req; 954 | tmpl = conn = req = -1; 955 | 956 | result = http; 957 | 958 | bail: 959 | if (req < 0) sceHttpDeleteRequest(req); 960 | if (conn < 0) sceHttpDeleteConnection(conn); 961 | if (tmpl < 0) sceHttpDeleteTemplate(tmpl); 962 | } 963 | 964 | return result; 965 | } 966 | 967 | int pkgi_http_response_length(pkgi_http* http, int64_t* length) 968 | { 969 | if (http->local) 970 | { 971 | *length = (int64_t)http->size; 972 | return 1; 973 | } 974 | else 975 | { 976 | int res; 977 | int status; 978 | if ((res = sceHttpGetStatusCode(http->req, &status)) < 0) 979 | { 980 | LOG("sceHttpGetStatusCode failed: 0x%08x", res); 981 | return 0; 982 | } 983 | 984 | LOG("http status code = %d", status); 985 | 986 | if (status == 200 || status == 206) 987 | { 988 | char* headers; 989 | unsigned int size; 990 | if (sceHttpGetAllResponseHeaders(http->req, &headers, &size) >= 0) 991 | { 992 | LOG("response headers:"); 993 | LOG("%.*s", (int)size, headers); 994 | } 995 | 996 | uint64_t content_length; 997 | res = sceHttpGetResponseContentLength(http->req, &content_length); 998 | if (res == (int)SCE_HTTP_ERROR_NO_CONTENT_LENGTH || res == (int)SCE_HTTP_ERROR_CHUNK_ENC) 999 | { 1000 | LOG("http response has no content length (or chunked encoding)"); 1001 | *length = 0; 1002 | } 1003 | else if (res < 0) 1004 | { 1005 | LOG("sceHttpGetResponseContentLength failed: 0x%08x", res); 1006 | return 0; 1007 | } 1008 | else 1009 | { 1010 | LOG("http response length = %llu", content_length); 1011 | *length = (int64_t)content_length; 1012 | } 1013 | return 1; 1014 | } 1015 | return 0; 1016 | } 1017 | } 1018 | 1019 | int pkgi_http_read(pkgi_http* http, void* buffer, uint32_t size) 1020 | { 1021 | if (http->local) 1022 | { 1023 | int read = sceIoPread(http->fd, buffer, size, http->offset); 1024 | http->offset += read; 1025 | return read; 1026 | } 1027 | else 1028 | { 1029 | // LOG("http asking to read %u bytes", size); 1030 | int read = sceHttpReadData(http->req, buffer, size); 1031 | // LOG("http read %d bytes", size, read); 1032 | if (read < 0) 1033 | { 1034 | LOG("sceHttpReadData failed: 0x%08x", read); 1035 | } 1036 | return read; 1037 | } 1038 | } 1039 | 1040 | void pkgi_http_close(pkgi_http* http) 1041 | { 1042 | LOG("http close"); 1043 | if (http->local) 1044 | { 1045 | sceIoClose(http->fd); 1046 | } 1047 | else 1048 | { 1049 | sceHttpDeleteRequest(http->req); 1050 | sceHttpDeleteConnection(http->conn); 1051 | sceHttpDeleteTemplate(http->tmpl); 1052 | } 1053 | http->used = 0; 1054 | } 1055 | 1056 | int pkgi_mkdirs(char* path) 1057 | { 1058 | // LOG("pkgi_mkdirs enter %llu", sceKernelGetProcessTimeWide() / 1000); 1059 | char* ptr = path; 1060 | while (*ptr) 1061 | { 1062 | while (*ptr && *ptr != '/') 1063 | { 1064 | ptr++; 1065 | } 1066 | char last = *ptr; 1067 | *ptr = 0; 1068 | LOG("mkdir %s", path); 1069 | // LOG("BEFORE MKDIR %llu", sceKernelGetProcessTimeWide() / 1000); 1070 | int err = sceIoMkdir(path, 0777); 1071 | // LOG("AFTER MKDIR %llu", sceKernelGetProcessTimeWide() / 1000); 1072 | if (err < 0 && err != PKGI_ERRNO_EEXIST) 1073 | { 1074 | LOG("sceIoMkdir %s err=0x%08x", path, (uint32_t)err); 1075 | // LOG("pkgi_mkdirs exit %llu", sceKernelGetProcessTimeWide() / 1000); 1076 | return 0; 1077 | } 1078 | *ptr++ = last; 1079 | if (last == 0) 1080 | { 1081 | break; 1082 | } 1083 | } 1084 | 1085 | // LOG("pkgi_mkdirs exit %llu", sceKernelGetProcessTimeWide() / 1000); 1086 | return 1; 1087 | } 1088 | 1089 | void pkgi_rm(const char* file) 1090 | { 1091 | int err = sceIoRemove(file); 1092 | if (err < 0) 1093 | { 1094 | LOG("error removing %s file, err=0x%08x", err); 1095 | } 1096 | } 1097 | 1098 | int64_t pkgi_get_size(const char* path) 1099 | { 1100 | SceIoStat stat; 1101 | int err = sceIoGetstat(path, &stat); 1102 | if (err < 0) 1103 | { 1104 | LOG("cannot get size of %s, err=0x%08x", path, err); 1105 | return -1; 1106 | } 1107 | return stat.st_size; 1108 | } 1109 | 1110 | void* pkgi_create(const char* path) 1111 | { 1112 | LOG("sceIoOpen create on %s", path); 1113 | SceUID fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777); 1114 | if (fd < 0) 1115 | { 1116 | LOG("cannot create %s, err=0x%08x", path, fd); 1117 | return NULL; 1118 | } 1119 | LOG("sceIoOpen returned fd=%d", fd); 1120 | 1121 | return (void*)(intptr_t)fd; 1122 | } 1123 | 1124 | void* pkgi_openrw(const char* path) 1125 | { 1126 | LOG("sceIoOpen openrw on %s", path); 1127 | SceUID fd = sceIoOpen(path, SCE_O_RDWR, 0777); 1128 | if (fd < 0) 1129 | { 1130 | LOG("cannot openrw %s, err=0x%08x", path, fd); 1131 | return NULL; 1132 | } 1133 | LOG("sceIoOpen returned fd=%d", fd); 1134 | 1135 | return (void*)(intptr_t)fd; 1136 | } 1137 | 1138 | void* pkgi_append(const char* path) 1139 | { 1140 | LOG("sceIoOpen append on %s", path); 1141 | SceUID fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_APPEND, 0777); 1142 | if (fd < 0) 1143 | { 1144 | LOG("cannot append %s, err=0x%08x", path, fd); 1145 | return NULL; 1146 | } 1147 | LOG("sceIoOpen returned fd=%d", fd); 1148 | 1149 | return (void*)(intptr_t)fd; 1150 | } 1151 | 1152 | int pkgi_read(void* f, void* buffer, uint32_t size) 1153 | { 1154 | LOG("asking to read %u bytes", size); 1155 | int read = sceIoRead((SceUID)(intptr_t)f, buffer, size); 1156 | if (read < 0) 1157 | { 1158 | LOG("sceIoRead error 0x%08x", read); 1159 | } 1160 | else 1161 | { 1162 | LOG("read %d bytes", read); 1163 | } 1164 | return read; 1165 | } 1166 | 1167 | int pkgi_write(void* f, const void* buffer, uint32_t size) 1168 | { 1169 | // LOG("asking to write %u bytes", size); 1170 | int write = sceIoWrite((SceUID)(intptr_t)f, buffer, size); 1171 | if (write < 0) 1172 | { 1173 | LOG("sceIoWrite error 0x%08x", write); 1174 | return -1; 1175 | } 1176 | 1177 | // LOG("wrote %d bytes", write); 1178 | return (uint32_t)write == size; 1179 | } 1180 | 1181 | void pkgi_close(void* f) 1182 | { 1183 | SceUID fd = (SceUID)(intptr_t)f; 1184 | LOG("closing file %d", fd); 1185 | int err = sceIoClose(fd); 1186 | if (err < 0) 1187 | { 1188 | LOG("close error 0x%08x", err); 1189 | } 1190 | } 1191 | -------------------------------------------------------------------------------- /pkgi_zrif.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_zrif.h" 2 | #include "pkgi_utils.h" 3 | #include "pkgi.h" 4 | #include "puff.h" 5 | 6 | #include 7 | 8 | #define ADLER32_MOD 65521 9 | 10 | #define ZLIB_DEFLATE_METHOD 8 11 | #define ZLIB_DICTIONARY_ID_ZRIF 0x627d1d5d 12 | 13 | static const uint8_t zrif_dict[] = 14 | { 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 37 | 48, 48, 54, 48, 48, 48, 48, 55, 48, 48, 48, 48, 56, 0, 48, 48, 48, 48, 51, 48, 48, 48, 48, 52, 48, 48, 48, 48, 38 | 53, 48, 95, 48, 48, 45, 65, 68, 68, 67, 79, 78, 84, 48, 48, 48, 48, 50, 45, 80, 67, 83, 71, 48, 48, 48, 48, 39 | 48, 48, 48, 48, 48, 48, 49, 45, 80, 67, 83, 69, 48, 48, 48, 45, 80, 67, 83, 70, 48, 48, 48, 45, 80, 67, 83, 40 | 67, 48, 48, 48, 45, 80, 67, 83, 68, 48, 48, 48, 45, 80, 67, 83, 65, 48, 48, 48, 45, 80, 67, 83, 66, 48, 48, 41 | 48, 0, 1, 0, 1, 0, 1, 0, 2, 239, 205, 171, 137, 103, 69, 35, 1, 42 | }; 43 | 44 | static const uint8_t b64d[] = 45 | { 46 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 47 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 48 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 49 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 50 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 51 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 52 | 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 53 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 54 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 55 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 56 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 57 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 58 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 59 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 60 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 61 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62 | }; 63 | 64 | static uint32_t adler32(const uint8_t* data, size_t size) 65 | { 66 | uint32_t a = 1; 67 | uint32_t b = 0; 68 | 69 | for (size_t i = 0; i < size; i++) 70 | { 71 | a = (a + data[i]) % ADLER32_MOD; 72 | b = (b + a) % ADLER32_MOD; 73 | } 74 | 75 | return (b << 16) | a; 76 | } 77 | 78 | static uint32_t base64_decode(const char* in, uint8_t* out) 79 | { 80 | const uint8_t* out0 = out; 81 | const uint8_t* in8 = (uint8_t*)in; 82 | 83 | size_t len = strlen(in); 84 | if (in[len - 1] == '=') 85 | { 86 | len--; 87 | } 88 | if (in[len - 1] == '=') 89 | { 90 | len--; 91 | } 92 | 93 | for (size_t i = 0; i < len / 4; i++) 94 | { 95 | *out++ = (b64d[in8[0]] << 2) + ((b64d[in8[1]] & 0x30) >> 4); 96 | *out++ = (b64d[in8[1]] << 4) + (b64d[in8[2]] >> 2); 97 | *out++ = (b64d[in8[2]] << 6) + b64d[in8[3]]; 98 | in8 += 4; 99 | } 100 | 101 | size_t left = len % 4; 102 | if (left == 2) 103 | { 104 | *out++ = (b64d[in8[0]] << 2) + ((b64d[in8[1]] & 0x30) >> 4); 105 | *out++ = (b64d[in8[1]] << 4); 106 | } 107 | else if (left == 3) 108 | { 109 | *out++ = (b64d[in8[0]] << 2) + ((b64d[in8[1]] & 0x30) >> 4); 110 | *out++ = (b64d[in8[1]] << 4) + (b64d[in8[2]] >> 2); 111 | *out++ = b64d[in8[2]] << 6; 112 | } 113 | 114 | return (uint32_t)(out - out0); 115 | } 116 | 117 | // https://www.ietf.org/rfc/rfc1950.txt 118 | static uint32_t zlib_inflate(const uint8_t* in, uint32_t inlen, uint8_t* out, uint32_t outlen, char* error, uint32_t error_size) 119 | { 120 | if (inlen < 2 + 4) 121 | { 122 | pkgi_strncpy(error, error_size, "zRIF is too short"); 123 | return 0; 124 | } 125 | 126 | if (((in[0] << 8) + in[1]) % 31 != 0) 127 | { 128 | pkgi_strncpy(error, error_size, "zRIF header is corrupted"); 129 | return 0; 130 | } 131 | 132 | if ((in[0] & 0xf) != ZLIB_DEFLATE_METHOD) 133 | { 134 | pkgi_strncpy(error, error_size, "only deflate method supported in zRIF"); 135 | return 0; 136 | } 137 | 138 | unsigned long slen = inlen - 4; 139 | unsigned long dlen = outlen; 140 | unsigned long dictlen = 0; 141 | 142 | if (in[1] & (1 << 5)) 143 | { 144 | memcpy(out, zrif_dict, sizeof(zrif_dict)); 145 | dictlen = sizeof(zrif_dict); 146 | 147 | if (get32be(in + 2) != ZLIB_DICTIONARY_ID_ZRIF) 148 | { 149 | pkgi_strncpy(error, error_size, "zRIF uses unknown dictionary"); 150 | return 0; 151 | } 152 | 153 | in += 6; 154 | slen -= 6; 155 | } 156 | else 157 | { 158 | in += 2; 159 | slen -= 2; 160 | } 161 | 162 | if (puff(dictlen, out, &dlen, in, &slen) != 0) 163 | { 164 | pkgi_strncpy(error, error_size, "failed to uncompress zRIF"); 165 | return 0; 166 | } 167 | memmove(out, out + dictlen, dlen); 168 | 169 | if (adler32(out, dlen) != get32be(in + slen)) 170 | { 171 | pkgi_strncpy(error, error_size, "zRIF is corrupted, wrong checksum"); 172 | return 0; 173 | } 174 | 175 | return dlen; 176 | } 177 | 178 | int pkgi_zrif_decode(const char* str, uint8_t* rif, char* error, uint32_t error_size) 179 | { 180 | uint8_t raw[512]; 181 | uint32_t len = base64_decode(str, raw); 182 | 183 | uint8_t out[512 + sizeof(zrif_dict)]; 184 | len = zlib_inflate(raw, len, out, sizeof(out), error, error_size); 185 | if (len != 512) 186 | { 187 | pkgi_strncpy(error, error_size, "wrong size of zRIF, is it corrupted?"); 188 | return 0; 189 | } 190 | 191 | memcpy(rif, out, 512); 192 | return 1; 193 | } 194 | -------------------------------------------------------------------------------- /pkgi_zrif.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int pkgi_zrif_decode(const char* str, uint8_t* rif, char* error, uint32_t error_size); 6 | -------------------------------------------------------------------------------- /puff.c: -------------------------------------------------------------------------------- 1 | /* 2 | * puff.c 3 | * Copyright (C) 2002-2013 Mark Adler 4 | * For conditions of distribution and use, see copyright notice in puff.h 5 | * version 2.3, 21 Jan 2013 6 | */ 7 | 8 | #include /* for setjmp(), longjmp(), and jmp_buf */ 9 | #include "puff.h" /* prototype for puff() */ 10 | 11 | #define local static /* for local function definitions */ 12 | 13 | #define MAXBITS 15 /* maximum bits in a code */ 14 | #define MAXLCODES 286 /* maximum number of literal/length codes */ 15 | #define MAXDCODES 30 /* maximum number of distance codes */ 16 | #define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ 17 | #define FIXLCODES 288 /* number of fixed literal/length codes */ 18 | 19 | /* input and output state */ 20 | struct state { 21 | /* output state */ 22 | unsigned char *out; /* output buffer */ 23 | unsigned long outlen; /* available space at out */ 24 | unsigned long outcnt; /* bytes written to out so far */ 25 | 26 | /* input state */ 27 | const unsigned char *in; /* input buffer */ 28 | unsigned long inlen; /* available input at in */ 29 | unsigned long incnt; /* bytes read so far */ 30 | int bitbuf; /* bit buffer */ 31 | int bitcnt; /* number of bits in bit buffer */ 32 | 33 | /* input limit error return state for bits() and decode() */ 34 | jmp_buf env; 35 | }; 36 | 37 | local int bits(struct state *s, int need) 38 | { 39 | long val; /* bit accumulator (can use up to 20 bits) */ 40 | 41 | /* load at least need bits into val */ 42 | val = s->bitbuf; 43 | while (s->bitcnt < need) { 44 | if (s->incnt == s->inlen) 45 | longjmp(s->env, 1); /* out of input */ 46 | val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ 47 | s->bitcnt += 8; 48 | } 49 | 50 | /* drop need bits and update buffer, always zero to seven bits left */ 51 | s->bitbuf = (int)(val >> need); 52 | s->bitcnt -= need; 53 | 54 | /* return need bits, zeroing the bits above that */ 55 | return (int)(val & ((1L << need) - 1)); 56 | } 57 | 58 | local int stored(struct state *s) 59 | { 60 | unsigned len; /* length of stored block */ 61 | 62 | /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ 63 | s->bitbuf = 0; 64 | s->bitcnt = 0; 65 | 66 | /* get length and check against its one's complement */ 67 | if (s->incnt + 4 > s->inlen) 68 | return 2; /* not enough input */ 69 | len = s->in[s->incnt++]; 70 | len |= s->in[s->incnt++] << 8; 71 | if (s->in[s->incnt++] != (~len & 0xff) || 72 | s->in[s->incnt++] != ((~len >> 8) & 0xff)) 73 | return -2; /* didn't match complement! */ 74 | 75 | /* copy len bytes from in to out */ 76 | if (s->incnt + len > s->inlen) 77 | return 2; /* not enough input */ 78 | if (s->out != NIL) { 79 | if (s->outcnt + len > s->outlen) 80 | return 1; /* not enough output space */ 81 | while (len--) 82 | s->out[s->outcnt++] = s->in[s->incnt++]; 83 | } 84 | else { /* just scanning */ 85 | s->outcnt += len; 86 | s->incnt += len; 87 | } 88 | 89 | /* done with a valid stored block */ 90 | return 0; 91 | } 92 | 93 | struct huffman { 94 | short *count; /* number of symbols of each length */ 95 | short *symbol; /* canonically ordered symbols */ 96 | }; 97 | 98 | local int decode(struct state *s, const struct huffman *h) 99 | { 100 | int len; /* current number of bits in code */ 101 | int code; /* len bits being decoded */ 102 | int first; /* first code of length len */ 103 | int count; /* number of codes of length len */ 104 | int index; /* index of first code of length len in symbol table */ 105 | int bitbuf; /* bits from stream */ 106 | int left; /* bits left in next or left to process */ 107 | short *next; /* next number of codes */ 108 | 109 | bitbuf = s->bitbuf; 110 | left = s->bitcnt; 111 | code = first = index = 0; 112 | len = 1; 113 | next = h->count + 1; 114 | while (1) { 115 | while (left--) { 116 | code |= bitbuf & 1; 117 | bitbuf >>= 1; 118 | count = *next++; 119 | if (code - count < first) { /* if length len, return symbol */ 120 | s->bitbuf = bitbuf; 121 | s->bitcnt = (s->bitcnt - len) & 7; 122 | return h->symbol[index + (code - first)]; 123 | } 124 | index += count; /* else update for next length */ 125 | first += count; 126 | first <<= 1; 127 | code <<= 1; 128 | len++; 129 | } 130 | left = (MAXBITS+1) - len; 131 | if (left == 0) 132 | break; 133 | if (s->incnt == s->inlen) 134 | longjmp(s->env, 1); /* out of input */ 135 | bitbuf = s->in[s->incnt++]; 136 | if (left > 8) 137 | left = 8; 138 | } 139 | return -10; /* ran out of codes */ 140 | } 141 | 142 | local int construct(struct huffman *h, const short *length, int n) 143 | { 144 | int symbol; /* current symbol when stepping through length[] */ 145 | int len; /* current length when stepping through h->count[] */ 146 | int left; /* number of possible codes left of current length */ 147 | short offs[MAXBITS+1]; /* offsets in symbol table for each length */ 148 | 149 | /* count number of codes of each length */ 150 | for (len = 0; len <= MAXBITS; len++) 151 | h->count[len] = 0; 152 | for (symbol = 0; symbol < n; symbol++) 153 | (h->count[length[symbol]])++; /* assumes lengths are within bounds */ 154 | if (h->count[0] == n) /* no codes! */ 155 | return 0; /* complete, but decode() will fail */ 156 | 157 | /* check for an over-subscribed or incomplete set of lengths */ 158 | left = 1; /* one possible code of zero length */ 159 | for (len = 1; len <= MAXBITS; len++) { 160 | left <<= 1; /* one more bit, double codes left */ 161 | left -= h->count[len]; /* deduct count from possible codes */ 162 | if (left < 0) 163 | return left; /* over-subscribed--return negative */ 164 | } /* left > 0 means incomplete */ 165 | 166 | /* generate offsets into symbol table for each length for sorting */ 167 | offs[1] = 0; 168 | for (len = 1; len < MAXBITS; len++) 169 | offs[len + 1] = offs[len] + h->count[len]; 170 | 171 | /* 172 | * put symbols in table sorted by length, by symbol order within each 173 | * length 174 | */ 175 | for (symbol = 0; symbol < n; symbol++) 176 | if (length[symbol] != 0) 177 | h->symbol[offs[length[symbol]]++] = symbol; 178 | 179 | /* return zero for complete set, positive for incomplete set */ 180 | return left; 181 | } 182 | 183 | local int codes(struct state *s, 184 | const struct huffman *lencode, 185 | const struct huffman *distcode) 186 | { 187 | int symbol; /* decoded symbol */ 188 | int len; /* length for copy */ 189 | unsigned dist; /* distance for copy */ 190 | static const short lens[29] = { /* Size base for length codes 257..285 */ 191 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 192 | 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; 193 | static const short lext[29] = { /* Extra bits for length codes 257..285 */ 194 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 195 | 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; 196 | static const short dists[30] = { /* Offset base for distance codes 0..29 */ 197 | 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 198 | 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 199 | 8193, 12289, 16385, 24577}; 200 | static const short dext[30] = { /* Extra bits for distance codes 0..29 */ 201 | 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 202 | 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 203 | 12, 12, 13, 13}; 204 | 205 | /* decode literals and length/distance pairs */ 206 | do { 207 | symbol = decode(s, lencode); 208 | if (symbol < 0) 209 | return symbol; /* invalid symbol */ 210 | if (symbol < 256) { /* literal: symbol is the byte */ 211 | /* write out the literal */ 212 | if (s->out != NIL) { 213 | if (s->outcnt == s->outlen) 214 | return 1; 215 | s->out[s->outcnt] = symbol; 216 | } 217 | s->outcnt++; 218 | } 219 | else if (symbol > 256) { /* length */ 220 | /* get and compute length */ 221 | symbol -= 257; 222 | if (symbol >= 29) 223 | return -10; /* invalid fixed code */ 224 | len = lens[symbol] + bits(s, lext[symbol]); 225 | 226 | /* get and check distance */ 227 | symbol = decode(s, distcode); 228 | if (symbol < 0) 229 | return symbol; /* invalid symbol */ 230 | dist = dists[symbol] + bits(s, dext[symbol]); 231 | #ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR 232 | if (dist > s->outcnt) 233 | return -11; /* distance too far back */ 234 | #endif 235 | 236 | /* copy length bytes from distance bytes back */ 237 | if (s->out != NIL) { 238 | if (s->outcnt + len > s->outlen) 239 | return 1; 240 | while (len--) { 241 | s->out[s->outcnt] = 242 | #ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR 243 | dist > s->outcnt ? 244 | 0 : 245 | #endif 246 | s->out[s->outcnt - dist]; 247 | s->outcnt++; 248 | } 249 | } 250 | else 251 | s->outcnt += len; 252 | } 253 | } while (symbol != 256); /* end of block symbol */ 254 | 255 | /* done with a valid fixed or dynamic block */ 256 | return 0; 257 | } 258 | 259 | local int fixed(struct state *s) 260 | { 261 | static int virgin = 1; 262 | static short lencnt[MAXBITS+1], lensym[FIXLCODES]; 263 | static short distcnt[MAXBITS+1], distsym[MAXDCODES]; 264 | static struct huffman lencode, distcode; 265 | 266 | /* build fixed huffman tables if first call (may not be thread safe) */ 267 | if (virgin) { 268 | int symbol; 269 | short lengths[FIXLCODES]; 270 | 271 | /* construct lencode and distcode */ 272 | lencode.count = lencnt; 273 | lencode.symbol = lensym; 274 | distcode.count = distcnt; 275 | distcode.symbol = distsym; 276 | 277 | /* literal/length table */ 278 | for (symbol = 0; symbol < 144; symbol++) 279 | lengths[symbol] = 8; 280 | for (; symbol < 256; symbol++) 281 | lengths[symbol] = 9; 282 | for (; symbol < 280; symbol++) 283 | lengths[symbol] = 7; 284 | for (; symbol < FIXLCODES; symbol++) 285 | lengths[symbol] = 8; 286 | construct(&lencode, lengths, FIXLCODES); 287 | 288 | /* distance table */ 289 | for (symbol = 0; symbol < MAXDCODES; symbol++) 290 | lengths[symbol] = 5; 291 | construct(&distcode, lengths, MAXDCODES); 292 | 293 | /* do this just once */ 294 | virgin = 0; 295 | } 296 | 297 | /* decode data until end-of-block code */ 298 | return codes(s, &lencode, &distcode); 299 | } 300 | 301 | local int dynamic(struct state *s) 302 | { 303 | int nlen, ndist, ncode; /* number of lengths in descriptor */ 304 | int index; /* index of lengths[] */ 305 | int err; /* construct() return value */ 306 | short lengths[MAXCODES]; /* descriptor code lengths */ 307 | short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ 308 | short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ 309 | struct huffman lencode, distcode; /* length and distance codes */ 310 | static const short order[19] = /* permutation of code length codes */ 311 | {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; 312 | 313 | /* construct lencode and distcode */ 314 | lencode.count = lencnt; 315 | lencode.symbol = lensym; 316 | distcode.count = distcnt; 317 | distcode.symbol = distsym; 318 | 319 | /* get number of lengths in each table, check lengths */ 320 | nlen = bits(s, 5) + 257; 321 | ndist = bits(s, 5) + 1; 322 | ncode = bits(s, 4) + 4; 323 | if (nlen > MAXLCODES || ndist > MAXDCODES) 324 | return -3; /* bad counts */ 325 | 326 | /* read code length code lengths (really), missing lengths are zero */ 327 | for (index = 0; index < ncode; index++) 328 | lengths[order[index]] = bits(s, 3); 329 | for (; index < 19; index++) 330 | lengths[order[index]] = 0; 331 | 332 | /* build huffman table for code lengths codes (use lencode temporarily) */ 333 | err = construct(&lencode, lengths, 19); 334 | if (err != 0) /* require complete code set here */ 335 | return -4; 336 | 337 | /* read length/literal and distance code length tables */ 338 | index = 0; 339 | while (index < nlen + ndist) { 340 | int symbol; /* decoded value */ 341 | int len; /* last length to repeat */ 342 | 343 | symbol = decode(s, &lencode); 344 | if (symbol < 0) 345 | return symbol; /* invalid symbol */ 346 | if (symbol < 16) /* length in 0..15 */ 347 | lengths[index++] = symbol; 348 | else { /* repeat instruction */ 349 | len = 0; /* assume repeating zeros */ 350 | if (symbol == 16) { /* repeat last length 3..6 times */ 351 | if (index == 0) 352 | return -5; /* no last length! */ 353 | len = lengths[index - 1]; /* last length */ 354 | symbol = 3 + bits(s, 2); 355 | } 356 | else if (symbol == 17) /* repeat zero 3..10 times */ 357 | symbol = 3 + bits(s, 3); 358 | else /* == 18, repeat zero 11..138 times */ 359 | symbol = 11 + bits(s, 7); 360 | if (index + symbol > nlen + ndist) 361 | return -6; /* too many lengths! */ 362 | while (symbol--) /* repeat last or zero symbol times */ 363 | lengths[index++] = len; 364 | } 365 | } 366 | 367 | /* check for end-of-block code -- there better be one! */ 368 | if (lengths[256] == 0) 369 | return -9; 370 | 371 | /* build huffman table for literal/length codes */ 372 | err = construct(&lencode, lengths, nlen); 373 | if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1])) 374 | return -7; /* incomplete code ok only for single length 1 code */ 375 | 376 | /* build huffman table for distance codes */ 377 | err = construct(&distcode, lengths + nlen, ndist); 378 | if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1])) 379 | return -8; /* incomplete code ok only for single length 1 code */ 380 | 381 | /* decode data until end-of-block code */ 382 | return codes(s, &lencode, &distcode); 383 | } 384 | 385 | int puff(unsigned long dictlen, // length of custom dictionary 386 | unsigned char *dest, /* pointer to destination pointer */ 387 | unsigned long *destlen, /* amount of output space */ 388 | const unsigned char *source, /* pointer to source data pointer */ 389 | unsigned long *sourcelen) /* amount of input available */ 390 | { 391 | struct state s; /* input/output state */ 392 | int last, type; /* block information */ 393 | int err; /* return value */ 394 | 395 | /* initialize output state */ 396 | s.out = dest; 397 | s.outlen = *destlen; /* ignored if dest is NIL */ 398 | s.outcnt = dictlen; 399 | 400 | /* initialize input state */ 401 | s.in = source; 402 | s.inlen = *sourcelen; 403 | s.incnt = 0; 404 | s.bitbuf = 0; 405 | s.bitcnt = 0; 406 | 407 | /* return if bits() or decode() tries to read past available input */ 408 | if (setjmp(s.env) != 0) /* if came back here via longjmp() */ 409 | err = 2; /* then skip do-loop, return error */ 410 | else { 411 | /* process blocks until last block or error */ 412 | do { 413 | last = bits(&s, 1); /* one if last block */ 414 | type = bits(&s, 2); /* block type 0..3 */ 415 | err = type == 0 ? 416 | stored(&s) : 417 | (type == 1 ? 418 | fixed(&s) : 419 | (type == 2 ? 420 | dynamic(&s) : 421 | -1)); /* type == 3, invalid */ 422 | if (err != 0) 423 | break; /* return with error */ 424 | } while (!last); 425 | } 426 | 427 | /* update the lengths and return */ 428 | if (err <= 0) { 429 | *destlen = s.outcnt - dictlen; 430 | *sourcelen = s.incnt; 431 | } 432 | return err; 433 | } 434 | -------------------------------------------------------------------------------- /puff.h: -------------------------------------------------------------------------------- 1 | /* puff.h 2 | Copyright (C) 2002-2013 Mark Adler, all rights reserved 3 | version 2.3, 21 Jan 2013 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the author be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | Mark Adler madler@alumni.caltech.edu 22 | */ 23 | 24 | // Extra modifications to support custom dictionary for pkgi 25 | 26 | /* 27 | * See puff.c for purpose and usage. 28 | */ 29 | #ifndef NIL 30 | # define NIL ((unsigned char *)0) /* for no output option */ 31 | #endif 32 | 33 | int puff(unsigned long dictlen, // length of custom dictionary (must be placed in beginning of dest) 34 | unsigned char *dest, /* pointer to destination pointer */ 35 | unsigned long *destlen, /* amount of output space */ 36 | const unsigned char *source, /* pointer to source data pointer */ 37 | unsigned long *sourcelen); /* amount of input available */ 38 | -------------------------------------------------------------------------------- /sce_sys/icon0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmozeiko/pkgi/3c988705e494bc173284873fd21ad234c75d24e3/sce_sys/icon0.png -------------------------------------------------------------------------------- /sce_sys/livearea/contents/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmozeiko/pkgi/3c988705e494bc173284873fd21ad234c75d24e3/sce_sys/livearea/contents/bg.png -------------------------------------------------------------------------------- /sce_sys/livearea/contents/startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmozeiko/pkgi/3c988705e494bc173284873fd21ad234c75d24e3/sce_sys/livearea/contents/startup.png -------------------------------------------------------------------------------- /sce_sys/livearea/contents/template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bg.png 6 | 7 | 8 | 9 | startup.png 10 | 11 | 12 | -------------------------------------------------------------------------------- /simulator/pkgi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pkgi", "pkgi.vcxproj", "{4E8555F1-0655-4867-BF59-B56FFE6A2E3E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4E8555F1-0655-4867-BF59-B56FFE6A2E3E}.Debug|x64.ActiveCfg = Debug|x64 15 | {4E8555F1-0655-4867-BF59-B56FFE6A2E3E}.Debug|x64.Build.0 = Debug|x64 16 | {4E8555F1-0655-4867-BF59-B56FFE6A2E3E}.Release|x64.ActiveCfg = Release|x64 17 | {4E8555F1-0655-4867-BF59-B56FFE6A2E3E}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {86E4847B-5BE3-40D4-BE80-189CF41D649E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /simulator/pkgi.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {4E8555F1-0655-4867-BF59-B56FFE6A2E3E} 16 | Win32Proj 17 | pkgi 18 | 19 | 20 | 21 | Application 22 | true 23 | v141 24 | 25 | 26 | Application 27 | false 28 | v141 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | false 45 | 46 | 47 | false 48 | 49 | 50 | 51 | 52 | 53 | Level4 54 | Disabled 55 | PKGI_VERSION="00.05";PKGI_ENABLE_LOGGING;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 56 | ProgramDatabase 57 | true 58 | false 59 | MultiThreadedDebug 60 | 4152;4204 61 | false 62 | 63 | 64 | Console 65 | 66 | 67 | 68 | 69 | Level4 70 | 71 | 72 | MaxSpeed 73 | true 74 | PKGI_VERSION="00.05";PKGI_ENABLE_LOGGING;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 75 | true 76 | MultiThreaded 77 | 4152;4204 78 | false 79 | true 80 | Fast 81 | false 82 | 83 | 84 | Console 85 | true 86 | true 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | true 101 | true 102 | 103 | 104 | 105 | TurnOffAllWarnings 106 | TurnOffAllWarnings 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /simulator/pkgi.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --------------------------------------------------------------------------------