├── .github ├── CODEOWNERS └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── ICON0.PNG ├── LICENSE ├── README.md ├── data ├── CIRCLE.png ├── CROSS.png ├── LANG │ ├── ch.txt │ ├── de.po │ ├── es.po │ ├── fi.po │ ├── fr.po │ ├── id.po │ ├── it.po │ ├── pl.po │ ├── pt.po │ ├── tr.po │ └── zh.txt ├── SQUARE.png ├── TRIANGLE.png ├── background.png ├── fonts │ ├── NotoSansJP-Medium.otf │ └── OFL.txt └── pkgi.txt ├── docs ├── README.md ├── _config.yml ├── leon-luna.jpg └── screenshot.png ├── include ├── font-8x16.h ├── libfont.h ├── pkgi.h ├── pkgi_aes.h ├── pkgi_config.h ├── pkgi_db.h ├── pkgi_dialog.h ├── pkgi_download.h ├── pkgi_menu.h ├── pkgi_sha256.h ├── pkgi_style.h ├── pkgi_utils.h └── ttf_render.h ├── source ├── depackager.c ├── libfont.c ├── loadpng.c ├── pkg2iso.c ├── pkgi.c ├── pkgi_aes.c ├── pkgi_config.c ├── pkgi_db.c ├── pkgi_dialog.c ├── pkgi_download.c ├── pkgi_menu.c ├── pkgi_psp.c ├── ttf_fonts.c └── zip_util.c └── translate.po /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS info: https://help.github.com/en/articles/about-code-owners 2 | # Owners are automatically requested for review for PRs that changes code 3 | # that they own. 4 | * @bucanero 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build package 2 | 3 | on: [ push, pull_request, workflow_dispatch ] 4 | 5 | jobs: 6 | build_pkg: 7 | runs-on: ubuntu-22.04 8 | container: pspdev/pspdev:latest 9 | steps: 10 | 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Install dependencies 15 | run: | 16 | apk add zip 17 | 18 | - name: Checkout dbglogger 19 | uses: actions/checkout@v4 20 | with: 21 | repository: bucanero/dbglogger 22 | path: dbglogger 23 | 24 | - name: Install dbglogger 25 | working-directory: dbglogger 26 | run: | 27 | make -f Makefile.psp install 28 | 29 | - name: Build PKGi App Package (OFW) 30 | run: | 31 | mkdir ofw && cd ofw 32 | psp-cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_PRX=ON -DENC_PRX=ON 33 | make 34 | make createzip 35 | 36 | - name: Build PKGi App Package 37 | run: | 38 | psp-cmake . -DCMAKE_BUILD_TYPE=Release 39 | make 40 | make createzip 41 | 42 | - name: Get short SHA 43 | id: slug 44 | run: | 45 | printf '%s\n' "sha8=$(printf '%s\n' ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT 46 | 47 | - name: Push package artifact 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: pkgi-psp-build_${{ steps.slug.outputs.sha8 }} 51 | path: pkgi-psp.zip 52 | if-no-files-found: error 53 | 54 | - name: Push OFW artifact 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: pkgi-ofw-build_${{ steps.slug.outputs.sha8 }} 58 | path: ofw/pkgi-psp.zip 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the `pkgi-psp` project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased]() 6 | 7 | ## [v1.1.0](https://github.com/bucanero/pkgi-psp/releases/tag/v1.1.0) - 2022-03-03 8 | 9 | ### Added 10 | 11 | * Show battery level 12 | * .Zip file support 13 | - Download and extract `.zip` links 14 | * Local .PKG installation 15 | - Install `.pkg` files from the PSP's memory stick 16 | - Scan and list packages from `ms0:/PKG` 17 | * Add PSP-Go storage option 18 | - Edit `config.txt` to change storage location (add line `storage ms0`) 19 | 20 | ### Misc 21 | 22 | * Add OFW-compatible build 23 | 24 | ### Fixed 25 | 26 | * Fix progress bar ETA when resuming downloads 27 | 28 | ## [v1.0.0](https://github.com/bucanero/pkgi-psp/releases/tag/v1.0.0) - 2023-10-14 29 | 30 | ### Added 31 | 32 | * Package install as `ISO`/`CSO`/`Digital` 33 | * PSP Go internal storage detection (`ef0`/`ms0`) 34 | * Update database files from URLs (`Refresh` option) 35 | * Enabled `Search` option (on-screen keyboard) 36 | * Added PSX category 37 | * PKGi new version check & notification 38 | 39 | ### Fixed 40 | 41 | * Install and decrypt themes to `/PSP/THEME/` 42 | 43 | ### Misc 44 | 45 | * Improved download speed (~400Kb/s average) 46 | * Network proxy settings support 47 | 48 | ## [v0.8.0](https://github.com/bucanero/pkgi-psp/releases/tag/v0.8.0) - 2023-10-01 49 | 50 | First public release. In memory of Leon & Luna. 51 | 52 | ### Added 53 | 54 | * Download and install PKG files to the PSP 55 | * Support for loading multiple database files 56 | - Generic text database format support 57 | - Filter unsupported or missing URLs when loading a database 58 | * Content categorization and filtering 59 | * Support for `HTTP`, `HTTPS`, `FTP`, `FTPS` links with TLS v1.2 60 | * Localization support (Finnish, French, German, Indonesian, Italian, Polish, Portuguese, Spanish, Turkish) 61 | - Language detection based on PSP settings 62 | * Enter button detection (`cross`/`circle`) 63 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | 3 | if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) 4 | if(NOT DEFINED ENV{PSPDEV}) 5 | message(FATAL_ERROR "Please define PSPDEV to point to your SDK path!") 6 | endif() 7 | endif() 8 | 9 | project(pkgi-psp) 10 | 11 | find_package(SDL2 REQUIRED) 12 | find_package(CURL REQUIRED) 13 | 14 | option(PKGI_ENABLE_DEBUG "enables debug logging over udp multicast" OFF) 15 | 16 | if(PKGI_ENABLE_DEBUG) 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPKGI_ENABLE_LOGGING=1") 18 | endif() 19 | 20 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11 -G0") 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 22 | 23 | include_directories( 24 | include 25 | ${PSPDEV}/psp/include/freetype2 26 | ) 27 | 28 | link_directories( 29 | libs 30 | ) 31 | 32 | # From vita shell CMakeLists - credits TheFlow 33 | FUNCTION(ADD_RESOURCES out_var) 34 | SET(result) 35 | FOREACH(in_f ${ARGN}) 36 | SET(out_f "${CMAKE_CURRENT_BINARY_DIR}/${in_f}.o") 37 | GET_FILENAME_COMPONENT(out_dir ${out_f} DIRECTORY) 38 | ADD_CUSTOM_COMMAND(OUTPUT ${out_f} 39 | COMMAND ${CMAKE_COMMAND} -E make_directory ${out_dir} 40 | COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${in_f} 41 | DEPENDS ${in_f} 42 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 43 | COMMENT "Building resource ${out_f}" 44 | VERBATIM 45 | ) 46 | LIST(APPEND result ${out_f}) 47 | ENDFOREACH() 48 | SET(${out_var} "${result}" PARENT_SCOPE) 49 | ENDFUNCTION() 50 | 51 | file(GLOB res_files RELATIVE 52 | ${CMAKE_SOURCE_DIR} 53 | data/*.png 54 | data/*.ahx 55 | ) 56 | add_resources(apollo_res ${res_files}) 57 | 58 | add_executable(${PROJECT_NAME} 59 | ${apollo_res} 60 | source/pkg2iso.c 61 | source/depackager.c 62 | source/loadpng.c 63 | source/libfont.c 64 | source/ttf_fonts.c 65 | source/zip_util.c 66 | source/pkgi.c 67 | source/pkgi_aes.c 68 | source/pkgi_db.c 69 | source/pkgi_download.c 70 | source/pkgi_psp.c 71 | source/pkgi_config.c 72 | source/pkgi_dialog.c 73 | source/pkgi_menu.c 74 | ) 75 | 76 | target_link_libraries(${PROJECT_NAME} 77 | ${SDL2_LIBRARIES} 78 | CURL::libcurl 79 | dbglogger 80 | freetype 81 | png 82 | pspnet 83 | pspnet_apctl 84 | mini18n 85 | zip 86 | bz2 87 | z 88 | ) 89 | 90 | create_pbp_file( 91 | TARGET ${PROJECT_NAME} 92 | ICON_PATH ${CMAKE_SOURCE_DIR}/ICON0.PNG 93 | BACKGROUND_PATH NULL 94 | PREVIEW_PATH NULL 95 | TITLE "PKGi PSP" 96 | VERSION 01.10 97 | ) 98 | 99 | add_custom_target(createzip 100 | COMMAND @mkdir -p PKGI/LANG 101 | COMMAND @cp EBOOT.PBP PKGI/ 102 | COMMAND @cp ${CMAKE_SOURCE_DIR}/data/fonts/NotoSansJP-Medium.otf PKGI/FONT.OTF 103 | COMMAND @cp ${CMAKE_SOURCE_DIR}/data/LANG/* PKGI/LANG/ 104 | COMMAND @rm -fr pkgi-psp.zip 105 | COMMAND @zip -r pkgi-psp.zip PKGI/ 106 | ) 107 | 108 | add_custom_target(copy 109 | COMMAND @echo "Copying to $$PSPMS/PSP/GAME/PKGI/EBOOT.PBP ..." 110 | COMMAND @mkdir -p $$PSPMS/PSP/GAME/PKGI 111 | COMMAND @cp -v EBOOT.PBP $$PSPMS/PSP/GAME/PKGI/EBOOT.PBP 112 | DEPENDS ${PROJECT_NAME} 113 | ) 114 | 115 | add_custom_target(send 116 | COMMAND @echo "Uploading to ftp://$$PSPIP:1337/ms0:/PSP/GAME/PKGI/EBOOT.PBP ..." 117 | COMMAND @curl -T EBOOT.PBP ftp://$$PSPIP:1337/ms0:/PSP/GAME/PKGI/EBOOT.PBP 118 | DEPENDS ${PROJECT_NAME} 119 | ) 120 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Code of Conduct 2 | =============== 3 | 4 | My goal is to provide quality open-source software that everyone can use. 5 | While I may not be able to address every request or accept every contribution 6 | to this project, I will do my best to develop and maintain it for the common 7 | good. 8 | 9 | As part of the open-source community, I expect everyone to: 10 | 11 | - Be friendly and patient. 12 | - Be respectful, even if we disagree. 13 | - Be honest. 14 | - Be accepting of all people. 15 | - Fully explain your concerns, issues, or ideas. 16 | -------------------------------------------------------------------------------- /ICON0.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/ICON0.PNG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Damian Parrino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PKGi PSP 2 | 3 | [![Downloads][img_downloads]][pkgi_downloads] [![Release][img_latest]][pkgi_latest] [![License][img_license]][pkgi_license] 4 | [![Build package](https://github.com/bucanero/pkgi-psp/actions/workflows/build.yml/badge.svg)](https://github.com/bucanero/pkgi-psp/actions/workflows/build.yml) 5 | [![Twitter](https://img.shields.io/twitter/follow/dparrino?label=Follow)](https://twitter.com/dparrino) 6 | 7 | **PKGi PSP** is a PlayStation Portable port of [pkgi-ps3](https://github.com/bucanero/pkgi-ps3). 8 | 9 | This homebrew app allows to download and install `.pkg` files directly on your PSP. 10 | 11 | ![image](./docs/screenshot.png) 12 | 13 | **Comments, ideas, suggestions?** You can [contact me](https://github.com/bucanero/) on [Twitter](https://twitter.com/dparrino) and on [my website](http://www.bucanero.com.ar/). 14 | 15 | # Features 16 | 17 | * **easy to use:** list available downloads, including searching, filtering, and sorting. 18 | * **standalone:** no PC required, everything happens directly on the PSP. 19 | * **automatic downloads:** just choose an item, and it will be downloaded by the app to your Memory Stick (`ms0:/PKG/`). 20 | * **resumes interrupted downloads:** you can stop a download at any time, switch applications, and come back to resume the download later. 21 | * **localization support:** Finnish, French, German, Indonesian, Italian, Polish, Portuguese, Spanish, Turkish 22 | 23 | ### Notes: 24 | * **content activation:** downloaded content requires a valid license to run. If your PSP hasn't been activated, you can use one of the following plugins: 25 | - [npdrm_free plugin](https://github.com/qwikrazor87/npdrm_free) (PSP/PS1/PCEngine games and DLCs) 26 | - [npdrm_free_mod plugin](https://github.com/lusid1/npdrm_free_mod) (PSP/PS1 games only) 27 | - [nploader_mod](https://github.com/lusid1/nploader_mod) (PSP DLCs only) 28 | 29 | # Download 30 | 31 | Get the [latest version here][pkgi_latest]. 32 | 33 | ### Changelog 34 | 35 | See the [latest changes here](CHANGELOG.md). 36 | 37 | # Setup instructions 38 | 39 | You need to create a [`pkgi.txt`](#sample-db-file) file in `ms0:/PSP/GAME/PKGI/` that contains the items available for installation. 40 | The text database format is user customizable. Check [this section](#user-defined-db-format) to learn how to define your own custom DB format. 41 | 42 | ## Multiple databases 43 | 44 | You can also load additional database files: 45 | 46 | - `pkgi_games.txt` 47 | - `pkgi_dlcs.txt` 48 | - `pkgi_themes.txt` 49 | - `pkgi_psx.txt` 50 | - `pkgi_demos.txt` 51 | - `pkgi_updates.txt` 52 | - `pkgi_emulators.txt` 53 | - `pkgi_apps.txt` 54 | 55 | Items on each of these files will be auto-categorized to the file content type. **Note:** The app assumes that every database file has the same format, as defined in `dbformat.txt`. 56 | 57 | ## Online DB update 58 | 59 | You can refresh and sync an online database by adding the DB URL(s) to the `config.txt` file in `ms0:/PSP/GAME/PKGI/`. 60 | 61 | For example: 62 | 63 | ``` 64 | url_games http://www.mysite.com/mylist.csv 65 | url_demos http://www.demos.com/otherlist.csv 66 | url_emulators http://www.example.com/emulators.csv 67 | ``` 68 | 69 | Using this setup, 70 | - `pkgi_games.txt` will be updated with `http://www.mysite.com/mylist.csv`, 71 | - `pkgi_demos.txt` with `http://www.demos.com/otherlist.csv`, 72 | - and `pkgi_emulators.txt` with `http://www.example.com/emulators.csv`. 73 | 74 | Next time you open the app, you'll have an additional menu option ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png) called **Refresh**. When you select it, the local databases will be syncronized with the defined URLs. 75 | 76 | # DB formats 77 | 78 | The application needs a text database that contains the items available for installation, and it must follow the [default format definition](#default-db-format), or have a [custom format definition](#user-defined-db-format) file. 79 | 80 | ## Default DB format 81 | 82 | The default database file format uses a very simple CSV format where each line means one item in the list: 83 | 84 | ``` 85 | contentid,type,name,description,rap,url,size,checksum 86 | ``` 87 | 88 | where: 89 | 90 | | Column | Description | 91 | |--------|-------------| 92 | | `contentid` | is the full content id of the item, for example: `UP0000-NPXX99999_00-0000112223333000`. 93 | | `type` | is a number for the item's content type. See the [table below](#content-types) for details. (set it to 0 if unknown) 94 | | `name` | is a string for the item's name. 95 | | `description` | is a string for the item's description. 96 | | `rap` | the 16 hex bytes for a RAP file, if needed by the item (`.rap` files will be created on `ms0:/PKG/RAP`). Leave empty to skip the `.rap` file. 97 | | `url` | is the HTTP/HTTPS/FTP/FTPS URL where to download the `.pkg` file. 98 | | `size` | is the size in bytes of the `.pkg` file, or 0 if unknown. 99 | | `checksum` | is a SHA256 digest of the `.pkg` file (as 32 hex bytes) to make sure the file is not tampered with. Leave empty to skip the check. 100 | 101 | **Note:** `name` and `description` cannot contain newlines or commas. 102 | 103 | ### Sample DB file 104 | 105 | An example `pkgi.txt` file following the `contentid,type,name,description,rap,url,size,checksum` format: 106 | 107 | ``` 108 | U00001-NP0APOLLO_00-0000000000000000,8,Apollo Save Tool v1.3.0,Save-game manager,,https://github.com/bucanero/apollo-psp/releases/download/v1.3.0/apollo-psp.zip,6885196,DFF88635A044F319686231ACABB548CC42EA1CF3A992856572C7AF20F70D30ED 109 | U00001-CMFILEMGR_00-0000000000000000,8,CM File Manager v4.10,File manager,,https://github.com/joel16/CMFileManager-PSP/releases/download/v4.10/CMFileManager-PSP.zip,1815998,7A5FD10184546AB993A4D5F3054BCBA9E9B7A1C569EE26E366F1F5CC9DA5A554 110 | UP0001-PSPTOOL10_00-0000000000000000,8,PSP Tool v1.00,PSP Tool,,https://archive.org/download/psp-tool.-7z/PSP%20Tool.zip,5023819, 111 | UP0001-PSPFILER6_00-0000000000000000,8,PSP Filer v6.6,File manager,,https://wololo.net/download.php?f=filer6.6.zip,1295106, 112 | UP0001-NZPORTABL_00-0000000000000000,1,Nazi Zombies: Portable,Nightly 64 Mb,,https://github.com/nzp-team/nzportable/releases/download/nightly/nzportable-psp-64mb.zip,36163686, 113 | UP0001-IRISMAN00_00-VER4880000000000,8,IRISMAN 4.88.1,Backup Manager,,http://github.com/aldostools/IRISMAN/releases/download/4.88/IRISMAN_4.88.pkg,29411984,E6EF607F0002B31BFB148BE4FC9BDBACB4E53110751F0E667C701D40B5290570 114 | ``` 115 | 116 | ### Content types 117 | 118 | | Type value | Content type | DB File | 119 | |------------|--------------|---------| 120 | | 0 | Unknown | 121 | | 1 | Game | `pkgi_games.txt` 122 | | 2 | DLC | `pkgi_dlcs.txt` 123 | | 3 | Theme | `pkgi_themes.txt` 124 | | 4 | PSX | `pkgi_psx.txt` 125 | | 5 | Demo | `pkgi_demos.txt` 126 | | 6 | Update | `pkgi_updates.txt` 127 | | 7 | Emulator | `pkgi_emulators.txt` 128 | | 8 | Application | `pkgi_apps.txt` 129 | 130 | ## User-defined DB format 131 | 132 | To use a custom database format, you need to create a `dbformat.txt` file, and save it on `ms0:/PSP/GAME/PKGI/`. 133 | 134 | The `dbformat.txt` definition file is a 2-line text file: 135 | * Line 1: the custom delimiter character (e.g.: `;`, `,`, `|`, etc.) 136 | * Line 2: the column names for every column in the custom database, delimited by the proper delimiter defined in line 1 137 | 138 | **Note:** For the columns to be properly recognized, use the column tag names defined in the table above. 139 | 140 | All the columns are optional. Your database might have more (or less) columns, so any unrecognized column will be skipped. 141 | 142 | ### Example 143 | 144 | Example `dbformat.txt`, for a database using semi-colon (`;`) as separator: 145 | 146 | ``` 147 | ; 148 | name;TITLE ID;REGION;description;AUTHOR;TYPE;url;rap;size 149 | ``` 150 | 151 | **Result:** only the `name,description,url,rap,size` fields will be used. 152 | 153 | ### Example 154 | 155 | Example `dbformat.txt`, for a database using character pipe (`|`) as separator: 156 | 157 | ``` 158 | | 159 | REGION|TITLE|name|url|rap|contentid|DATE|PKG FILENAME|size|checksum 160 | ``` 161 | 162 | **Result:** only the `name,url,rap,contentid,size,checksum` fields will be used. 163 | 164 | # Usage 165 | 166 | Using the application is simple and straight-forward: 167 | 168 | - Move UP/DOWN to select the item you want to download, and press ![X button](https://github.com/bucanero/pkgi-ps3/raw/master/data/CROSS.png). 169 | - To see the item's details, press ![Square](https://github.com/bucanero/pkgi-ps3/raw/master/data/SQUARE.png). 170 | - To sort/filter/search press ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png). 171 | It will open the context menu. Press ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png) again to confirm the new settings, or press ![O button](https://github.com/bucanero/pkgi-ps3/raw/master/data/CIRCLE.png) to cancel any changes. 172 | - Press left or right trigger buttons L1/R1 to move pages up or down. 173 | - Press LEFT/RIGHT buttons to switch between categories. 174 | 175 | ### Notes 176 | 177 | - **RAP data:** if the item has `.rap` data, the file will be saved in the `ms0:/PKG/RAP/` folder. 178 | 179 | 180 | # Q&A 181 | 182 | 1. Where to get a `rap` string? 183 | 184 | You can use a tool like RIF2RAP to generate a `.rap` from your existing `.rif` files. Then you can use a tool like `hexdump` to get the hex byte string. 185 | 186 | 2. Where to get `.pkg` links? 187 | 188 | You can use [PSDLE][] to find `.pkg` URLs for the games you own. Then either use the original URL, or host the file on your own web server. 189 | 190 | 3. Where to remove interrupted/failed downloads to free up disk space? 191 | 192 | Check the `ms0:/PKG/` folder - each download will be in a separate `.pkg` file by its content ID. Simply delete the file and start again. 193 | 194 | 4. Download speed is too slow! 195 | 196 | Optimization is still pending. (Optional) Set `Power Save Settings` -> `WLAN Power save` -> `OFF` , if you want to speed up the download process. 197 | 198 | # Credits 199 | 200 | * [Bucanero](http://www.bucanero.com.ar/): Project developer 201 | 202 | ## Acknowledgements 203 | 204 | * [mmozeiko](https://github.com/mmozeiko/): [pkgi](https://github.com/mmozeiko/pkgi) (PS Vita), [pkg2zip](https://github.com/mmozeiko/pkg2zip) 205 | * [qwikrazor87](https://github.com/qwikrazor87/): [Depackager](https://github.com/bucanero/psptools/tree/master/depackager) 206 | 207 | # Building 208 | 209 | You need to have installed: 210 | 211 | - [PSP SDK](https://github.com/pspdev/) 212 | - [mbedTLS](https://github.com/pspdev/psp-packages/tree/master/mbedtls) library 213 | - [cURL](https://github.com/pspdev/psp-packages/tree/master/curl) library 214 | - [Mini18n](https://github.com/bucanero/mini18n) library 215 | - [dbglogger](https://github.com/bucanero/dbglogger) library (only required for debug logging) 216 | 217 | Run `cmake . && make` to create a release build. If you want to create a `.zip` file, run `make createzip`. 218 | 219 | ## Debugging 220 | 221 | To enable debug logging, pass `-DPKGI_ENABLE_DEBUG=ON` argument to `cmake`. The application will write debug messages to 222 | 223 | ms0:/pkgi-psp.log 224 | 225 | You can also set the `PSPIP` environment variable to your PSP's IP address, and use `make send` to upload `EBOOT.PBP` directly to the `ms0:/PSP/GAME/PKGI` folder. 226 | 227 | # License 228 | 229 | `pkgi-psp` is released under the [MIT License](LICENSE). 230 | 231 | [PSDLE]: https://repod.github.io/psdle/ 232 | [socat]: http://www.dest-unreach.org/socat/ 233 | [pkgi_downloads]: https://github.com/bucanero/pkgi-psp/releases 234 | [pkgi_latest]: https://github.com/bucanero/pkgi-psp/releases/latest 235 | [pkgi_license]: https://github.com/bucanero/pkgi-psp/blob/main/LICENSE 236 | [img_downloads]: https://img.shields.io/github/downloads/bucanero/pkgi-psp/total.svg?maxAge=3600 237 | [img_latest]: https://img.shields.io/github/release/bucanero/pkgi-psp.svg?maxAge=3600 238 | [img_license]: https://img.shields.io/github/license/bucanero/pkgi-psp.svg?maxAge=2592000 239 | -------------------------------------------------------------------------------- /data/CIRCLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/CIRCLE.png -------------------------------------------------------------------------------- /data/CROSS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/CROSS.png -------------------------------------------------------------------------------- /data/LANG/ch.txt: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PKGi PSP\n" 4 | "POT-Creation-Date: 2021-10-09 19:00-0300\n" 5 | "PO-Revision-Date: 2021-10-09 19:00-0300\n" 6 | "Last-Translator: Croden1999\n" 7 | "Language-Team: \n" 8 | "Language: ch\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: ./source\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-Flags-xgettext: --add-comments\n" 16 | "X-Poedit-SourceCharset: UTF-8\n" 17 | "X-Poedit-KeywordsList: _\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: pkgi.c:82 21 | msgid "Installing..." 22 | msgstr "安裝中" 23 | 24 | #: pkgi.c:82 25 | msgid "Please wait..." 26 | msgstr "請稍等..." 27 | 28 | #: pkgi.c:94 29 | msgid "Installation failed" 30 | msgstr "安裝失敗" 31 | 32 | #: pkgi.c:118 33 | msgid "Successfully installed" 34 | msgstr "下載完成" 35 | 36 | #: pkgi.c:122 37 | msgid "Task successfully queued (reboot to start)" 38 | msgstr "任務已成功排隊(重啓開始)" 39 | 40 | #: pkgi.c:163 41 | msgid "GB" 42 | msgstr "" 43 | 44 | #: pkgi.c:167 pkgi_download.c:249 45 | msgid "MB" 46 | msgstr "" 47 | 48 | #: pkgi.c:171 pkgi.c:504 pkgi_download.c:253 49 | msgid "KB" 50 | msgstr "" 51 | 52 | #: pkgi.c:175 53 | msgid "B" 54 | msgstr "" 55 | 56 | #: pkgi.c:185 57 | #, c-format 58 | msgid "pkg requires %u %s free space, but only %u %s available" 59 | msgstr "pkg需要%u %s剩餘空间, 但只有%u %s可用" 60 | 61 | #: pkgi.c:225 pkgi_menu.c:109 pkgi_menu.c:121 62 | msgid "All" 63 | msgstr "全部" 64 | 65 | #: pkgi.c:226 pkgi_menu.c:122 66 | msgid "Games" 67 | msgstr "游戲" 68 | 69 | #: pkgi.c:227 pkgi_menu.c:123 70 | msgid "DLCs" 71 | msgstr "追加内容" 72 | 73 | #: pkgi.c:228 pkgi_menu.c:124 74 | msgid "Themes" 75 | msgstr "主題" 76 | 77 | #: pkgi.c:229 pkgi_menu.c:125 78 | msgid "Avatars" 79 | msgstr "頭像" 80 | 81 | #: pkgi.c:230 pkgi_menu.c:126 82 | msgid "Demos" 83 | msgstr "試玩" 84 | 85 | #: pkgi.c:231 pkgi_menu.c:118 pkgi_menu.c:127 86 | msgid "Updates" 87 | msgstr "更新" 88 | 89 | #: pkgi.c:232 pkgi_menu.c:128 90 | msgid "Emulators" 91 | msgstr "模擬器" 92 | 93 | #: pkgi.c:233 pkgi_menu.c:129 94 | msgid "Apps" 95 | msgstr "應用程序" 96 | 97 | #: pkgi.c:234 pkgi_menu.c:130 98 | msgid "Tools" 99 | msgstr "工具" 100 | 101 | #: pkgi.c:235 102 | msgid "Unknown" 103 | msgstr "未知" 104 | 105 | #: pkgi.c:253 106 | msgid "Exit to XMB?" 107 | msgstr "退出到XMB嗎?" 108 | 109 | #: pkgi.c:436 110 | msgid "No items!" 111 | msgstr "無內容!" 112 | 113 | #: pkgi.c:462 114 | msgid "Item already installed, download again?" 115 | msgstr "內容已安裝,是否重新下载?" 116 | 117 | #: pkgi.c:471 pkgi_download.c:372 pkgi_download.c:652 118 | msgid "Downloading..." 119 | msgstr "下載中..." 120 | 121 | #: pkgi.c:471 pkgi.c:674 122 | msgid "Preparing..." 123 | msgstr "準備中..." 124 | 125 | #: pkgi.c:504 pkgi.c:508 126 | msgid "Refreshing" 127 | msgstr "刷新中" 128 | 129 | #: pkgi.c:563 pkgi.c:567 130 | msgid "Count" 131 | msgstr "總數" 132 | 133 | #: pkgi.c:575 134 | msgid "Free" 135 | msgstr "剩餘" 136 | 137 | #: pkgi.c:585 138 | msgid "Select" 139 | msgstr "選擇" 140 | 141 | #: pkgi.c:585 142 | msgid "Close" 143 | msgstr "關閉" 144 | 145 | #: pkgi.c:585 146 | msgid "Cancel" 147 | msgstr "取消" 148 | 149 | #: pkgi.c:589 pkgi_download.c:136 150 | msgid "Download" 151 | msgstr "下載" 152 | 153 | #: pkgi.c:589 154 | msgid "Menu" 155 | msgstr "菜單" 156 | 157 | #: pkgi.c:589 158 | msgid "Details" 159 | msgstr "詳細" 160 | 161 | #: pkgi.c:589 162 | msgid "Exit" 163 | msgstr "退出" 164 | 165 | #: pkgi.c:678 166 | msgid "Successfully downloaded PKGi PSP update" 167 | msgstr "下載PKGi PSP更新成功" 168 | 169 | #: pkgi.c:1051 170 | msgid "Search" 171 | msgstr "搜索" 172 | 173 | #: pkgi_db.c:145 pkgi_db.c:153 174 | msgid "failed to download list from" 175 | msgstr "下載列表失敗" 176 | 177 | #: pkgi_db.c:159 178 | msgid "list is too large... check for newer pkgi version!" 179 | msgstr "列表過大...檢查是不是新的pkgi版本!" 180 | 181 | #: pkgi_db.c:187 182 | msgid "list is empty... check the DB server" 183 | msgstr "列表為空...檢查數據庫服務器" 184 | 185 | #: pkgi_db.c:390 186 | msgid "ERROR: pkgi.txt file(s) missing or bad config.txt file" 187 | msgstr "錯誤:pkgi.txt文件丟失或配置文件錯誤" 188 | 189 | #: pkgi_dialog.c:80 190 | msgid "Content" 191 | msgstr "內容" 192 | 193 | #: pkgi_dialog.c:108 194 | msgid "ERROR" 195 | msgstr "錯誤" 196 | 197 | #: pkgi_dialog.c:169 198 | msgid "Failed to download the update list" 199 | msgstr "下载更新列表失败" 200 | 201 | #: pkgi_dialog.c:174 202 | msgid "update(s) loaded" 203 | msgstr "更新加載" 204 | 205 | #: pkgi_dialog.c:319 206 | #, c-format 207 | msgid "press %s to cancel" 208 | msgstr "按%s取消" 209 | 210 | #: pkgi_dialog.c:333 211 | #, c-format 212 | msgid "press %s to close - %s to scan updates" 213 | msgstr "按%s關閉 - %s掃描更新" 214 | 215 | #: pkgi_dialog.c:365 216 | msgid "Enter" 217 | msgstr "確認" 218 | 219 | #: pkgi_dialog.c:365 220 | msgid "Back" 221 | msgstr "返回" 222 | 223 | #: pkgi_dialog.c:367 224 | #, c-format 225 | msgid "press %s to close" 226 | msgstr "按%s關閉" 227 | 228 | #: pkgi_download.c:183 229 | msgid "Install" 230 | msgstr "安裝" 231 | 232 | #: pkgi_download.c:216 pkgi_download.c:220 pkgi_download.c:226 233 | msgid "ETA" 234 | msgstr "" 235 | 236 | #: pkgi_download.c:310 pkgi_download.c:392 237 | msgid "Could not send HTTP request" 238 | msgstr "無法發送HTTP請求" 239 | 240 | #: pkgi_download.c:317 pkgi_download.c:399 241 | msgid "HTTP request failed" 242 | msgstr "HTTP請求失敗" 243 | 244 | #: pkgi_download.c:322 pkgi_download.c:404 245 | msgid "HTTP response has unknown length" 246 | msgstr "未知HTTP響應長度" 247 | 248 | #: pkgi_download.c:331 249 | msgid "Not enough free space on HDD" 250 | msgstr "硬盤空間不足" 251 | 252 | #: pkgi_download.c:340 253 | msgid "Could not create task directory on HDD." 254 | msgstr "無法在硬盤創建任務目錄。" 255 | 256 | #: pkgi_download.c:348 257 | msgid "Saving background task..." 258 | msgstr "保存後臺任務中..." 259 | 260 | #: pkgi_download.c:354 261 | msgid "Could not create PKG file to HDD." 262 | msgstr "無法創建PKG文件到硬盤裏。" 263 | 264 | #: pkgi_download.c:360 265 | msgid "Could not create task files to HDD." 266 | msgstr "無法創建任務文件到硬盘裏。" 267 | 268 | #: pkgi_download.c:425 269 | msgid "HTTP download error" 270 | msgstr "HTTP下載錯誤" 271 | 272 | #: pkgi_download.c:432 273 | msgid "HTTP connection closed" 274 | msgstr "HTTP連接關閉" 275 | 276 | #: pkgi_download.c:445 277 | msgid "failed to write to" 278 | msgstr "寫入失敗" 279 | 280 | #: pkgi_download.c:465 281 | msgid "cannot create folder" 282 | msgstr "無法創建文件夹" 283 | 284 | #: pkgi_download.c:475 285 | msgid "cannot create file" 286 | msgstr "無法創建文件" 287 | 288 | #: pkgi_download.c:490 289 | msgid "cannot resume file" 290 | msgstr "無法恢復文件" 291 | 292 | #: pkgi_download.c:562 293 | msgid "pkg integrity failed, try downloading again" 294 | msgstr "pkg完整性失敗,請嘗試重新下載" 295 | 296 | #: pkgi_download.c:573 297 | msgid "Creating RAP file" 298 | msgstr "創建RAP文件中" 299 | 300 | #: pkgi_download.c:581 pkgi_download.c:626 301 | msgid "Cannot save" 302 | msgstr "無法保存" 303 | 304 | #: pkgi_download.c:621 305 | msgid "Creating RIF file" 306 | msgstr "創建RIF文件中" 307 | 308 | #: pkgi_download.c:646 309 | msgid "Resuming..." 310 | msgstr "恢復中..." 311 | 312 | #: pkgi_download.c:652 313 | msgid "Adding background task..." 314 | msgstr "添加後臺任務中..." 315 | 316 | #: pkgi_download.c:675 317 | msgid "Downloading icon" 318 | msgstr "下載圖標中" 319 | 320 | #: pkgi_download.c:713 321 | msgid "Could not create install directory on HDD." 322 | msgstr "無法在硬盤創建安裝目錄。" 323 | 324 | #: pkgi_menu.c:102 325 | msgid "Search..." 326 | msgstr "搜索..." 327 | 328 | #: pkgi_menu.c:103 329 | msgid "Sort by:" 330 | msgstr "排序方式:" 331 | 332 | #: pkgi_menu.c:104 333 | msgid "Title" 334 | msgstr "標題" 335 | 336 | #: pkgi_menu.c:105 337 | msgid "Region" 338 | msgstr "區域" 339 | 340 | #: pkgi_menu.c:106 341 | msgid "Name" 342 | msgstr "名稱" 343 | 344 | #: pkgi_menu.c:107 345 | msgid "Size" 346 | msgstr "大小" 347 | 348 | #: pkgi_menu.c:108 349 | msgid "Content:" 350 | msgstr "內容:" 351 | 352 | #: pkgi_menu.c:110 353 | msgid "Regions:" 354 | msgstr "區域:" 355 | 356 | #: pkgi_menu.c:111 357 | msgid "Asia" 358 | msgstr "亞洲" 359 | 360 | #: pkgi_menu.c:112 361 | msgid "Europe" 362 | msgstr "歐洲" 363 | 364 | #: pkgi_menu.c:113 365 | msgid "Japan" 366 | msgstr "日本" 367 | 368 | #: pkgi_menu.c:114 369 | msgid "USA" 370 | msgstr "美國" 371 | 372 | #: pkgi_menu.c:115 373 | msgid "Options:" 374 | msgstr "選項:" 375 | 376 | #: pkgi_menu.c:116 377 | msgid "ISO" 378 | msgstr "後臺下載" 379 | 380 | #: pkgi_menu.c:117 381 | msgid "Keep PKGs" 382 | msgstr "音樂" 383 | 384 | #: pkgi_menu.c:119 385 | msgid "Refresh..." 386 | msgstr "刷新中..." 387 | 388 | #: pkgi_menu.c:326 389 | msgid "Direct DL" 390 | msgstr "直接下載" 391 | -------------------------------------------------------------------------------- /data/LANG/de.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/de.po -------------------------------------------------------------------------------- /data/LANG/es.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/es.po -------------------------------------------------------------------------------- /data/LANG/fi.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/fi.po -------------------------------------------------------------------------------- /data/LANG/fr.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/fr.po -------------------------------------------------------------------------------- /data/LANG/id.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PKGi PSP\n" 4 | "POT-Creation-Date: 2021-10-09 19:00-0300\n" 5 | "PO-Revision-Date: 2021-10-09 19:00-0300\n" 6 | "Last-Translator: Fajar Maulana\n" 7 | "Language-Team: \n" 8 | "Language: id\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: ./source\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-Flags-xgettext: --add-comments\n" 16 | "X-Poedit-SourceCharset: UTF-8\n" 17 | "X-Poedit-KeywordsList: _\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: pkgi.c:82 21 | msgid "Installing..." 22 | msgstr "Menginstal..." 23 | 24 | #: pkgi.c:82 25 | msgid "Please wait..." 26 | msgstr "Harap tunggu" 27 | 28 | #: pkgi.c:94 29 | msgid "Installation failed" 30 | msgstr "Penginstalan gagal" 31 | 32 | #: pkgi.c:118 33 | msgid "Successfully installed" 34 | msgstr "Terunduh dengan sukses" 35 | 36 | #: pkgi.c:122 37 | msgid "Task successfully queued (reboot to start)" 38 | msgstr "Tugas berhasil ditambahkan ke antrean (reboot untuk memulai)" 39 | 40 | #: pkgi.c:163 41 | msgid "GB" 42 | msgstr "GB" 43 | 44 | #: pkgi.c:167 pkgi_download.c:249 45 | msgid "MB" 46 | msgstr "MB" 47 | 48 | #: pkgi.c:171 pkgi.c:504 pkgi_download.c:253 49 | msgid "KB" 50 | msgstr "KB" 51 | 52 | #: pkgi.c:175 53 | msgid "B" 54 | msgstr "B" 55 | 56 | #: pkgi.c:185 57 | #, c-format 58 | msgid "pkg requires %u %s free space, but only %u %s available" 59 | msgstr "pkg memerlukan ruang kosong sebesar %u %s, tetapi Anda hanya memiliki %u %s ruang yang tersisa" 60 | 61 | #: pkgi.c:225 pkgi_menu.c:109 pkgi_menu.c:121 62 | msgid "All" 63 | msgstr "Semua" 64 | 65 | #: pkgi.c:226 pkgi_menu.c:122 66 | msgid "Games" 67 | msgstr "Gim" 68 | 69 | #: pkgi.c:227 pkgi_menu.c:123 70 | msgid "DLCs" 71 | msgstr "DLC" 72 | 73 | #: pkgi.c:228 pkgi_menu.c:124 74 | msgid "Themes" 75 | msgstr "Tema" 76 | 77 | #: pkgi.c:229 pkgi_menu.c:125 78 | msgid "Avatars" 79 | msgstr "Avatar" 80 | 81 | #: pkgi.c:230 pkgi_menu.c:126 82 | msgid "Demos" 83 | msgstr "Demo" 84 | 85 | #: pkgi.c:231 pkgi_menu.c:118 pkgi_menu.c:127 86 | msgid "Updates" 87 | msgstr "Pembaruan" 88 | 89 | #: pkgi.c:232 pkgi_menu.c:128 90 | msgid "Emulators" 91 | msgstr "Emulator" 92 | 93 | #: pkgi.c:233 pkgi_menu.c:129 94 | msgid "Apps" 95 | msgstr "Aplikasi" 96 | 97 | #: pkgi.c:234 pkgi_menu.c:130 98 | msgid "Tools" 99 | msgstr "Peralatan" 100 | 101 | #: pkgi.c:235 102 | msgid "Unknown" 103 | msgstr "Tidak Dikenal" 104 | 105 | #: pkgi.c:253 106 | msgid "Exit to XMB?" 107 | msgstr "Keluar ke XMB" 108 | 109 | #: pkgi.c:436 110 | msgid "No items!" 111 | msgstr "Tidak ada item!" 112 | 113 | #: pkgi.c:462 114 | msgid "Item already installed, download again?" 115 | msgstr "Item sudah terinstal, unduh kembali?" 116 | 117 | #: pkgi.c:471 pkgi_download.c:372 pkgi_download.c:652 118 | msgid "Downloading..." 119 | msgstr "Mengunduh..." 120 | 121 | #: pkgi.c:471 pkgi.c:674 122 | msgid "Preparing..." 123 | msgstr "Mempersiapkan..." 124 | 125 | #: pkgi.c:504 pkgi.c:508 126 | msgid "Refreshing" 127 | msgstr "Menyegarkan" 128 | 129 | #: pkgi.c:563 pkgi.c:567 130 | msgid "Count" 131 | msgstr "Jumlah" 132 | 133 | #: pkgi.c:575 134 | msgid "Free" 135 | msgstr "Bebas" 136 | 137 | #: pkgi.c:585 138 | msgid "Select" 139 | msgstr "Pilih" 140 | 141 | #: pkgi.c:585 142 | msgid "Close" 143 | msgstr "Tutup" 144 | 145 | #: pkgi.c:585 146 | msgid "Cancel" 147 | msgstr "Batal" 148 | 149 | #: pkgi.c:589 pkgi_download.c:136 150 | msgid "Download" 151 | msgstr "Unduh" 152 | 153 | #: pkgi.c:589 154 | msgid "Menu" 155 | msgstr "Menu" 156 | 157 | #: pkgi.c:589 158 | msgid "Details" 159 | msgstr "Detail" 160 | 161 | #: pkgi.c:589 162 | msgid "Exit" 163 | msgstr "Keluar" 164 | 165 | #: pkgi.c:678 166 | msgid "Successfully downloaded PKGi PSP update" 167 | msgstr "Pembaruan PKGi PSP berhasil terunduh" 168 | 169 | #: pkgi.c:1051 170 | msgid "Search" 171 | msgstr "Cari" 172 | 173 | #: pkgi_db.c:145 pkgi_db.c:153 174 | msgid "failed to download list from" 175 | msgstr "gagal mengunduh daftar dari" 176 | 177 | #: pkgi_db.c:159 178 | msgid "list is too large... check for newer pkgi version!" 179 | msgstr "daftar terlalu besar... periksa versi pkgi terbaru!" 180 | 181 | #: pkgi_db.c:187 182 | msgid "list is empty... check the DB server" 183 | msgstr "daftar kosong... periksa penyedia DB" 184 | 185 | #: pkgi_db.c:390 186 | msgid "ERROR: pkgi.txt file(s) missing or bad config.txt file" 187 | msgstr "EROR: berkas pkgi.txt tidak ada atau berkas config.txt rusak" 188 | 189 | #: pkgi_dialog.c:80 190 | msgid "Content" 191 | msgstr "Konten" 192 | 193 | #: pkgi_dialog.c:108 194 | msgid "ERROR" 195 | msgstr "EROR" 196 | 197 | #: pkgi_dialog.c:169 198 | msgid "Failed to download the update list" 199 | msgstr "Gagal mengunduh daftar pembaruan" 200 | 201 | #: pkgi_dialog.c:174 202 | msgid "update(s) loaded" 203 | msgstr "pembaruan dimuat" 204 | 205 | #: pkgi_dialog.c:319 206 | #, c-format 207 | msgid "press %s to cancel" 208 | msgstr "tekan %s untuk membatalkan" 209 | 210 | #: pkgi_dialog.c:333 211 | #, c-format 212 | msgid "press %s to close - %s to scan updates" 213 | msgstr "tekan %s untuk menutup - %s untuk memindai pembaruan" 214 | 215 | #: pkgi_dialog.c:365 216 | msgid "Enter" 217 | msgstr "Masuk" 218 | 219 | #: pkgi_dialog.c:365 220 | msgid "Back" 221 | msgstr "Kembali" 222 | 223 | #: pkgi_dialog.c:367 224 | #, c-format 225 | msgid "press %s to close" 226 | msgstr "tekan %s untuk menutup" 227 | 228 | #: pkgi_download.c:183 229 | msgid "Install" 230 | msgstr "Instal" 231 | 232 | #: pkgi_download.c:216 pkgi_download.c:220 pkgi_download.c:226 233 | msgid "ETA" 234 | msgstr "Estimasi" 235 | 236 | #: pkgi_download.c:310 pkgi_download.c:392 237 | msgid "Could not send HTTP request" 238 | msgstr "Tidak dapat mengirim permintaan HTTP" 239 | 240 | #: pkgi_download.c:317 pkgi_download.c:399 241 | msgid "HTTP request failed" 242 | msgstr "Permintaan HTTP gagal" 243 | 244 | #: pkgi_download.c:322 pkgi_download.c:404 245 | msgid "HTTP response has unknown length" 246 | msgstr "Panjang respons HTTP tidak diketahui" 247 | 248 | #: pkgi_download.c:331 249 | msgid "Not enough free space on HDD" 250 | msgstr "Sisa ruang HDD tidak cukup" 251 | 252 | #: pkgi_download.c:340 253 | msgid "Could not create task directory on HDD." 254 | msgstr "Tidak dapat membuat direktori tugas di HDD." 255 | 256 | #: pkgi_download.c:348 257 | msgid "Saving background task..." 258 | msgstr "Menyimpan tugas latar belakang..." 259 | 260 | #: pkgi_download.c:354 261 | msgid "Could not create PKG file to HDD." 262 | msgstr "Tidak dapat menciptakan berkas PKG di HDD." 263 | 264 | #: pkgi_download.c:360 265 | msgid "Could not create task files to HDD." 266 | msgstr "Tidak dapat menciptakan berkas tugas di HDD." 267 | 268 | #: pkgi_download.c:425 269 | msgid "HTTP download error" 270 | msgstr "Unduhan HTTP eror" 271 | 272 | #: pkgi_download.c:432 273 | msgid "Koneksi HTTP tertutup" 274 | msgstr "HTTP connection closed" 275 | 276 | #: pkgi_download.c:445 277 | msgid "gagal menulis ke" 278 | msgstr "gagal menulis ke" 279 | 280 | #: pkgi_download.c:465 281 | msgid "cannot creat folder" 282 | msgstr "tidak dapat menciptakan folder" 283 | 284 | #: pkgi_download.c:475 285 | msgid "cannot create file" 286 | msgstr "tidak dapat menciptakan berkas" 287 | 288 | #: pkgi_download.c:490 289 | msgid "cannot resume file" 290 | msgstr "tidak dapat melanjutkan berkas" 291 | 292 | #: pkgi_download.c:562 293 | msgid "pkg integrity failed, try downloading again" 294 | msgstr "integritas pkg gagal, coba unduh kembali" 295 | 296 | #: pkgi_download.c:573 297 | msgid "Creating RAP file" 298 | msgstr "Menciptakan berkas RAP" 299 | 300 | #: pkgi_download.c:581 pkgi_download.c:626 301 | msgid "Cannot save" 302 | msgstr "" 303 | 304 | #: pkgi_download.c:621 305 | msgid "Creating RIF file" 306 | msgstr "Menciptakan berkas RIF" 307 | 308 | #: pkgi_download.c:646 309 | msgid "Resuming..." 310 | msgstr "Melanjutkan" 311 | 312 | #: pkgi_download.c:652 313 | msgid "Adding background task..." 314 | msgstr "Menambahkan tugas latar belakang..." 315 | 316 | #: pkgi_download.c:675 317 | msgid "Downloading icon" 318 | msgstr "Mengunduh ikon" 319 | 320 | #: pkgi_download.c:713 321 | msgid "Could not create install directory on HDD." 322 | msgstr "Tidak dapat menciptakan direktori pemasangan di HDD." 323 | 324 | #: pkgi_menu.c:102 325 | msgid "Search..." 326 | msgstr "Cari..." 327 | 328 | #: pkgi_menu.c:103 329 | msgid "Sort by:" 330 | msgstr "Sortir menurut:" 331 | 332 | #: pkgi_menu.c:104 333 | msgid "Title" 334 | msgstr "Judul" 335 | 336 | #: pkgi_menu.c:105 337 | msgid "Region" 338 | msgstr "Region" 339 | 340 | #: pkgi_menu.c:106 341 | msgid "Name" 342 | msgstr "Nama" 343 | 344 | #: pkgi_menu.c:107 345 | msgid "Size" 346 | msgstr "Ukuran" 347 | 348 | #: pkgi_menu.c:108 349 | msgid "Content:" 350 | msgstr "Konten" 351 | 352 | #: pkgi_menu.c:110 353 | msgid "Regions:" 354 | msgstr "Region:" 355 | 356 | #: pkgi_menu.c:111 357 | msgid "Asia" 358 | msgstr "Asia" 359 | 360 | #: pkgi_menu.c:112 361 | msgid "Europe" 362 | msgstr "Eropa" 363 | 364 | #: pkgi_menu.c:113 365 | msgid "Japan" 366 | msgstr "Jepang" 367 | 368 | #: pkgi_menu.c:114 369 | msgid "USA" 370 | msgstr "USA" 371 | 372 | #: pkgi_menu.c:115 373 | msgid "Options:" 374 | msgstr "Pilihan:" 375 | 376 | #: pkgi_menu.c:116 377 | msgid "ISO" 378 | msgstr "" 379 | 380 | #: pkgi_menu.c:117 381 | msgid "Keep PKGs" 382 | msgstr "" 383 | 384 | #: pkgi_menu.c:119 385 | msgid "Refresh..." 386 | msgstr "Segarkan..." 387 | 388 | #msgid "---translated by---" 389 | #msgstr "Jeremy Belpois (Fajar Maulana)" -------------------------------------------------------------------------------- /data/LANG/it.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/it.po -------------------------------------------------------------------------------- /data/LANG/pl.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PKGi PSP\n" 4 | "POT-Creation-Date: 2021-10-09 17:56-0300\n" 5 | "PO-Revision-Date: 2023-02-05 20:08-0300\n" 6 | "Last-Translator: DEX357\n" 7 | "Language-Team: \n" 8 | "Language: pl\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: ./source\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-Flags-xgettext: --add-comments\n" 16 | "X-Poedit-SourceCharset: UTF-8\n" 17 | "X-Poedit-KeywordsList: _\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: pkgi.c:82 21 | msgid "Installing..." 22 | msgstr "Instalowanie..." 23 | 24 | #: pkgi.c:82 25 | msgid "Please wait..." 26 | msgstr "Prosze czekac..." 27 | 28 | #: pkgi.c:94 29 | msgid "Installation failed" 30 | msgstr "Instalacja nie powiodla sie" 31 | 32 | #: pkgi.c:118 33 | msgid "Successfully installed" 34 | msgstr "Pobrano pomyslnie" 35 | 36 | #: pkgi.c:122 37 | msgid "Task successfully queued (reboot to start)" 38 | msgstr "Zadanie umieszczone w kolejce. (Zrestartuj, aby rozpoczac)" 39 | 40 | #: pkgi.c:163 41 | msgid "GB" 42 | msgstr "GB" 43 | 44 | #: pkgi.c:167 pkgi_download.c:249 45 | msgid "MB" 46 | msgstr "MB" 47 | 48 | #: pkgi.c:171 pkgi.c:504 pkgi_download.c:253 49 | msgid "KB" 50 | msgstr "KB" 51 | 52 | #: pkgi.c:175 53 | msgid "B" 54 | msgstr "B" 55 | 56 | #: pkgi.c:185 57 | #, c-format 58 | msgid "pkg requires %u %s free space, but only %u %s available" 59 | msgstr "pkg wymaga %u %s wolnego miejsca, ale dostepne jest tylko %u %s" 60 | 61 | #: pkgi.c:225 pkgi_menu.c:109 pkgi_menu.c:121 62 | msgid "All" 63 | msgstr "Wszystko" 64 | 65 | #: pkgi.c:226 pkgi_menu.c:122 66 | msgid "Games" 67 | msgstr "Gry" 68 | 69 | #: pkgi.c:227 pkgi_menu.c:123 70 | msgid "DLCs" 71 | msgstr "DLC" 72 | 73 | #: pkgi.c:228 pkgi_menu.c:124 74 | msgid "Themes" 75 | msgstr "Motywy" 76 | 77 | #: pkgi.c:229 pkgi_menu.c:125 78 | msgid "Avatars" 79 | msgstr "Awatary" 80 | 81 | #: pkgi.c:230 pkgi_menu.c:126 82 | msgid "Demos" 83 | msgstr "Dema" 84 | 85 | #: pkgi.c:231 pkgi_menu.c:118 pkgi_menu.c:127 86 | msgid "Updates" 87 | msgstr "Aktualizacje" 88 | 89 | #: pkgi.c:232 pkgi_menu.c:128 90 | msgid "Emulators" 91 | msgstr "Emulatory" 92 | 93 | #: pkgi.c:233 pkgi_menu.c:129 94 | msgid "Apps" 95 | msgstr "Aplikacje" 96 | 97 | #: pkgi.c:234 pkgi_menu.c:130 98 | msgid "Tools" 99 | msgstr "Narzedzia" 100 | 101 | #: pkgi.c:235 102 | msgid "Unknown" 103 | msgstr "Nieznany" 104 | 105 | #: pkgi.c:253 106 | msgid "Exit to XMB?" 107 | msgstr "Wyjsc do XMB?" 108 | 109 | #: pkgi.c:436 110 | msgid "No items!" 111 | msgstr "Brak danych!" 112 | 113 | #: pkgi.c:462 114 | msgid "Item already installed, download again?" 115 | msgstr "Element juz zainstalowany, pobrac ponownie?" 116 | 117 | #: pkgi.c:471 pkgi_download.c:372 pkgi_download.c:652 118 | msgid "Downloading..." 119 | msgstr "Pobieranie..." 120 | 121 | #: pkgi.c:471 pkgi.c:674 122 | msgid "Preparing..." 123 | msgstr "Przygotowywanie..." 124 | 125 | #: pkgi.c:504 pkgi.c:508 126 | msgid "Refreshing" 127 | msgstr "Odswiezanie" 128 | 129 | #: pkgi.c:563 pkgi.c:567 130 | msgid "Count" 131 | msgstr "Ilosc" 132 | 133 | #: pkgi.c:575 134 | msgid "Free" 135 | msgstr "Wolne" 136 | 137 | #: pkgi.c:585 138 | msgid "Select" 139 | msgstr "Wybierz" 140 | 141 | #: pkgi.c:585 142 | msgid "Close" 143 | msgstr "Zamknij" 144 | 145 | #: pkgi.c:585 146 | msgid "Cancel" 147 | msgstr "Anuluj" 148 | 149 | #: pkgi.c:589 pkgi_download.c:136 150 | msgid "Download" 151 | msgstr "Pobierz" 152 | 153 | #: pkgi.c:589 154 | msgid "Menu" 155 | msgstr "Menu" 156 | 157 | #: pkgi.c:589 158 | msgid "Details" 159 | msgstr "Szczegoly" 160 | 161 | #: pkgi.c:589 162 | msgid "Exit" 163 | msgstr "Wyjdz" 164 | 165 | #: pkgi.c:678 166 | msgid "Successfully downloaded PKGi PSP update" 167 | msgstr "Pomyslnie pobrano aktualizacje PKGi PSP" 168 | 169 | #: pkgi.c:1051 170 | msgid "Search" 171 | msgstr "Szukaj" 172 | 173 | #: pkgi_db.c:145 pkgi_db.c:153 174 | msgid "failed to download list from" 175 | msgstr "nie udalo sie pobrac listy z" 176 | 177 | #: pkgi_db.c:159 178 | msgid "list is too large... check for newer pkgi version!" 179 | msgstr "lista jest za duza... sprawdz nowsza wersje pkgi!" 180 | 181 | #: pkgi_db.c:187 182 | msgid "list is empty... check the DB server" 183 | msgstr "lista jest pusta... sprawdz serwer DB" 184 | 185 | #: pkgi_db.c:390 186 | msgid "ERROR: pkgi.txt file(s) missing or bad config.txt file" 187 | msgstr "BLAD: brak pliku(ow) pkgi.txt lub zly plik config.txt" 188 | 189 | #: pkgi_dialog.c:80 190 | msgid "Content" 191 | msgstr "Zawartosc" 192 | 193 | #: pkgi_dialog.c:108 194 | msgid "ERROR" 195 | msgstr "BLAD" 196 | 197 | #: pkgi_dialog.c:169 198 | msgid "Failed to download the update list" 199 | msgstr "Nie udalo sie pobrac listy aktualizacji" 200 | 201 | #: pkgi_dialog.c:174 202 | msgid "update(s) loaded" 203 | msgstr "zaladowano aktualizacje" 204 | 205 | #: pkgi_dialog.c:319 206 | #, c-format 207 | msgid "press %s to cancel" 208 | msgstr "nacisnij %s aby anulowac" 209 | 210 | #: pkgi_dialog.c:333 211 | #, c-format 212 | msgid "press %s to close - %s to scan updates" 213 | msgstr "nacisnij %s, aby zamknac - %s aby przeskanowac aktualizacje" 214 | 215 | #: pkgi_dialog.c:365 216 | msgid "Enter" 217 | msgstr "Wejdz" 218 | 219 | #: pkgi_dialog.c:365 220 | msgid "Back" 221 | msgstr "Cofnij" 222 | 223 | #: pkgi_dialog.c:367 224 | #, c-format 225 | msgid "press %s to close" 226 | msgstr "nacisnij %s aby zamknac" 227 | 228 | #. 00000069 - Display title 229 | #: pkgi_download.c:183 230 | msgid "Install" 231 | msgstr "Zainstaluj" 232 | 233 | #: pkgi_download.c:216 pkgi_download.c:220 pkgi_download.c:226 234 | msgid "ETA" 235 | msgstr "ETA" 236 | 237 | #: pkgi_download.c:310 pkgi_download.c:392 238 | msgid "Could not send HTTP request" 239 | msgstr "Nie mozna wyslac zadania HTTP" 240 | 241 | #: pkgi_download.c:317 pkgi_download.c:399 242 | msgid "HTTP request failed" 243 | msgstr "Zadanie HTTP nie powiodlo sie" 244 | 245 | #: pkgi_download.c:322 pkgi_download.c:404 246 | msgid "HTTP response has unknown length" 247 | msgstr "Odpowiedz HTTP ma nieznana dlugosc" 248 | 249 | #: pkgi_download.c:331 250 | msgid "Not enough free space on HDD" 251 | msgstr "Nie wystarczajaca ilosc miejsca na dysku twardym" 252 | 253 | #: pkgi_download.c:340 254 | msgid "Could not create task directory on HDD." 255 | msgstr "Nie mozna utworzyc katalogu zadan na dysku twardym." 256 | 257 | #: pkgi_download.c:348 258 | msgid "Saving background task..." 259 | msgstr "Zapisywanie w tle..." 260 | 261 | #: pkgi_download.c:354 262 | msgid "Could not create PKG file to HDD." 263 | msgstr "Nie mozna utworzyc pliku PKG na HDD." 264 | 265 | #: pkgi_download.c:360 266 | msgid "Could not create task files to HDD." 267 | msgstr "Nie mozna utworzyc plikow zadan na HDD." 268 | 269 | #: pkgi_download.c:425 270 | msgid "HTTP download error" 271 | msgstr "Blad pobierania HTTP" 272 | 273 | #: pkgi_download.c:432 274 | msgid "HTTP connection closed" 275 | msgstr "Polaczenie HTTP zamkniete" 276 | 277 | #: pkgi_download.c:445 278 | msgid "failed to write to" 279 | msgstr "zapisywanie nie powiodlo sie" 280 | 281 | #: pkgi_download.c:465 282 | msgid "cannot create folder" 283 | msgstr "nie mozna utworzyc folderu" 284 | 285 | #: pkgi_download.c:475 286 | msgid "cannot create file" 287 | msgstr "nie mozna stworzyc pliku" 288 | 289 | #: pkgi_download.c:490 290 | msgid "cannot resume file" 291 | msgstr "nie mozna wznowic pliku" 292 | 293 | #: pkgi_download.c:562 294 | msgid "pkg integrity failed, try downloading again" 295 | msgstr "Integralnosc pakietu nie powiodla sie, sprobuj pobrac ponownie" 296 | 297 | #: pkgi_download.c:573 298 | msgid "Creating RAP file" 299 | msgstr "Tworzenie pliku RAP" 300 | 301 | #: pkgi_download.c:581 pkgi_download.c:626 302 | msgid "Cannot save" 303 | msgstr "Nie mozna zapisac" 304 | 305 | #: pkgi_download.c:621 306 | msgid "Creating RIF file" 307 | msgstr "Tworzenie pliku RIF" 308 | 309 | #: pkgi_download.c:646 310 | msgid "Resuming..." 311 | msgstr "Wznawianie..." 312 | 313 | #: pkgi_download.c:652 314 | msgid "Adding background task..." 315 | msgstr "Dodawanie zadania w tle..." 316 | 317 | #: pkgi_download.c:675 318 | msgid "Downloading icon" 319 | msgstr "Pobieranie ikon" 320 | 321 | #: pkgi_download.c:713 322 | msgid "Could not create install directory on HDD." 323 | msgstr "Nie mozna utworzyc katalogu instalacyjnego na dysku twardym." 324 | 325 | #: pkgi_menu.c:102 326 | msgid "Search..." 327 | msgstr "Szukaj..." 328 | 329 | #: pkgi_menu.c:103 330 | msgid "Sort by:" 331 | msgstr "Sortuj wedlug:" 332 | 333 | #: pkgi_menu.c:104 334 | msgid "Title" 335 | msgstr "Tytul" 336 | 337 | #: pkgi_menu.c:105 338 | msgid "Region" 339 | msgstr "Region" 340 | 341 | #: pkgi_menu.c:106 342 | msgid "Name" 343 | msgstr "Nazwa" 344 | 345 | #: pkgi_menu.c:107 346 | msgid "Size" 347 | msgstr "Rozmiar" 348 | 349 | #: pkgi_menu.c:108 350 | msgid "Content:" 351 | msgstr "Zawartosc:" 352 | 353 | #: pkgi_menu.c:110 354 | msgid "Regions:" 355 | msgstr "Regiony:" 356 | 357 | #: pkgi_menu.c:111 358 | msgid "Asia" 359 | msgstr "Azja" 360 | 361 | #: pkgi_menu.c:112 362 | msgid "Europe" 363 | msgstr "Europa" 364 | 365 | #: pkgi_menu.c:113 366 | msgid "Japan" 367 | msgstr "Japonia" 368 | 369 | #: pkgi_menu.c:114 370 | msgid "USA" 371 | msgstr "USA" 372 | 373 | #: pkgi_menu.c:115 374 | msgid "Options:" 375 | msgstr "Opcje:" 376 | 377 | #: pkgi_menu.c:116 378 | msgid "ISO" 379 | msgstr "" 380 | 381 | #: pkgi_menu.c:117 382 | msgid "Keep PKGs" 383 | msgstr "" 384 | 385 | #: pkgi_menu.c:119 386 | msgid "Refresh..." 387 | msgstr "Odswiez..." 388 | 389 | #msgid "---translated by---" 390 | #msgstr "DEX357, PoliPyc, olokos" 391 | -------------------------------------------------------------------------------- /data/LANG/pt.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/pt.po -------------------------------------------------------------------------------- /data/LANG/tr.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/LANG/tr.po -------------------------------------------------------------------------------- /data/LANG/zh.txt: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PKGi PSP\n" 4 | "POT-Creation-Date: 2021-10-09 19:00-0300\n" 5 | "PO-Revision-Date: 2021-10-09 19:00-0300\n" 6 | "Last-Translator: Croden1999\n" 7 | "Language-Team: \n" 8 | "Language: zh\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: ./source\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-Flags-xgettext: --add-comments\n" 16 | "X-Poedit-SourceCharset: UTF-8\n" 17 | "X-Poedit-KeywordsList: _\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: pkgi.c:82 21 | msgid "Installing..." 22 | msgstr "安装中" 23 | 24 | #: pkgi.c:82 25 | msgid "Please wait..." 26 | msgstr "请稍等..." 27 | 28 | #: pkgi.c:94 29 | msgid "Installation failed" 30 | msgstr "安装失败" 31 | 32 | #: pkgi.c:118 33 | msgid "Successfully installed" 34 | msgstr "下载完成" 35 | 36 | #: pkgi.c:122 37 | msgid "Task successfully queued (reboot to start)" 38 | msgstr "任务已成功排队(重启开始)" 39 | 40 | #: pkgi.c:163 41 | msgid "GB" 42 | msgstr "" 43 | 44 | #: pkgi.c:167 pkgi_download.c:249 45 | msgid "MB" 46 | msgstr "" 47 | 48 | #: pkgi.c:171 pkgi.c:504 pkgi_download.c:253 49 | msgid "KB" 50 | msgstr "" 51 | 52 | #: pkgi.c:175 53 | msgid "B" 54 | msgstr "" 55 | 56 | #: pkgi.c:185 57 | #, c-format 58 | msgid "pkg requires %u %s free space, but only %u %s available" 59 | msgstr "pkg需要%u %s剩余空间, 但只有%u %s可用" 60 | 61 | #: pkgi.c:225 pkgi_menu.c:109 pkgi_menu.c:121 62 | msgid "All" 63 | msgstr "全部" 64 | 65 | #: pkgi.c:226 pkgi_menu.c:122 66 | msgid "Games" 67 | msgstr "游戏" 68 | 69 | #: pkgi.c:227 pkgi_menu.c:123 70 | msgid "DLCs" 71 | msgstr "追加内容" 72 | 73 | #: pkgi.c:228 pkgi_menu.c:124 74 | msgid "Themes" 75 | msgstr "主题" 76 | 77 | #: pkgi.c:229 pkgi_menu.c:125 78 | msgid "Avatars" 79 | msgstr "头像" 80 | 81 | #: pkgi.c:230 pkgi_menu.c:126 82 | msgid "Demos" 83 | msgstr "试玩" 84 | 85 | #: pkgi.c:231 pkgi_menu.c:118 pkgi_menu.c:127 86 | msgid "Updates" 87 | msgstr "更新" 88 | 89 | #: pkgi.c:232 pkgi_menu.c:128 90 | msgid "Emulators" 91 | msgstr "模拟器" 92 | 93 | #: pkgi.c:233 pkgi_menu.c:129 94 | msgid "Apps" 95 | msgstr "应用程序" 96 | 97 | #: pkgi.c:234 pkgi_menu.c:130 98 | msgid "Tools" 99 | msgstr "工具" 100 | 101 | #: pkgi.c:235 102 | msgid "Unknown" 103 | msgstr "未知" 104 | 105 | #: pkgi.c:253 106 | msgid "Exit to XMB?" 107 | msgstr "退出到XMB吗?" 108 | 109 | #: pkgi.c:436 110 | msgid "No items!" 111 | msgstr "没有内容!" 112 | 113 | #: pkgi.c:462 114 | msgid "Item already installed, download again?" 115 | msgstr "内容已安装,是否重新下载?" 116 | 117 | #: pkgi.c:471 pkgi_download.c:372 pkgi_download.c:652 118 | msgid "Downloading..." 119 | msgstr "下载中..." 120 | 121 | #: pkgi.c:471 pkgi.c:674 122 | msgid "Preparing..." 123 | msgstr "准备中..." 124 | 125 | #: pkgi.c:504 pkgi.c:508 126 | msgid "Refreshing" 127 | msgstr "刷新中" 128 | 129 | #: pkgi.c:563 pkgi.c:567 130 | msgid "Count" 131 | msgstr "总数" 132 | 133 | #: pkgi.c:575 134 | msgid "Free" 135 | msgstr "剩余" 136 | 137 | #: pkgi.c:585 138 | msgid "Select" 139 | msgstr "选择" 140 | 141 | #: pkgi.c:585 142 | msgid "Close" 143 | msgstr "关闭" 144 | 145 | #: pkgi.c:585 146 | msgid "Cancel" 147 | msgstr "取消" 148 | 149 | #: pkgi.c:589 pkgi_download.c:136 150 | msgid "Download" 151 | msgstr "下载" 152 | 153 | #: pkgi.c:589 154 | msgid "Menu" 155 | msgstr "菜单" 156 | 157 | #: pkgi.c:589 158 | msgid "Details" 159 | msgstr "详细" 160 | 161 | #: pkgi.c:589 162 | msgid "Exit" 163 | msgstr "退出" 164 | 165 | #: pkgi.c:678 166 | msgid "Successfully downloaded PKGi PSP update" 167 | msgstr "下载PKGi PSP更新成功" 168 | 169 | #: pkgi.c:1051 170 | msgid "Search" 171 | msgstr "搜索" 172 | 173 | #: pkgi_db.c:145 pkgi_db.c:153 174 | msgid "failed to download list from" 175 | msgstr "下载列表失败" 176 | 177 | #: pkgi_db.c:159 178 | msgid "list is too large... check for newer pkgi version!" 179 | msgstr "列表过大...检查是不是新的pkgi版本!" 180 | 181 | #: pkgi_db.c:187 182 | msgid "list is empty... check the DB server" 183 | msgstr "列表为空...检查数据库服务器" 184 | 185 | #: pkgi_db.c:390 186 | msgid "ERROR: pkgi.txt file(s) missing or bad config.txt file" 187 | msgstr "错误:pkgi.txt文件丢失或配置文件错误" 188 | 189 | #: pkgi_dialog.c:80 190 | msgid "Content" 191 | msgstr "内容" 192 | 193 | #: pkgi_dialog.c:108 194 | msgid "ERROR" 195 | msgstr "错误" 196 | 197 | #: pkgi_dialog.c:169 198 | msgid "Failed to download the update list" 199 | msgstr "下载更新列表失败" 200 | 201 | #: pkgi_dialog.c:174 202 | msgid "update(s) loaded" 203 | msgstr "更新加载" 204 | 205 | #: pkgi_dialog.c:319 206 | #, c-format 207 | msgid "press %s to cancel" 208 | msgstr "按%s取消" 209 | 210 | #: pkgi_dialog.c:333 211 | #, c-format 212 | msgid "press %s to close - %s to scan updates" 213 | msgstr "按%s关闭 - %s扫描更新" 214 | 215 | #: pkgi_dialog.c:365 216 | msgid "Enter" 217 | msgstr "确认" 218 | 219 | #: pkgi_dialog.c:365 220 | msgid "Back" 221 | msgstr "返回" 222 | 223 | #: pkgi_dialog.c:367 224 | #, c-format 225 | msgid "press %s to close" 226 | msgstr "按%s关闭" 227 | 228 | #: pkgi_download.c:183 229 | msgid "Install" 230 | msgstr "安装" 231 | 232 | #: pkgi_download.c:216 pkgi_download.c:220 pkgi_download.c:226 233 | msgid "ETA" 234 | msgstr "" 235 | 236 | #: pkgi_download.c:310 pkgi_download.c:392 237 | msgid "Could not send HTTP request" 238 | msgstr "无法发送HTTP请求" 239 | 240 | #: pkgi_download.c:317 pkgi_download.c:399 241 | msgid "HTTP request failed" 242 | msgstr "HTTP请求失败" 243 | 244 | #: pkgi_download.c:322 pkgi_download.c:404 245 | msgid "HTTP response has unknown length" 246 | msgstr "未知HTTP响应长度" 247 | 248 | #: pkgi_download.c:331 249 | msgid "Not enough free space on HDD" 250 | msgstr "硬盘空间不足" 251 | 252 | #: pkgi_download.c:340 253 | msgid "Could not create task directory on HDD." 254 | msgstr "无法在硬盘创建任务目录。" 255 | 256 | #: pkgi_download.c:348 257 | msgid "Saving background task..." 258 | msgstr "保存后台任务中..." 259 | 260 | #: pkgi_download.c:354 261 | msgid "Could not create PKG file to HDD." 262 | msgstr "无法创建PKG文件到硬盘。" 263 | 264 | #: pkgi_download.c:360 265 | msgid "Could not create task files to HDD." 266 | msgstr "无法创建任务文件到硬盘。" 267 | 268 | #: pkgi_download.c:425 269 | msgid "HTTP download error" 270 | msgstr "HTTP下载错误" 271 | 272 | #: pkgi_download.c:432 273 | msgid "HTTP connection closed" 274 | msgstr "HTTP连接关闭" 275 | 276 | #: pkgi_download.c:445 277 | msgid "failed to write to" 278 | msgstr "写入失败" 279 | 280 | #: pkgi_download.c:465 281 | msgid "cannot create folder" 282 | msgstr "无法创建文件夹" 283 | 284 | #: pkgi_download.c:475 285 | msgid "cannot create file" 286 | msgstr "无法创建文件" 287 | 288 | #: pkgi_download.c:490 289 | msgid "cannot resume file" 290 | msgstr "无法恢复文件" 291 | 292 | #: pkgi_download.c:562 293 | msgid "pkg integrity failed, try downloading again" 294 | msgstr "pkg完整性失败,请尝试重新下载" 295 | 296 | #: pkgi_download.c:573 297 | msgid "Creating RAP file" 298 | msgstr "创建RAP文件中" 299 | 300 | #: pkgi_download.c:581 pkgi_download.c:626 301 | msgid "Cannot save" 302 | msgstr "无法保存" 303 | 304 | #: pkgi_download.c:621 305 | msgid "Creating RIF file" 306 | msgstr "创建RIF文件中" 307 | 308 | #: pkgi_download.c:646 309 | msgid "Resuming..." 310 | msgstr "恢复中..." 311 | 312 | #: pkgi_download.c:652 313 | msgid "Adding background task..." 314 | msgstr "添加后台任务中..." 315 | 316 | #: pkgi_download.c:675 317 | msgid "Downloading icon" 318 | msgstr "下载图标中" 319 | 320 | #: pkgi_download.c:713 321 | msgid "Could not create install directory on HDD." 322 | msgstr "无法在硬盘创建安装目录。" 323 | 324 | #: pkgi_menu.c:102 325 | msgid "Search..." 326 | msgstr "搜索..." 327 | 328 | #: pkgi_menu.c:103 329 | msgid "Sort by:" 330 | msgstr "排序方式:" 331 | 332 | #: pkgi_menu.c:104 333 | msgid "Title" 334 | msgstr "标题" 335 | 336 | #: pkgi_menu.c:105 337 | msgid "Region" 338 | msgstr "区域" 339 | 340 | #: pkgi_menu.c:106 341 | msgid "Name" 342 | msgstr "名称" 343 | 344 | #: pkgi_menu.c:107 345 | msgid "Size" 346 | msgstr "大小" 347 | 348 | #: pkgi_menu.c:108 349 | msgid "Content:" 350 | msgstr "内容:" 351 | 352 | #: pkgi_menu.c:110 353 | msgid "Regions:" 354 | msgstr "区域:" 355 | 356 | #: pkgi_menu.c:111 357 | msgid "Asia" 358 | msgstr "亚洲" 359 | 360 | #: pkgi_menu.c:112 361 | msgid "Europe" 362 | msgstr "欧洲" 363 | 364 | #: pkgi_menu.c:113 365 | msgid "Japan" 366 | msgstr "日本" 367 | 368 | #: pkgi_menu.c:114 369 | msgid "USA" 370 | msgstr "美国" 371 | 372 | #: pkgi_menu.c:115 373 | msgid "Options:" 374 | msgstr "选项:" 375 | 376 | #: pkgi_menu.c:116 377 | msgid "ISO" 378 | msgstr "后台下载" 379 | 380 | #: pkgi_menu.c:117 381 | msgid "Keep PKGs" 382 | msgstr "音乐" 383 | 384 | #: pkgi_menu.c:119 385 | msgid "Refresh..." 386 | msgstr "刷新中..." 387 | 388 | #: pkgi_menu.c:326 389 | msgid "Direct DL" 390 | msgstr "直接下载" 391 | -------------------------------------------------------------------------------- /data/SQUARE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/SQUARE.png -------------------------------------------------------------------------------- /data/TRIANGLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/TRIANGLE.png -------------------------------------------------------------------------------- /data/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/background.png -------------------------------------------------------------------------------- /data/fonts/NotoSansJP-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/data/fonts/NotoSansJP-Medium.otf -------------------------------------------------------------------------------- /data/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 Google Inc. All Rights Reserved. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /data/pkgi.txt: -------------------------------------------------------------------------------- 1 | U00001-NP0APOLLO_00-0000000000000000,8,Apollo Save Tool v1.3.0,Save-game manager,,https://github.com/bucanero/apollo-psp/releases/download/v1.3.0/apollo-psp.zip,6885196,DFF88635A044F319686231ACABB548CC42EA1CF3A992856572C7AF20F70D30ED 2 | U00001-CMFILEMGR_00-0000000000000000,8,CM File Manager v4.10,File manager,,https://github.com/joel16/CMFileManager-PSP/releases/download/v4.10/CMFileManager-PSP.zip,1815998, 3 | U00001-PSPTOOL10_00-0000000000000000,8,PSP Tool v1.00,PSP Tool,,https://archive.org/download/psp-tool.-7z/PSP%20Tool.zip,5023819, 4 | U00001-PSPFILER6_00-0000000000000000,8,PSP Filer v6.6,File manager,,https://wololo.net/download.php?f=filer6.6.zip,1295106, 5 | U00001-NZPORTABL_00-0000000000000000,1,Nazi Zombies: Portable,Nightly 64 Mb,,https://github.com/nzp-team/nzportable/releases/download/nightly/nzportable-psp-64mb.zip,36163686, 6 | U00001-WOLFENS3D_00-0000000000000000,1,Wolfenstein 3D,v6.0 shareware,,https://archive.org/download/wolf-3-d-v-6.0.7z/Wolf3D_V6.0.zip,8043837, 7 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # PKGi PSP 2 | 3 | [![Downloads][img_downloads]][pkgi_downloads] [![Release][img_latest]][pkgi_latest] [![License][img_license]][pkgi_license] 4 | [![Build package](https://github.com/bucanero/pkgi-psp/actions/workflows/build.yml/badge.svg)](https://github.com/bucanero/pkgi-psp/actions/workflows/build.yml) 5 | [![Twitter](https://img.shields.io/twitter/follow/dparrino?label=Follow)](https://twitter.com/dparrino) 6 | 7 | **PKGi PSP** is a PlayStation Portable port of [pkgi-ps3](https://github.com/bucanero/pkgi-ps3). 8 | 9 | This homebrew app allows to download and install `.pkg` files directly on your PSP. 10 | 11 | ![image](./screenshot.png) 12 | 13 | **Comments, ideas, suggestions?** You can [contact me](https://github.com/bucanero/) on [Twitter](https://twitter.com/dparrino) and on [my website](http://www.bucanero.com.ar/). 14 | 15 | # Features 16 | 17 | * **easy to use:** list available downloads, including searching, filtering, and sorting. 18 | * **standalone:** no PC required, everything happens directly on the PSP. 19 | * **automatic downloads:** just choose an item, and it will be downloaded by the app to your Memory Stick (`ms0:/PKG/`). 20 | * **resumes interrupted downloads:** you can stop a download at any time, switch applications, and come back to resume the download later. 21 | * **localization support:** Finnish, French, German, Indonesian, Italian, Polish, Portuguese, Spanish, Turkish 22 | 23 | ### Notes: 24 | * **content activation:** downloaded content requires a valid license to run. If your PSP hasn't been activated, you can use one of the following plugins: 25 | - [npdrm_free plugin](https://github.com/qwikrazor87/npdrm_free) (PSP/PS1/PCEngine games and DLCs) 26 | - [npdrm_free_mod plugin](https://github.com/lusid1/npdrm_free_mod) (PSP/PS1 games only) 27 | - [nploader_mod](https://github.com/lusid1/nploader_mod) (PSP DLCs only) 28 | 29 | # Download 30 | 31 | Get the [latest version here][pkgi_latest]. 32 | 33 | ### Changelog 34 | 35 | See the [latest changes here](CHANGELOG.md). 36 | 37 | # Setup instructions 38 | 39 | You need to create a [`pkgi.txt`](#sample-db-file) file in `ms0:/PSP/GAME/PKGI/` that contains the items available for installation. 40 | The text database format is user customizable. Check [this section](#user-defined-db-format) to learn how to define your own custom DB format. 41 | 42 | ## Multiple databases 43 | 44 | You can also load additional database files: 45 | 46 | - `pkgi_games.txt` 47 | - `pkgi_dlcs.txt` 48 | - `pkgi_themes.txt` 49 | - `pkgi_psx.txt` 50 | - `pkgi_demos.txt` 51 | - `pkgi_updates.txt` 52 | - `pkgi_emulators.txt` 53 | - `pkgi_apps.txt` 54 | 55 | Items on each of these files will be auto-categorized to the file content type. **Note:** The app assumes that every database file has the same format, as defined in `dbformat.txt`. 56 | 57 | ## Online DB update 58 | 59 | You can refresh and sync an online database by adding the DB URL(s) to the `config.txt` file in `ms0:/PSP/GAME/PKGI/`. 60 | 61 | For example: 62 | 63 | ``` 64 | url_games http://www.mysite.com/mylist.csv 65 | url_demos http://www.demos.com/otherlist.csv 66 | url_emulators http://www.example.com/emulators.csv 67 | ``` 68 | 69 | Using this setup, 70 | - `pkgi_games.txt` will be updated with `http://www.mysite.com/mylist.csv`, 71 | - `pkgi_demos.txt` with `http://www.demos.com/otherlist.csv`, 72 | - and `pkgi_emulators.txt` with `http://www.example.com/emulators.csv`. 73 | 74 | Next time you open the app, you'll have an additional menu option ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png) called **Refresh**. When you select it, the local databases will be syncronized with the defined URLs. 75 | 76 | # DB formats 77 | 78 | The application needs a text database that contains the items available for installation, and it must follow the [default format definition](#default-db-format), or have a [custom format definition](#user-defined-db-format) file. 79 | 80 | ## Default DB format 81 | 82 | The default database file format uses a very simple CSV format where each line means one item in the list: 83 | 84 | ``` 85 | contentid,type,name,description,rap,url,size,checksum 86 | ``` 87 | 88 | where: 89 | 90 | | Column | Description | 91 | |--------|-------------| 92 | | `contentid` | is the full content id of the item, for example: `UP0000-NPXX99999_00-0000112223333000`. 93 | | `type` | is a number for the item's content type. See the [table below](#content-types) for details. (set it to 0 if unknown) 94 | | `name` | is a string for the item's name. 95 | | `description` | is a string for the item's description. 96 | | `rap` | the 16 hex bytes for a RAP file, if needed by the item (`.rap` files will be created on `ms0:/PKG/RAP`). Leave empty to skip the `.rap` file. 97 | | `url` | is the HTTP/HTTPS/FTP/FTPS URL where to download the `.pkg` file. 98 | | `size` | is the size in bytes of the `.pkg` file, or 0 if unknown. 99 | | `checksum` | is a SHA256 digest of the `.pkg` file (as 32 hex bytes) to make sure the file is not tampered with. Leave empty to skip the check. 100 | 101 | **Note:** `name` and `description` cannot contain newlines or commas. 102 | 103 | ### Sample DB file 104 | 105 | An example `pkgi.txt` file following the `contentid,type,name,description,rap,url,size,checksum` format: 106 | 107 | ``` 108 | U00001-NP0APOLLO_00-0000000000000000,8,Apollo Save Tool v1.3.0,Save-game manager,,https://github.com/bucanero/apollo-psp/releases/download/v1.3.0/apollo-psp.zip,6885196,DFF88635A044F319686231ACABB548CC42EA1CF3A992856572C7AF20F70D30ED 109 | U00001-CMFILEMGR_00-0000000000000000,8,CM File Manager v4.10,File manager,,https://github.com/joel16/CMFileManager-PSP/releases/download/v4.10/CMFileManager-PSP.zip,1815998,7A5FD10184546AB993A4D5F3054BCBA9E9B7A1C569EE26E366F1F5CC9DA5A554 110 | UP0001-PSPTOOL10_00-0000000000000000,8,PSP Tool v1.00,PSP Tool,,https://archive.org/download/psp-tool.-7z/PSP%20Tool.zip,5023819, 111 | UP0001-PSPFILER6_00-0000000000000000,8,PSP Filer v6.6,File manager,,https://wololo.net/download.php?f=filer6.6.zip,1295106, 112 | UP0001-NZPORTABL_00-0000000000000000,1,Nazi Zombies: Portable,Nightly 64 Mb,,https://github.com/nzp-team/nzportable/releases/download/nightly/nzportable-psp-64mb.zip,36163686, 113 | UP0001-IRISMAN00_00-VER4880000000000,8,IRISMAN 4.88.1,Backup Manager,,http://github.com/aldostools/IRISMAN/releases/download/4.88/IRISMAN_4.88.pkg,29411984,E6EF607F0002B31BFB148BE4FC9BDBACB4E53110751F0E667C701D40B5290570 114 | ``` 115 | 116 | ### Content types 117 | 118 | | Type value | Content type | DB File | 119 | |------------|--------------|---------| 120 | | 0 | Unknown | 121 | | 1 | Game | `pkgi_games.txt` 122 | | 2 | DLC | `pkgi_dlcs.txt` 123 | | 3 | Theme | `pkgi_themes.txt` 124 | | 4 | PSX | `pkgi_psx.txt` 125 | | 5 | Demo | `pkgi_demos.txt` 126 | | 6 | Update | `pkgi_updates.txt` 127 | | 7 | Emulator | `pkgi_emulators.txt` 128 | | 8 | Application | `pkgi_apps.txt` 129 | 130 | ## User-defined DB format 131 | 132 | To use a custom database format, you need to create a `dbformat.txt` file, and save it on `ms0:/PSP/GAME/PKGI/`. 133 | 134 | The `dbformat.txt` definition file is a 2-line text file: 135 | * Line 1: the custom delimiter character (e.g.: `;`, `,`, `|`, etc.) 136 | * Line 2: the column names for every column in the custom database, delimited by the proper delimiter defined in line 1 137 | 138 | **Note:** For the columns to be properly recognized, use the column tag names defined in the table above. 139 | 140 | All the columns are optional. Your database might have more (or less) columns, so any unrecognized column will be skipped. 141 | 142 | ### Example 143 | 144 | Example `dbformat.txt`, for a database using semi-colon (`;`) as separator: 145 | 146 | ``` 147 | ; 148 | name;TITLE ID;REGION;description;AUTHOR;TYPE;url;rap;size 149 | ``` 150 | 151 | **Result:** only the `name,description,url,rap,size` fields will be used. 152 | 153 | ### Example 154 | 155 | Example `dbformat.txt`, for a database using character pipe (`|`) as separator: 156 | 157 | ``` 158 | | 159 | REGION|TITLE|name|url|rap|contentid|DATE|PKG FILENAME|size|checksum 160 | ``` 161 | 162 | **Result:** only the `name,url,rap,contentid,size,checksum` fields will be used. 163 | 164 | # Usage 165 | 166 | Using the application is simple and straight-forward: 167 | 168 | - Move UP/DOWN to select the item you want to download, and press ![X button](https://github.com/bucanero/pkgi-ps3/raw/master/data/CROSS.png). 169 | - To see the item's details, press ![Square](https://github.com/bucanero/pkgi-ps3/raw/master/data/SQUARE.png). 170 | - To sort/filter/search press ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png). 171 | It will open the context menu. Press ![Triangle](https://github.com/bucanero/pkgi-ps3/raw/master/data/TRIANGLE.png) again to confirm the new settings, or press ![O button](https://github.com/bucanero/pkgi-ps3/raw/master/data/CIRCLE.png) to cancel any changes. 172 | - Press left or right trigger buttons L1/R1 to move pages up or down. 173 | - Press LEFT/RIGHT buttons to switch between categories. 174 | 175 | ### Notes 176 | 177 | - **RAP data:** if the item has `.rap` data, the file will be saved in the `ms0:/PKG/RAP/` folder. 178 | 179 | 180 | # Q&A 181 | 182 | 1. Where to get a `rap` string? 183 | 184 | You can use a tool like RIF2RAP to generate a `.rap` from your existing `.rif` files. Then you can use a tool like `hexdump` to get the hex byte string. 185 | 186 | 2. Where to get `.pkg` links? 187 | 188 | You can use [PSDLE][] to find `.pkg` URLs for the games you own. Then either use the original URL, or host the file on your own web server. 189 | 190 | 3. Where to remove interrupted/failed downloads to free up disk space? 191 | 192 | Check the `ms0:/PKG/` folder - each download will be in a separate `.pkg` file by its content ID. Simply delete the file and start again. 193 | 194 | 4. Download speed is too slow! 195 | 196 | Optimization is still pending. (Optional) Set `Power Save Settings` -> `WLAN Power save` -> `OFF` , if you want to speed up the download process. 197 | 198 | # Credits 199 | 200 | * [Bucanero](http://www.bucanero.com.ar/): Project developer 201 | 202 | ## Acknowledgements 203 | 204 | * [mmozeiko](https://github.com/mmozeiko/): [pkgi](https://github.com/mmozeiko/pkgi) (PS Vita), [pkg2zip](https://github.com/mmozeiko/pkg2zip) 205 | * [qwikrazor87](https://github.com/qwikrazor87/): [Depackager](https://github.com/bucanero/psptools/tree/master/depackager) 206 | 207 | # Building 208 | 209 | You need to have installed: 210 | 211 | - [PSP SDK](https://github.com/pspdev/) 212 | - [mbedTLS](https://github.com/pspdev/psp-packages/tree/master/mbedtls) library 213 | - [cURL](https://github.com/pspdev/psp-packages/tree/master/curl) library 214 | - [Mini18n](https://github.com/bucanero/mini18n) library 215 | - [dbglogger](https://github.com/bucanero/dbglogger) library (only required for debug logging) 216 | 217 | Run `cmake . && make` to create a release build. If you want to create a `.zip` file, run `make createzip`. 218 | 219 | ## Debugging 220 | 221 | To enable debug logging, pass `-DPKGI_ENABLE_DEBUG=ON` argument to `cmake`. The application will write debug messages to 222 | 223 | ms0:/pkgi-psp.log 224 | 225 | You can also set the `PSPIP` environment variable to your PSP's IP address, and use `make send` to upload `EBOOT.PBP` directly to the `ms0:/PSP/GAME/PKGI` folder. 226 | 227 | # License 228 | 229 | `pkgi-psp` is released under the [MIT License](LICENSE). 230 | 231 | [PSDLE]: https://repod.github.io/psdle/ 232 | [socat]: http://www.dest-unreach.org/socat/ 233 | [pkgi_downloads]: https://github.com/bucanero/pkgi-psp/releases 234 | [pkgi_latest]: https://github.com/bucanero/pkgi-psp/releases/latest 235 | [pkgi_license]: https://github.com/bucanero/pkgi-psp/blob/main/LICENSE 236 | [img_downloads]: https://img.shields.io/github/downloads/bucanero/pkgi-psp/total.svg?maxAge=3600 237 | [img_latest]: https://img.shields.io/github/release/bucanero/pkgi-psp.svg?maxAge=3600 238 | [img_license]: https://img.shields.io/github/license/bucanero/pkgi-psp.svg?maxAge=2592000 239 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate 2 | show_downloads: true 3 | -------------------------------------------------------------------------------- /docs/leon-luna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/docs/leon-luna.jpg -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bucanero/pkgi-psp/9f440ce6af0d0f9fe97eed1c0b6220399cb06792/docs/screenshot.png -------------------------------------------------------------------------------- /include/libfont.h: -------------------------------------------------------------------------------- 1 | /* 2 | TINY3D - font library / (c) 2010 Hermes 3 | 4 | */ 5 | 6 | #ifndef LIBFONT_H 7 | #define LIBFONT_H 8 | 9 | //#include "menu.h" 10 | void DrawTexture(pkgi_texture tex, int x, int y, int z, int w, int h, uint32_t rgba); 11 | 12 | /* NOTE: LIBFONT is thinkin to work with Tiny3D 2D mode: you need to call tiny3d_Project2D() before to work with draw functions */ 13 | 14 | // max number of special characters 15 | 16 | #define MAX_SPECIAL_CHARS 0x20 17 | 18 | // initialize all datas. After you call it you don't have any font to use 19 | 20 | void ResetFont(); 21 | 22 | // used as byte_order in AddFontFromBitmapArray() 23 | 24 | #define BIT0_FIRST_PIXEL 0 25 | #define BIT7_FIRST_PIXEL 1 26 | 27 | /* add one font from one bitmap array. You can define the font range with first_char and last_char (in my sample i use two fonts that starts 28 | at chr 32 and finish iat chr 255 and other font starts at 0 and finish at 254. fonts can have one depth of 1, 2, 4, 8 bits (used as pixel intensity) 29 | to create smooth fonts and you can select the byte order (some fonts can use bit 7 as first pixel or bit 0) . 30 | 31 | w and h must be 8, 16, 32, ..... 32 | 33 | It receive one RSX texture pointer and return the texture pointer increased and aligned to 16 bytes think to use this pointer to build the next 34 | RSX texture. 35 | */ 36 | 37 | uint8_t * AddFontFromBitmapArray(uint8_t *font, uint8_t *texture, uint8_t first_char, uint8_t last_char, int w, int h, int bits_per_pixel, int byte_order); 38 | 39 | /* 40 | add one bitmap font creating it from True Type Fonts. You can define the font range with first_char and last_char in the range 0 to 255 41 | w and h must be 8, 16, 32, ..... 42 | 43 | The callback is used to create the font externally (it don't need to use freetype library directly) 44 | 45 | It receive one RSX texture pointer and return the texture pointer increased and aligned to 16 bytes think to use this pointer to build the next 46 | RSX texture. 47 | */ 48 | 49 | uint8_t * AddFontFromTTF(uint8_t *texture, uint8_t first_char, uint8_t last_char, int w, int h, 50 | void (* ttf_callback) (uint8_t chr, uint8_t * bitmap, short *w, short *h, short *y_correction)); 51 | 52 | /* function to select the current font to use (the first is 0. if you select an undefined font, it uses font 0) */ 53 | 54 | void SetCurrentFont(int nfont); 55 | 56 | // font are resizable: the minimun is 8 x 8 but you can use as you want. Remember the font quality depends of the original size 57 | 58 | void SetFontSize(int sx, int sy); 59 | 60 | // select the color and the background color for the font. if you use 0 for bkcolor background is not drawing 61 | 62 | void SetFontColor(uint32_t color, uint32_t bkcolor); 63 | 64 | #define FONT_ALIGN_LEFT 0 65 | #define FONT_ALIGN_SCREEN_CENTER 1 66 | #define FONT_ALIGN_RIGHT 2 67 | #define FONT_ALIGN_CENTER 3 68 | 69 | // Set the DrawString alignment (0=left,1=center,2=right). don't use '\n' or unsupported characters here 70 | 71 | void SetFontAlign(int mode); 72 | 73 | // compatibility with old name 74 | #define SetFontAutocenter SetFontAutoCenter 75 | 76 | // enable the auto new line if width is different to 0. When one word exceed the width specified, it skip to the next line 77 | 78 | void SetFontAutoNewLine(int width); 79 | 80 | // Z used to draw the font. Usually is 0.0f 81 | 82 | void SetFontZ(float z); 83 | 84 | // last X used 85 | 86 | float GetFontX(); 87 | 88 | // last Y used 89 | 90 | float GetFontY(); 91 | 92 | // set mono spacing (if 0 uses normal space) 93 | 94 | void SetMonoSpace(int space); 95 | 96 | // set spacing 97 | 98 | void SetExtraSpace(int space); 99 | 100 | // register special character into font 101 | 102 | void RegisterSpecialCharacter(char value, short fy, float scale, pkgi_texture image); 103 | 104 | // get width of string based on font 105 | 106 | int WidthFromStr(const char * str); 107 | 108 | // function to draw one character 109 | 110 | void DrawChar(float x, float y, float z, uint8_t chr); 111 | 112 | // function to draw one string monospaced. It return X incremented 113 | 114 | float DrawStringMono(float x, float y, const char *str); 115 | 116 | // function to draw one string. It return X incremented 117 | 118 | float DrawString(float x, float y, const char *str); 119 | 120 | // function to draw with fomat string similar to printf. It return X incremented 121 | 122 | float DrawFormatString(float x, float y, char *format, ...); 123 | float DrawFormatStringMono(float x, float y, char *format, ...); 124 | 125 | #endif -------------------------------------------------------------------------------- /include/pkgi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "pkgi_dialog.h" 6 | 7 | #define PKGI_UPDATE_URL "https://api.github.com/repos/bucanero/pkgi-psp/releases/latest" 8 | #define PKGI_VERSION "1.1.0" 9 | 10 | #define PKGI_BUTTON_SELECT 0x000001 11 | #define PKGI_BUTTON_START 0x000008 12 | #define PKGI_BUTTON_UP 0x000010 13 | #define PKGI_BUTTON_RIGHT 0x000020 14 | #define PKGI_BUTTON_DOWN 0x000040 15 | #define PKGI_BUTTON_LEFT 0x000080 16 | 17 | #define PKGI_BUTTON_LT 0x000100 // L1 18 | #define PKGI_BUTTON_RT 0x000200 // R1 19 | 20 | #define PKGI_BUTTON_X 0x004000 // cross 21 | #define PKGI_BUTTON_O 0x002000 // circle 22 | #define PKGI_BUTTON_T 0x001000 // triangle 23 | #define PKGI_BUTTON_S 0x008000 // square 24 | 25 | #define PKGI_UNUSED(x) (void)(x) 26 | 27 | #define PKGI_APP_FOLDER "." 28 | #define PKGI_RAP_FOLDER "/PKG/RAP" 29 | #define PKGI_TMP_FOLDER "/PKG" 30 | #define PKGI_INSTALL_FOLDER "/PSP/GAME" 31 | 32 | 33 | #define PKGI_COUNTOF(arr) (sizeof(arr)/sizeof(0[arr])) 34 | 35 | #ifdef PKGI_ENABLE_LOGGING 36 | #include 37 | #define LOG dbglogger_log 38 | #else 39 | #define LOG(...) 40 | #endif 41 | 42 | int pkgi_snprintf(char* buffer, uint32_t size, const char* msg, ...); 43 | void pkgi_vsnprintf(char* buffer, uint32_t size, const char* msg, va_list args); 44 | char* pkgi_strstr(const char* str, const char* sub); 45 | int pkgi_stricontains(const char* str, const char* sub); 46 | int pkgi_stricmp(const char* a, const char* b); 47 | void pkgi_strncpy(char* dst, uint32_t size, const char* src); 48 | char* pkgi_strrchr(const char* str, char ch); 49 | uint32_t pkgi_strlen(const char *str); 50 | int64_t pkgi_strtoll(const char* str); 51 | void pkgi_memcpy(void* dst, const void* src, uint32_t size); 52 | void pkgi_memmove(void* dst, const void* src, uint32_t size); 53 | int pkgi_memequ(const void* a, const void* b, uint32_t size); 54 | void* pkgi_malloc(uint32_t size); 55 | void pkgi_free(void* ptr); 56 | 57 | int pkgi_ok_button(void); 58 | int pkgi_cancel_button(void); 59 | 60 | void pkgi_start(void); 61 | int pkgi_update(pkgi_input* input); 62 | void pkgi_swap(void); 63 | void pkgi_end(void); 64 | 65 | int pkgi_get_battery_charge(int* status); 66 | const char* pkgi_get_storage_device(void); 67 | int pkgi_is_psp_go(uint8_t dev); 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(int iso_mode, int remove_pkg); 76 | 77 | uint32_t pkgi_time_msec(void); 78 | 79 | typedef void pkgi_thread_entry(void); 80 | void pkgi_start_thread(const char* name, pkgi_thread_entry* start); 81 | void pkgi_thread_exit(void); 82 | void pkgi_sleep(uint32_t msec); 83 | 84 | int pkgi_load(const char* name, void* data, uint32_t max); 85 | int pkgi_save(const char* name, const void* data, uint32_t size); 86 | 87 | void pkgi_lock_process(void); 88 | void pkgi_unlock_process(void); 89 | 90 | int pkgi_dialog_lock(void); 91 | int pkgi_dialog_unlock(void); 92 | 93 | void pkgi_dialog_input_text(const char* title, const char* text); 94 | int pkgi_dialog_input_update(void); 95 | void pkgi_dialog_input_get_text(char* text, uint32_t size); 96 | 97 | int pkgi_check_free_space(uint64_t http_length); 98 | 99 | typedef struct pkgi_http pkgi_http; 100 | 101 | int pkgi_validate_url(const char* url); 102 | pkgi_http* pkgi_http_get(const char* url, const char* content, uint64_t offset); 103 | int pkgi_http_response_length(pkgi_http* http, int64_t* length); 104 | int pkgi_http_read(pkgi_http* http, void* write_func, void* xferinfo_func); 105 | void pkgi_http_close(pkgi_http* http); 106 | 107 | int pkgi_mkdirs(const char* path); 108 | void pkgi_rm(const char* file); 109 | int64_t pkgi_get_size(const char* path); 110 | 111 | // creates file (if it exists, truncates size to 0) 112 | void* pkgi_create(const char* path); 113 | // open existing file in read mode, fails if file does not exist 114 | void* pkgi_open(const char* path); 115 | // open file for writing, next write will append data to end of it 116 | void* pkgi_append(const char* path); 117 | 118 | void pkgi_close(void* f); 119 | 120 | int pkgi_read(void* f, void* buffer, uint32_t size); 121 | int pkgi_write(void* f, const void* buffer, uint32_t size); 122 | 123 | // UI stuff 124 | struct pkgi_texture_s 125 | { 126 | void *texture; 127 | unsigned short width; 128 | unsigned short height; 129 | }; 130 | 131 | typedef struct pkgi_texture_s* pkgi_texture; 132 | 133 | #define pkgi_load_image_buffer(name, type) \ 134 | ({ extern const uint8_t _binary_data_##name##_##type##_start; \ 135 | extern const uint8_t _binary_data_##name##_##type##_size; \ 136 | pkgi_load_##type##_raw((void*) &_binary_data_##name##_##type##_start , (int) &_binary_data_##name##_##type##_size); \ 137 | }) 138 | 139 | pkgi_texture loadPngTexture(const char* path, const void* buffer); 140 | pkgi_texture pkgi_load_png_raw(const void* data, uint32_t size); 141 | pkgi_texture pkgi_load_png_file(const char* filename); 142 | void pkgi_draw_background(pkgi_texture texture); 143 | void pkgi_draw_texture(pkgi_texture texture, int x, int y); 144 | void pkgi_draw_texture_z(pkgi_texture texture, int x, int y, int z, float scale); 145 | void pkgi_free_texture(pkgi_texture texture); 146 | 147 | void pkgi_clip_set(int x, int y, int w, int h); 148 | void pkgi_clip_remove(void); 149 | void pkgi_draw_rect(int x, int y, int w, int h, uint32_t color); 150 | void pkgi_draw_rect_z(int x, int y, int z, int w, int h, uint32_t color); 151 | void pkgi_draw_fill_rect(int x, int y, int w, int h, uint32_t color); 152 | void pkgi_draw_fill_rect_z(int x, int y, int z, int w, int h, uint32_t color); 153 | void pkgi_draw_text(int x, int y, uint32_t color, const char* text); 154 | void pkgi_draw_text_z(int x, int y, int z, uint32_t color, const char* text); 155 | void pkgi_draw_text_ttf(int x, int y, int z, uint32_t color, const char* text); 156 | int pkgi_text_width(const char* text); 157 | int pkgi_text_width_ttf(const char* text); 158 | int pkgi_text_height(const char* text); 159 | -------------------------------------------------------------------------------- /include/pkgi_aes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_utils.h" 4 | #include 5 | 6 | 7 | void aes128_init(mbedtls_aes_context* ctx, const uint8_t* key); 8 | void aes128_init_dec(mbedtls_aes_context* ctx, const uint8_t* key); 9 | 10 | void aes128_ecb_encrypt(mbedtls_aes_context* ctx, const uint8_t* input, uint8_t* output); 11 | void aes128_ecb_decrypt(mbedtls_aes_context* ctx, const uint8_t* input, uint8_t* output); 12 | 13 | void aes128_ctr_xor(mbedtls_aes_context* ctx, const uint8_t* iv, uint64_t block, uint8_t* buffer, size_t size); 14 | 15 | void aes128_cmac(const uint8_t* key, const uint8_t* buffer, uint32_t size, uint8_t* mac); 16 | 17 | void aes128_psp_decrypt(mbedtls_aes_context* ctx, const uint8_t* iv, uint32_t index, uint8_t* buffer, uint32_t size); 18 | -------------------------------------------------------------------------------- /include/pkgi_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_db.h" 4 | 5 | void pkgi_load_config(Config* config, char* update_url, uint32_t update_len); 6 | void pkgi_save_config(const Config* config, const char* update_url, uint32_t update_len); 7 | 8 | const char* pkgi_content_tag(ContentType content); 9 | const char* pkgi_get_user_language(void); 10 | -------------------------------------------------------------------------------- /include/pkgi_db.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | typedef enum { 7 | PresenceUnknown, 8 | PresenceIncomplete, 9 | PresenceInstalled, 10 | PresenceMissing, 11 | } DbPresence; 12 | 13 | typedef enum { 14 | SortByTitle, 15 | SortByRegion, 16 | SortByName, 17 | SortBySize, 18 | } DbSort; 19 | 20 | typedef enum { 21 | SortAscending, 22 | SortDescending, 23 | } DbSortOrder; 24 | 25 | typedef enum { 26 | DbFilterRegionASA = 0x01, 27 | DbFilterRegionEUR = 0x02, 28 | DbFilterRegionJPN = 0x04, 29 | DbFilterRegionUSA = 0x08, 30 | 31 | // TODO: implement these two 32 | DbFilterInstalled = 0x10, 33 | DbFilterMissing = 0x20, 34 | 35 | DbFilterContentGame = 0x000100, 36 | DbFilterContentDLC = 0x000200, 37 | DbFilterContentTheme = 0x000400, 38 | DbFilterContentPSX = 0x000800, 39 | DbFilterContentDemo = 0x001000, 40 | DbFilterContentUpdate = 0x002000, 41 | DbFilterContentEmulator = 0x004000, 42 | DbFilterContentApp = 0x008000, 43 | DbFilterContentLocal = 0x010000, 44 | 45 | DbFilterAllRegions = DbFilterRegionUSA | DbFilterRegionEUR | DbFilterRegionJPN | DbFilterRegionASA, 46 | DbFilterAllContent = DbFilterContentGame | DbFilterContentDLC | DbFilterContentTheme | DbFilterContentPSX | 47 | DbFilterContentDemo | DbFilterContentUpdate | DbFilterContentEmulator | DbFilterContentApp | DbFilterContentLocal, 48 | DbFilterAll = DbFilterAllRegions | DbFilterAllContent | DbFilterInstalled | DbFilterMissing, 49 | } DbFilter; 50 | 51 | typedef enum { 52 | ContentUnknown, 53 | ContentGame, 54 | ContentDLC, 55 | ContentTheme, 56 | ContentPSX, 57 | ContentDemo, 58 | ContentUpdate, 59 | ContentEmulator, 60 | ContentApp, 61 | ContentLocal, 62 | MAX_CONTENT_TYPES 63 | } ContentType; 64 | 65 | typedef struct { 66 | DbPresence presence; 67 | const char* content; 68 | ContentType type; 69 | const char* name; 70 | const char* description; 71 | const uint8_t* rap; 72 | const char* url; 73 | const uint8_t* digest; 74 | int64_t size; 75 | } DbItem; 76 | 77 | typedef enum { 78 | RegionASA, 79 | RegionEUR, 80 | RegionJPN, 81 | RegionUSA, 82 | RegionUnknown, 83 | } GameRegion; 84 | 85 | typedef struct Config { 86 | DbSort sort; 87 | DbSortOrder order; 88 | uint32_t filter; 89 | uint8_t content; 90 | uint8_t version_check; 91 | uint8_t install_mode_iso; 92 | uint8_t keep_pkg; 93 | uint8_t allow_refresh; 94 | uint8_t storage; 95 | char language[3]; 96 | } Config; 97 | 98 | 99 | int pkgi_db_reload(char* error, uint32_t error_size); 100 | int pkgi_db_update(const char* update_url, uint32_t update_len, char* error, uint32_t error_size); 101 | void pkgi_db_get_update_status(uint32_t* updated, uint32_t* total); 102 | int pkgi_db_load_xml_updates(const char* content_id, const char* name); 103 | 104 | void pkgi_db_configure(const char* search, const Config* config); 105 | 106 | uint32_t pkgi_db_count(void); 107 | uint32_t pkgi_db_total(void); 108 | DbItem* pkgi_db_get(uint32_t index); 109 | 110 | GameRegion pkgi_get_region(const char* content); 111 | ContentType pkgi_get_content_type(uint32_t content); 112 | -------------------------------------------------------------------------------- /include/pkgi_dialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "pkgi_db.h" 5 | 6 | #define MDIALOG_OK 0 7 | #define MDIALOG_YESNO 1 8 | 9 | typedef struct pkgi_input { 10 | uint64_t delta; // microseconds from previous frame 11 | uint32_t pressed; // button pressed in last frame 12 | uint32_t down; // button is currently down 13 | uint32_t active; // button is pressed in last frame, or held down for a long time (10 frames) 14 | } pkgi_input; 15 | 16 | typedef void (*pkgi_dialog_callback_t)(int); 17 | 18 | void pkgi_dialog_init(void); 19 | 20 | int pkgi_dialog_is_open(void); 21 | int pkgi_dialog_is_cancelled(void); 22 | void pkgi_dialog_allow_close(int allow); 23 | void pkgi_dialog_message(const char* title, const char* text); 24 | void pkgi_dialog_error(const char* text); 25 | void pkgi_dialog_details(DbItem* item, const char* type); 26 | void pkgi_dialog_ok_cancel(const char* title, const char* text, pkgi_dialog_callback_t callback); 27 | 28 | void pkgi_dialog_start_progress(const char* title, const char* text, float progress); 29 | void pkgi_dialog_set_progress_title(const char* title); 30 | void pkgi_dialog_update_progress(const char* text, const char* extra, const char* eta, float progress); 31 | 32 | void pkgi_dialog_close(void); 33 | 34 | void pkgi_do_dialog(pkgi_input* input); 35 | 36 | int pkgi_msg_dialog(int tdialog, const char * str); 37 | -------------------------------------------------------------------------------- /include/pkgi_download.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "pkgi_db.h" 5 | 6 | #define PKGI_RAP_SIZE 16 7 | 8 | int pkgi_download(const DbItem* item); 9 | char * pkgi_http_download_buffer(const char* url, uint32_t* buf_size); 10 | 11 | void progress_screen_refresh(void); 12 | void update_install_progress(const char *filename, int64_t progress); 13 | int install_psp_pkg(const char *file); 14 | int convert_psp_pkg_iso(const char* pkg_arg, int cso); 15 | int extract_zip(const char* zip_file); 16 | -------------------------------------------------------------------------------- /include/pkgi_menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_db.h" 4 | #include "pkgi_dialog.h" 5 | 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); 20 | 21 | int pkgi_do_menu(pkgi_input* input); 22 | -------------------------------------------------------------------------------- /include/pkgi_sha256.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgi_utils.h" 4 | #include 5 | 6 | #define SHA256_DIGEST_SIZE 32 7 | -------------------------------------------------------------------------------- /include/pkgi_style.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PKGI_SCREEN_WIDTH 480 4 | #define PKGI_SCREEN_HEIGHT 272 5 | 6 | #define PKGI_COLOR(R, G, B) (((R)<<24) | ((G)<<16) | ((B)<<8)) 7 | #define RGBA_COLOR(C, ALPHA) ((C) | ALPHA) 8 | 9 | #define RGBA_R(c) (uint8_t)((c & 0xFF000000) >> 24) 10 | #define RGBA_G(c) (uint8_t)((c & 0x00FF0000) >> 16) 11 | #define RGBA_B(c) (uint8_t)((c & 0x0000FF00) >> 8) 12 | #define RGBA_A(c) (uint8_t) (c & 0x000000FF) 13 | 14 | #define PKGI_UTF8_O "\xfa" // "\xe2\x97\x8b" // 0x25cb 15 | #define PKGI_UTF8_X "\xfb" // "\xe2\x95\xb3" // 0x2573 16 | #define PKGI_UTF8_T "\xfc" // "\xe2\x96\xb3" // 0x25b3 17 | #define PKGI_UTF8_S "\xfd" // "\xe2\x96\xa1" // 0x25a1 18 | 19 | 20 | #define PKGI_UTF8_INSTALLED "\x04"//"\xe2\x97\x8f" // 0x25cf 21 | #define PKGI_UTF8_PARTIAL "\x09"//"\xe2\x97\x8b" // 0x25cb 22 | 23 | #define PKGI_UTF8_B "B" 24 | #define PKGI_UTF8_KB "Kb" // "\xe3\x8e\x85" // 0x3385 25 | #define PKGI_UTF8_MB "Mb" // "\xe3\x8e\x86" // 0x3386 26 | #define PKGI_UTF8_GB "Gb" // "\xe3\x8e\x87" // 0x3387 27 | 28 | #define PKGI_UTF8_CLEAR "\xaf" // 0x00d7 29 | 30 | #define PKGI_UTF8_SORT_ASC "\x1e" //"\xe2\x96\xb2" // 0x25b2 31 | #define PKGI_UTF8_SORT_DESC "\x1f" //"\xe2\x96\xbc" // 0x25bc 32 | 33 | #define PKGI_UTF8_CHECK_ON "\x04"//"\xe2\x97\x8f" // 0x25cf 34 | #define PKGI_UTF8_CHECK_OFF "\x09"//"\xe2\x97\x8b" // 0x25cb 35 | 36 | #define PKGI_COLOR_DIALOG_BACKGROUND PKGI_COLOR(48, 48, 48) 37 | #define PKGI_COLOR_MENU_BORDER PKGI_COLOR(80, 80, 255) 38 | #define PKGI_COLOR_MENU_BACKGROUND PKGI_COLOR(48, 48, 48) 39 | #define PKGI_COLOR_TEXT_MENU PKGI_COLOR(255, 255, 255) 40 | #define PKGI_COLOR_TEXT_MENU_SELECTED PKGI_COLOR(0, 255, 0) 41 | #define PKGI_COLOR_TEXT PKGI_COLOR(255, 255, 255) 42 | #define PKGI_COLOR_TEXT_HEAD PKGI_COLOR(255, 255, 255) 43 | #define PKGI_COLOR_TEXT_TAIL PKGI_COLOR(255, 255, 255) 44 | #define PKGI_COLOR_TEXT_DIALOG PKGI_COLOR(255, 255, 255) 45 | #define PKGI_COLOR_TEXT_ERROR PKGI_COLOR(255, 50, 50) 46 | #define PKGI_COLOR_TEXT_SHADOW PKGI_COLOR(0, 0, 0) 47 | #define PKGI_COLOR_HLINE PKGI_COLOR(200, 200, 200) 48 | #define PKGI_COLOR_SCROLL_BAR PKGI_COLOR(255, 255, 255) 49 | #define PKGI_COLOR_BATTERY_LOW PKGI_COLOR(255, 50, 50) 50 | #define PKGI_COLOR_BATTERY_CHARGING PKGI_COLOR(50, 255, 50) 51 | #define PKGI_COLOR_SELECTED_BACKGROUND PKGI_COLOR(60, 60, 60) 52 | #define PKGI_COLOR_PROGRESS_BACKGROUND PKGI_COLOR(128, 128, 128) 53 | #define PKGI_COLOR_PROGRESS_BAR PKGI_COLOR(128, 255, 0) 54 | 55 | #define PKGI_ANIMATION_SPEED 4000 // px/second 56 | 57 | #define PKGI_FONT_8x16 0 58 | #define PKGI_FONT_Z 1000 59 | #define PKGI_FONT_WIDTH 8 60 | #define PKGI_FONT_HEIGHT 16 61 | #define PKGI_FONT_SHADOW 2 62 | 63 | #define PKGI_MAIN_COLUMN_PADDING 10 64 | #define PKGI_MAIN_HLINE_EXTRA 5 65 | #define PKGI_MAIN_ROW_PADDING 1 66 | #define PKGI_MAIN_HLINE_HEIGHT 2 67 | #define PKGI_MAIN_TEXT_PADDING 5 68 | #define PKGI_MAIN_SCROLL_WIDTH 2 69 | #define PKGI_MAIN_SCROLL_PADDING 2 70 | #define PKGI_MAIN_SCROLL_MIN_HEIGHT 50 71 | #define PKGI_MAIN_HMARGIN 8 72 | #define PKGI_MAIN_VMARGIN 4 73 | 74 | #define PKGI_DIALOG_TEXT_Z 800 75 | #define PKGI_DIALOG_HMARGIN 50 76 | #define PKGI_DIALOG_VMARGIN 50 77 | #define PKGI_DIALOG_PADDING 30 78 | #define PKGI_DIALOG_WIDTH (PKGI_SCREEN_WIDTH - 2*PKGI_DIALOG_HMARGIN) 79 | #define PKGI_DIALOG_HEIGHT (PKGI_SCREEN_HEIGHT - 2*PKGI_DIALOG_VMARGIN) 80 | 81 | #define PKGI_DIALOG_PROCESS_BAR_HEIGHT 10 82 | #define PKGI_DIALOG_PROCESS_BAR_PADDING 10 83 | #define PKGI_DIALOG_PROCESS_BAR_CHUNK 200 84 | 85 | #define PKGI_MENU_Z 900 86 | #define PKGI_MENU_TEXT_Z 800 87 | #define PKGI_MENU_WIDTH 150 88 | #define PKGI_MENU_HEIGHT 260 89 | #define PKGI_MENU_LEFT_PADDING 20 90 | #define PKGI_MENU_TOP_PADDING 20 91 | -------------------------------------------------------------------------------- /include/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 | -------------------------------------------------------------------------------- /include/ttf_render.h: -------------------------------------------------------------------------------- 1 | #ifndef TTF_RENDER_H 2 | #define TTF_RENDER_H 3 | 4 | 5 | int TTFLoadFont(int set, const char * path, void * from_memory, int size_from_memory); 6 | void TTFUnloadFont(); 7 | 8 | void TTF_to_Bitmap(uint8_t chr, uint8_t * bitmap, short *w, short *h, short *y_correction); 9 | 10 | int Render_String_UTF8(uint16_t * bitmap, int w, int h, uint8_t *string, int sw, int sh); 11 | 12 | 13 | // initialize and create textures slots for ttf 14 | uint16_t * init_ttf_table(uint8_t *texture); 15 | 16 | // do one time per frame to reset the character use flag 17 | void reset_ttf_frame(void); 18 | 19 | // define window mode and size 20 | 21 | #define WIN_AUTO_LF 1 22 | #define WIN_SKIP_LF 2 23 | #define WIN_DOUBLE_LF 4 24 | 25 | void set_ttf_window(int x, int y, int width, int height, uint32_t mode); 26 | 27 | extern float Y_ttf; 28 | extern float Z_ttf; 29 | 30 | // display UTF8 string int posx/posy position. With color 0 don't display and refresh/calculate the width. 31 | // color is the character color and sw/sh the width/height of the characters 32 | 33 | int display_ttf_string(int posx, int posy, const char *string, uint32_t color, uint32_t bkcolor, int sw, int sh, int (*DrawIcon)(int, int, char)); 34 | int width_ttf_string(const char *string, int sw, int sh); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /source/depackager.c: -------------------------------------------------------------------------------- 1 | /* 2 | * based on depackager by qwikrazor87 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pkgi.h" 12 | #include "pkgi_download.h" 13 | 14 | #define DEPACKAGER_VER 3 15 | 16 | /* 17 | typedef struct { 18 | u32 magic; 19 | u16 pkg_revision; 20 | u16 pkg_type; 21 | u32 pkg_metadata_offset; 22 | u32 pkg_metadata_count; 23 | u32 pkg_metadata_size; 24 | u32 item_count; 25 | u64 total_size; 26 | u64 data_offset; 27 | u64 data_size; 28 | u8 contentid[0x30]; 29 | u8 digest[0x10]; 30 | u8 pkg_data_riv[0x10]; 31 | u8 pkg_header_digest[0x40]; 32 | } PKG_HEADER; 33 | 34 | typedef struct { 35 | u32 magic; // 0x7F657874 (".ext") 36 | u32 unknown_1; // Maybe version. Always 1. 37 | u32 ext_hdr_size; // Extended header size. ex: 0x40 38 | u32 ext_data_size; // ex: 0x180 39 | u32 main_and_ext_headers_hmac_offset; // ex: 0x100 40 | u32 metadata_header_hmac_offset; // ex: 0x360, 0x390, 0x490 41 | u64 tail_offset; // Tail size seems to be always 0x1A0 42 | u32 padding1; 43 | u32 pkg_key_id; // Id of the AES key used for decryption. PSP = 0x1, PS Vita = 0xC0000002, PSM = 0xC0000004 44 | u32 full_header_hmac_offset; // ex: none (old pkg): 0, 0x930 45 | u8 padding2[0x14]; 46 | } PKG_EXT_HEADER; 47 | */ 48 | 49 | 50 | static u8 static_public_key[16]; 51 | 52 | static const u8 PSPAESKey[16] __attribute__((aligned(16))) = { 53 | 0x07, 0xF2, 0xC6, 0x82, 0x90, 0xB5, 0x0D, 0x2C, 0x33, 0x81, 0x8D, 0x70, 0x9B, 0x60, 0xE6, 0x2B 54 | }; 55 | 56 | 57 | static void AES128_ECB_encrypt(uint8_t* input, const uint8_t* key, uint8_t *output) 58 | { 59 | mbedtls_aes_context ctx; 60 | 61 | mbedtls_aes_init(&ctx); 62 | mbedtls_aes_setkey_enc(&ctx, key, 128); 63 | mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, input, output); 64 | } 65 | 66 | static void AES128_ECB_decrypt(uint8_t* input, const uint8_t* key, uint8_t *output) 67 | { 68 | mbedtls_aes_context ctx; 69 | 70 | mbedtls_aes_init(&ctx); 71 | mbedtls_aes_setkey_dec(&ctx, key, 128); 72 | mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_DECRYPT, input, output); 73 | } 74 | 75 | static u32 toU32(char *_buf) 76 | { 77 | u8 *buf = (u8 *)_buf; 78 | u32 b1 = buf[0] << 24; 79 | u32 b2 = buf[1] << 16; 80 | u32 b3 = buf[2] << 8; 81 | u32 size = b1 | b2 | b3 | buf[3]; 82 | 83 | return size; 84 | } 85 | 86 | static int is_pkg_supported(const char *file) 87 | { 88 | char buf[16]; 89 | 90 | SceUID fd = sceIoOpen(file, PSP_O_RDONLY, 0777); 91 | if (fd >= 0) { 92 | sceIoRead(fd, buf, 4); 93 | sceIoClose(fd); 94 | 95 | return memcmp(buf, "\x7FPKG", 4) == 0; 96 | } else 97 | return 0; 98 | } 99 | 100 | static int is_pkg_type_supported(const char *file) 101 | { 102 | char buf[16]; 103 | 104 | SceUID fd = sceIoOpen(file, PSP_O_RDONLY, 0777); 105 | if (fd >= 0) { 106 | sceIoRead(fd, buf, 8); 107 | sceIoClose(fd); 108 | 109 | return memcmp(buf, "\x7FPKG\x80\x00\x00\x02", 8) == 0; 110 | } else 111 | return 0; 112 | } 113 | 114 | static void view_pkg_info(const char *file) 115 | { 116 | char buf[256]; 117 | 118 | LOG("PKG info viewer"); 119 | 120 | SceUID fd = sceIoOpen(file, PSP_O_RDONLY, 0777); 121 | sceIoRead(fd, buf, sizeof(buf)); 122 | sceIoClose(fd); 123 | 124 | u8 version = (u8)buf[4]; 125 | 126 | LOG("PKG type: %s", buf[7] == 1 ? "PS3 (currently unsupported)" : buf[7] == 2 ? "PSP" : "unknown"); 127 | LOG("PKG version: %s", (version == 0x80) ? "retail" : (version == 0x90) ? "debug (currently unsupported)" : "unknown"); 128 | LOG("Content ID: %s", buf + 0x30); 129 | LOG("PKG file count: %d", toU32(buf + 0x14)); 130 | LOG("PKG size: %d bytes", toU32(buf + 0x1C)); 131 | LOG("Encrypted size: %d bytes", toU32(buf + 0x2C)); 132 | } 133 | 134 | static void xor128(u8 *dst, u8 *xor1, u8 *xor2) 135 | { 136 | int i; 137 | 138 | for (i = 0; i < 16; i++) 139 | dst[i] = xor1[i] ^ xor2[i]; 140 | } 141 | 142 | static void iter128(u8 *buf) 143 | { 144 | int i; 145 | 146 | for (i = 15; i >= 0; i--) { 147 | buf[i]++; 148 | 149 | if (buf[i]) 150 | break; 151 | } 152 | } 153 | 154 | static void setiter128(u8 *dst, int size) 155 | { 156 | memcpy(dst, static_public_key, 16); 157 | 158 | int i; 159 | 160 | for (i = 0; i < size; i++) 161 | iter128(dst); 162 | } 163 | 164 | int install_psp_pkg(const char *file) 165 | { 166 | char *tmpBuf; 167 | char gameid[10]; 168 | char pkgBuf[256]; 169 | u8 public_key[16], xor_key[16]; 170 | SceOff progress = 0; 171 | 172 | LOG("PSP depackager v%d", DEPACKAGER_VER); 173 | LOG("PSP PKG installer"); 174 | 175 | if(is_pkg_supported(file)) 176 | view_pkg_info(file); 177 | 178 | if (!is_pkg_type_supported(file)) { 179 | LOG("Unsupported PKG type detected."); 180 | return 0; 181 | } 182 | 183 | SceUID fd = sceIoOpen(file, PSP_O_RDONLY, 0777); 184 | SceSize fdsize = sceIoLseek(fd, 0, PSP_SEEK_END); 185 | sceIoLseek(fd, 0, PSP_SEEK_SET); 186 | sceIoRead(fd, pkgBuf, 256); 187 | 188 | if (toU32(pkgBuf + 0x1C) != fdsize) { 189 | sceIoClose(fd); 190 | LOG("Corrupt PKG detected"); 191 | LOG("detected size: %d bytes", fdsize); 192 | LOG("expected size: %d bytes", toU32(pkgBuf + 0x1C)); 193 | return 0; 194 | } 195 | 196 | sceIoLseek(fd, toU32(pkgBuf + 8) + 0x14, PSP_SEEK_SET); 197 | sceIoRead(fd, gameid, 4); 198 | 199 | if (toU32(gameid) == 9) { 200 | sceIoClose(fd); 201 | LOG("Theme PKG detected"); 202 | return (convert_psp_pkg_iso(file, 0)); 203 | } 204 | 205 | //1 MiB buffer 206 | tmpBuf = (char *)malloc(1024 * 1024); 207 | if (!tmpBuf) { 208 | LOG("Error allocating memory: 0x%08X", 1024 * 1024); 209 | return 0; 210 | } 211 | 212 | sceIoLseek(fd, toU32(pkgBuf + 8) + 0x48, PSP_SEEK_SET); 213 | sceIoRead(fd, gameid, 9); 214 | gameid[9] = 0; 215 | 216 | memcpy(public_key, pkgBuf + 0x70, 16); 217 | memcpy(static_public_key, pkgBuf + 0x70, 16); 218 | 219 | u32 enc_start = toU32(pkgBuf + 0x24); 220 | u32 files = toU32(pkgBuf + 0x14); 221 | 222 | sceIoLseek(fd, enc_start, PSP_SEEK_SET); 223 | sceIoRead(fd, tmpBuf, files * 32); 224 | 225 | int i, j, pspcount = 0; 226 | u32 file_name[files], file_name_len[files], file_offset[files], file_size[files], is_file[files]; 227 | 228 | for (i = 0; i < (int)(files * 2); i++) { 229 | AES128_ECB_encrypt(public_key, PSPAESKey, xor_key); 230 | xor128((u8 *)(tmpBuf + (i * 16)), (u8 *)(tmpBuf + (i * 16)), xor_key); 231 | iter128(public_key); 232 | } 233 | 234 | for (i = 0; i < (int)files; i++) { 235 | if (((u8 *)tmpBuf)[(i * 32) + 24] == 0x90) { 236 | file_name[pspcount] = toU32(tmpBuf + i * 32); 237 | file_name_len[pspcount] = toU32(tmpBuf + i * 32 + 4); 238 | file_offset[pspcount] = toU32(tmpBuf + i * 32 + 12); 239 | file_size[pspcount] = toU32(tmpBuf + i * 32 + 20); 240 | is_file[pspcount] = ((tmpBuf[i * 32 + 27] != 4) && file_size[pspcount]); 241 | pspcount++; 242 | } 243 | } 244 | 245 | int files_extracted = 0; 246 | 247 | for (i = 0; i < pspcount; i++) { 248 | sceIoLseek(fd, enc_start + file_name[i], PSP_SEEK_SET); 249 | int namesize = (file_name_len[i] + 15) & -16; 250 | sceIoRead(fd, tmpBuf, namesize); 251 | memcpy(public_key, pkgBuf + 0x70, 16); 252 | 253 | setiter128(public_key, file_name[i] >> 4); 254 | 255 | for (j = 0; j < (namesize >> 4); j++) { 256 | AES128_ECB_encrypt(public_key, PSPAESKey, xor_key); 257 | xor128((u8 *)(tmpBuf + (j * 16)), (u8 *)(tmpBuf + (j * 16)), xor_key); 258 | iter128(public_key); 259 | } 260 | 261 | char path[256]; 262 | snprintf(path, sizeof(path), "%s%s/%s/%s", pkgi_get_storage_device(), PKGI_INSTALL_FOLDER, gameid, tmpBuf + 15); 263 | char* slash = strrchr(path, '/'); 264 | *slash = 0; 265 | pkgi_mkdirs(path); 266 | *slash = '/'; 267 | 268 | if (is_file[i]) { 269 | LOG("Currently extracting: %s", path); 270 | files_extracted++; 271 | 272 | progress = sceIoLseek(fd, enc_start + file_offset[i], PSP_SEEK_SET); 273 | sceIoRead(fd, tmpBuf, 1024 * 1024); 274 | update_install_progress(path + 14, progress); 275 | 276 | setiter128(public_key, file_offset[i] >> 4); 277 | 278 | SceUID dstfd = sceIoOpen(path, 0x602, 0777); 279 | 280 | u32 szcheck = 0, mincheck = 0; 281 | 282 | LOG("%d/%d bytes", mincheck, file_size[i]); 283 | 284 | for (j = 0; j < (int)(file_size[i] >> 4); j++) { 285 | if (szcheck == 1024 * 1024) { 286 | szcheck = 0; 287 | mincheck += 1024 * 1024; 288 | 289 | sceIoWrite(dstfd, tmpBuf, 1024 * 1024); 290 | sceIoRead(fd, tmpBuf, 1024 * 1024); 291 | update_install_progress(NULL, progress + mincheck); 292 | 293 | LOG("%d/%d bytes", mincheck, file_size[i]); 294 | } 295 | 296 | AES128_ECB_encrypt(public_key, PSPAESKey, xor_key); 297 | xor128((u8 *)((tmpBuf + (j * 16)) - mincheck), (u8 *)((tmpBuf + (j * 16)) - mincheck), xor_key); 298 | iter128(public_key); 299 | 300 | szcheck += 16; 301 | } 302 | 303 | if (mincheck < file_size[i]) 304 | { 305 | sceIoWrite(dstfd, tmpBuf, file_size[i] - mincheck); 306 | update_install_progress(NULL, progress + file_size[i]); 307 | } 308 | 309 | sceIoClose(dstfd); 310 | } 311 | } 312 | 313 | update_install_progress(file + 9, fdsize); 314 | sceIoClose(fd); 315 | free(tmpBuf); 316 | 317 | LOG("files extracted: %d", files_extracted); 318 | LOG("Installation complete"); 319 | 320 | return 1; 321 | } 322 | -------------------------------------------------------------------------------- /source/libfont.c: -------------------------------------------------------------------------------- 1 | /* 2 | TINY3D - font library / (c) 2010 Hermes 3 | 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pkgi.h" 12 | #include "libfont.h" 13 | #include "ttf_render.h" 14 | #include "pkgi_style.h" 15 | 16 | extern SDL_Renderer* renderer; 17 | 18 | struct t_font_description 19 | { 20 | int w, h, bh; 21 | 22 | uint8_t first_char; 23 | uint8_t last_char; 24 | 25 | uint8_t* rsx_text_offset; 26 | uint32_t rsx_bytes_per_char; 27 | uint32_t color_format; 28 | 29 | short fw[256]; // chr width 30 | short fy[256]; // chr y correction 31 | }; 32 | 33 | static struct t_font_datas 34 | { 35 | int number_of_fonts; 36 | int current_font; 37 | 38 | struct t_font_description fonts[8]; 39 | 40 | int sx, sy; 41 | int mono; 42 | int extra; 43 | 44 | uint32_t color, bkcolor; 45 | 46 | int align; //0 = left, 1 = center, 2 = right 47 | int autonewline; 48 | 49 | float X,Y,Z; 50 | 51 | } font_datas; 52 | 53 | typedef struct t_special_char 54 | { 55 | char value; 56 | short fy; 57 | float scale; 58 | pkgi_texture image; 59 | } special_char; 60 | 61 | static special_char special_chars[MAX_SPECIAL_CHARS]; 62 | static int special_char_index = 0; 63 | 64 | 65 | void DrawTexture(pkgi_texture tex, int x, int y, int z, int w, int h, uint32_t rgba) 66 | { 67 | SDL_Rect dest = { 68 | .x = x, 69 | .y = y, 70 | .w = w, 71 | .h = h, 72 | }; 73 | 74 | SDL_SetTextureAlphaMod(tex->texture, RGBA_A(rgba)); 75 | SDL_RenderCopy(renderer, tex->texture, NULL, &dest); 76 | } 77 | 78 | special_char* GetSpecialCharFromValue(const char value) 79 | { 80 | int x; 81 | special_char* ret = NULL; 82 | 83 | for (x = 0; x < special_char_index; x++) 84 | { 85 | if (special_chars[x].value == value) 86 | ret = &(special_chars[x]); 87 | } 88 | return ret; 89 | } 90 | 91 | void ResetFont(void) 92 | { 93 | font_datas.current_font = font_datas.number_of_fonts =0; 94 | 95 | font_datas.color = 0xffffffff; 96 | font_datas.bkcolor = 0; 97 | font_datas.align = FONT_ALIGN_LEFT; 98 | font_datas.X = font_datas.Y = font_datas.Z = 0.0f; 99 | font_datas.autonewline = 0; 100 | 101 | font_datas.sx = font_datas.sy = 8; 102 | font_datas.mono = 0; 103 | } 104 | 105 | uint8_t * AddFontFromBitmapArray(uint8_t *font, uint8_t *texture, uint8_t first_char, uint8_t last_char, int w, int h, int bits_per_pixel, int byte_order) 106 | { 107 | int n, a, b; 108 | uint32_t buf[w*h]; 109 | uint8_t i; 110 | 111 | if(font_datas.number_of_fonts >= 8) return texture; 112 | 113 | font_datas.fonts[font_datas.number_of_fonts].w = w; 114 | font_datas.fonts[font_datas.number_of_fonts].h = h; 115 | font_datas.fonts[font_datas.number_of_fonts].bh = h; 116 | font_datas.fonts[font_datas.number_of_fonts].color_format = 1; //TINY3D_TEX_FORMAT_A4R4G4B4; //TINY3D_TEX_FORMAT_A8R8G8B8; 117 | font_datas.fonts[font_datas.number_of_fonts].first_char = first_char; 118 | font_datas.fonts[font_datas.number_of_fonts].last_char = last_char; 119 | font_datas.align = FONT_ALIGN_LEFT; 120 | 121 | font_datas.color = 0xffffffff; 122 | font_datas.bkcolor = 0x0; 123 | 124 | font_datas.sx = w; 125 | font_datas.sy = h; 126 | 127 | font_datas.Z = 0.0f; 128 | 129 | for(n = 0; n < 256; n++) { 130 | font_datas.fonts[font_datas.number_of_fonts].fw[n] = 0; 131 | font_datas.fonts[font_datas.number_of_fonts].fy[n] = 0; 132 | } 133 | 134 | for(n = first_char; n <= last_char; n++) { 135 | 136 | font_datas.fonts[font_datas.number_of_fonts].fw[n] = w; 137 | 138 | texture = (uint8_t *) ((((long) texture) + 15) & ~15); 139 | 140 | if(n == first_char) font_datas.fonts[font_datas.number_of_fonts].rsx_text_offset = texture; //tiny3d_TextureOffset(texture); 141 | 142 | if(n == first_char+1) font_datas.fonts[font_datas.number_of_fonts].rsx_bytes_per_char = texture 143 | - font_datas.fonts[font_datas.number_of_fonts].rsx_text_offset; 144 | 145 | for(a = 0; a < h; a++) { 146 | for(b = 0; b < w; b++) { 147 | 148 | i = font[(b * bits_per_pixel)/8]; 149 | 150 | if(byte_order) 151 | i= (i << ((b & (7/bits_per_pixel)) * bits_per_pixel))>> (8-bits_per_pixel); 152 | else 153 | i >>= (b & (7/bits_per_pixel)) * bits_per_pixel; 154 | 155 | i = (i & ((1 << bits_per_pixel)-1)) * 255 / ((1 << bits_per_pixel)-1); 156 | 157 | if(i) {//TINY3D_TEX_FORMAT_A1R5G5B5 158 | //i>>=3; 159 | //*((u16 *) texture) = (1<<15) | (i<<10) | (i<<5) | (i); 160 | //TINY3D_TEX_FORMAT_A4R4G4B4 161 | buf[a*w + b] = 0x000000FF; 162 | // i>>=4; 163 | // *((u16 *) texture) = (i<<12) | 0xfff; 164 | } else { 165 | // texture[0] = texture[1] = 0x0; //texture[2] = 0x0; 166 | buf[a*w + b] = 0x0; // alpha 167 | } 168 | } 169 | font += (((w+7) & ~7) * bits_per_pixel) / 8; 170 | } 171 | 172 | // Black font texture 173 | SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*) buf, w, h, 32, 4 * w, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); 174 | *((SDL_Texture**) texture) = SDL_CreateTextureFromSurface(renderer, surface); 175 | SDL_FreeSurface(surface); 176 | texture += sizeof(SDL_Texture*); 177 | 178 | // White font texture 179 | for (a = 0; a < h*w; a++) 180 | if (buf[a]) buf[a] = 0xFFFFFFFF; 181 | 182 | surface = SDL_CreateRGBSurfaceFrom((void*) buf, w, h, 32, 4 * w, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); 183 | *((SDL_Texture**) texture) = SDL_CreateTextureFromSurface(renderer, surface); 184 | SDL_FreeSurface(surface); 185 | texture += sizeof(SDL_Texture*); 186 | } 187 | 188 | texture = (uint8_t *) ((((long) texture) + 15) & ~15); 189 | 190 | font_datas.number_of_fonts++; 191 | 192 | return texture; 193 | } 194 | 195 | /* 196 | uint8_t * AddFontFromTTF(uint8_t *texture, uint8_t first_char, uint8_t last_char, int w, int h, 197 | void (* ttf_callback) (uint8_t chr, uint8_t * bitmap, short *w, short *h, short *y_correction)) 198 | { 199 | int n, a, b; 200 | uint8_t i; 201 | uint8_t *font; 202 | static uint8_t letter_bitmap[257 * 256]; 203 | 204 | int bits_per_pixel = 8; 205 | 206 | if(font_datas.number_of_fonts >= 8) return texture; 207 | 208 | if(h < 8) h = 8; 209 | if(w < 8) w = 8; 210 | if(h > 256) h = 256; 211 | if(w > 256) w = 256; 212 | 213 | font_datas.fonts[font_datas.number_of_fonts].w = w; 214 | font_datas.fonts[font_datas.number_of_fonts].h = h; 215 | font_datas.fonts[font_datas.number_of_fonts].bh = h+4; 216 | font_datas.fonts[font_datas.number_of_fonts].color_format = TINY3D_TEX_FORMAT_A4R4G4B4; 217 | font_datas.fonts[font_datas.number_of_fonts].first_char = first_char; 218 | font_datas.fonts[font_datas.number_of_fonts].last_char = last_char; 219 | font_datas.align = FONT_ALIGN_LEFT; 220 | 221 | font_datas.color = 0xffffffff; 222 | font_datas.bkcolor = 0x0; 223 | 224 | font_datas.sx = w; 225 | font_datas.sy = h; 226 | 227 | font_datas.Z = 0.0f; 228 | 229 | for(n = 0; n < 256; n++) { 230 | font_datas.fonts[font_datas.number_of_fonts].fw[n] = 0; 231 | font_datas.fonts[font_datas.number_of_fonts].fy[n] = 0; 232 | } 233 | 234 | 235 | for(n = first_char; n <= last_char; n++) { 236 | 237 | short hh = h; 238 | 239 | font = letter_bitmap; 240 | 241 | font_datas.fonts[font_datas.number_of_fonts].fw[n] = (short) w; 242 | 243 | ttf_callback((uint8_t) (n & 255), letter_bitmap, &font_datas.fonts[font_datas.number_of_fonts].fw[n], &hh, &font_datas.fonts[font_datas.number_of_fonts].fy[n]); 244 | 245 | // letter background correction 246 | if((hh + font_datas.fonts[font_datas.number_of_fonts].fy[n]) > font_datas.fonts[font_datas.number_of_fonts].bh) 247 | font_datas.fonts[font_datas.number_of_fonts].bh = hh + font_datas.fonts[font_datas.number_of_fonts].fy[n]; 248 | 249 | texture = (uint8_t *) ((((long) texture) + 15) & ~15); 250 | 251 | if(n == first_char) font_datas.fonts[font_datas.number_of_fonts].rsx_text_offset = tiny3d_TextureOffset(texture); 252 | 253 | if(n == first_char+1) font_datas.fonts[font_datas.number_of_fonts].rsx_bytes_per_char = tiny3d_TextureOffset(texture) 254 | - font_datas.fonts[font_datas.number_of_fonts].rsx_text_offset; 255 | 256 | for(a = 0; a < h; a++) { 257 | for(b = 0; b < w; b++) { 258 | 259 | i = font[(b * bits_per_pixel)/8]; 260 | 261 | i >>= (b & (7/bits_per_pixel)) * bits_per_pixel; 262 | 263 | i = (i & ((1 << bits_per_pixel)-1)) * 255 / ((1 << bits_per_pixel)-1); 264 | 265 | if(i) {//TINY3D_TEX_FORMAT_A4R4G4B4 266 | i>>=4; 267 | *((u16 *) texture) = (i<<12) | 0xfff; 268 | } else { 269 | 270 | texture[0] = texture[1] = 0x0; //texture[2] = 0x0; 271 | //texture[3] = 0x0; // alpha 272 | } 273 | texture+=2; 274 | 275 | } 276 | 277 | font += (w * bits_per_pixel) / 8; 278 | 279 | } 280 | 281 | } 282 | 283 | texture = (uint8_t *) ((((long) texture) + 15) & ~15); 284 | 285 | font_datas.number_of_fonts++; 286 | 287 | return texture; 288 | } 289 | */ 290 | 291 | void SetCurrentFont(int nfont) 292 | { 293 | if(nfont < 0 || nfont >= font_datas.number_of_fonts) nfont = 0; 294 | 295 | font_datas.current_font = nfont; 296 | } 297 | 298 | void SetFontSize(int sx, int sy) 299 | { 300 | font_datas.sx = sx; 301 | font_datas.sy = sy; 302 | } 303 | 304 | void SetFontColor(uint32_t color, uint32_t bkcolor) 305 | { 306 | font_datas.color = color; 307 | font_datas.bkcolor = bkcolor; 308 | } 309 | 310 | void SetFontAlign(int mode) 311 | { 312 | font_datas.align = mode; 313 | font_datas.autonewline = 0; 314 | } 315 | 316 | void SetFontAutoNewLine(int width) 317 | { 318 | font_datas.autonewline = width; 319 | font_datas.align = FONT_ALIGN_LEFT; 320 | } 321 | 322 | void SetFontZ(float z) 323 | { 324 | font_datas.Z = z; 325 | } 326 | 327 | float GetFontX() 328 | { 329 | return font_datas.X; 330 | } 331 | 332 | float GetFontY() 333 | { 334 | return font_datas.Y; 335 | } 336 | 337 | void SetMonoSpace(int space) 338 | { 339 | font_datas.mono = space; 340 | } 341 | 342 | void SetExtraSpace(int space) 343 | { 344 | font_datas.extra = space; 345 | } 346 | 347 | void RegisterSpecialCharacter(char value, short fy, float scale, pkgi_texture image) 348 | { 349 | // Verify special character 350 | if (value == 0) 351 | return; 352 | if (image->texture == NULL) 353 | return; 354 | if (image->width == 0 || image->height == 0) 355 | return; 356 | 357 | // Verify value is not in use 358 | if (GetSpecialCharFromValue(value)) 359 | return; 360 | 361 | // Verify room in array 362 | if (special_char_index < MAX_SPECIAL_CHARS) 363 | { 364 | special_chars[special_char_index].value = value; 365 | special_chars[special_char_index].fy = fy; 366 | special_chars[special_char_index].scale = scale; 367 | special_chars[special_char_index].image = image; 368 | 369 | special_char_index++; 370 | } 371 | } 372 | 373 | int WidthFromStr(const char * str) 374 | { 375 | return width_ttf_string(str, font_datas.sx, font_datas.sy); 376 | } 377 | 378 | int WidthFromStrMono(uint8_t * str) 379 | { 380 | int w = 0; 381 | 382 | while(*str) { 383 | w += (font_datas.sx * font_datas.mono) / font_datas.fonts[font_datas.current_font].w + font_datas.extra; 384 | str++; 385 | } 386 | 387 | return w; 388 | } 389 | 390 | int DrawCharSpecial(float x, float y, float z, const special_char* schr, uint8_t draw) 391 | { 392 | float dx = (float)font_datas.sx / (float)schr->image->width; 393 | float dy = (float)font_datas.sy / (float)schr->image->height; 394 | float min = (dx > dy ? dy : dx); 395 | 396 | dx = (min * schr->scale * schr->image->width); 397 | dy = (min * schr->scale * schr->image->height); 398 | 399 | if (!draw) 400 | return (int)dx; 401 | 402 | if (schr->fy) 403 | y += (float)schr->fy; 404 | else 405 | y += ((float)font_datas.sy - dy)/2; 406 | 407 | // Load sprite texture 408 | DrawTexture(schr->image, x, y, z, dx, dy, 0xFFFFFF00 | RGBA_A(font_datas.color)); 409 | 410 | return (int)dx; 411 | } 412 | 413 | static void orbis2dDrawChar(float x, float y, const uint8_t* bitmap, int bw, int bh, int dw, int dh, uint32_t rgba) 414 | { 415 | SDL_Texture* sdl_tex = ((SDL_Texture**) bitmap)[(rgba & 0xFFFFFF00) ? 1 : 0]; 416 | SDL_Rect dest = { 417 | .x = x, 418 | .y = y, 419 | .w = dw, 420 | .h = dh, 421 | }; 422 | 423 | SDL_SetTextureAlphaMod(sdl_tex, RGBA_A(rgba)); 424 | SDL_RenderCopy(renderer, sdl_tex, NULL, &dest); 425 | } 426 | 427 | void DrawCharMono(float x, float y, float z, uint8_t chr) 428 | { 429 | float dx = font_datas.sx, dy = font_datas.sy; 430 | float dx2 = (dx * font_datas.mono) / font_datas.fonts[font_datas.current_font].w; 431 | float dy2 = (float)(dy * font_datas.fonts[font_datas.current_font].bh) / (float)font_datas.fonts[font_datas.current_font].h; 432 | 433 | if (font_datas.number_of_fonts <= 0) return; 434 | 435 | if (chr < font_datas.fonts[font_datas.current_font].first_char) return; 436 | 437 | if (font_datas.bkcolor) { 438 | SDL_FRect rect = { 439 | .x = x, 440 | .y = y, 441 | .w = dx2, 442 | .h = dy2, 443 | }; 444 | 445 | SDL_SetRenderDrawColor(renderer, RGBA_R(font_datas.bkcolor), RGBA_G(font_datas.bkcolor), RGBA_B(font_datas.bkcolor), RGBA_A(font_datas.bkcolor)); 446 | SDL_RenderFillRectF(renderer, &rect); 447 | } 448 | 449 | y += (float)(font_datas.fonts[font_datas.current_font].fy[chr] * font_datas.sy) / (float)(font_datas.fonts[font_datas.current_font].h); 450 | 451 | if (chr > font_datas.fonts[font_datas.current_font].last_char) return; 452 | 453 | // Load sprite texture 454 | orbis2dDrawChar(x, y, font_datas.fonts[font_datas.current_font].rsx_text_offset + font_datas.fonts[font_datas.current_font].rsx_bytes_per_char 455 | * (chr - font_datas.fonts[font_datas.current_font].first_char), font_datas.fonts[font_datas.current_font].w, 456 | font_datas.fonts[font_datas.current_font].h, dx, dy, font_datas.color); 457 | } 458 | 459 | void DrawChar(float x, float y, float z, uint8_t chr) 460 | { 461 | special_char* schr = GetSpecialCharFromValue(chr); 462 | if (schr) 463 | { 464 | DrawCharSpecial(x, y, z, schr, 1); 465 | return; 466 | } 467 | 468 | float dx = font_datas.sx, dy = font_datas.sy; 469 | float dx2 = (dx * font_datas.fonts[font_datas.current_font].fw[chr]) / font_datas.fonts[font_datas.current_font].w; 470 | float dy2 = (float)(dy * font_datas.fonts[font_datas.current_font].bh) / (float)font_datas.fonts[font_datas.current_font].h; 471 | 472 | if (font_datas.number_of_fonts <= 0) return; 473 | 474 | if (chr < font_datas.fonts[font_datas.current_font].first_char) return; 475 | 476 | if (font_datas.bkcolor) { 477 | SDL_FRect rect = { 478 | .x = x, 479 | .y = y, 480 | .w = dx2, 481 | .h = dy2, 482 | }; 483 | 484 | SDL_SetRenderDrawColor(renderer, RGBA_R(font_datas.bkcolor), RGBA_G(font_datas.bkcolor), RGBA_B(font_datas.bkcolor), RGBA_A(font_datas.bkcolor)); 485 | SDL_RenderFillRectF(renderer, &rect); 486 | } 487 | 488 | y += (float)(font_datas.fonts[font_datas.current_font].fy[chr] * font_datas.sy) / (float)(font_datas.fonts[font_datas.current_font].h); 489 | 490 | if (chr > font_datas.fonts[font_datas.current_font].last_char) return; 491 | 492 | // Load sprite texture 493 | orbis2dDrawChar(x, y, font_datas.fonts[font_datas.current_font].rsx_text_offset + font_datas.fonts[font_datas.current_font].rsx_bytes_per_char 494 | * (chr - font_datas.fonts[font_datas.current_font].first_char), font_datas.fonts[font_datas.current_font].w, 495 | font_datas.fonts[font_datas.current_font].h, dx, dy, font_datas.color); 496 | } 497 | 498 | static int i_must_break_line(const char *str, float x) 499 | { 500 | int xx =0; 501 | int dx = (font_datas.sx+font_datas.extra); 502 | 503 | while(*str) { 504 | if(((uint8_t)*str) <= 32) break; 505 | xx += dx * font_datas.fonts[font_datas.current_font].fw[((uint8_t)*str)] / font_datas.fonts[font_datas.current_font].w; 506 | str++; 507 | } 508 | 509 | if(*str && (x+xx) >= font_datas.autonewline) return 1; 510 | 511 | return 0; 512 | } 513 | 514 | float DrawStringMono(float x, float y, const char *str) 515 | { 516 | float initX = x; 517 | int dx = font_datas.sx; 518 | font_datas.mono = font_datas.fonts[font_datas.current_font].w; 519 | 520 | switch (font_datas.align) 521 | { 522 | case FONT_ALIGN_SCREEN_CENTER: 523 | x= (PKGI_SCREEN_WIDTH - WidthFromStrMono((uint8_t *) str)) / 2; 524 | break; 525 | 526 | case FONT_ALIGN_RIGHT: 527 | x -= WidthFromStrMono((uint8_t *) str); 528 | break; 529 | 530 | case FONT_ALIGN_CENTER: 531 | x -= WidthFromStrMono((uint8_t *) str)/2; 532 | break; 533 | 534 | default: 535 | break; 536 | } 537 | 538 | while (*str) { 539 | 540 | if(*str == '\n') { 541 | x = initX; 542 | y += font_datas.sy * font_datas.fonts[font_datas.current_font].bh / font_datas.fonts[font_datas.current_font].h; 543 | str++; 544 | continue; 545 | } else { 546 | if(font_datas.autonewline && i_must_break_line(str, x)) { 547 | x = initX; 548 | y += font_datas.sy * font_datas.fonts[font_datas.current_font].bh / font_datas.fonts[font_datas.current_font].h; 549 | } 550 | } 551 | 552 | DrawChar(x, y, font_datas.Z, (uint8_t) *str); 553 | x += (dx * font_datas.mono) / font_datas.fonts[font_datas.current_font].w + font_datas.extra; 554 | str++; 555 | } 556 | 557 | font_datas.X = x; font_datas.Y = y; 558 | 559 | return x; 560 | } 561 | 562 | int skip_icon(int x, int y, char c) 563 | { 564 | special_char* schr = GetSpecialCharFromValue(c); 565 | if (schr) 566 | return DrawCharSpecial(x, y, font_datas.Z, schr, 0); 567 | 568 | else return 0; 569 | } 570 | 571 | int draw_icon(int x, int y, char c) 572 | { 573 | special_char* schr = GetSpecialCharFromValue(c); 574 | if (schr) 575 | return DrawCharSpecial(x, y, font_datas.Z, schr, 1); 576 | 577 | else return 0; 578 | } 579 | 580 | float DrawString(float x, float y, const char *str) 581 | { 582 | switch (font_datas.align) 583 | { 584 | case FONT_ALIGN_SCREEN_CENTER: 585 | x= (PKGI_SCREEN_WIDTH - WidthFromStr(str)) / 2; 586 | break; 587 | 588 | case FONT_ALIGN_RIGHT: 589 | x -= WidthFromStr(str); 590 | break; 591 | 592 | case FONT_ALIGN_CENTER: 593 | x -= WidthFromStr(str)/2; 594 | break; 595 | 596 | default: 597 | break; 598 | } 599 | 600 | display_ttf_string((int)x +1, (int)y +1, str, 0x00000000 | (font_datas.color & 0x000000ff), 0, font_datas.sx, font_datas.sy+4, &skip_icon); 601 | 602 | return display_ttf_string((int)x, (int)y, str, font_datas.color, font_datas.bkcolor, font_datas.sx, font_datas.sy+4, &draw_icon); 603 | } 604 | 605 | float DrawFormatString(float x, float y, char *format, ...) 606 | { 607 | char buff[4096]; 608 | va_list opt; 609 | 610 | va_start(opt, format); 611 | vsprintf((void *) buff, format, opt); 612 | va_end(opt); 613 | 614 | return DrawString(x, y, buff); 615 | } 616 | 617 | float DrawFormatStringMono(float x, float y, char *format, ...) 618 | { 619 | char buff[4096]; 620 | char *str = buff; 621 | va_list opt; 622 | 623 | va_start(opt, format); 624 | vsprintf((void *) buff, format, opt); 625 | va_end(opt); 626 | 627 | while (*str) { 628 | DrawCharMono(x, y, font_datas.Z, (uint8_t) *str); 629 | x += font_datas.sx; 630 | str++; 631 | } 632 | 633 | return(x); 634 | } 635 | -------------------------------------------------------------------------------- /source/loadpng.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pkgi.h" 8 | //#include "menu.h" 9 | 10 | #define PNG_SIGSIZE (8) 11 | 12 | typedef struct rawImage 13 | { 14 | uint32_t *datap; 15 | unsigned short width; 16 | unsigned short height; 17 | } rawImage_t __attribute__ ((aligned (16))); 18 | 19 | 20 | extern SDL_Renderer* renderer; 21 | 22 | static rawImage_t * imgCreateEmptyTexture(unsigned int w, unsigned int h) 23 | { 24 | rawImage_t *img=NULL; 25 | img=malloc(sizeof(rawImage_t)); 26 | if(img!=NULL) 27 | { 28 | img->datap=malloc(w*h*4); 29 | if(img->datap==NULL) 30 | { 31 | free(img); 32 | return NULL; 33 | } 34 | img->width=w; 35 | img->height=h; 36 | } 37 | return img; 38 | } 39 | 40 | static void imgReadPngFromBuffer(png_structp png_ptr, png_bytep data, png_size_t length) 41 | { 42 | png_voidp *address = png_get_io_ptr(png_ptr); 43 | memcpy(data, (void *)*address, length); 44 | *address += length; 45 | } 46 | 47 | static rawImage_t *imgLoadPngGeneric(const void *io_ptr, png_rw_ptr read_data_fn) 48 | { 49 | png_structp png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); 50 | if (png_ptr==NULL) 51 | goto error_create_read; 52 | 53 | png_infop info_ptr=png_create_info_struct(png_ptr); 54 | if (info_ptr==NULL) 55 | goto error_create_info; 56 | 57 | png_bytep *row_ptrs=NULL; 58 | 59 | if (setjmp(png_jmpbuf(png_ptr))) 60 | { 61 | png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); 62 | if (row_ptrs!=NULL) 63 | free(row_ptrs); 64 | 65 | return NULL; 66 | } 67 | 68 | png_set_read_fn(png_ptr,(png_voidp)io_ptr,read_data_fn); 69 | png_set_sig_bytes(png_ptr,PNG_SIGSIZE); 70 | png_read_info(png_ptr,info_ptr); 71 | 72 | unsigned int width, height; 73 | int bit_depth, color_type; 74 | 75 | png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL); 76 | 77 | if ((color_type==PNG_COLOR_TYPE_PALETTE && bit_depth<=8) 78 | || (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) 79 | || png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS) 80 | || (bit_depth==16)) 81 | { 82 | png_set_expand(png_ptr); 83 | } 84 | 85 | if (bit_depth == 16) 86 | png_set_scale_16(png_ptr); 87 | 88 | if (bit_depth==8 && color_type==PNG_COLOR_TYPE_RGB) 89 | png_set_filler(png_ptr,0xFF,PNG_FILLER_AFTER); 90 | 91 | if (color_type==PNG_COLOR_TYPE_GRAY || 92 | color_type==PNG_COLOR_TYPE_GRAY_ALPHA) 93 | png_set_gray_to_rgb(png_ptr); 94 | 95 | if (color_type==PNG_COLOR_TYPE_PALETTE) { 96 | png_set_palette_to_rgb(png_ptr); 97 | png_set_filler(png_ptr,0xFF,PNG_FILLER_AFTER); 98 | } 99 | 100 | if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth < 8) 101 | png_set_expand_gray_1_2_4_to_8(png_ptr); 102 | 103 | if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) 104 | png_set_tRNS_to_alpha(png_ptr); 105 | 106 | if (bit_depth<8) 107 | png_set_packing(png_ptr); 108 | 109 | png_read_update_info(png_ptr, info_ptr); 110 | 111 | row_ptrs = (png_bytep *)malloc(sizeof(png_bytep)*height); 112 | if (!row_ptrs) 113 | goto error_alloc_rows; 114 | 115 | rawImage_t *texture = imgCreateEmptyTexture(width,height); 116 | if (!texture) 117 | goto error_create_tex; 118 | 119 | for (int i=0; idatap + i*width); 121 | 122 | png_read_image(png_ptr, row_ptrs); 123 | 124 | png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); 125 | free(row_ptrs); 126 | 127 | return texture; 128 | 129 | error_create_tex: 130 | free(row_ptrs); 131 | error_alloc_rows: 132 | png_destroy_info_struct(png_ptr,&info_ptr); 133 | error_create_info: 134 | png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); 135 | error_create_read: 136 | return NULL; 137 | } 138 | 139 | static rawImage_t *imgLoadPngFromBuffer(const void *buffer) 140 | { 141 | if(png_sig_cmp((png_byte *)buffer, 0, PNG_SIGSIZE) != 0) 142 | return NULL; 143 | 144 | uint64_t buffer_address=(uint32_t)buffer+PNG_SIGSIZE; 145 | 146 | return imgLoadPngGeneric((void *)&buffer_address, imgReadPngFromBuffer); 147 | } 148 | 149 | static rawImage_t *imgLoadPngFromFile(const char *path) 150 | { 151 | uint8_t *buf; 152 | size_t size; 153 | rawImage_t *img; 154 | 155 | size = pkgi_get_size(path); 156 | if (size < 0) 157 | return NULL; 158 | 159 | buf = malloc(size); 160 | if (!buf) 161 | return NULL; 162 | 163 | if(pkgi_load(path, buf, size) < 0) 164 | return NULL; 165 | 166 | img = imgLoadPngFromBuffer(buf); 167 | free(buf); 168 | 169 | return img; 170 | } 171 | 172 | pkgi_texture loadPngTexture(const char* path, const void* buffer) 173 | { 174 | rawImage_t* raw; 175 | pkgi_texture tex; 176 | 177 | tex = malloc(sizeof(struct pkgi_texture_s)); 178 | if (!tex) 179 | return NULL; 180 | 181 | raw = path ? imgLoadPngFromFile(path) : imgLoadPngFromBuffer(buffer); 182 | if (!raw) 183 | { 184 | free(tex); 185 | return NULL; 186 | } 187 | 188 | SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(raw->datap, raw->width, raw->height, 32, 4 * raw->width, 189 | 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); 190 | 191 | tex->texture = SDL_CreateTextureFromSurface(renderer, surface); 192 | tex->width = raw->width; 193 | tex->height = raw->height; 194 | 195 | SDL_FreeSurface(surface); 196 | free(raw->datap); 197 | free(raw); 198 | 199 | return tex; 200 | } 201 | -------------------------------------------------------------------------------- /source/pkgi_aes.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_aes.h" 2 | #include "pkgi_utils.h" 3 | 4 | #include 5 | 6 | 7 | void aes128_init(mbedtls_aes_context* ctx, const uint8_t* key) 8 | { 9 | mbedtls_aes_init(ctx); 10 | mbedtls_aes_setkey_enc(ctx, key, 128); 11 | } 12 | 13 | void aes128_init_dec(mbedtls_aes_context* ctx, const uint8_t* key) 14 | { 15 | mbedtls_aes_init(ctx); 16 | mbedtls_aes_setkey_dec(ctx, key, 128); 17 | /* 18 | mbedtls_aes_context enc; 19 | aes128_init(&enc, key); 20 | */ 21 | } 22 | 23 | void aes128_ecb_encrypt(mbedtls_aes_context* ctx, const uint8_t* input, uint8_t* output) 24 | { 25 | mbedtls_aes_crypt_ecb(ctx, MBEDTLS_AES_ENCRYPT, input, output); 26 | } 27 | 28 | void aes128_ecb_decrypt(mbedtls_aes_context* ctx, const uint8_t* input, uint8_t* output) 29 | { 30 | mbedtls_aes_crypt_ecb(ctx, MBEDTLS_AES_DECRYPT, input, output); 31 | } 32 | 33 | static void ctr_add(uint8_t* counter, uint64_t n) 34 | { 35 | for (int i=15; i>=0; i--) 36 | { 37 | n = n + counter[i]; 38 | counter[i] = (uint8_t)n; 39 | n >>= 8; 40 | } 41 | } 42 | 43 | void aes128_ctr_xor(mbedtls_aes_context* context, const uint8_t* iv, uint64_t block, uint8_t* buffer, size_t size) 44 | { 45 | uint8_t tmp[16]; 46 | uint8_t counter[16]; 47 | for (uint32_t i=0; i<16; i++) 48 | { 49 | counter[i] = iv[i]; 50 | } 51 | ctr_add(counter, block); 52 | 53 | while (size >= 16) 54 | { 55 | aes128_ecb_encrypt(context, counter, tmp); 56 | for (uint32_t i=0; i<16; i++) 57 | { 58 | *buffer++ ^= tmp[i]; 59 | } 60 | ctr_add(counter, 1); 61 | size -= 16; 62 | } 63 | 64 | if (size != 0) 65 | { 66 | aes128_ecb_encrypt(context, counter, tmp); 67 | for (size_t i=0; ikey, key); 101 | memset(ctx->last, 0, 16); 102 | ctx->size = 0; 103 | } 104 | 105 | static void aes128_cmac_update(aes128_cmac_ctx* ctx, const uint8_t* buffer, uint32_t size) 106 | { 107 | if (ctx->size + size <= 16) 108 | { 109 | memcpy(ctx->block + ctx->size, buffer, size); 110 | ctx->size += size; 111 | return; 112 | } 113 | 114 | if (ctx->size != 0) 115 | { 116 | uint32_t avail = 16 - ctx->size; 117 | memcpy(ctx->block + ctx->size, buffer, avail < size ? avail : size); 118 | buffer += avail; 119 | size -= avail; 120 | 121 | aes128_cmac_process(&ctx->key, ctx->last, ctx->block, 16); 122 | } 123 | 124 | if (size >= 16) 125 | { 126 | uint32_t full = (size - 1) & ~15; 127 | aes128_cmac_process(&ctx->key, ctx->last, buffer, full); 128 | buffer += full; 129 | size -= full; 130 | } 131 | 132 | memcpy(ctx->block, buffer, size); 133 | ctx->size = size; 134 | } 135 | 136 | static void cmac_gfmul(uint8_t* block) 137 | { 138 | uint8_t carry = 0; 139 | for (int i = 15; i >= 0; i--) 140 | { 141 | uint8_t x = block[i]; 142 | block[i] = (block[i] << 1) | (carry >> 7); 143 | carry = x; 144 | } 145 | 146 | block[15] ^= (carry & 0x80 ? 0x87 : 0); 147 | } 148 | 149 | static void aes128_cmac_done(aes128_cmac_ctx* ctx, uint8_t* mac) 150 | { 151 | uint8_t zero[16] = { 0 }; 152 | aes128_ecb_encrypt(&ctx->key, zero, mac); 153 | 154 | cmac_gfmul(mac); 155 | 156 | if (ctx->size != 16) 157 | { 158 | cmac_gfmul(mac); 159 | 160 | ctx->block[ctx->size] = 0x80; 161 | memset(ctx->block + ctx->size + 1, 0, 16 - (ctx->size + 1)); 162 | } 163 | 164 | for (size_t i = 0; i < 16; i++) 165 | { 166 | mac[i] ^= ctx->block[i]; 167 | } 168 | 169 | aes128_cmac_process(&ctx->key, mac, ctx->last, 16); 170 | } 171 | 172 | void aes128_cmac(const uint8_t* key, const uint8_t* buffer, uint32_t size, uint8_t* mac) 173 | { 174 | aes128_cmac_ctx ctx; 175 | aes128_cmac_init(&ctx, key); 176 | aes128_cmac_update(&ctx, buffer, size); 177 | aes128_cmac_done(&ctx, mac); 178 | } 179 | 180 | void aes128_psp_decrypt(mbedtls_aes_context* ctx, const uint8_t* iv, uint32_t index, uint8_t* buffer, uint32_t size) 181 | { 182 | if(size % 16 != 0) 183 | return; 184 | 185 | uint8_t GCC_ALIGN(16) prev[16]; 186 | uint8_t GCC_ALIGN(16) block[16]; 187 | 188 | if (index == 0) 189 | { 190 | memset(prev, 0, 16); 191 | } 192 | else 193 | { 194 | memcpy(prev, iv, 12); 195 | set32le(prev + 12, index); 196 | } 197 | 198 | memcpy(block, iv, 16); 199 | set32le(block + 12, index); 200 | 201 | for (uint32_t i = 0; i < size; i += 16) 202 | { 203 | set32le(block + 12, get32le(block + 12) + 1); 204 | 205 | uint8_t out[16]; 206 | aes128_ecb_decrypt(ctx, block, out); 207 | 208 | for (size_t k = 0; k < 16; k++) 209 | { 210 | *buffer++ ^= prev[k] ^ out[k]; 211 | } 212 | memcpy(prev, block, 16); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /source/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->version_check = 1; 116 | config->install_mode_iso = 0; 117 | config->keep_pkg = 0; 118 | config->content = 0; 119 | config->allow_refresh = 0; 120 | config->storage = 0; 121 | pkgi_strncpy(config->language, 3, pkgi_get_user_language()); 122 | 123 | char data[4096]; 124 | char path[256]; 125 | pkgi_snprintf(path, sizeof(path), "%s/config.txt", pkgi_get_config_folder()); 126 | LOG("config location: %s", path); 127 | 128 | int loaded = pkgi_load(path, data, sizeof(data) - 1); 129 | if (loaded > 0) 130 | { 131 | data[loaded] = '\n'; 132 | 133 | LOG("config.txt loaded, parsing"); 134 | char* text = data; 135 | char* end = data + loaded + 1; 136 | 137 | if (loaded > 3 && (uint8_t)text[0] == 0xef && (uint8_t)text[1] == 0xbb && (uint8_t)text[2] == 0xbf) 138 | { 139 | text += 3; 140 | } 141 | 142 | while (text < end) 143 | { 144 | char* key = text; 145 | 146 | text = skipnonws(text, end); 147 | if (text == end) break; 148 | 149 | *text++ = 0; 150 | 151 | text = skipws(text, end); 152 | if (text == end) break; 153 | 154 | char* value = text; 155 | 156 | text = skipnonws(text, end); 157 | if (text == end) break; 158 | 159 | *text++ = 0; 160 | 161 | text = skipws(text, end); 162 | 163 | if (pkgi_stricontains(key, "url")) 164 | { 165 | for (int i = 0; i < MAX_CONTENT_TYPES; i++) 166 | if (pkgi_stricmp(key+3, pkgi_content_tag(i)) == 0) 167 | { 168 | pkgi_strncpy(refresh_url + refresh_len*i, refresh_len, value); 169 | config->allow_refresh = 1; 170 | } 171 | } 172 | else if (pkgi_stricmp(key, "sort") == 0) 173 | { 174 | config->sort = parse_sort(value, SortByName); 175 | } 176 | else if (pkgi_stricmp(key, "order") == 0) 177 | { 178 | config->order = parse_order(value, SortAscending); 179 | } 180 | else if (pkgi_stricmp(key, "filter") == 0) 181 | { 182 | config->filter = parse_filter(value, DbFilterAll); 183 | } 184 | else if (pkgi_stricmp(key, "no_version_check") == 0) 185 | { 186 | config->version_check = 0; 187 | } 188 | else if (pkgi_stricmp(key, "install_mode_iso") == 0) 189 | { 190 | config->install_mode_iso = (uint8_t)pkgi_strtoll(value); 191 | } 192 | else if (pkgi_stricmp(key, "keep_pkg") == 0) 193 | { 194 | config->keep_pkg = 1; 195 | } 196 | else if (pkgi_stricmp(key, "content") == 0) 197 | { 198 | config->content = (uint8_t)pkgi_strtoll(value); 199 | } 200 | else if (pkgi_stricmp(key, "language") == 0) 201 | { 202 | pkgi_strncpy(config->language, 2, value); 203 | } 204 | else if (pkgi_stricmp(key, "storage") == 0) 205 | { 206 | config->storage = (pkgi_stricmp("ms0", value) == 0); 207 | if (config->storage) pkgi_mkdirs("ms0:" PKGI_RAP_FOLDER); 208 | } 209 | } 210 | } 211 | else 212 | { 213 | LOG("config.txt cannot be loaded, using default values"); 214 | } 215 | if (config->content == 0) 216 | { 217 | config->filter |= DbFilterAllContent; 218 | } 219 | else 220 | { 221 | config->filter |= (128 << config->content); 222 | } 223 | } 224 | 225 | const char* pkgi_content_tag(ContentType content) 226 | { 227 | switch (content) 228 | { 229 | case ContentGame: return "_games"; 230 | case ContentDLC: return "_dlcs"; 231 | case ContentTheme: return "_themes"; 232 | case ContentPSX: return "_psx"; 233 | case ContentDemo: return "_demos"; 234 | case ContentUpdate: return "_updates"; 235 | case ContentEmulator: return "_emulators"; 236 | case ContentApp: return "_apps"; 237 | default: return ""; 238 | } 239 | } 240 | 241 | static const char* sort_str(DbSort sort) 242 | { 243 | switch (sort) 244 | { 245 | case SortByTitle: return "title"; 246 | case SortByRegion: return "region"; 247 | case SortByName: return "name"; 248 | case SortBySize: return "size"; 249 | default: return ""; 250 | } 251 | } 252 | 253 | static const char* order_str(DbSortOrder order) 254 | { 255 | switch (order) 256 | { 257 | case SortAscending: return "asc"; 258 | case SortDescending: return "desc"; 259 | default: return ""; 260 | } 261 | } 262 | 263 | void pkgi_save_config(const Config* config, const char* update_url, uint32_t update_len) 264 | { 265 | char data[4096]; 266 | int len = 0; 267 | 268 | for (int i = 0; i < MAX_CONTENT_TYPES; i++) 269 | { 270 | const char* tmp_url = update_url + update_len*i; 271 | if (update_url && tmp_url[0] != 0) 272 | { 273 | len += pkgi_snprintf(data + len, sizeof(data) - len, "url%s %s\n", pkgi_content_tag(i), tmp_url); 274 | } 275 | } 276 | len += pkgi_snprintf(data + len, sizeof(data) - len, "language %s\n", config->language); 277 | len += pkgi_snprintf(data + len, sizeof(data) - len, "content %d\n", config->content); 278 | len += pkgi_snprintf(data + len, sizeof(data) - len, "sort %s\n", sort_str(config->sort)); 279 | len += pkgi_snprintf(data + len, sizeof(data) - len, "order %s\n", order_str(config->order)); 280 | len += pkgi_snprintf(data + len, sizeof(data) - len, "filter "); 281 | const char* sep = ""; 282 | if (config->filter & DbFilterRegionASA) 283 | { 284 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sASA", sep); 285 | sep = ","; 286 | } 287 | if (config->filter & DbFilterRegionEUR) 288 | { 289 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sEUR", sep); 290 | sep = ","; 291 | } 292 | if (config->filter & DbFilterRegionJPN) 293 | { 294 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sJPN", sep); 295 | sep = ","; 296 | } 297 | if (config->filter & DbFilterRegionUSA) 298 | { 299 | len += pkgi_snprintf(data + len, sizeof(data) - len, "%sUSA", sep); 300 | sep = ","; 301 | } 302 | len += pkgi_snprintf(data + len, sizeof(data) - len, "\n"); 303 | 304 | if (!config->version_check) 305 | { 306 | len += pkgi_snprintf(data + len, sizeof(data) - len, "no_version_check 1\n"); 307 | } 308 | 309 | if (config->install_mode_iso) 310 | { 311 | len += pkgi_snprintf(data + len, sizeof(data) - len, "install_mode_iso %d\n", config->install_mode_iso); 312 | } 313 | 314 | if (config->keep_pkg) 315 | { 316 | len += pkgi_snprintf(data + len, sizeof(data) - len, "keep_pkg 1\n"); 317 | } 318 | 319 | if (config->storage) 320 | { 321 | len += pkgi_snprintf(data + len, sizeof(data) - len, "storage ms0\n"); 322 | } 323 | 324 | char path[256]; 325 | pkgi_snprintf(path, sizeof(path), "%s/config.txt", pkgi_get_config_folder()); 326 | 327 | if (pkgi_save(path, data, len)) 328 | { 329 | LOG("saved config.txt"); 330 | } 331 | else 332 | { 333 | LOG("cannot save config.txt"); 334 | } 335 | } -------------------------------------------------------------------------------- /source/pkgi_dialog.c: -------------------------------------------------------------------------------- 1 | #include "pkgi_dialog.h" 2 | #include "pkgi_style.h" 3 | #include "pkgi_utils.h" 4 | #include "pkgi.h" 5 | 6 | #include 7 | #include 8 | 9 | typedef enum { 10 | DialogNone, 11 | DialogMessage, 12 | DialogError, 13 | DialogProgress, 14 | DialogOkCancel, 15 | DialogDetails 16 | } DialogType; 17 | 18 | static DialogType dialog_type; 19 | static char dialog_title[256]; 20 | static char dialog_text[256]; 21 | static char dialog_extra[256]; 22 | static char dialog_eta[256]; 23 | static float dialog_progress; 24 | static int dialog_allow_close; 25 | static int dialog_cancelled; 26 | static pkgi_texture pkg_icon = NULL; 27 | static DbItem* db_item = NULL; 28 | static pkgi_dialog_callback_t dialog_callback = NULL; 29 | 30 | static int32_t dialog_width; 31 | static int32_t dialog_height; 32 | static int32_t dialog_delta; 33 | 34 | volatile int msg_dialog_action = 0; 35 | 36 | 37 | void pkgi_dialog_init(void) 38 | { 39 | dialog_type = DialogNone; 40 | dialog_allow_close = 1; 41 | } 42 | 43 | int pkgi_dialog_is_open(void) 44 | { 45 | return dialog_type != DialogNone; 46 | } 47 | 48 | int pkgi_dialog_is_cancelled(void) 49 | { 50 | return dialog_cancelled; 51 | } 52 | 53 | void pkgi_dialog_allow_close(int allow) 54 | { 55 | pkgi_dialog_lock(); 56 | dialog_allow_close = allow; 57 | pkgi_dialog_unlock(); 58 | } 59 | 60 | void pkgi_dialog_data_init(DialogType type, const char* title, const char* text) 61 | { 62 | pkgi_strncpy(dialog_title, sizeof(dialog_title), title); 63 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 64 | dialog_extra[0] = 0; 65 | dialog_eta[0] = 0; 66 | 67 | dialog_cancelled = 0; 68 | dialog_type = type; 69 | dialog_delta = 1; 70 | } 71 | 72 | void pkgi_dialog_details(DbItem *item, const char* content_type) 73 | { 74 | pkgi_dialog_lock(); 75 | 76 | // pkgi_snprintf(dialog_extra, sizeof(dialog_extra), PKGI_TMP_FOLDER "/%.9s.PNG", item->content + 7); 77 | // if (!pkg_icon && pkgi_get_size(dialog_extra)) 78 | // pkg_icon = pkgi_load_png_file(dialog_extra); 79 | 80 | pkgi_snprintf(dialog_extra, sizeof(dialog_extra), "ID: %s\n%s: %s - RAP(%s) SHA256(%s)", 81 | item->content, _("Content"), content_type, 82 | (item->rap ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF), 83 | (item->digest ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF)); 84 | 85 | pkgi_dialog_data_init(DialogDetails, item->name, dialog_extra); 86 | pkgi_strncpy(dialog_extra, sizeof(dialog_extra), item->description); 87 | 88 | db_item = item; 89 | pkgi_dialog_unlock(); 90 | } 91 | 92 | void pkgi_dialog_message(const char* title, const char* text) 93 | { 94 | pkgi_dialog_lock(); 95 | pkgi_dialog_data_init(DialogMessage, title, text); 96 | pkgi_dialog_unlock(); 97 | } 98 | 99 | void pkgi_dialog_ok_cancel(const char* title, const char* text, pkgi_dialog_callback_t callback) 100 | { 101 | pkgi_dialog_lock(); 102 | pkgi_dialog_data_init(DialogOkCancel, title, text); 103 | dialog_callback = callback; 104 | pkgi_dialog_unlock(); 105 | } 106 | 107 | void pkgi_dialog_error(const char* text) 108 | { 109 | pkgi_dialog_lock(); 110 | pkgi_dialog_data_init(DialogError, _("ERROR"), text); 111 | pkgi_dialog_unlock(); 112 | } 113 | 114 | void pkgi_dialog_start_progress(const char* title, const char* text, float progress) 115 | { 116 | pkgi_dialog_lock(); 117 | pkgi_dialog_data_init(DialogProgress, title, text); 118 | dialog_progress = progress; 119 | pkgi_dialog_unlock(); 120 | } 121 | 122 | void pkgi_dialog_set_progress_title(const char* title) 123 | { 124 | pkgi_dialog_lock(); 125 | pkgi_strncpy(dialog_title, sizeof(dialog_title), title); 126 | pkgi_dialog_unlock(); 127 | } 128 | 129 | void pkgi_dialog_update_progress(const char* text, const char* extra, const char* eta, float progress) 130 | { 131 | pkgi_dialog_lock(); 132 | 133 | pkgi_strncpy(dialog_text, sizeof(dialog_text), text); 134 | pkgi_strncpy(dialog_extra, sizeof(dialog_extra), extra ? extra : ""); 135 | pkgi_strncpy(dialog_eta, sizeof(dialog_eta), eta ? eta : ""); 136 | 137 | dialog_progress = (progress > 1.0f) ? 1.0f : progress; 138 | 139 | pkgi_dialog_unlock(); 140 | } 141 | 142 | void pkgi_dialog_close(void) 143 | { 144 | dialog_delta = -1; 145 | } 146 | 147 | void pkgi_do_dialog(pkgi_input* input) 148 | { 149 | pkgi_dialog_lock(); 150 | 151 | if (dialog_allow_close) 152 | { 153 | if ((dialog_type == DialogMessage || dialog_type == DialogError || dialog_type == DialogDetails) && (input->pressed & pkgi_ok_button())) 154 | { 155 | dialog_delta = -1; 156 | } 157 | else if ((dialog_type == DialogProgress || dialog_type == DialogOkCancel) && (input->pressed & pkgi_cancel_button())) 158 | { 159 | dialog_cancelled = 1; 160 | } 161 | else if (dialog_type == DialogOkCancel && (input->pressed & pkgi_ok_button())) 162 | { 163 | dialog_delta = -1; 164 | if (dialog_callback) 165 | { 166 | dialog_callback(MDIALOG_OK); 167 | dialog_callback = NULL; 168 | } 169 | } 170 | /* 171 | else if (dialog_type == DialogDetails && (input->pressed & PKGI_BUTTON_S)) 172 | { 173 | int updates = pkgi_db_load_xml_updates(db_item->content, db_item->name); 174 | if (updates < 0) 175 | { 176 | pkgi_strncpy(dialog_text, sizeof(dialog_text), _("Failed to download the update list")); 177 | dialog_type = DialogError; 178 | } 179 | else 180 | { 181 | pkgi_snprintf(dialog_text, sizeof(dialog_text), "%d %s", updates, _("update(s) loaded")); 182 | dialog_type = DialogMessage; 183 | } 184 | } 185 | */ 186 | } 187 | 188 | if (dialog_delta != 0) 189 | { 190 | dialog_width += dialog_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED / 1000); 191 | dialog_height += dialog_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED / 500); 192 | 193 | if (dialog_delta < 0 && (dialog_width <= 0 || dialog_height <= 0)) 194 | { 195 | dialog_type = DialogNone; 196 | dialog_text[0] = 0; 197 | dialog_extra[0] = 0; 198 | dialog_eta[0] = 0; 199 | 200 | dialog_width = 0; 201 | dialog_height = 0; 202 | dialog_delta = 0; 203 | 204 | if (pkg_icon) 205 | { 206 | pkgi_free_texture(pkg_icon); 207 | pkg_icon = NULL; 208 | } 209 | 210 | pkgi_dialog_unlock(); 211 | return; 212 | } 213 | else if (dialog_delta > 0) 214 | { 215 | if (dialog_width >= PKGI_DIALOG_WIDTH && dialog_height >= PKGI_DIALOG_HEIGHT) 216 | { 217 | dialog_delta = 0; 218 | } 219 | dialog_width = min32(dialog_width, PKGI_DIALOG_WIDTH); 220 | dialog_height = min32(dialog_height, PKGI_DIALOG_HEIGHT); 221 | } 222 | } 223 | 224 | DialogType local_type = dialog_type; 225 | char local_title[256]; 226 | char local_text[256]; 227 | char local_extra[256]; 228 | char local_eta[256]; 229 | float local_progress = dialog_progress; 230 | int local_allow_close = dialog_allow_close; 231 | int32_t local_width = dialog_width; 232 | int32_t local_height = dialog_height; 233 | 234 | pkgi_strncpy(local_title, sizeof(local_title), dialog_title); 235 | pkgi_strncpy(local_text, sizeof(local_text), dialog_text); 236 | pkgi_strncpy(local_extra, sizeof(local_extra), dialog_extra); 237 | pkgi_strncpy(local_eta, sizeof(local_eta), dialog_eta); 238 | 239 | pkgi_dialog_unlock(); 240 | 241 | if (local_width != 0 && local_height != 0) 242 | { 243 | pkgi_draw_fill_rect_z((PKGI_SCREEN_WIDTH - local_width) / 2, (PKGI_SCREEN_HEIGHT - local_height) / 2, PKGI_MENU_Z, local_width, local_height, PKGI_COLOR_MENU_BACKGROUND); 244 | pkgi_draw_rect_z((PKGI_SCREEN_WIDTH - local_width) / 2, (PKGI_SCREEN_HEIGHT - local_height) / 2, PKGI_MENU_Z, local_width, local_height, PKGI_COLOR_MENU_BORDER); 245 | } 246 | 247 | if (local_width != PKGI_DIALOG_WIDTH || local_height != PKGI_DIALOG_HEIGHT) 248 | { 249 | return; 250 | } 251 | 252 | int font_height = pkgi_text_height("M"); 253 | 254 | int w = PKGI_SCREEN_WIDTH - 2 * PKGI_DIALOG_HMARGIN; 255 | int h = PKGI_SCREEN_HEIGHT - 2 * PKGI_DIALOG_VMARGIN; 256 | 257 | if (local_title[0]) 258 | { 259 | uint32_t color; 260 | if (local_type == DialogError) 261 | { 262 | color = PKGI_COLOR_TEXT_ERROR; 263 | } 264 | else 265 | { 266 | color = PKGI_COLOR_TEXT_DIALOG; 267 | } 268 | 269 | int width = pkgi_text_width_ttf(local_title); 270 | if (width > w + 2 * PKGI_DIALOG_PADDING) 271 | { 272 | pkgi_clip_set(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_DIALOG_VMARGIN + font_height, w - 2 * PKGI_DIALOG_PADDING, h - 2 * PKGI_DIALOG_PADDING); 273 | pkgi_draw_text_ttf(0, 0, PKGI_DIALOG_TEXT_Z, color, local_title); 274 | pkgi_clip_remove(); 275 | } 276 | else 277 | { 278 | pkgi_draw_text_ttf((PKGI_SCREEN_WIDTH - width) / 2, PKGI_DIALOG_VMARGIN + font_height, PKGI_DIALOG_TEXT_Z, color, local_title); 279 | } 280 | } 281 | 282 | if (local_type == DialogProgress) 283 | { 284 | int extraw = pkgi_text_width(local_extra); 285 | 286 | int availw = PKGI_SCREEN_WIDTH - 2 * (PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING) - (extraw ? extraw + 10 : 10); 287 | pkgi_clip_set(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2 - font_height - PKGI_DIALOG_PROCESS_BAR_PADDING, availw, font_height + 2); 288 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2 - font_height - PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, local_text); 289 | pkgi_clip_remove(); 290 | 291 | if (local_extra[0]) 292 | { 293 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2 + PKGI_DIALOG_PROCESS_BAR_HEIGHT + PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, local_extra); 294 | } 295 | 296 | if (local_progress < 0) 297 | { 298 | uint32_t avail = w - 2 * PKGI_DIALOG_PADDING; 299 | 300 | uint32_t start = (pkgi_time_msec() / 2) % (avail + PKGI_DIALOG_PROCESS_BAR_CHUNK); 301 | uint32_t end = start < PKGI_DIALOG_PROCESS_BAR_CHUNK ? start : start + PKGI_DIALOG_PROCESS_BAR_CHUNK > avail + PKGI_DIALOG_PROCESS_BAR_CHUNK ? avail : start; 302 | start = start < PKGI_DIALOG_PROCESS_BAR_CHUNK ? 0 : start - PKGI_DIALOG_PROCESS_BAR_CHUNK; 303 | 304 | pkgi_draw_fill_rect_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2, PKGI_MENU_Z, avail, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BACKGROUND); 305 | pkgi_draw_fill_rect_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING + start, PKGI_SCREEN_HEIGHT / 2, PKGI_MENU_Z, end - start, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BAR); 306 | } 307 | else 308 | { 309 | pkgi_draw_fill_rect_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2, PKGI_MENU_Z, w - 2 * PKGI_DIALOG_PADDING, PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BACKGROUND); 310 | pkgi_draw_fill_rect_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2, PKGI_MENU_Z, (int)((w - 2 * PKGI_DIALOG_PADDING) * local_progress), PKGI_DIALOG_PROCESS_BAR_HEIGHT, PKGI_COLOR_PROGRESS_BAR); 311 | 312 | char percent[256]; 313 | pkgi_snprintf(percent, sizeof(percent), "%.0f%%", local_progress * 100.f); 314 | 315 | int percentw = pkgi_text_width(percent); 316 | pkgi_draw_text_z((PKGI_SCREEN_WIDTH - percentw) / 2, PKGI_SCREEN_HEIGHT / 2 + PKGI_DIALOG_PROCESS_BAR_HEIGHT + PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, percent); 317 | } 318 | 319 | if (local_eta[0]) 320 | { 321 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + w - (PKGI_DIALOG_PADDING + pkgi_text_width(local_eta)), PKGI_SCREEN_HEIGHT / 2 + PKGI_DIALOG_PROCESS_BAR_HEIGHT + PKGI_DIALOG_PROCESS_BAR_PADDING, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, local_eta); 322 | } 323 | 324 | if (local_allow_close) 325 | { 326 | char text[256]; 327 | pkgi_snprintf(text, sizeof(text), _("press %s to cancel"), pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_O : PKGI_UTF8_X); 328 | pkgi_draw_text_z((PKGI_SCREEN_WIDTH - pkgi_text_width(text)) / 2, PKGI_DIALOG_VMARGIN + h - 2 * font_height, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, text); 329 | } 330 | } 331 | else if (local_type == DialogDetails) 332 | { 333 | // pkgi_draw_texture_z(pkg_icon, PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING + 425, PKGI_DIALOG_VMARGIN + PKGI_DIALOG_PADDING + 25, PKGI_DIALOG_TEXT_Z, 0.5); 334 | 335 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_DIALOG_VMARGIN + PKGI_DIALOG_PADDING + font_height*2, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, local_text); 336 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_DIALOG_VMARGIN + PKGI_DIALOG_PADDING + font_height*4, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, local_extra); 337 | 338 | if (local_allow_close) 339 | { 340 | char text[256]; 341 | pkgi_snprintf(text, sizeof(text), _("press %s to close"), pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_X : PKGI_UTF8_O, PKGI_UTF8_S); 342 | pkgi_draw_text_z((PKGI_SCREEN_WIDTH - pkgi_text_width(text)) / 2, PKGI_DIALOG_VMARGIN + h - 2 * font_height, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, text); 343 | } 344 | } 345 | else 346 | { 347 | uint32_t color; 348 | if (local_type == DialogMessage || local_type == DialogOkCancel) 349 | { 350 | color = PKGI_COLOR_TEXT_DIALOG; 351 | } 352 | else // local_type == DialogError 353 | { 354 | color = PKGI_COLOR_TEXT_ERROR; 355 | } 356 | 357 | int textw = pkgi_text_width(local_text); 358 | if (textw > w + 2 * PKGI_DIALOG_PADDING) 359 | { 360 | 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); 361 | pkgi_draw_text_z(PKGI_DIALOG_HMARGIN + PKGI_DIALOG_PADDING, PKGI_SCREEN_HEIGHT / 2 - font_height / 2, PKGI_DIALOG_TEXT_Z, color, local_text); 362 | pkgi_clip_remove(); 363 | } 364 | else 365 | { 366 | pkgi_draw_text_z((PKGI_SCREEN_WIDTH - textw) / 2, PKGI_SCREEN_HEIGHT / 2 - font_height / 2, PKGI_DIALOG_TEXT_Z, color, local_text); 367 | } 368 | 369 | if (local_allow_close) 370 | { 371 | char text[256]; 372 | if (local_type == DialogOkCancel) 373 | pkgi_snprintf(text, sizeof(text), "%s %s %s %s", pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_X : PKGI_UTF8_O, _("Enter"), pkgi_cancel_button() == PKGI_BUTTON_O ? PKGI_UTF8_O : PKGI_UTF8_X, _("Back")); 374 | else 375 | pkgi_snprintf(text, sizeof(text), _("press %s to close"), pkgi_ok_button() == PKGI_BUTTON_X ? PKGI_UTF8_X : PKGI_UTF8_O); 376 | 377 | pkgi_draw_text_z((PKGI_SCREEN_WIDTH - pkgi_text_width(text)) / 2, PKGI_DIALOG_VMARGIN + h - 2 * font_height, PKGI_DIALOG_TEXT_Z, PKGI_COLOR_TEXT_DIALOG, text); 378 | } 379 | } 380 | } 381 | /* 382 | void msg_dialog_event(msgButton button, void *userdata) 383 | { 384 | switch(button) { 385 | 386 | case MSG_DIALOG_BTN_YES: 387 | msg_dialog_action = 1; 388 | break; 389 | case MSG_DIALOG_BTN_NO: 390 | case MSG_DIALOG_BTN_ESCAPE: 391 | case MSG_DIALOG_BTN_NONE: 392 | msg_dialog_action = 2; 393 | break; 394 | default: 395 | break; 396 | } 397 | } 398 | 399 | int pkgi_msg_dialog(int tdialog, const char * str) 400 | { 401 | msg_dialog_action = 0; 402 | 403 | msgType mtype = MSG_DIALOG_NORMAL; 404 | mtype |= (tdialog ? (MSG_DIALOG_BTN_TYPE_YESNO | MSG_DIALOG_DEFAULT_CURSOR_NO) : MSG_DIALOG_BTN_TYPE_OK); 405 | 406 | msgDialogOpen2(mtype, str, msg_dialog_event, NULL, NULL); 407 | 408 | while(!msg_dialog_action) 409 | { 410 | pkgi_swap(); 411 | } 412 | 413 | msgDialogAbort(); 414 | pkgi_sleep(100); 415 | 416 | return (msg_dialog_action == 1); 417 | } 418 | */ -------------------------------------------------------------------------------- /source/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_sha256.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | static char root[256]; 16 | static char resume_file[256]; 17 | 18 | static pkgi_http* http; 19 | static const DbItem* db_item; 20 | static int download_resume; 21 | 22 | static uint64_t initial_offset; // where http download resumes 23 | static uint64_t download_offset; // pkg absolute offset 24 | static uint64_t download_size; // pkg total size (from http request) 25 | 26 | static mbedtls_sha256_context sha; 27 | 28 | static void* item_file; // current file handle 29 | static char item_name[256]; // current file name 30 | static char item_path[256]; // current file path 31 | 32 | // pkg header 33 | static uint64_t total_size; 34 | 35 | // UI stuff 36 | static char dialog_extra[256]; 37 | static char dialog_eta[256]; 38 | static uint32_t info_start; 39 | static uint32_t info_update; 40 | 41 | 42 | static void calculate_eta(uint32_t speed) 43 | { 44 | uint64_t seconds = (total_size - download_offset) / speed; 45 | if (seconds < 60) 46 | { 47 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "%s: %us", _("ETA"), (uint32_t)seconds); 48 | } 49 | else if (seconds < 3600) 50 | { 51 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "%s: %um %02us", _("ETA"), (uint32_t)(seconds / 60), (uint32_t)(seconds % 60)); 52 | } 53 | else 54 | { 55 | uint32_t hours = (uint32_t)(seconds / 3600); 56 | uint32_t minutes = (uint32_t)((seconds - hours * 3600) / 60); 57 | pkgi_snprintf(dialog_eta, sizeof(dialog_eta), "%s: %uh %02um", _("ETA"), hours, minutes); 58 | } 59 | } 60 | 61 | /* follow the CURLOPT_XFERINFOFUNCTION callback definition */ 62 | static int update_progress(void *p, int64_t dltotal, int64_t dlnow, int64_t ultotal, int64_t ulnow) 63 | { 64 | uint32_t info_now = pkgi_time_msec(); 65 | 66 | if (info_now >= info_update) 67 | { 68 | char text[256]; 69 | pkgi_snprintf(text, sizeof(text), "%s", item_name); 70 | 71 | if (download_resume) 72 | { 73 | // if resuming download, then there is no "download speed" 74 | dialog_extra[0] = 0; 75 | } 76 | else 77 | { 78 | // report download speed 79 | uint32_t speed = (uint32_t)(((download_offset - initial_offset) * 1000) / (info_now - info_start)); 80 | if (speed > 10 * 1000 * 1024) 81 | { 82 | pkgi_snprintf(dialog_extra, sizeof(dialog_extra), "%u %s/s", speed / 1024 / 1024, _("MB")); 83 | } 84 | else if (speed > 1000) 85 | { 86 | pkgi_snprintf(dialog_extra, sizeof(dialog_extra), "%u %s/s", speed / 1024, _("KB")); 87 | } 88 | 89 | if (speed != 0) 90 | { 91 | // report ETA 92 | calculate_eta(speed); 93 | } 94 | } 95 | 96 | float percent = total_size ? (float)((double)download_offset / total_size) : 0.f; 97 | 98 | pkgi_dialog_update_progress(text, dialog_extra, dialog_eta, percent); 99 | info_update = info_now + 500; 100 | progress_screen_refresh(); 101 | } 102 | 103 | return (pkgi_dialog_is_cancelled()); 104 | } 105 | 106 | static size_t write_verify_data(void *buffer, size_t size, size_t nmemb, void *stream) 107 | { 108 | size_t realsize = size * nmemb; 109 | 110 | if (pkgi_write(item_file, buffer, realsize)) 111 | { 112 | download_offset += realsize; 113 | mbedtls_sha256_update(&sha, buffer, realsize); 114 | return (realsize); 115 | } 116 | 117 | return 0; 118 | } 119 | 120 | static void download_start(void) 121 | { 122 | LOG("resuming pkg download from %llu offset", initial_offset); 123 | download_offset = initial_offset; 124 | download_resume = 0; 125 | info_update = pkgi_time_msec() + 1000; 126 | pkgi_dialog_set_progress_title(_("Downloading...")); 127 | } 128 | 129 | static int download_data(void) 130 | { 131 | if (!http) 132 | { 133 | LOG("requesting %s @ %llu", db_item->url, initial_offset); 134 | http = pkgi_http_get(db_item->url, db_item->content, initial_offset); 135 | if (!http) 136 | { 137 | pkgi_dialog_error(_("Could not send HTTP request")); 138 | return 0; 139 | } 140 | 141 | int64_t http_length; 142 | if (!pkgi_http_response_length(http, &http_length)) 143 | { 144 | pkgi_dialog_error(_("HTTP request failed")); 145 | return 0; 146 | } 147 | if (http_length < 0) 148 | { 149 | pkgi_dialog_error(_("HTTP response has unknown length")); 150 | return 0; 151 | } 152 | 153 | download_size = http_length; 154 | total_size = initial_offset + download_size; 155 | 156 | if (!pkgi_check_free_space(http_length)) 157 | { 158 | LOG("error! out of space"); 159 | return 0; 160 | } 161 | 162 | LOG("http response length = %lld, total pkg size = %llu", http_length, total_size); 163 | info_start = pkgi_time_msec(); 164 | info_update = pkgi_time_msec() + 500; 165 | } 166 | 167 | if (!pkgi_http_read(http, &write_verify_data, &update_progress)) 168 | { 169 | pkgi_save(resume_file, &sha, sizeof(sha)); 170 | 171 | if (!pkgi_dialog_is_cancelled()) 172 | { 173 | pkgi_dialog_error(_("HTTP download error")); 174 | } 175 | return 0; 176 | } 177 | 178 | return 1; 179 | } 180 | 181 | // this includes creating of all the parent folders necessary to actually create file 182 | static int create_file(void) 183 | { 184 | char folder[256]; 185 | pkgi_strncpy(folder, sizeof(folder), item_path); 186 | char* last = pkgi_strrchr(folder, '/'); 187 | *last = 0; 188 | 189 | if (!pkgi_mkdirs(folder)) 190 | { 191 | char error[256]; 192 | pkgi_snprintf(error, sizeof(error), "%s %s", _("cannot create folder"), folder); 193 | pkgi_dialog_error(error); 194 | return 0; 195 | } 196 | 197 | LOG("creating %s file", item_name); 198 | item_file = pkgi_create(item_path); 199 | if (!item_file) 200 | { 201 | char error[256]; 202 | pkgi_snprintf(error, sizeof(error), "%s %s", _("cannot create file"), item_name); 203 | pkgi_dialog_error(error); 204 | return 0; 205 | } 206 | 207 | return 1; 208 | } 209 | 210 | static int resume_partial_file(void) 211 | { 212 | LOG("resuming %s file", item_name); 213 | item_file = pkgi_append(item_path); 214 | if (!item_file) 215 | { 216 | char error[256]; 217 | pkgi_snprintf(error, sizeof(error), "%s %s", _("cannot resume file"), item_name); 218 | pkgi_dialog_error(error); 219 | return 0; 220 | } 221 | 222 | return 1; 223 | } 224 | 225 | static int download_pkg_file(void) 226 | { 227 | int result = 0; 228 | 229 | pkgi_strncpy(item_name, sizeof(item_name), root); 230 | pkgi_snprintf(item_path, sizeof(item_path), "%s%s/%s", pkgi_get_storage_device(), pkgi_get_temp_folder(), root); 231 | LOG("downloading %s", item_name); 232 | 233 | if (download_resume) 234 | { 235 | initial_offset = pkgi_get_size(item_path); 236 | if (!resume_partial_file()) goto bail; 237 | download_start(); 238 | } 239 | else 240 | { 241 | if (!create_file()) goto bail; 242 | } 243 | 244 | if (!download_data()) goto bail; 245 | 246 | LOG("%s downloaded", item_path); 247 | result = 1; 248 | 249 | bail: 250 | if (item_file != NULL) 251 | { 252 | pkgi_close(item_file); 253 | item_file = NULL; 254 | } 255 | return result; 256 | } 257 | 258 | static int check_integrity(const uint8_t* digest) 259 | { 260 | if (!digest) 261 | { 262 | LOG("no integrity provided, skipping check"); 263 | return 1; 264 | } 265 | 266 | uint8_t check[SHA256_DIGEST_SIZE]; 267 | mbedtls_sha256_finish(&sha, check); 268 | 269 | LOG("checking integrity of pkg"); 270 | if (!pkgi_memequ(digest, check, SHA256_DIGEST_SIZE)) 271 | { 272 | LOG("pkg integrity is wrong, removing %s & resume data", item_path); 273 | 274 | pkgi_rm(item_path); 275 | pkgi_rm(resume_file); 276 | 277 | pkgi_dialog_error(_("pkg integrity failed, try downloading again")); 278 | return 0; 279 | } 280 | 281 | LOG("pkg integrity check succeeded"); 282 | return 1; 283 | } 284 | 285 | static int create_rap(const char* contentid, const uint8_t* rap) 286 | { 287 | LOG("creating %s.rap", contentid); 288 | pkgi_dialog_update_progress(_("Creating RAP file"), NULL, NULL, 1.f); 289 | 290 | char path[256]; 291 | pkgi_snprintf(path, sizeof(path), "%s%s/%s.rap", pkgi_get_storage_device(), PKGI_RAP_FOLDER, contentid); 292 | 293 | if (!pkgi_save(path, rap, PKGI_RAP_SIZE)) 294 | { 295 | char error[256]; 296 | pkgi_snprintf(error, sizeof(error), "%s %s.rap", _("Cannot save"), contentid); 297 | pkgi_dialog_error(error); 298 | return 0; 299 | } 300 | 301 | LOG("RAP file created"); 302 | return 1; 303 | } 304 | 305 | static int is_zip(const char* filename) 306 | { 307 | const char* extension = pkgi_strrchr(filename, '.'); 308 | return extension && (pkgi_stricmp(extension, ".zip") == 0); 309 | } 310 | 311 | int pkgi_download(const DbItem* item) 312 | { 313 | int result = 0; 314 | 315 | pkgi_snprintf(root, sizeof(root), "%s.%s", item->content, is_zip(item->url) ? "zip" : "pkg"); 316 | LOG("package installation file: %s", root); 317 | 318 | pkgi_snprintf(resume_file, sizeof(resume_file), "%s%s/%s.resume", pkgi_get_storage_device(), pkgi_get_temp_folder(), item->content); 319 | if (pkgi_load(resume_file, &sha, sizeof(sha)) == sizeof(sha)) 320 | { 321 | LOG("resume file exists, trying to resume"); 322 | pkgi_dialog_set_progress_title(_("Resuming...")); 323 | download_resume = 1; 324 | } 325 | else 326 | { 327 | if (item->type == ContentLocal) 328 | { 329 | pkgi_strncpy(root, sizeof(root), item->url); 330 | pkgi_snprintf(item_path, sizeof(item_path), "%s%s/%s", pkgi_get_storage_device(), pkgi_get_temp_folder(), root); 331 | return 1; 332 | } 333 | 334 | LOG("cannot load resume file, starting download from scratch"); 335 | pkgi_dialog_set_progress_title(_("Downloading...")); 336 | download_resume = 0; 337 | mbedtls_sha256_init(&sha); 338 | mbedtls_sha256_starts(&sha, 0); 339 | } 340 | 341 | http = NULL; 342 | item_file = NULL; 343 | total_size = 0; 344 | download_size = 0; 345 | download_offset = 0; 346 | initial_offset = 0; 347 | db_item = item; 348 | 349 | dialog_extra[0] = 0; 350 | dialog_eta[0] = 0; 351 | info_start = pkgi_time_msec(); 352 | info_update = info_start + 1000; 353 | 354 | if (item->rap) 355 | { 356 | if (!create_rap(item->content, item->rap)) goto finish; 357 | } 358 | 359 | if (!download_pkg_file()) goto finish; 360 | if (!check_integrity(item->digest)) goto finish; 361 | 362 | pkgi_rm(resume_file); 363 | result = 1; 364 | 365 | finish: 366 | if (http) 367 | { 368 | pkgi_http_close(http); 369 | } 370 | 371 | return result; 372 | } 373 | 374 | void update_install_progress(const char *filename, int64_t progress) 375 | { 376 | download_offset = progress; 377 | if (filename) pkgi_strncpy(item_name, sizeof(item_name), filename); 378 | update_progress(NULL, 0, 0, 0, 0); 379 | } 380 | 381 | int pkgi_install(int iso_mode, int remove_pkg) 382 | { 383 | int result; 384 | 385 | download_size = pkgi_get_size(item_path); 386 | info_start = pkgi_time_msec(); 387 | 388 | initial_offset = 0; 389 | download_offset = 0; 390 | total_size = download_size; 391 | 392 | // check if it's a zip file 393 | if (is_zip(item_path)) 394 | result = extract_zip(item_path); 395 | else 396 | result = iso_mode ? convert_psp_pkg_iso(item_path, (iso_mode == 2)) : install_psp_pkg(item_path); 397 | 398 | if (result && remove_pkg) 399 | { 400 | pkgi_rm(item_path); 401 | } 402 | 403 | return (result); 404 | } 405 | -------------------------------------------------------------------------------- /source/pkgi_menu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pkgi_menu.h" 3 | #include "pkgi_config.h" 4 | #include "pkgi_style.h" 5 | #include "pkgi.h" 6 | 7 | static int menu_search_clear; 8 | 9 | static Config menu_config; 10 | static uint32_t menu_selected; 11 | static int menu_allow_refresh; 12 | 13 | static MenuResult menu_result; 14 | 15 | static int32_t menu_width; 16 | static int32_t menu_delta; 17 | static int32_t pkgi_menu_width = 0; 18 | 19 | typedef enum { 20 | MenuSearch, 21 | MenuSearchClear, 22 | MenuText, 23 | MenuSort, 24 | MenuFilter, 25 | MenuRefresh, 26 | MenuMode, 27 | MenuUpdate, 28 | MenuKeepPkg, 29 | MenuContent 30 | } MenuType; 31 | 32 | typedef struct { 33 | MenuType type; 34 | const char* text; 35 | uint32_t value; 36 | } MenuEntry; 37 | 38 | static MenuEntry menu_entries[] = 39 | { 40 | { MenuSearch, "Search...", 0 }, 41 | { MenuSearchClear, PKGI_UTF8_CLEAR " clear", 0 }, 42 | 43 | { MenuText, "Sort by:", 0 }, 44 | { MenuSort, "Title", SortByTitle }, 45 | { MenuSort, "Region", SortByRegion }, 46 | { MenuSort, "Name", SortByName }, 47 | { MenuSort, "Size", SortBySize }, 48 | 49 | { MenuText, "Content:", 0 }, 50 | { MenuContent, "All", 0 }, 51 | 52 | { MenuRefresh, "Refresh...", 0 }, 53 | 54 | { MenuText, "Regions:", 0 }, 55 | { MenuFilter, "Asia", DbFilterRegionASA }, 56 | { MenuFilter, "Europe", DbFilterRegionEUR }, 57 | { MenuFilter, "Japan", DbFilterRegionJPN }, 58 | { MenuFilter, "USA", DbFilterRegionUSA }, 59 | 60 | { MenuText, "Options:", 0 }, 61 | { MenuMode, "Digital", 0 }, 62 | { MenuKeepPkg, "Keep PKGs", 1 }, 63 | { MenuUpdate, "Updates", 1 }, 64 | }; 65 | 66 | static MenuEntry content_entries[] = 67 | { 68 | { MenuFilter, "All", DbFilterAllContent }, 69 | { MenuFilter, "Games", DbFilterContentGame }, 70 | { MenuFilter, "DLCs", DbFilterContentDLC }, 71 | { MenuFilter, "Themes", DbFilterContentTheme }, 72 | { MenuFilter, "PSX", DbFilterContentPSX }, 73 | { MenuFilter, "Demos", DbFilterContentDemo }, 74 | { MenuFilter, "Updates", DbFilterContentUpdate }, 75 | { MenuFilter, "Emulators", DbFilterContentEmulator }, 76 | { MenuFilter, "Apps", DbFilterContentApp }, 77 | { MenuFilter, "Local PKGs", DbFilterContentLocal } 78 | }; 79 | 80 | static MenuEntry format_entries[] = 81 | { 82 | { MenuMode, "Digital", 0 }, 83 | { MenuMode, "ISO", 1 }, 84 | { MenuMode, "CSO", 2 } 85 | }; 86 | 87 | int pkgi_menu_is_open(void) 88 | { 89 | return menu_width != 0; 90 | } 91 | 92 | MenuResult pkgi_menu_result() 93 | { 94 | return menu_result; 95 | } 96 | 97 | void pkgi_menu_get(Config* config) 98 | { 99 | *config = menu_config; 100 | } 101 | 102 | static void set_max_width(const MenuEntry* entries, int size) 103 | { 104 | for (int j, i = 0; i < size; i++) 105 | { 106 | if ((j = pkgi_text_width(entries[i].text) + PKGI_MENU_LEFT_PADDING*2) > pkgi_menu_width/2) 107 | pkgi_menu_width = j*2; 108 | } 109 | } 110 | 111 | void pkgi_menu_start(int search_clear, const Config* config) 112 | { 113 | menu_search_clear = search_clear; 114 | menu_width = 1; 115 | menu_delta = 1; 116 | menu_config = *config; 117 | menu_allow_refresh = config->allow_refresh; 118 | 119 | menu_entries[0].text = _("Search..."); 120 | menu_entries[2].text = _("Sort by:"); 121 | menu_entries[3].text = _("Title"); 122 | menu_entries[4].text = _("Region"); 123 | menu_entries[5].text = _("Name"); 124 | menu_entries[6].text = _("Size"); 125 | menu_entries[7].text = _("Content:"); 126 | menu_entries[8].text = _("All"); 127 | menu_entries[9].text = _("Refresh..."); 128 | menu_entries[10].text = _("Regions:"); 129 | menu_entries[11].text = _("Asia"); 130 | menu_entries[12].text = _("Europe"); 131 | menu_entries[13].text = _("Japan"); 132 | menu_entries[14].text = _("USA"); 133 | menu_entries[15].text = _("Options:"); 134 | menu_entries[16].text = _("ISO"); 135 | menu_entries[17].text = _("Keep PKGs"); 136 | menu_entries[18].text = _("Updates"); 137 | 138 | content_entries[0].text = _("All"); 139 | content_entries[1].text = _("Games"); 140 | content_entries[2].text = _("DLCs"); 141 | content_entries[3].text = _("Themes"); 142 | content_entries[4].text = _("PSX"); 143 | content_entries[5].text = _("Demos"); 144 | content_entries[6].text = _("Updates"); 145 | content_entries[7].text = _("Emulators"); 146 | content_entries[8].text = _("Apps"); 147 | content_entries[9].text = _("Local PKGs"); 148 | 149 | format_entries[0].text = _("Digital"); 150 | format_entries[1].text = _("ISO"); 151 | format_entries[2].text = _("CSO"); 152 | 153 | if (pkgi_menu_width) 154 | return; 155 | 156 | pkgi_menu_width = PKGI_MENU_WIDTH; 157 | set_max_width(menu_entries, PKGI_COUNTOF(menu_entries)); 158 | set_max_width(content_entries, PKGI_COUNTOF(content_entries)); 159 | } 160 | 161 | int pkgi_do_menu(pkgi_input* input) 162 | { 163 | if (menu_delta != 0) 164 | { 165 | menu_width += menu_delta * (int32_t)(input->delta * PKGI_ANIMATION_SPEED/ 3000); 166 | 167 | if (menu_delta < 0 && menu_width <= 0) 168 | { 169 | menu_width = 0; 170 | menu_delta = 0; 171 | return 0; 172 | } 173 | else if (menu_delta > 0 && menu_width >= pkgi_menu_width) 174 | { 175 | menu_width = pkgi_menu_width; 176 | menu_delta = 0; 177 | } 178 | } 179 | 180 | if (menu_width != 0) 181 | { 182 | pkgi_draw_fill_rect_z(PKGI_SCREEN_WIDTH - (menu_width + PKGI_MAIN_HMARGIN), PKGI_MAIN_VMARGIN, PKGI_MENU_Z, menu_width, PKGI_MENU_HEIGHT, PKGI_COLOR_MENU_BACKGROUND); 183 | pkgi_draw_rect_z(PKGI_SCREEN_WIDTH - (menu_width + PKGI_MAIN_HMARGIN), PKGI_MAIN_VMARGIN, PKGI_MENU_Z, menu_width, PKGI_MENU_HEIGHT, PKGI_COLOR_MENU_BORDER); 184 | } 185 | 186 | if (input->active & PKGI_BUTTON_UP) 187 | { 188 | do { 189 | if (menu_selected == 0) 190 | { 191 | menu_selected = PKGI_COUNTOF(menu_entries) - 1; 192 | } 193 | else 194 | { 195 | menu_selected--; 196 | } 197 | } while (menu_entries[menu_selected].type == MenuText 198 | || (menu_entries[menu_selected].type == MenuSearchClear && !menu_search_clear) 199 | || (menu_entries[menu_selected].type == MenuRefresh && !menu_allow_refresh)); 200 | } 201 | 202 | if (input->active & PKGI_BUTTON_DOWN) 203 | { 204 | do { 205 | if (menu_selected == PKGI_COUNTOF(menu_entries) - 1) 206 | { 207 | menu_selected = 0; 208 | } 209 | else 210 | { 211 | menu_selected++; 212 | } 213 | } while (menu_entries[menu_selected].type == MenuText 214 | || (menu_entries[menu_selected].type == MenuSearchClear && !menu_search_clear) 215 | || (menu_entries[menu_selected].type == MenuRefresh && !menu_allow_refresh)); 216 | } 217 | 218 | if (input->pressed & pkgi_cancel_button()) 219 | { 220 | menu_result = MenuResultCancel; 221 | menu_delta = -1; 222 | return 1; 223 | } 224 | else if (input->pressed & PKGI_BUTTON_T) 225 | { 226 | menu_result = MenuResultAccept; 227 | menu_delta = -1; 228 | return 1; 229 | } 230 | else if (input->pressed & pkgi_ok_button()) 231 | { 232 | MenuType type = menu_entries[menu_selected].type; 233 | if (type == MenuSearch) 234 | { 235 | menu_result = MenuResultSearch; 236 | menu_delta = -1; 237 | return 1; 238 | } 239 | if (type == MenuSearchClear) 240 | { 241 | menu_selected--; 242 | menu_result = MenuResultSearchClear; 243 | menu_delta = -1; 244 | return 1; 245 | } 246 | else if (type == MenuRefresh) 247 | { 248 | menu_result = MenuResultRefresh; 249 | menu_delta = -1; 250 | return 1; 251 | } 252 | else if (type == MenuSort) 253 | { 254 | DbSort value = (DbSort)menu_entries[menu_selected].value; 255 | if (menu_config.sort == value) 256 | { 257 | menu_config.order = menu_config.order == SortAscending ? SortDescending : SortAscending; 258 | } 259 | else 260 | { 261 | menu_config.sort = value; 262 | } 263 | } 264 | else if (type == MenuFilter) 265 | { 266 | menu_config.filter ^= menu_entries[menu_selected].value; 267 | } 268 | else if (type == MenuMode) 269 | { 270 | menu_config.install_mode_iso++; 271 | if (menu_config.install_mode_iso == PKGI_COUNTOF(format_entries)) 272 | menu_config.install_mode_iso = 0; 273 | } 274 | else if (type == MenuKeepPkg) 275 | { 276 | menu_config.keep_pkg ^= menu_entries[menu_selected].value; 277 | } 278 | else if (type == MenuUpdate) 279 | { 280 | menu_config.version_check ^= menu_entries[menu_selected].value; 281 | } 282 | else if (type == MenuContent) 283 | { 284 | menu_config.filter ^= content_entries[menu_config.content].value; 285 | 286 | menu_config.content++; 287 | if (menu_config.content == MAX_CONTENT_TYPES) 288 | menu_config.content = 0; 289 | 290 | menu_config.filter ^= content_entries[menu_config.content].value; 291 | } 292 | } 293 | 294 | if (menu_width != pkgi_menu_width) 295 | { 296 | return 1; 297 | } 298 | 299 | int font_height = pkgi_text_height("M"); 300 | 301 | int y = PKGI_MENU_TOP_PADDING; 302 | for (uint32_t i = 0; i < PKGI_COUNTOF(menu_entries); i++) 303 | { 304 | const MenuEntry* entry = menu_entries + i; 305 | 306 | MenuType type = entry->type; 307 | if (type == MenuText) 308 | { 309 | y += font_height; 310 | } 311 | else if (type == MenuSearchClear && !menu_search_clear) 312 | { 313 | continue; 314 | } 315 | else if (type == MenuRefresh) 316 | { 317 | if (!menu_allow_refresh) 318 | { 319 | continue; 320 | } 321 | y += font_height; 322 | } 323 | 324 | int x = PKGI_SCREEN_WIDTH - (pkgi_menu_width + PKGI_MAIN_HMARGIN) + PKGI_MENU_LEFT_PADDING + (i > 9 ? pkgi_menu_width/2 : 0); 325 | if (i == 10) 326 | { 327 | y = PKGI_MENU_TOP_PADDING + font_height*2; 328 | } 329 | 330 | char text[64]; 331 | if (type == MenuSearch || type == MenuSearchClear || type == MenuText || type == MenuRefresh) 332 | { 333 | pkgi_strncpy(text, sizeof(text), entry->text); 334 | } 335 | else if (type == MenuSort) 336 | { 337 | if (menu_config.sort == (DbSort)entry->value) 338 | { 339 | pkgi_snprintf(text, sizeof(text), "%s %s", 340 | menu_config.order == SortAscending ? PKGI_UTF8_SORT_ASC : PKGI_UTF8_SORT_DESC, 341 | entry->text); 342 | } 343 | else 344 | { 345 | x += pkgi_text_width(PKGI_UTF8_SORT_ASC " "); 346 | pkgi_strncpy(text, sizeof(text), entry->text); 347 | } 348 | } 349 | else if (type == MenuFilter) 350 | { 351 | pkgi_snprintf(text, sizeof(text), "%s %s", 352 | menu_config.filter & entry->value ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF, 353 | entry->text); 354 | } 355 | else if (type == MenuMode) 356 | { 357 | pkgi_snprintf(text, sizeof(text), PKGI_UTF8_CLEAR " %s", format_entries[menu_config.install_mode_iso].text); 358 | } 359 | else if (type == MenuKeepPkg) 360 | { 361 | pkgi_snprintf(text, sizeof(text), "%s %s", 362 | menu_config.keep_pkg == entry->value ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF, entry->text); 363 | } 364 | else if (type == MenuUpdate) 365 | { 366 | pkgi_snprintf(text, sizeof(text), "%s %s", 367 | menu_config.version_check == entry->value ? PKGI_UTF8_CHECK_ON : PKGI_UTF8_CHECK_OFF, entry->text); 368 | } 369 | else if (type == MenuContent) 370 | { 371 | pkgi_snprintf(text, sizeof(text), PKGI_UTF8_CLEAR " %s", content_entries[menu_config.content].text); 372 | } 373 | 374 | if (menu_selected == i) 375 | { 376 | pkgi_draw_fill_rect_z(PKGI_SCREEN_WIDTH - (pkgi_menu_width + PKGI_MAIN_HMARGIN/2) + (i > 9 ? pkgi_menu_width/2 : 0), y, PKGI_MENU_Z, pkgi_menu_width/2 - PKGI_MAIN_HMARGIN, font_height, PKGI_COLOR(20, 20, 20)); 377 | } 378 | pkgi_draw_text_z(x, y, PKGI_MENU_TEXT_Z, PKGI_COLOR_TEXT_MENU, text); 379 | 380 | if (type != MenuSearchClear || !menu_search_clear) 381 | { 382 | y += font_height; 383 | } 384 | } 385 | 386 | return 1; 387 | } 388 | -------------------------------------------------------------------------------- /source/ttf_fonts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //#include "types.h" 6 | #include "ttf_render.h" 7 | #include "pkgi_style.h" 8 | 9 | extern SDL_Renderer* renderer; 10 | 11 | /******************************************************************************************************************************************************/ 12 | /* TTF functions to load and convert fonts */ 13 | /******************************************************************************************************************************************************/ 14 | 15 | static int ttf_inited = 0; 16 | 17 | static FT_Library freetype; 18 | static FT_Face face[4]; 19 | static int f_face[4] = {0, 0, 0, 0}; 20 | 21 | static int file_exist(const char* path) 22 | { 23 | FILE* f = fopen(path, "rb"); 24 | if (f) { 25 | fclose(f); 26 | return 1; 27 | } 28 | return 0; 29 | } 30 | 31 | int TTFLoadFont(int set, const char * path, void * from_memory, int size_from_memory) 32 | { 33 | 34 | if(!ttf_inited) 35 | FT_Init_FreeType(&freetype); 36 | ttf_inited = 1; 37 | 38 | f_face[set] = 0; 39 | 40 | if(path) { 41 | if((file_exist(path) == 0) || FT_New_Face(freetype, path, 0, &face[set])<0) return -1; 42 | } else { 43 | if(FT_New_Memory_Face(freetype, from_memory, size_from_memory, 0, &face[set])) return -1; 44 | } 45 | 46 | f_face[set] = 1; 47 | 48 | return 0; 49 | } 50 | 51 | /* release all */ 52 | 53 | void TTFUnloadFont() 54 | { 55 | if(!ttf_inited) return; 56 | FT_Done_FreeType(freetype); 57 | ttf_inited = 0; 58 | } 59 | 60 | /* function to render the character 61 | chr : character from 0 to 255 62 | bitmap: uint8_t bitmap passed to render the character character (max 256 x 256 x 1 (8 bits Alpha)) 63 | *w : w is the bitmap width as input and the width of the character (used to increase X) as output 64 | *h : h is the bitmap height as input and the height of the character (used to Y correction combined with y_correction) as output 65 | y_correction : the Y correction to display the character correctly in the screen 66 | 67 | */ 68 | /* 69 | void TTF_to_Bitmap(uint8_t chr, uint8_t * bitmap, short *w, short *h, short *y_correction) 70 | { 71 | if(f_face[0]) FT_Set_Pixel_Sizes(face[0], (*w), (*h)); 72 | if(f_face[1]) FT_Set_Pixel_Sizes(face[1], (*w), (*h)); 73 | if(f_face[2]) FT_Set_Pixel_Sizes(face[2], (*w), (*h)); 74 | if(f_face[3]) FT_Set_Pixel_Sizes(face[3], (*w), (*h)); 75 | 76 | FT_GlyphSlot slot; 77 | 78 | memset(bitmap, 0, (*w) * (*h)); 79 | 80 | FT_UInt index; 81 | 82 | if(f_face[0] && (index = FT_Get_Char_Index(face[0], (char) chr))!=0 83 | && !FT_Load_Glyph(face[0], index, FT_LOAD_RENDER )) slot = face[0]->glyph; 84 | else if(f_face[1] && (index = FT_Get_Char_Index(face[1], (char) chr))!=0 85 | && !FT_Load_Glyph(face[1], index, FT_LOAD_RENDER )) slot = face[1]->glyph; 86 | else if(f_face[2] && (index = FT_Get_Char_Index(face[2], (char) chr))!=0 87 | && !FT_Load_Glyph(face[2], index, FT_LOAD_RENDER )) slot = face[2]->glyph; 88 | else if(f_face[3] && (index = FT_Get_Char_Index(face[3], (char) chr))!=0 89 | && !FT_Load_Glyph(face[3], index, FT_LOAD_RENDER )) slot = face[3]->glyph; 90 | else {(*w) = 0; return;} 91 | 92 | int n, m, ww; 93 | 94 | *y_correction = (*h) - 1 - slot->bitmap_top; 95 | 96 | ww = 0; 97 | 98 | for(n = 0; n < slot->bitmap.rows; n++) { 99 | for (m = 0; m < slot->bitmap.width; m++) { 100 | 101 | if(m >= (*w) || n >= (*h)) continue; 102 | 103 | bitmap[m] = (uint8_t) slot->bitmap.buffer[ww + m]; 104 | } 105 | 106 | bitmap += *w; 107 | 108 | ww += slot->bitmap.width; 109 | } 110 | 111 | *w = ((slot->advance.x + 31) >> 6) + ((slot->bitmap_left < 0) ? -slot->bitmap_left : 0); 112 | *h = slot->bitmap.rows; 113 | } 114 | 115 | int Render_String_UTF8(uint16_t * bitmap, int w, int h, uint8_t *string, int sw, int sh) 116 | { 117 | int posx = 0; 118 | int n, m, ww, ww2; 119 | uint8_t color; 120 | uint32_t ttf_char; 121 | 122 | if(f_face[0]) FT_Set_Pixel_Sizes(face[0], sw, sh); 123 | if(f_face[1]) FT_Set_Pixel_Sizes(face[1], sw, sh); 124 | if(f_face[2]) FT_Set_Pixel_Sizes(face[2], sw, sh); 125 | if(f_face[3]) FT_Set_Pixel_Sizes(face[3], sw, sh); 126 | 127 | //FT_Set_Pixel_Sizes(face, sw, sh); 128 | FT_GlyphSlot slot = NULL; 129 | 130 | memset(bitmap, 0, w * h * 2); 131 | 132 | while(*string) { 133 | 134 | if(*string == 32 || *string == 9) {posx += sw>>1; string++; continue;} 135 | 136 | if(*string & 128) { 137 | m = 1; 138 | 139 | if((*string & 0xf8)==0xf0) { // 4 bytes 140 | ttf_char = (uint32_t) (*(string++) & 3); 141 | m = 3; 142 | } else if((*string & 0xE0)==0xE0) { // 3 bytes 143 | ttf_char = (uint32_t) (*(string++) & 0xf); 144 | m = 2; 145 | } else if((*string & 0xE0)==0xC0) { // 2 bytes 146 | ttf_char = (uint32_t) (*(string++) & 0x1f); 147 | m = 1; 148 | } else {string++;continue;} // error! 149 | 150 | for(n = 0; n < m; n++) { 151 | if(!*string) break; // error! 152 | if((*string & 0xc0) != 0x80) break; // error! 153 | ttf_char = (ttf_char <<6) |((uint32_t) (*(string++) & 63)); 154 | } 155 | 156 | if((n != m) && !*string) break; 157 | 158 | } else ttf_char = (uint32_t) *(string++); 159 | 160 | if(ttf_char == 13 || ttf_char == 10) ttf_char='/'; 161 | 162 | FT_UInt index; 163 | 164 | if(f_face[0] && (index = FT_Get_Char_Index(face[0], ttf_char))!=0 165 | && !FT_Load_Glyph(face[0], index, FT_LOAD_RENDER )) slot = face[0]->glyph; 166 | else if(f_face[1] && (index = FT_Get_Char_Index(face[1], ttf_char))!=0 167 | && !FT_Load_Glyph(face[1], index, FT_LOAD_RENDER )) slot = face[1]->glyph; 168 | else if(f_face[2] && (index = FT_Get_Char_Index(face[2], ttf_char))!=0 169 | && !FT_Load_Glyph(face[2], index, FT_LOAD_RENDER )) slot = face[2]->glyph; 170 | else if(f_face[3] && (index = FT_Get_Char_Index(face[3], ttf_char))!=0 171 | && !FT_Load_Glyph(face[3], index, FT_LOAD_RENDER )) slot = face[3]->glyph; 172 | else ttf_char = 0; 173 | 174 | if(ttf_char!=0 && slot->bitmap.buffer) { 175 | ww = ww2 = 0; 176 | 177 | int y_correction = sh - 1 - slot->bitmap_top; 178 | if(y_correction < 0) y_correction = 0; 179 | ww2 = y_correction * w; 180 | 181 | for(n = 0; n < slot->bitmap.rows; n++) { 182 | if(n + y_correction >= h) break; 183 | for (m = 0; m < slot->bitmap.width; m++) { 184 | 185 | if(m + posx >= w) continue; 186 | 187 | color = (uint8_t) slot->bitmap.buffer[ww + m]; 188 | 189 | if(color) bitmap[posx + m + ww2] = (color<<8) | 0xfff; 190 | } 191 | 192 | ww2 += w; 193 | 194 | ww += slot->bitmap.width; 195 | } 196 | 197 | } 198 | 199 | if(slot) posx+= slot->bitmap.width; 200 | } 201 | return posx; 202 | } 203 | */ 204 | // constructor dinamico de fuentes 32 x 32 205 | 206 | typedef struct ttf_dyn { 207 | uint32_t ttf; 208 | SDL_Texture *text[2]; 209 | uint32_t r_use; 210 | uint16_t y_start; 211 | uint16_t width; 212 | uint16_t height; 213 | uint16_t flags; 214 | 215 | } ttf_dyn; 216 | 217 | #define MAX_CHARS 1600 218 | #define TEX_SZ 32 219 | 220 | static ttf_dyn ttf_font_datas[MAX_CHARS]; 221 | 222 | static uint32_t r_use= 0; 223 | 224 | float Y_ttf = 0.0f; 225 | float Z_ttf = 0.0f; 226 | 227 | static int Win_X_ttf = 0; 228 | static int Win_Y_ttf = 0; 229 | static int Win_W_ttf = PKGI_SCREEN_WIDTH; 230 | static int Win_H_ttf = PKGI_SCREEN_HEIGHT; 231 | 232 | 233 | static uint32_t Win_flag = 0; 234 | 235 | void set_ttf_window(int x, int y, int width, int height, uint32_t mode) 236 | { 237 | Win_X_ttf = x; 238 | Win_Y_ttf = y; 239 | Win_W_ttf = width; 240 | Win_H_ttf = height; 241 | Win_flag = mode; 242 | Y_ttf = 0.0f; 243 | Z_ttf = 0.0f; 244 | 245 | } 246 | 247 | uint16_t * init_ttf_table(uint8_t *texture) 248 | { 249 | int n; 250 | 251 | r_use= 0; 252 | for(n= 0; n < MAX_CHARS; n++) { 253 | memset(&ttf_font_datas[n], 0, sizeof(ttf_dyn)); 254 | } 255 | 256 | return (uint16_t*)texture; 257 | 258 | } 259 | 260 | void reset_ttf_frame(void) 261 | { 262 | int n; 263 | 264 | for(n = 0; n < MAX_CHARS; n++) { 265 | 266 | ttf_font_datas[n].flags &= 1; 267 | 268 | } 269 | 270 | r_use++; 271 | 272 | } 273 | static void DrawBox_ttf(float x, float y, float z, float w, float h, uint32_t rgba) 274 | { 275 | SDL_FRect rect = { 276 | .x = x, 277 | .y = y, 278 | .w = w, 279 | .h = h, 280 | }; 281 | 282 | SDL_SetRenderDrawColor(renderer, RGBA_R(rgba), RGBA_G(rgba), RGBA_B(rgba), RGBA_A(rgba)); 283 | SDL_RenderFillRectF(renderer, &rect); 284 | /* 285 | tiny3d_SetPolygon(TINY3D_QUADS); 286 | 287 | 288 | tiny3d_VertexPos(x , y , z); 289 | tiny3d_VertexColor(rgba); 290 | 291 | tiny3d_VertexPos(x + w, y , z); 292 | 293 | tiny3d_VertexPos(x + w, y + h, z); 294 | 295 | tiny3d_VertexPos(x , y + h, z); 296 | 297 | tiny3d_End(); 298 | */ 299 | } 300 | 301 | static SDL_Texture* create_texture(const uint8_t* bitmap, uint32_t rgba) 302 | { 303 | uint32_t buf[TEX_SZ * TEX_SZ]; 304 | 305 | for (int i = 0; i < TEX_SZ*TEX_SZ; i++) 306 | buf[i] = bitmap[i] ? ((rgba & 0xFFFFFF00) | bitmap[i]) : 0; 307 | 308 | SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*) buf, TEX_SZ, TEX_SZ, 32, 4 * TEX_SZ, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); 309 | SDL_Texture* sdl_tex = SDL_CreateTextureFromSurface(renderer, surface); 310 | SDL_SetTextureBlendMode(sdl_tex, SDL_BLENDMODE_BLEND); 311 | SDL_FreeSurface(surface); 312 | 313 | return (sdl_tex); 314 | } 315 | 316 | static void DrawTextBox_ttf(SDL_Texture* bitmap, float x, float y, float z, float w, float h, uint32_t rgba, float tx, float ty) 317 | { 318 | SDL_FRect dest = { 319 | .x = x, 320 | .y = y, 321 | .w = w, 322 | .h = h, 323 | }; 324 | 325 | SDL_SetTextureAlphaMod(bitmap, RGBA_A(rgba)); 326 | SDL_RenderCopyF(renderer, bitmap, NULL, &dest); 327 | } 328 | 329 | #define TTF_UX 30 * (TEX_SZ /32) 330 | #define TTF_UY 24 * (TEX_SZ /32) 331 | 332 | 333 | int display_ttf_string(int posx, int posy, const char *string, uint32_t color, uint32_t bkcolor, int sw, int sh, int (*DrawIcon_cb)(int, int, char)) 334 | { 335 | int l,n, m, ww, ww2; 336 | uint8_t colorc; 337 | uint32_t ttf_char; 338 | uint8_t *ustring = (uint8_t *) string; 339 | 340 | int lenx = 0; 341 | 342 | while(*ustring) { 343 | 344 | if(posy >= Win_H_ttf) break; 345 | 346 | if(*ustring == ' ') {posx += sw>>1; ustring++; continue;} 347 | 348 | if(*ustring & 128) { 349 | m = 1; 350 | 351 | if((*ustring & 0xf8)==0xf0) { // 4 bytes 352 | ttf_char = (uint32_t) (*(ustring++) & 3); 353 | m = 3; 354 | } else if((*ustring & 0xE0)==0xE0) { // 3 bytes 355 | ttf_char = (uint32_t) (*(ustring++) & 0xf); 356 | m = 2; 357 | } else if((*ustring & 0xE0)==0xC0) { // 2 bytes 358 | ttf_char = (uint32_t) (*(ustring++) & 0x1f); 359 | m = 1; 360 | } else {ustring++;continue;} // error! 361 | 362 | for(n = 0; n < m; n++) { 363 | if(!*ustring) break; // error! 364 | if((*ustring & 0xc0) != 0x80) break; // error! 365 | ttf_char = (ttf_char <<6) |((uint32_t) (*(ustring++) & 63)); 366 | } 367 | 368 | if((n != m) && !*ustring) break; 369 | 370 | } else ttf_char = (uint32_t) *(ustring++); 371 | 372 | 373 | if(Win_flag & WIN_SKIP_LF) { 374 | if(ttf_char == '\r' || ttf_char == '\n') ttf_char=' '; 375 | } else { 376 | if(Win_flag & WIN_DOUBLE_LF) { 377 | if(ttf_char == '\r') {if(posx > lenx) lenx = posx; posx = 0;continue;} 378 | if(ttf_char == '\n') {posy += sh;continue;} 379 | } else { 380 | if(ttf_char == '\n') {if(posx > lenx) lenx = posx; posx = 0;posy += sh;continue;} 381 | } 382 | } 383 | 384 | if ((ttf_char < 32) && DrawIcon_cb) { 385 | int resx = DrawIcon_cb(posx, posy, (char) ttf_char); 386 | if (resx > 0) { 387 | posx += resx; 388 | continue; 389 | } 390 | else 391 | ttf_char='?'; 392 | } 393 | 394 | // search ttf_char 395 | if(ttf_char < 128) n= ttf_char; 396 | else { 397 | m= 0; 398 | int rel=0; 399 | 400 | for(n= 128; n < MAX_CHARS; n++) { 401 | if(!(ttf_font_datas[n].flags & 1)) m= n; 402 | 403 | if((ttf_font_datas[n].flags & 3)==1) { 404 | int trel= r_use - ttf_font_datas[n].r_use; 405 | if(m==0) {m= n;rel = trel;} 406 | else if(rel > trel) {m= n;rel = trel;} 407 | 408 | } 409 | if(ttf_font_datas[n].ttf == ttf_char) break; 410 | } 411 | 412 | if(m==0) m = 128; 413 | 414 | } 415 | 416 | if(n >= MAX_CHARS) {ttf_font_datas[m].flags = 0; l= m;} else l=n; 417 | 418 | // building the character 419 | if(!(ttf_font_datas[l].flags & 1)) { 420 | 421 | if(f_face[0]) FT_Set_Pixel_Sizes(face[0], TTF_UX, TTF_UY); 422 | if(f_face[1]) FT_Set_Pixel_Sizes(face[1], TTF_UX, TTF_UY); 423 | if(f_face[2]) FT_Set_Pixel_Sizes(face[2], TTF_UX, TTF_UY); 424 | if(f_face[3]) FT_Set_Pixel_Sizes(face[3], TTF_UX, TTF_UY); 425 | 426 | FT_GlyphSlot slot = NULL; 427 | 428 | uint8_t bitmap[TEX_SZ * TEX_SZ]; 429 | memset(bitmap, 0, TEX_SZ * TEX_SZ); 430 | 431 | /////////// 432 | 433 | FT_UInt index; 434 | 435 | if(f_face[0] && (index = FT_Get_Char_Index(face[0], ttf_char))!=0 436 | && !FT_Load_Glyph(face[0], index, FT_LOAD_RENDER )) slot = face[0]->glyph; 437 | else if(f_face[1] && (index = FT_Get_Char_Index(face[1], ttf_char))!=0 438 | && !FT_Load_Glyph(face[1], index, FT_LOAD_RENDER )) slot = face[1]->glyph; 439 | else if(f_face[2] && (index = FT_Get_Char_Index(face[2], ttf_char))!=0 440 | && !FT_Load_Glyph(face[2], index, FT_LOAD_RENDER )) slot = face[2]->glyph; 441 | else if(f_face[3] && (index = FT_Get_Char_Index(face[3], ttf_char))!=0 442 | && !FT_Load_Glyph(face[3], index, FT_LOAD_RENDER )) slot = face[3]->glyph; 443 | else ttf_char = 0; 444 | 445 | if(ttf_char!=0) { 446 | ww = ww2 = 0; 447 | 448 | int y_correction = TTF_UY - 1 - slot->bitmap_top; 449 | if(y_correction < 0) y_correction = 0; 450 | 451 | ttf_font_datas[l].flags = 1; 452 | ttf_font_datas[l].y_start = y_correction; 453 | ttf_font_datas[l].height = slot->bitmap.rows; 454 | ttf_font_datas[l].width = slot->bitmap.width; 455 | ttf_font_datas[l].ttf = ttf_char; 456 | 457 | 458 | for(n = 0; n < slot->bitmap.rows; n++) { 459 | if(n >= TEX_SZ) break; 460 | for (m = 0; m < slot->bitmap.width; m++) { 461 | 462 | if(m >= TEX_SZ) continue; 463 | 464 | colorc = (uint8_t) slot->bitmap.buffer[ww + m]; 465 | 466 | if(colorc) bitmap[m + ww2] = colorc; //(colorc<<8) | 0xfff; 467 | } 468 | 469 | ww2 += TEX_SZ; 470 | 471 | ww += slot->bitmap.width; 472 | } 473 | ttf_font_datas[l].text[0] = create_texture(bitmap, 0x000000FF); 474 | ttf_font_datas[l].text[1] = create_texture(bitmap, 0xFFFFFFFF); 475 | } 476 | else continue; 477 | } 478 | 479 | // displaying the character 480 | ttf_font_datas[l].flags |= 2; // in use 481 | ttf_font_datas[l].r_use = r_use; 482 | 483 | if((Win_flag & WIN_AUTO_LF) && (posx + (ttf_font_datas[l].width * sw / TEX_SZ) + 1) > Win_W_ttf) { 484 | posx = 0; 485 | posy += sh; 486 | } 487 | 488 | uint32_t ccolor = color; 489 | uint32_t cx =(ttf_font_datas[l].width * sw / TEX_SZ) + 1; 490 | 491 | // skip if out of window 492 | if((posx + cx) > Win_W_ttf || (posy + sh) > Win_H_ttf ) ccolor = 0; 493 | 494 | if(ccolor) { 495 | // tiny3d_SetTextureWrap(0, tiny3d_TextureOffset(bitmap), 32, 32, 32 * 2, 496 | // TINY3D_TEX_FORMAT_A4R4G4B4, TEXTWRAP_CLAMP, TEXTWRAP_CLAMP, TEXTURE_LINEAR); 497 | 498 | if (bkcolor != 0) DrawBox_ttf((float) (Win_X_ttf + posx), (float) (Win_Y_ttf + posy) + ((float) ttf_font_datas[l].y_start * sh) * 0.03125f, 499 | Z_ttf, (float) sw, (float) sh, bkcolor); 500 | DrawTextBox_ttf(ttf_font_datas[l].text[((color & 0xFFFFFF00) != 0)], (float) (Win_X_ttf + posx), (float) (Win_Y_ttf + posy) + ((float) ttf_font_datas[l].y_start * sh) * 0.03125f, 501 | Z_ttf, (float) sw, (float) sh, color, 0.99f, 0.99f); 502 | } 503 | 504 | posx+= cx; 505 | } 506 | 507 | Y_ttf = (float) posy + sh; 508 | 509 | if(posx < lenx) posx = lenx; 510 | return posx; 511 | } 512 | 513 | int width_ttf_string(const char *string, int sw, int sh) 514 | { 515 | return (display_ttf_string(0, 0, string, 0, 0, sw, sh, NULL)); 516 | } 517 | -------------------------------------------------------------------------------- /source/zip_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "pkgi.h" 9 | #include "pkgi_download.h" 10 | 11 | #define UNZIP_BUF_SIZE 0x20000 12 | 13 | static inline uint64_t min64(uint64_t a, uint64_t b) 14 | { 15 | return a < b ? a : b; 16 | } 17 | 18 | int extract_zip(const char* zip_file) 19 | { 20 | char path[256]; 21 | uint8_t* buffer; 22 | int64_t zsize = pkgi_get_size(zip_file); 23 | struct zip* archive = zip_open(zip_file, ZIP_RDONLY | ZIP_CHECKCONS, NULL); 24 | int files = zip_get_num_files(archive); 25 | 26 | LOG("Extracting %s to <%s>...", zip_file, pkgi_get_storage_device()); 27 | 28 | if (files <= 0) { 29 | LOG("Empty ZIP file."); 30 | zip_close(archive); 31 | return 0; 32 | } 33 | 34 | buffer = malloc(UNZIP_BUF_SIZE); 35 | if (!buffer) 36 | return 0; 37 | 38 | for (int i = 0; i < files; i++) { 39 | const char* filename = zip_get_name(archive, i, 0); 40 | 41 | update_install_progress(filename, (zsize * i)/files); 42 | LOG("Unzip [%d/%d] '%s'...", i+1, files, filename); 43 | 44 | if (!filename) 45 | continue; 46 | 47 | if (filename[0] == '/') 48 | filename++; 49 | 50 | if (strncasecmp(filename, "PSP/GAME/", 9) == 0) 51 | filename += 9; 52 | 53 | snprintf(path, sizeof(path)-1, "%s/PSP/GAME/%s", pkgi_get_storage_device(), filename); 54 | char* slash = strrchr(path, '/'); 55 | *slash = 0; 56 | pkgi_mkdirs(path); 57 | *slash = '/'; 58 | 59 | if (filename[strlen(filename) - 1] == '/') 60 | continue; 61 | 62 | struct zip_stat st; 63 | if (zip_stat_index(archive, i, 0, &st)) { 64 | LOG("Unable to access file %s in zip.", filename); 65 | continue; 66 | } 67 | struct zip_file* zfd = zip_fopen_index(archive, i, 0); 68 | if (!zfd) { 69 | LOG("Unable to open file %s in zip.", filename); 70 | continue; 71 | } 72 | 73 | FILE* tfd = fopen(path, "wb"); 74 | if(!tfd) { 75 | free(buffer); 76 | zip_fclose(zfd); 77 | zip_close(archive); 78 | LOG("Error opening temporary file '%s'.", path); 79 | return 0; 80 | } 81 | 82 | uint64_t pos = 0, count; 83 | while (pos < st.size) { 84 | count = min64(UNZIP_BUF_SIZE, st.size - pos); 85 | if (zip_fread(zfd, buffer, count) != count) { 86 | free(buffer); 87 | fclose(tfd); 88 | zip_fclose(zfd); 89 | zip_close(archive); 90 | LOG("Error reading from zip."); 91 | return 0; 92 | } 93 | 94 | fwrite(buffer, count, 1, tfd); 95 | pos += count; 96 | } 97 | 98 | zip_fclose(zfd); 99 | fclose(tfd); 100 | 101 | update_install_progress(NULL, zsize * (i+1)/files); 102 | } 103 | 104 | if (archive) { 105 | zip_close(archive); 106 | } 107 | 108 | update_install_progress(NULL, zsize); 109 | free(buffer); 110 | 111 | return files; 112 | } 113 | -------------------------------------------------------------------------------- /translate.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PKGi PSP\n" 4 | "POT-Creation-Date: 2023-09-29 19:00-0300\n" 5 | "PO-Revision-Date: 2023-09-29 19:00-0300\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: en\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: ./source\n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "X-Poedit-Flags-xgettext: --add-comments\n" 16 | "X-Poedit-SourceCharset: UTF-8\n" 17 | "X-Poedit-KeywordsList: _\n" 18 | "X-Poedit-SearchPath-0: .\n" 19 | 20 | #: pkgi.c:82 21 | msgid "Installing..." 22 | msgstr "" 23 | 24 | #: pkgi.c:82 25 | msgid "Please wait..." 26 | msgstr "" 27 | 28 | #: pkgi.c:94 29 | msgid "Installation failed" 30 | msgstr "" 31 | 32 | #: pkgi.c:118 33 | msgid "Successfully downloaded" 34 | msgstr "" 35 | 36 | #: pkgi.c:122 37 | msgid "Task successfully queued (reboot to start)" 38 | msgstr "" 39 | 40 | #: pkgi.c:163 41 | msgid "GB" 42 | msgstr "" 43 | 44 | #: pkgi.c:167 pkgi_download.c:249 45 | msgid "MB" 46 | msgstr "" 47 | 48 | #: pkgi.c:171 pkgi.c:504 pkgi_download.c:253 49 | msgid "KB" 50 | msgstr "" 51 | 52 | #: pkgi.c:175 53 | msgid "B" 54 | msgstr "" 55 | 56 | #: pkgi.c:185 57 | #, c-format 58 | msgid "pkg requires %u %s free space, but only %u %s available" 59 | msgstr "" 60 | 61 | #: pkgi.c:225 pkgi_menu.c:109 pkgi_menu.c:121 62 | msgid "All" 63 | msgstr "" 64 | 65 | #: pkgi.c:226 pkgi_menu.c:122 66 | msgid "Games" 67 | msgstr "" 68 | 69 | #: pkgi.c:227 pkgi_menu.c:123 70 | msgid "DLCs" 71 | msgstr "" 72 | 73 | #: pkgi.c:228 pkgi_menu.c:124 74 | msgid "Themes" 75 | msgstr "" 76 | 77 | #: pkgi.c:229 pkgi_menu.c:125 78 | msgid "Avatars" 79 | msgstr "" 80 | 81 | #: pkgi.c:230 pkgi_menu.c:126 82 | msgid "Demos" 83 | msgstr "" 84 | 85 | #: pkgi.c:231 pkgi_menu.c:118 pkgi_menu.c:127 86 | msgid "Updates" 87 | msgstr "" 88 | 89 | #: pkgi.c:232 pkgi_menu.c:128 90 | msgid "Emulators" 91 | msgstr "" 92 | 93 | #: pkgi.c:233 pkgi_menu.c:129 94 | msgid "Apps" 95 | msgstr "" 96 | 97 | #: pkgi.c:234 pkgi_menu.c:130 98 | msgid "Tools" 99 | msgstr "" 100 | 101 | #: pkgi.c:235 102 | msgid "Unknown" 103 | msgstr "" 104 | 105 | #: pkgi.c:253 106 | msgid "Exit to XMB?" 107 | msgstr "" 108 | 109 | #: pkgi.c:436 110 | msgid "No items!" 111 | msgstr "" 112 | 113 | #: pkgi.c:462 114 | msgid "Item already installed, download again?" 115 | msgstr "" 116 | 117 | #: pkgi.c:471 pkgi_download.c:372 pkgi_download.c:652 118 | msgid "Downloading..." 119 | msgstr "" 120 | 121 | #: pkgi.c:471 pkgi.c:674 122 | msgid "Preparing..." 123 | msgstr "" 124 | 125 | #: pkgi.c:504 pkgi.c:508 126 | msgid "Refreshing" 127 | msgstr "" 128 | 129 | #: pkgi.c:563 pkgi.c:567 130 | msgid "Count" 131 | msgstr "" 132 | 133 | #: pkgi.c:575 134 | msgid "Free" 135 | msgstr "" 136 | 137 | #: pkgi.c:585 138 | msgid "Select" 139 | msgstr "" 140 | 141 | #: pkgi.c:585 142 | msgid "Close" 143 | msgstr "" 144 | 145 | #: pkgi.c:585 146 | msgid "Cancel" 147 | msgstr "" 148 | 149 | #: pkgi.c:589 pkgi_download.c:136 150 | msgid "Download" 151 | msgstr "" 152 | 153 | #: pkgi.c:589 154 | msgid "Menu" 155 | msgstr "" 156 | 157 | #: pkgi.c:589 158 | msgid "Details" 159 | msgstr "" 160 | 161 | #: pkgi.c:589 162 | msgid "Exit" 163 | msgstr "" 164 | 165 | #: pkgi.c:678 166 | msgid "Successfully downloaded PKGi PSP update" 167 | msgstr "" 168 | 169 | #: pkgi.c:1051 170 | msgid "Search" 171 | msgstr "" 172 | 173 | #: pkgi_db.c:145 pkgi_db.c:153 174 | msgid "failed to download list from" 175 | msgstr "" 176 | 177 | #: pkgi_db.c:159 178 | msgid "list is too large... check for newer pkgi version!" 179 | msgstr "" 180 | 181 | #: pkgi_db.c:187 182 | msgid "list is empty... check the DB server" 183 | msgstr "" 184 | 185 | #: pkgi_db.c:390 186 | msgid "ERROR: pkgi.txt file(s) missing or bad config.txt file" 187 | msgstr "" 188 | 189 | #: pkgi_dialog.c:80 190 | msgid "Content" 191 | msgstr "" 192 | 193 | #: pkgi_dialog.c:108 194 | msgid "ERROR" 195 | msgstr "" 196 | 197 | #: pkgi_dialog.c:169 198 | msgid "Failed to download the update list" 199 | msgstr "" 200 | 201 | #: pkgi_dialog.c:174 202 | msgid "update(s) loaded" 203 | msgstr "" 204 | 205 | #: pkgi_dialog.c:319 206 | #, c-format 207 | msgid "press %s to cancel" 208 | msgstr "" 209 | 210 | #: pkgi_dialog.c:333 211 | #, c-format 212 | msgid "press %s to close - %s to scan updates" 213 | msgstr "" 214 | 215 | #: pkgi_dialog.c:365 216 | msgid "Enter" 217 | msgstr "" 218 | 219 | #: pkgi_dialog.c:365 220 | msgid "Back" 221 | msgstr "" 222 | 223 | #: pkgi_dialog.c:367 224 | #, c-format 225 | msgid "press %s to close" 226 | msgstr "" 227 | 228 | #: pkgi_download.c:183 229 | msgid "Install" 230 | msgstr "" 231 | 232 | #: pkgi_download.c:216 pkgi_download.c:220 pkgi_download.c:226 233 | msgid "ETA" 234 | msgstr "" 235 | 236 | #: pkgi_download.c:310 pkgi_download.c:392 237 | msgid "Could not send HTTP request" 238 | msgstr "" 239 | 240 | #: pkgi_download.c:317 pkgi_download.c:399 241 | msgid "HTTP request failed" 242 | msgstr "" 243 | 244 | #: pkgi_download.c:322 pkgi_download.c:404 245 | msgid "HTTP response has unknown length" 246 | msgstr "" 247 | 248 | #: pkgi_download.c:331 249 | msgid "Not enough free space on HDD" 250 | msgstr "" 251 | 252 | #: pkgi_download.c:340 253 | msgid "Could not create task directory on HDD." 254 | msgstr "" 255 | 256 | #: pkgi_download.c:348 257 | msgid "Saving background task..." 258 | msgstr "" 259 | 260 | #: pkgi_download.c:354 261 | msgid "Could not create PKG file to HDD." 262 | msgstr "" 263 | 264 | #: pkgi_download.c:360 265 | msgid "Could not create task files to HDD." 266 | msgstr "" 267 | 268 | #: pkgi_download.c:425 269 | msgid "HTTP download error" 270 | msgstr "" 271 | 272 | #: pkgi_download.c:432 273 | msgid "HTTP connection closed" 274 | msgstr "" 275 | 276 | #: pkgi_download.c:445 277 | msgid "failed to write to" 278 | msgstr "" 279 | 280 | #: pkgi_download.c:465 281 | msgid "cannot create folder" 282 | msgstr "" 283 | 284 | #: pkgi_download.c:475 285 | msgid "cannot create file" 286 | msgstr "" 287 | 288 | #: pkgi_download.c:490 289 | msgid "cannot resume file" 290 | msgstr "" 291 | 292 | #: pkgi_download.c:562 293 | msgid "pkg integrity failed, try downloading again" 294 | msgstr "" 295 | 296 | #: pkgi_download.c:573 297 | msgid "Creating RAP file" 298 | msgstr "" 299 | 300 | #: pkgi_download.c:581 pkgi_download.c:626 301 | msgid "Cannot save" 302 | msgstr "" 303 | 304 | #: pkgi_download.c:621 305 | msgid "Creating RIF file" 306 | msgstr "" 307 | 308 | #: pkgi_download.c:646 309 | msgid "Resuming..." 310 | msgstr "" 311 | 312 | #: pkgi_download.c:652 313 | msgid "Adding background task..." 314 | msgstr "" 315 | 316 | #: pkgi_download.c:675 317 | msgid "Downloading icon" 318 | msgstr "" 319 | 320 | #: pkgi_download.c:713 321 | msgid "Could not create install directory on HDD." 322 | msgstr "" 323 | 324 | #: pkgi_menu.c:102 325 | msgid "Search..." 326 | msgstr "" 327 | 328 | #: pkgi_menu.c:103 329 | msgid "Sort by:" 330 | msgstr "" 331 | 332 | #: pkgi_menu.c:104 333 | msgid "Title" 334 | msgstr "" 335 | 336 | #: pkgi_menu.c:105 337 | msgid "Region" 338 | msgstr "" 339 | 340 | #: pkgi_menu.c:106 341 | msgid "Name" 342 | msgstr "" 343 | 344 | #: pkgi_menu.c:107 345 | msgid "Size" 346 | msgstr "" 347 | 348 | #: pkgi_menu.c:108 349 | msgid "Content:" 350 | msgstr "" 351 | 352 | #: pkgi_menu.c:110 353 | msgid "Regions:" 354 | msgstr "" 355 | 356 | #: pkgi_menu.c:111 357 | msgid "Asia" 358 | msgstr "" 359 | 360 | #: pkgi_menu.c:112 361 | msgid "Europe" 362 | msgstr "" 363 | 364 | #: pkgi_menu.c:113 365 | msgid "Japan" 366 | msgstr "" 367 | 368 | #: pkgi_menu.c:114 369 | msgid "USA" 370 | msgstr "" 371 | 372 | #: pkgi_menu.c:115 373 | msgid "Options:" 374 | msgstr "" 375 | 376 | #: pkgi_menu.c:116 377 | msgid "ISO" 378 | msgstr "" 379 | 380 | #: pkgi_menu.c:117 381 | msgid "Keep PKGs" 382 | msgstr "" 383 | 384 | #: pkgi_menu.c:119 385 | msgid "Refresh..." 386 | msgstr "" 387 | --------------------------------------------------------------------------------