diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5a986a..99e21a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,34 +1,30 @@ name: Build -on: [pull_request, push] + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + jobs: build: runs-on: ubuntu-24.04 + container: + image: pspdev/pspdev:latest steps: - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install build-essential libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev g++-mingw-w64-i686 - - - name: Build for Ubuntu - run: | - cmake -B build-ubuntu - cmake --build build-ubuntu - - - name: Upload for Ubuntu - uses: actions/upload-artifact@v4 - with: - name: GravityDefied - path: build-ubuntu/GravityDefied + - name: Workaround Git Permissions + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Build for Windows + - name: Build for PSP run: | - cmake -DCMAKE_TOOLCHAIN_FILE=cmake/mingw-toolchain.cmake -B build-win32 - cmake --build build-win32 + export PATH=/usr/local/pspdev/bin:$PATH + psp-cmake -B build-psp + make -C build-psp - - name: Upload for Windows + - name: Upload for PSP uses: actions/upload-artifact@v4 with: - name: GravityDefied.exe - path: build-win32/GravityDefied.exe + name: GravityDefied-PSP + path: build-psp/EBOOT.PBP diff --git a/.gitignore b/.gitignore index 0236496..3e7994b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build .cache .vscode +build-psp/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ed7b65b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,52 @@ +# Gravity Defied (PSP Port) Agent Guidelines + +## PSP Build Instructions + +This project targets the PlayStation Portable (PSP) hardware. It requires the PSPDEV toolchain to build the target artifact (`EBOOT.PBP`). + +### Toolchain Prerequisites +- A working PSPDEV toolchain installation. +- Ensure the `psp-cmake` and `psp-g++` binaries are available in your `$PATH`. +- Typically, the toolchain is located at `/usr/local/pspdev`. + +Before building, set up the environment: +```bash +export PATH=/usr/local/pspdev/bin:$PATH +``` + +### Build Commands +To compile and produce `EBOOT.PBP` from scratch: +```bash +# 1. Generate build files with psp-cmake +psp-cmake -B build-psp + +# 2. Compile and package the binary +make -C build-psp +``` + +The resulting PSP executable will be generated at `build-psp/EBOOT.PBP`. + +### Project Structure & PSP Hardware Dependencies +- **C++ Source Files**: Located in `src/`. +- **Game Assets**: Located in `assets/`. +- **PSP Specific Macros**: Located in `src/main.cpp` (using `PSP_MODULE_INFO` and ``). +- **Dependencies**: The project requires `SDL2`, `SDL2_ttf`, and `SDL2_image` ports for PSP, linked automatically via standard CMake `pkg-config` and `target_link_libraries`. +- **CMake config**: The primary configuration is in `CMakeLists.txt`. For PSP builds, the toolchain provides a `create_pbp_file` macro to package the EBOOT.PBP. + +### Asset Packing Rules +When the `EBOOT.PBP` is generated, it bundles the executable along with the following UI assets from the `assets/` directory: +- **Title (SFO metadata)**: `Gravity Defied: Trial Racing` +- **XMB Icon**: `assets/ICON0.png` +- **XMB Background**: `assets/PIC1.png` + +These are defined inside `CMakeLists.txt` using the `create_pbp_file` command. The PSP DEV script `pack-pbp` uses these arguments to assemble the `EBOOT.PBP` output. + +### Guidelines for Testing +- The output `EBOOT.PBP` can be tested using the **PPSSPP** emulator or executed on **real PSP hardware** running Custom Firmware (CFW). +- For real hardware, copy the output `EBOOT.PBP` to `ms0:/PSP/GAME/GravityDefied/EBOOT.PBP`. +- Automated testing (`ctest`) is currently unconfigured for this target. Verification must be done either manually (via emulator/hardware). + +### File Boundaries +- Do not modify or commit files in the `build-psp/` directory (these are generated outputs, such as intermediate `.o` / `.obj` files, and raw ELF binaries). +- Do not manually construct or commit `PARAM.SFO`. It is generated automatically during the build process via `mksfo` or `mksfoex`. +- Do not add Windows or other platform-specific metadata files (e.g., MSVC projects) as this build focuses on PSP hardware. diff --git a/CMakeLists.txt b/CMakeLists.txt index 06f1426..9d557bd 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,12 @@ cmake_minimum_required(VERSION 3.24) project(gravity_defied_cpp C CXX) -unset(ENV{PKG_CONFIG_PATH}) - -if(WIN32) - include(cmake/mingw-sdl2.cmake) - download_mingw_sdl2( - "2.26.1" # SDL2 - "2.20.1" # SDL2_ttf - "2.6.2" # SDL2_image - ) -endif() - -find_package(PkgConfig REQUIRED) - -pkg_search_module(SDL2 REQUIRED IMPORTED_TARGET sdl2) -pkg_search_module(SDL2_TTF REQUIRED IMPORTED_TARGET SDL2_ttf) -pkg_search_module(SDL2_IMAGE REQUIRED IMPORTED_TARGET SDL2_image) +execute_process( + COMMAND psp-config --psp-prefix + OUTPUT_VARIABLE PSP_PREFIX_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) -# Create a sources variable with a link to all cpp files to compile set(SOURCES src/utils/Time.cpp src/utils/EmbedFileStream.cpp @@ -46,6 +34,10 @@ set(SOURCES src/lcdui/FontStorage.cpp src/rms/RecordEnumerationImpl.cpp src/rms/RecordStore.cpp + src/psp/glib2d.c + src/psp/intra/intraFont.c + src/psp/intra/libccc.c + src/psp/pspSetup.cpp ) include(cmake/CMakeRC.cmake) @@ -68,43 +60,34 @@ cmrc_add_resource_library(Assets assets/FontSansSerif.ttf ) -# Compile resources when building for Windows. -if(WIN32) - enable_language(RC) - list(APPEND SOURCES winres/res.rc) -endif() - add_executable(GravityDefied ${SOURCES}) target_compile_features(GravityDefied PRIVATE cxx_std_17) -target_compile_options(GravityDefied PRIVATE -Wall -Wextra) +target_compile_options(GravityDefied PRIVATE -Wall -Wextra -fno-exceptions -fno-rtti) target_include_directories(GravityDefied PRIVATE - ${SDL2_INCLUDE_DIRS} - ${SDL2_TTF_INCLUDE_DIRS} - ${SDL2_IMAGE_INCLUDE_DIRS}) - -target_link_directories(GravityDefied PRIVATE - ${SDL2_LIBRARY_DIRS} - ${SDL2_TTF_LIBRARY_DIRS} - ${SDL2_IMAGE_LIBRARY_DIRS}) + src + src/psp + src/psp/intra + ${PSP_PREFIX_DIR}/include/freetype2 + $ENV{PSPDEV}/psp/include/freetype2) set_target_properties(GravityDefied PROPERTIES LINK_FLAGS_RELEASE -s) -if(WIN32) - target_link_options(GravityDefied PRIVATE -static -mwindows) - target_link_libraries(GravityDefied - ${SDL2_STATIC_LIBRARIES} - ${SDL2_TTF_STATIC_LIBRARIES} - ${SDL2_IMAGE_STATIC_LIBRARIES} - Assets::Assets) -else() - target_link_libraries(GravityDefied - ${SDL2_LIBRARIES} - ${SDL2_TTF_LIBRARIES} - ${SDL2_IMAGE_LIBRARIES} - Assets::Assets) -endif() +target_link_libraries(GravityDefied PRIVATE + png jpeg bz2 pspgu pspgum pspdisplay pspge pspctrl psppower psprtc pspvram + freetype z m stdc++ + Assets::Assets) # Copy assets to the build directory file(COPY assets/levels.mrg DESTINATION ${CMAKE_BINARY_DIR}) + +if(PSP OR DEFINED ENV{PSPDEV}) + create_pbp_file( + TARGET GravityDefied + TITLE "Gravity Defied: Trial Racing" + ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets/ICON0.png" + PREVIEW_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets/PIC0.png" + BACKGROUND_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets/PIC1.png" + ) +endif() diff --git a/README.md b/README.md index 3e357bf..52a8fff 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,62 @@ -# Gravity Defied Cpp - -![](./screens/demo1.gif) +# Gravity Defied C++ PSP (AI Homebrew Port) **Gravity Defied** is a legendary mototrial racing mobile game. It was originally developed by Codebrew Software in 2004 for J2ME platform. -This is the C++ & SDL2 port of Gravity Defied, which includes all the features of the original game. +This project is an experimental native PlayStation Portable (PSP) homebrew port based on the C++ / SDL2 codebase, brought to the platform with the assistance of AI development tools (Google Jules). It aims to deliver all features and authentic physics of the original game directly on real PSP hardware and emulators. + +***Disclaimer +This project is an unofficial fan homebrew and is not associated with Codebrew Software in any fashion. All rights to the original Gravity Defied, including its name, logotype, brand, assets, and original codebase, belong to Codebrew Software.*** + +## Custom Level Packs + +The release build includes the original track set along with **5 top community level packs from [gdtr.net](https://gdtr.net)** ready to play. + +### Adding Your Own Tracks +Put any `.mrg` level pack files into the `levels/` folder inside the game directory: +`ms0:/PSP/GAME//levels/your_pack.mrg` + +Select your pack in **Menu > Play > Level pack:**. Each pack maintains its own independent track progress and saves. + +--- + +## Controls + +### Menu Navigation -***This Project is not associated with Codebrew Software in any fashion. All rights to the original Gravity Defied, it's name, logotype, brand and all that stuff belong to Codebrew Software.*** +| Button | Action | +| :--- | :--- | +| **D-Pad / Analog Stick** | Navigate menu options | +| **✕ (Cross) / START** | Confirm / Select | +| **○ (Circle)** | Back / Cancel | +| **HOME / PS** | Open standard PSP exit dialog | +| **△, □, SELECT, L, R** | Disabled in menus | + +--- + +### In-Game (Race) + +Controls replicate the authentic J2ME mobile phone keypad layout: + +| Action | D-Pad / Analog Stick | Face Buttons | J2ME Equivalent | +| :--- | :--- | :--- | :--- | +| **Accelerate (Gas)** | **Up** | **△ (Triangle)** | `KEY_NUM2` / `UP` | +| **Brake / Reverse** | **Down** | **✕ (Cross)** | `KEY_NUM8` / `DOWN` | +| **Lean Backward** | **Left** | **□ (Square)** | `KEY_NUM4` / `LEFT` | +| **Lean Forward** | **Right** | **○ (Circle)** | `KEY_NUM6` / `RIGHT` | + +#### Shortcuts & System + +* **L Trigger:** Gas + Lean Backward (`KEY_NUM1`) +* **R Trigger:** Gas + Lean Forward (`KEY_NUM3`) +* **START:** Pause / Resume +* **HOME / PS:** Standard PSP firmware exit dialog # Authors +### PSP Homebrew port +* **[bzz11g](https://github.com/bzz11g)** +* **jules.google.com** + ### Gravity Defied Cpp authors * **[rgimad](https://github.com/rgimad)** - idea & coding * **[AntonEvmenenko](https://github.com/AntonEvmenenko)** - coding @@ -24,5 +71,3 @@ This is the C++ & SDL2 port of Gravity Defied, which includes all the features o For more information, please visit official site of Codebrew Software: [codebrew.se](http://codebrew.se) # Contributing - -If you want to contribute, feel free to discuss your idea with the rest of the developers in our [Telegram group](https://t.me/+sOfxjrsbkbEwNzc6) diff --git a/assets/ICON0.png b/assets/ICON0.png new file mode 100644 index 0000000..1e8f7e0 Binary files /dev/null and b/assets/ICON0.png differ diff --git a/assets/PARAM.SFO b/assets/PARAM.SFO new file mode 100644 index 0000000..155d849 Binary files /dev/null and b/assets/PARAM.SFO differ diff --git a/assets/PIC0.png b/assets/PIC0.png new file mode 100644 index 0000000..2b4312c Binary files /dev/null and b/assets/PIC0.png differ diff --git a/assets/PIC1.png b/assets/PIC1.png new file mode 100644 index 0000000..1aaa6b7 Binary files /dev/null and b/assets/PIC1.png differ diff --git a/patch_main.diff b/patch_main.diff new file mode 100644 index 0000000..c385f81 --- /dev/null +++ b/patch_main.diff @@ -0,0 +1,47 @@ +<<<<<<< SEARCH +#ifdef PSP +#include +PSP_MODULE_INFO("GravityDefied", 0, 1, 0); +PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU); +#endif + +int main(int argc, char** argv) +{ + try { + std::unique_ptr micro = std::make_unique(); +======= +#ifdef PSP +#include +PSP_MODULE_INFO("GravityDefied", 0, 1, 0); +PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU); + +int exit_callback(int arg1, int arg2, void *common) { + sceKernelExitGame(); + return 0; +} + +int CallbackThread(SceSize args, void *argp) { + int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); + sceKernelRegisterExitCallback(cbid); + sceKernelSleepThreadCB(); + return 0; +} + +int SetupCallbacks(void) { + int thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0); + if(thid >= 0) { + sceKernelStartThread(thid, 0, 0); + } + return thid; +} +#endif + +int main(int argc, char** argv) +{ +#ifdef PSP + SetupCallbacks(); +#endif + + try { + std::unique_ptr micro = std::make_unique(); +>>>>>>> REPLACE diff --git a/screens/demo1.gif b/screens/demo1.gif deleted file mode 100644 index 39eeadb..0000000 Binary files a/screens/demo1.gif and /dev/null differ diff --git a/src/GameCanvas.cpp b/src/GameCanvas.cpp index 46aec65..b284bdd 100644 --- a/src/GameCanvas.cpp +++ b/src/GameCanvas.cpp @@ -355,8 +355,6 @@ void GameCanvas::renderFender(int x, int y, int angleF16) void GameCanvas::clearScreenWithWhite() { - graphics->setColor(255, 255, 255); - graphics->fillRect(0, 0, width, height2); } void GameCanvas::setColor(int red, int green, int blue) diff --git a/src/GameLevel.cpp b/src/GameLevel.cpp index 5b9bcbc..629fc5c 100644 --- a/src/GameLevel.cpp +++ b/src/GameLevel.cpp @@ -65,8 +65,8 @@ int GameLevel::method_181(int var1) void GameLevel::setMinMaxX(int minX, int maxX) { - this->minX = minX << 16 >> 3; - this->maxX = maxX << 16 >> 3; + this->minX = (minX - 128) << 16 >> 3; + this->maxX = (maxX + 128) << 16 >> 3; } void GameLevel::method_183(int var1, int var2) @@ -134,6 +134,9 @@ void GameLevel::renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16) var10 = (int)(((int64_t)var10 << 32) / (int64_t)(var11 >> 1 >> 1) >> 16); gameCanvas->setColor(0, 170, 0); + bool startFlagRendered = false; + bool finishFlagRendered = false; + while (lineNo < pointsCount - 1) { int var4 = var9; int var5 = var10; @@ -158,17 +161,35 @@ void GameLevel::renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16) // render far start flag gameCanvas->renderStartFlag((pointPositions[startFlagPoint][0] + var4) << 3 >> 16, (pointPositions[startFlagPoint][1] + var5) << 3 >> 16); gameCanvas->setColor(0, 170, 0); + startFlagRendered = true; } if (finishFlagPoint == lineNo) { // render far finish flag gameCanvas->renderFinishFlag((pointPositions[finishFlagPoint][0] + var4) << 3 >> 16, (pointPositions[finishFlagPoint][1] + var5) << 3 >> 16); gameCanvas->setColor(0, 170, 0); + finishFlagRendered = true; } if (pointPositions[lineNo][0] > maxX) { break; } ++lineNo; } + + if (!startFlagRendered && startFlagPoint < pointsCount) { + int sx = (pointPositions[startFlagPoint][0] << 3 >> 16) + gameCanvas->getDx(); + if (sx >= -64 && sx <= gameCanvas->getWidth() + 64) { + gameCanvas->renderStartFlag(pointPositions[startFlagPoint][0] << 3 >> 16, pointPositions[startFlagPoint][1] << 3 >> 16); + gameCanvas->setColor(0, 170, 0); + } + } + if (!finishFlagRendered && finishFlagPoint < pointsCount) { + int fx = (pointPositions[finishFlagPoint][0] << 3 >> 16) + gameCanvas->getDx(); + if (fx >= -64 && fx <= gameCanvas->getWidth() + 64) { + gameCanvas->renderFinishFlag(pointPositions[finishFlagPoint][0] << 3 >> 16, pointPositions[finishFlagPoint][1] << 3 >> 16); + gameCanvas->setColor(0, 170, 0); + } + } + gameCanvas->drawLine(pointPositions[pointsCount - 1][0] << 3 >> 16, pointPositions[pointsCount - 1][1] << 3 >> 16, (pointPositions[pointsCount - 1][0] + var9) << 3 >> 16, (pointPositions[pointsCount - 1][1] + var10) << 3 >> 16); if (LevelLoader::isEnabledShadows) { renderShadow(gameCanvas, var7, var8); @@ -183,21 +204,42 @@ void GameLevel::renderTrackNearestGreenLine(GameCanvas* gameCanvas) if (pointNo > 0) { --pointNo; } + + bool startFlagRendered = false; + bool finishFlagRendered = false; + while (pointNo < pointsCount - 1) { gameCanvas->drawLine(pointPositions[pointNo][0] << 3 >> 16, pointPositions[pointNo][1] << 3 >> 16, pointPositions[pointNo + 1][0] << 3 >> 16, pointPositions[pointNo + 1][1] << 3 >> 16); if (startFlagPoint == pointNo) { gameCanvas->renderStartFlag(pointPositions[startFlagPoint][0] << 3 >> 16, pointPositions[startFlagPoint][1] << 3 >> 16); gameCanvas->setColor(0, 255, 0); + startFlagRendered = true; } if (finishFlagPoint == pointNo) { gameCanvas->renderFinishFlag(pointPositions[finishFlagPoint][0] << 3 >> 16, pointPositions[finishFlagPoint][1] << 3 >> 16); gameCanvas->setColor(0, 255, 0); + finishFlagRendered = true; } if (pointPositions[pointNo][0] > maxX) { break; } ++pointNo; } + + if (!startFlagRendered && startFlagPoint < pointsCount) { + int sx = (pointPositions[startFlagPoint][0] << 3 >> 16) + gameCanvas->getDx(); + if (sx >= -64 && sx <= gameCanvas->getWidth() + 64) { + gameCanvas->renderStartFlag(pointPositions[startFlagPoint][0] << 3 >> 16, pointPositions[startFlagPoint][1] << 3 >> 16); + gameCanvas->setColor(0, 255, 0); + } + } + if (!finishFlagRendered && finishFlagPoint < pointsCount) { + int fx = (pointPositions[finishFlagPoint][0] << 3 >> 16) + gameCanvas->getDx(); + if (fx >= -64 && fx <= gameCanvas->getWidth() + 64) { + gameCanvas->renderFinishFlag(pointPositions[finishFlagPoint][0] << 3 >> 16, pointPositions[finishFlagPoint][1] << 3 >> 16); + gameCanvas->setColor(0, 255, 0); + } + } } void GameLevel::load(FileStream* inStream) diff --git a/src/GameMenu.cpp b/src/GameMenu.cpp index b3b4bd1..3de8a35 100644 --- a/src/GameMenu.cpp +++ b/src/GameMenu.cpp @@ -41,7 +41,7 @@ GameMenu::GameMenu(std::string var1, Micro* micro, GameMenu* var3, char* inputSt } field_104 = xPos + 7; - field_103 = 2; + field_103 = 0; field_110 = 0; if (field_94 != "") { field_107 = (canvasHeight - (field_101 << 1) - 10 - font->getBaselinePosition()) / (font2->getBaselinePosition() + field_103); diff --git a/src/LevelLoader.cpp b/src/LevelLoader.cpp index fa3d73c..ade89b6 100644 --- a/src/LevelLoader.cpp +++ b/src/LevelLoader.cpp @@ -18,33 +18,54 @@ const int LevelLoader::field_118 = 1; bool LevelLoader::isEnabledPerspective = true; bool LevelLoader::isEnabledShadows = true; -LevelLoader::LevelLoader(const std::filesystem::path& mrgFilePath) +LevelLoader::LevelLoader(const std::string& mrgFilePath) { for (int i = 0; i < 3; ++i) { field_123[i] = (int)((int64_t)((GamePhysics::const175_1_half[i] + 19660) >> 1) * (int64_t)((GamePhysics::const175_1_half[i] + 19660) >> 1) >> 16); field_124[i] = (int)((int64_t)((GamePhysics::const175_1_half[i] - 19660) >> 1) * (int64_t)((GamePhysics::const175_1_half[i] - 19660) >> 1) >> 16); } - if (!mrgFilePath.string().empty()) { - FileStream* fileStream = new FileStream(mrgFilePath, std::ios::in | std::ios::binary); - if (!fileStream->isOpen()) { - throw std::system_error(errno, std::system_category(), "Failed to open " + mrgFilePath.string()); + levelFileStream = nullptr; + load(mrgFilePath); +} + +LevelLoader::~LevelLoader() +{ + if (levelFileStream != nullptr) { + delete levelFileStream; + } +} + +void LevelLoader::load(const std::string& mrgFilePath) +{ + if (levelFileStream != nullptr) { + delete levelFileStream; + levelFileStream = nullptr; + } + + levelNames = std::vector>(3); + levelOffsetInFile = std::vector>(3); + + FileStream* fileStream = nullptr; + if (!mrgFilePath.empty()) { + fileStream = new FileStream(mrgFilePath, std::ios::in | std::ios::binary); + } + + if (fileStream == nullptr || !fileStream->isOpen()) { + if (fileStream != nullptr) { + delete fileStream; + fileStream = nullptr; } - levelFileStream = fileStream; - } else { EmbedFileStream* embedFileStream = new EmbedFileStream("levels.mrg"); levelFileStream = static_cast(embedFileStream); + } else { + levelFileStream = fileStream; } loadLevels(); method_87(); } -LevelLoader::~LevelLoader() -{ - delete levelFileStream; -} - void LevelLoader::loadLevels() { std::vector var3(40); diff --git a/src/LevelLoader.h b/src/LevelLoader.h index 66c6912..856c855 100644 --- a/src/LevelLoader.h +++ b/src/LevelLoader.h @@ -29,6 +29,7 @@ class LevelLoader { void loadLevels(); public: + void load(const std::string& mrgFilePath); static const int field_114; static const int field_115; static const int field_116; @@ -46,7 +47,7 @@ class LevelLoader { int field_137; int field_138; - LevelLoader(const std::filesystem::path& mrgFilePath); + LevelLoader(const std::string& mrgFilePath); ~LevelLoader(); std::string getName(int league, int level); diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index caff4e0..535eca7 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -1,3 +1,4 @@ +#include #include "MenuManager.h" #include "rms/RecordStoreException.h" #include "rms/RecordStoreNotOpenException.h" @@ -25,7 +26,6 @@ void MenuManager::initPart(int var1) case 1: field_341 = defaultInputString; field_374 = { "On", "Off" }; - field_375 = { "Keyset 1", "Keyset 2", "Keyset 3" }; recordManager = new RecordManager(); field_337 = -1L; field_338 = -1; @@ -38,72 +38,18 @@ void MenuManager::initPart(int var1) field_278[var11] = -127; } - try { - recordStore = RecordStore::openRecordStore("GDTRStates", true); - isRecordStoreOpened = true; - return; - } catch (RecordStoreException& var9) { - isRecordStoreOpened = false; - return; - } - case 2: { - recorcStoreRecordId = -1; - - RecordEnumeration* records; - try { - records = recordStore->enumerateRecords(nullptr, nullptr, false); - } catch (RecordStoreNotOpenException& var8) { - return; - } - - std::vector var3; - if (records->numRecords() > 0) { - try { - var3 = records->nextRecord(); - records->reset(); - recorcStoreRecordId = records->nextRecordId(); - } catch (RecordStoreException& var7) { - return; - } - - if (var3.size() <= 19) { - for (std::size_t i = 0; i < var3.size(); ++i) { - field_278[i] = var3[i]; - } - } - - records->destroy(); - } - - var3 = method_216(16, (int8_t)-1); - if (!var3.empty() && var3[0] != -1) { - for (var4 = 0; var4 < 3; ++var4) { - field_341[var4] = var3[var4]; - } - } - - if (field_341[0] == 82 && field_341[1] == 75 && field_341[2] == 69) { - availableLeagues = 3; - field_344 = 2; - field_342[0] = (int8_t)(micro->levelLoader->levelNames[0].size() - 1); - field_342[1] = (int8_t)(micro->levelLoader->levelNames[1].size() - 1); - field_342[2] = (int8_t)(micro->levelLoader->levelNames[2].size() - 1); - return; - } - - availableLeagues = 0; - field_344 = 1; - field_342[0] = 0; - field_342[1] = 0; - field_342[2] = -1; - } + recordStore = RecordStore::openRecordStore("GDTRStates", true); + isRecordStoreOpened = (recordStore != nullptr); + return; + case 2: + loadStateFromRecordStore(); return; case 3: isDisablePerspective = method_217(0, isDisablePerspective); isDisabledShadows = method_217(1, isDisabledShadows); isDisabledDriverSprite = method_217(2, isDisabledDriverSprite); isDisabledBikeSprite = method_217(3, isDisabledBikeSprite); - field_367 = method_217(14, field_367); + field_367 = 0; isDisableLookAhead = method_217(4, isDisableLookAhead); field_369 = method_217(11, field_369); field_370 = method_217(10, field_370); @@ -121,9 +67,9 @@ void MenuManager::initPart(int var1) } } - try { - field_345.at(field_370) = field_369; - } catch (std::exception& var6) { + if (field_370 >= 0 && field_370 < static_cast(field_345.size())) { + field_345[field_370] = field_369; + } else { field_370 = 0; field_369 = 0; field_345[field_370] = field_369; @@ -186,9 +132,9 @@ void MenuManager::initPart(int var1) settingsStringTrack = new SettingsStringRender("Track", field_345[field_370], this, levelNames[field_370], false, micro, gameMenuPlay, false); settingsStringLeague = new SettingsStringRender("League", field_371, this, leagueNames, false, micro, gameMenuPlay, false); - try { + if (field_370 >= 0 && field_370 < 4) { settingsStringTrack->setAvailableOptions(field_342[field_370]); - } catch (std::exception& var5) { + } else { settingsStringTrack->setAvailableOptions(0); } @@ -198,6 +144,34 @@ void MenuManager::initPart(int var1) gameMenuHighscore->addMenuElement(settingStringBack); taskStart = new SettingsStringRender("Start>", 0, this, std::vector(), false, micro, gameMenuMain, true); gameMenuPlay->addMenuElement(taskStart); + + + + packNames.clear(); + packPaths.clear(); + packNames.push_back("Original"); + packPaths.push_back(""); + + { + DIR* dir = opendir("levels"); + if (dir != nullptr) { + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + std::string name = entry->d_name; + if (name.length() > 4 && name.substr(name.length() - 4) == ".mrg") { + packNames.push_back(name.substr(0, name.length() - 4)); + packPaths.push_back("levels/" + name); + } + } + closedir(dir); + } + } + + settingStringPack = new SettingsStringRender("Level pack", 0, this, packNames, false, micro, gameMenuPlay, false); + settingStringPack->setDisableHorizontalCycling(true); + gameMenuPacks = settingStringPack->getGameMenu(); + gameMenuPlay->addMenuElement(settingStringPack); + gameMenuPlay->addMenuElement(settingStringLevel); gameMenuPlay->addMenuElement(settingsStringTrack); gameMenuPlay->addMenuElement(settingsStringLeague); @@ -208,7 +182,6 @@ void MenuManager::initPart(int var1) shadowsSetting = new SettingsStringRender("Shadows", isDisabledShadows, this, field_374, true, micro, gameMenuOptions, false); driverSpriteSetting = new SettingsStringRender("Driver sprite", isDisabledDriverSprite, this, field_374, true, micro, gameMenuOptions, false); bikeSpriteSetting = new SettingsStringRender("Bike sprite", isDisabledBikeSprite, this, field_374, true, micro, gameMenuOptions, false); - inputSetting = new SettingsStringRender("Input", field_367, this, field_375, false, micro, gameMenuOptions, false); lookAheadSetting = new SettingsStringRender("Look ahead", isDisableLookAhead, this, field_374, true, micro, gameMenuOptions, false); clearHighscoreSetting = new TimerOrMotoPartOrMenuElem("Clear highscore", gameMenuConfirmClear, this); return; @@ -217,7 +190,6 @@ void MenuManager::initPart(int var1) gameMenuOptions->addMenuElement(shadowsSetting); gameMenuOptions->addMenuElement(driverSpriteSetting); gameMenuOptions->addMenuElement(bikeSpriteSetting); - gameMenuOptions->addMenuElement(inputSetting); gameMenuOptions->addMenuElement(lookAheadSetting); gameMenuOptions->addMenuElement(clearHighscoreSetting); gameMenuOptions->addMenuElement(settingStringBack); @@ -240,14 +212,8 @@ void MenuManager::initPart(int var1) gameMenuHelp->addMenuElement(field_318); field_319 = new GameMenu("Keys", micro, gameMenuHelp); field_320 = new TimerOrMotoPartOrMenuElem("Keys", field_319, this); - addTextRender(field_319, "- " + field_375[0] + " -"); + addTextRender(field_319, "- Controls -"); addTextRender(field_319, "UP accelerates, DOWN brakes, RIGHT leans forward and LEFT leans backward. 1 accelerates and leans backward. 3 accelerates and leans forward. 7 brakes and leans backward. 9 brakes and leans forward."); - field_319->addMenuElement(field_376.get()); - addTextRender(field_319, "- " + field_375[1] + " -"); - addTextRender(field_319, "1 accelerates, 4 brakes, 6 leans forward and 5 leans backward."); - field_319->addMenuElement(field_376.get()); - addTextRender(field_319, "- " + field_375[2] + " -"); - addTextRender(field_319, "3 accelerates, 6 brakes, 5 leans forward and 4 leans backward."); field_319->addMenuElement(settingStringBack); gameMenuHelp->addMenuElement(field_320); field_321 = new GameMenu("Unlocking", micro, gameMenuHelp); @@ -277,9 +243,6 @@ void MenuManager::initPart(int var1) addTextRender(gameMenuOptions2, "Bike Sprite: On / Off"); addTextRender(gameMenuOptions2, "Default: . uses a texture for the bike. uses line graphics."); gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Input: Keyset 1,2,3 "); - addTextRender(gameMenuOptions2, "Default: <1>. Determines which type of input should be used when playing. See \"Keys\" in the help menu for more info."); - gameMenuOptions2->addMenuElement(field_376.get()); addTextRender(gameMenuOptions2, "Look ahead: On/Off"); addTextRender(gameMenuOptions2, "Default: . Turns on and off smart camera movement."); gameMenuOptions2->addMenuElement(field_376.get()); @@ -313,6 +276,53 @@ void MenuManager::initPart(int var1) } } + +void MenuManager::loadStateFromRecordStore() +{ + recorcStoreRecordId = -1; + if (!recordStore) return; + + RecordEnumeration* records = recordStore->enumerateRecords(nullptr, nullptr, false); + if (!records) return; + + std::vector var3; + if (records->numRecords() > 0) { + var3 = records->nextRecord(); + records->reset(); + recorcStoreRecordId = records->nextRecordId(); + + if (var3.size() <= 19) { + for (std::size_t i = 0; i < var3.size(); ++i) { + field_278[i] = var3[i]; + } + } + + records->destroy(); + } + + var3 = method_216(16, (int8_t)-1); + if (!var3.empty() && var3[0] != -1) { + for (int var4 = 0; var4 < 3; ++var4) { + field_341[var4] = var3[var4]; + } + } + + if (field_341[0] == 82 && field_341[1] == 75 && field_341[2] == 69) { + availableLeagues = 3; + field_344 = 2; + field_342[0] = (int8_t)(micro->levelLoader->levelNames[0].size() > 0 ? micro->levelLoader->levelNames[0].size() - 1 : 0); + field_342[1] = (int8_t)(micro->levelLoader->levelNames[1].size() > 0 ? micro->levelLoader->levelNames[1].size() - 1 : 0); + field_342[2] = (int8_t)(micro->levelLoader->levelNames[2].size() > 0 ? micro->levelLoader->levelNames[2].size() - 1 : 0); + return; + } + + availableLeagues = 0; + field_344 = 1; + field_342[0] = 0; + field_342[1] = 0; + field_342[2] = -1; +} + void MenuManager::addTextRender(GameMenu* gameMenu, std::string text) { std::vector var3 = TextRender::makeMultilineTextRenders(text, micro); @@ -517,7 +527,7 @@ void MenuManager::method_201(int var1) int64_t currentTimeMillis = Time::currentTimeMillis(); micro->gameCanvas->isDrawingTime = false; int64_t var6 = 0L; - int8_t var8 = 50; + int8_t var8 = 30; micro->gamePhysics->method_53(); micro->gameToMenu(); @@ -532,30 +542,15 @@ void MenuManager::method_201(int var1) micro->gamePhysics->method_53(); repaint(); if ((var20 = Time::currentTimeMillis()) - var6 < (int64_t)var8) { - // try { - // synchronized (field_359) { - // field_359.wait((int64_t) var8 - (var20 - var6) < 1L ? 1L : (int64_t) var8 - (var20 - var6)); - // } - // } catch (InterruptedException var16) { - // } Time::sleep((int64_t)var8 - (var20 - var6) < 1L ? 1L : (int64_t)var8 - (var20 - var6)); - var6 = Time::currentTimeMillis(); } else { var6 = var20; } } else { - var8 = 50; + var8 = 30; if ((var20 = Time::currentTimeMillis()) - var6 < (int64_t)var8) { - // try { - // Object var21; - // synchronized (var21 = new Object()) { - // var21.wait((int64_t) var8 - (var20 - var6) < 1L ? 1L : (int64_t) var8 - (var20 - var6)); - // } - // } catch (InterruptedException var14) { - // } Time::sleep((int64_t)var8 - (var20 - var6) < 1L ? 1L : (int64_t)var8 - (var20 - var6)); - var6 = Time::currentTimeMillis(); } else { var6 = var20; @@ -738,11 +733,9 @@ void MenuManager::saveSmthToRecordStoreAndCloseIt() { if (isRecordStoreOpened) { method_208(); - - try { + if (recordStore) { recordStore->closeRecordStore(); isRecordStoreOpened = false; - } catch (RecordStoreException& var1) { } } @@ -751,13 +744,15 @@ void MenuManager::saveSmthToRecordStoreAndCloseIt() void MenuManager::method_208() { + if (!recordStore) return; + copyThreeBytesFromArr(16, field_341); setValue(0, (int8_t)perspectiveSetting->getCurrentOptionPos()); setValue(1, (int8_t)shadowsSetting->getCurrentOptionPos()); setValue(2, (int8_t)driverSpriteSetting->getCurrentOptionPos()); setValue(3, (int8_t)bikeSpriteSetting->getCurrentOptionPos()); - setValue(14, (int8_t)inputSetting->getCurrentOptionPos()); + setValue(14, 0); setValue(4, (int8_t)lookAheadSetting->getCurrentOptionPos()); setValue(5, (int8_t)settingsStringLeague->getMaxAvailableOptionPos()); setValue(6, (int8_t)settingStringLevel->getMaxAvailableOptionPos()); @@ -770,17 +765,9 @@ void MenuManager::method_208() } if (recorcStoreRecordId == -1) { - try { - recorcStoreRecordId = recordStore->addRecord(field_278, 0, 19); - } catch (RecordStoreNotOpenException& var2) { - } catch (RecordStoreException& var3) { - } + recorcStoreRecordId = recordStore->addRecord(field_278, 0, 19); } else { - try { - recordStore->setRecord(recorcStoreRecordId, field_278, 0, 19); - } catch (RecordStoreNotOpenException& var4) { - } catch (RecordStoreException& var5) { - } + recordStore->setRecord(recorcStoreRecordId, field_278, 0, 19); } } @@ -805,7 +792,39 @@ void MenuManager::showAlert(std::string title, std::string alertText, Image* ima void MenuManager::processMenu(IGameMenuElement* menuElement) { - if (menuElement == taskStart) { + if (menuElement == settingStringPack) { + if (settingStringPack->method_114()) { + method_1(gameMenuPacks, false); + gameMenuPacks->method_83(settingStringPack->getCurrentOptionPos()); + } else { + method_208(); + if (isRecordStoreOpened && recordStore) { + recordStore->closeRecordStore(); + } + + std::string packName = packNames[settingStringPack->getCurrentOptionPos()]; + RecordStore::setPackPrefix(packName == "Original" ? "" : packName + "_"); + + recordStore = RecordStore::openRecordStore("GDTRStates", true); + isRecordStoreOpened = (recordStore != nullptr); + + micro->levelLoader->load(packPaths[settingStringPack->getCurrentOptionPos()]); + this->levelNames = micro->levelLoader->levelNames; + + loadStateFromRecordStore(); + + settingStringLevel->setCurentOptionPos(field_370); + settingsStringTrack->setCurentOptionPos(field_345[field_370]); + + settingStringLevel->setOptionsList(field_361); + settingStringLevel->setAvailableOptions(field_344); + settingStringLevel->init(); + + settingsStringTrack->setOptionsList(levelNames[field_370]); + settingsStringTrack->setAvailableOptions(field_342[field_370]); + settingsStringTrack->init(); + } + } else if (menuElement == taskStart) { if (settingStringLevel->getCurrentOptionPos() <= settingStringLevel->getMaxAvailableOptionPos() && settingsStringTrack->getCurrentOptionPos() <= settingsStringTrack->getMaxAvailableOptionPos() && settingsStringLeague->getCurrentOptionPos() <= settingsStringLeague->getMaxAvailableOptionPos()) { micro->gamePhysics->disableGenerateInputAI(); micro->levelLoader->method_88(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()); @@ -832,15 +851,6 @@ void MenuManager::processMenu(IGameMenuElement* menuElement) return; } } else { - if (menuElement == inputSetting) { - if (inputSetting->method_114()) { - inputSetting->setCurentOptionPos(inputSetting->getCurrentOptionPos() + 1); - } - - micro->gameCanvas->method_163(inputSetting->getCurrentOptionPos()); - return; - } - if (menuElement == lookAheadSetting) { micro->gamePhysics->setEnableLookAhead(lookAheadSetting->getCurrentOptionPos() == 0); return; @@ -1096,7 +1106,6 @@ void MenuManager::exit() field_341[0] = 65; field_341[1] = 65; field_341[2] = 65; - inputSetting->setCurentOptionPos(0); field_342[0] = 0; field_342[1] = 0; field_342[2] = -1; diff --git a/src/MenuManager.h b/src/MenuManager.h index 45fae25..e24f9c0 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -38,6 +39,10 @@ class MenuManager : public IMenuManager { TimerOrMotoPartOrMenuElem* taskPlayMenu; TimerOrMotoPartOrMenuElem* taskOptions; TimerOrMotoPartOrMenuElem* taskHelp; + SettingsStringRender* settingStringPack; + std::vector packNames; + std::vector packPaths; + GameMenu* gameMenuPacks; SettingsStringRender* settingStringLevel; GameMenu* gameMenuStringLevel; SettingsStringRender* settingsStringTrack; @@ -51,7 +56,6 @@ class MenuManager : public IMenuManager { SettingsStringRender* shadowsSetting; SettingsStringRender* driverSpriteSetting; SettingsStringRender* bikeSpriteSetting; - SettingsStringRender* inputSetting; SettingsStringRender* lookAheadSetting; TimerOrMotoPartOrMenuElem* clearHighscoreSetting; TimerOrMotoPartOrMenuElem* field_313; @@ -114,11 +118,11 @@ class MenuManager : public IMenuManager { int8_t field_372 = 0; int8_t field_373 = 0; std::vector field_374; - std::vector field_375; std::unique_ptr field_376; // Alert alert = nullptr; // TODO void addTextRender(GameMenu* gameMenu, std::string text); + void loadStateFromRecordStore(); void method_197(); void fillCanvasWithImage(Graphics* graphics); void processNonFireKeyCode(int keyCode); diff --git a/src/Micro.cpp b/src/Micro.cpp index 13f1bee..f2e2cec 100644 --- a/src/Micro.cpp +++ b/src/Micro.cpp @@ -140,23 +140,28 @@ void Micro::destroyApp(bool var1) (void)var1; field_249 = false; field_242 = true; - menuManager->saveSmthToRecordStoreAndCloseIt(); + if (menuManager != nullptr) { + menuManager->saveSmthToRecordStoreAndCloseIt(); + } + RecordStore::saveAllOpened(); } void Micro::startApp(int argc, char** argv) { - if (argc > 1) { + const char* progName = (argc > 0 && argv != nullptr && argv[0] != nullptr) ? argv[0] : nullptr; + + if (argc > 1 && argv != nullptr && argv[1] != nullptr) { std::string argv1(argv[1]); if (argv1 == "-h" || argv1 == "--help") { - showHelp(argv[0]); + showHelp(progName != nullptr ? progName : "GravityDefied"); return; } this->mrgFilePath = argv1; } - RecordStore::setRecordStoreDir(argv[0]); + RecordStore::setRecordStoreDir(progName); field_249 = true; // if (thread == null) { @@ -190,113 +195,81 @@ void Micro::run() menuManager->method_211(var5); } - bool var10000; - try { - if (isInGameMenu) { - menuManager->method_201(1); - if (menuManager->method_196()) { - restart(true); - } + if (isInGameMenu) { + menuManager->method_201(1); + if (menuManager->method_196()) { + restart(true); } + } - for (int i = numPhysicsLoops; i > 0; --i) { - if (field_248) { - gameTimeMs += 20L; - } + for (int i = numPhysicsLoops; i > 0; --i) { + if (field_248) { + gameTimeMs += 20L; + } - if (timeMs == 0L) { - timeMs = Time::currentTimeMillis(); - } + if (timeMs == 0L) { + timeMs = Time::currentTimeMillis(); + } - if ((var5 = gamePhysics->updatePhysics()) == 3 && field_246 == 0L) { - field_246 = Time::currentTimeMillis() + 3000L; - gameCanvas->scheduleGameTimerTask("Crashed", 3000); - gameCanvas->repaint(); - gameCanvas->serviceRepaints(); + if ((var5 = gamePhysics->updatePhysics()) == 3 && field_246 == 0L) { + field_246 = Time::currentTimeMillis() + 3000L; + gameCanvas->scheduleGameTimerTask("Crashed", 3000); + gameCanvas->repaint(); + gameCanvas->serviceRepaints(); + } + + if (field_246 != 0L && field_246 < Time::currentTimeMillis()) { + restart(true); + } + + if (var5 == 5) { + gameCanvas->scheduleGameTimerTask("Crashed", 3000); + gameCanvas->repaint(); + gameCanvas->serviceRepaints(); + + int64_t var7 = 1000L; + if (field_246 > 0L) { + var7 = std::min(field_246 - Time::currentTimeMillis(), static_cast(1000)); } - if (field_246 != 0L && field_246 < Time::currentTimeMillis()) { - restart(true); + if (var7 > 0L) { + Time::sleep(var7); } - if (var5 == 5) { - gameCanvas->scheduleGameTimerTask("Crashed", 3000); - gameCanvas->repaint(); - gameCanvas->serviceRepaints(); - - // try { - // long var7 = 1000L; - // if (this.field_246 > 0L) { - // var7 = Math.min(this.field_246 - System.currentTimeMillis(), 1000L); - // } - - // if (var7 > 0L) { - // Thread.sleep(var7); - // } - // } catch (InterruptedException var12) { - // } - int64_t var7 = 1000L; - if (field_246 > 0L) { - var7 = std::min(field_246 - Time::currentTimeMillis(), static_cast(1000)); - } - - if (var7 > 0L) { - Time::sleep(var7); - } + restart(true); + } else if (var5 == 4) { + timeMs = 0L; + gameTimeMs = 0L; + } else if (var5 == 1 || var5 == 2) { + if (var5 == 2) { + gameTimeMs -= 10L; + } + goalLoop(); + menuManager->method_215(gameTimeMs / 10L); + menuManager->method_201(2); + if (menuManager->method_196()) { restart(true); - } else if (var5 == 4) { - timeMs = 0L; - gameTimeMs = 0L; - } else if (var5 == 1 || var5 == 2) { - if (var5 == 2) { - gameTimeMs -= 10L; - } - - goalLoop(); - menuManager->method_215(gameTimeMs / 10L); - menuManager->method_201(2); - if (menuManager->method_196()) { - restart(true); - } - - if (!field_249) { - break; - } } - field_248 = var5 != 4; + if (!field_249) { + break; + } } - var10000 = field_249; - } catch (std::exception& var15) { - continue; + field_248 = var5 != 4; } - if (!var10000) { - break; + gamePhysics->method_53(); + int64_t var1; + if ((var1 = Time::currentTimeMillis()) - var3 < 30L) { + Time::sleep(std::max(30LL - (var1 - var3), 1LL)); + var3 = Time::currentTimeMillis(); + } else { + var3 = var1; } - try { - gamePhysics->method_53(); - int64_t var1; - if ((var1 = Time::currentTimeMillis()) - var3 < 30L) { - // try { - // synchronized (this) { - // wait(Math.max(30L - (var1 - var3), 1L)); - // } - // } catch (InterruptedException var11) { - // } - Time::sleep(std::max(30LL - (var1 - var3), 1LL)); - - var3 = Time::currentTimeMillis(); - } else { - var3 = var1; - } - - gameCanvas->repaint(); - } catch (std::exception& var14) { - } + gameCanvas->repaint(); } destroyApp(true); @@ -319,21 +292,10 @@ void Micro::goalLoop() for (int i = numPhysicsLoops; i > 0; --i) { if (gamePhysics->updatePhysics() == 5) { - // try { - // long deltaTime; - // if ((deltaTime = timeMs - System.currentTimeMillis()) > 0L) { - // Thread.sleep(deltaTime); - // } - - // return; - // } catch (InterruptedException var12) { - // return; - // } int64_t deltaTime; if ((deltaTime = timeMs - Time::currentTimeMillis()) > 0L) { Time::sleep(deltaTime); } - return; } } @@ -341,14 +303,7 @@ void Micro::goalLoop() gamePhysics->method_53(); int64_t var2; if ((var2 = Time::currentTimeMillis()) - var4 < 30L) { - // try { - // synchronized (this) { - // wait(Math.max(30L - (var2 - var4), 1L)); - // } - // } catch (InterruptedException var14) { - // } Time::sleep(std::max(30LL - (var2 - var4), 1LL)); - var4 = Time::currentTimeMillis(); } else { var4 = var2; diff --git a/src/Micro.h b/src/Micro.h index 073617c..6446661 100644 --- a/src/Micro.h +++ b/src/Micro.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include class GameCanvas; @@ -12,7 +11,7 @@ class Micro { private: int64_t goLoadingStep(); void destroyApp(bool var1); - std::filesystem::path mrgFilePath; + std::string mrgFilePath; public: GameCanvas* gameCanvas; diff --git a/src/RecordManager.cpp b/src/RecordManager.cpp index 3a4f57d..869d9ce 100644 --- a/src/RecordManager.cpp +++ b/src/RecordManager.cpp @@ -9,35 +9,23 @@ void RecordManager::method_8(int var1, int var2) { resetRecordsTime(); - try { - str = std::to_string(var1) + std::to_string(var2); - recordStore = RecordStore::openRecordStore(str, true); - } catch (RecordStoreException& var9) { + str = std::to_string(var1) + std::to_string(var2); + recordStore = RecordStore::openRecordStore(str, true); + if (!recordStore) { return; } packedRecordInfoRecordId = -1; - RecordEnumeration* recordEnum; - try { - recordEnum = recordStore->enumerateRecords(nullptr, nullptr, false); - } catch (RecordStoreNotOpenException& var8) { + RecordEnumeration* recordEnum = recordStore->enumerateRecords(nullptr, nullptr, false); + if (!recordEnum) { return; } if (recordEnum->numRecords() > 0) { - std::vector var4; - try { - var4 = recordEnum->nextRecord(); - recordEnum->reset(); - packedRecordInfoRecordId = recordEnum->nextRecordId(); - } catch (RecordStoreNotOpenException& var5) { - return; - } catch (InvalidRecordIDException& var6) { - return; - } catch (RecordStoreException& var7) { - return; - } + std::vector var4 = recordEnum->nextRecord(); + recordEnum->reset(); + packedRecordInfoRecordId = recordEnum->nextRecordId(); loadRecordInfo(var4); recordEnum->destroy(); @@ -156,19 +144,12 @@ std::vector RecordManager::getRecordDescription(int var1) void RecordManager::writeRecordInfo() { + if (!recordStore) return; getLevelInfo(packedRecordInfo); if (packedRecordInfoRecordId == -1) { - try { - packedRecordInfoRecordId = recordStore->addRecord(packedRecordInfo, 0, 96); - } catch (RecordStoreNotOpenException& var1) { - } catch (RecordStoreException& var2) { - } + packedRecordInfoRecordId = recordStore->addRecord(packedRecordInfo, 0, 96); } else { - try { - recordStore->setRecord(packedRecordInfoRecordId, packedRecordInfo, 0, 96); - } catch (RecordStoreNotOpenException& var3) { - } catch (RecordStoreException& var4) { - } + recordStore->setRecord(packedRecordInfoRecordId, packedRecordInfo, 0, 96); } } @@ -216,11 +197,7 @@ void RecordManager::deleteRecordStores() for (std::size_t i = 0; i < names.size(); ++i) { if (names[i] != "GDTRStates") { - try { - // RecordStore *var10000 = recordStore; - RecordStore::deleteRecordStore(names[i]); - } catch (RecordStoreException& var3) { - } + RecordStore::deleteRecordStore(names[i]); } } } @@ -228,10 +205,6 @@ void RecordManager::deleteRecordStores() void RecordManager::closeRecordStore() { if (recordStore != nullptr) { - try { - recordStore->closeRecordStore(); - return; - } catch (RecordStoreException& var1) { - } + recordStore->closeRecordStore(); } } diff --git a/src/SettingsStringRender.cpp b/src/SettingsStringRender.cpp index 9962e23..3b99e11 100644 --- a/src/SettingsStringRender.cpp +++ b/src/SettingsStringRender.cpp @@ -5,6 +5,11 @@ #include "GameCanvas.h" #include "lcdui/Graphics.h" +void SettingsStringRender::setDisableHorizontalCycling(bool disable) +{ + disableHorizontalCycling = disable; +} + SettingsStringRender::SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool var5, Micro* micro, GameMenu* gameMenu, bool useColon) { this->micro = micro; @@ -128,6 +133,7 @@ void SettingsStringRender::menuElemMethod(int var1) menuManager->processMenu(this); return; case 2: + if (disableHorizontalCycling) return; if (field_146) { if (currentOptionPos == 1) { currentOptionPos = 0; @@ -148,6 +154,7 @@ void SettingsStringRender::menuElemMethod(int var1) selectCurrentOptionName(); return; case 3: + if (disableHorizontalCycling) return; if (field_146) { if (currentOptionPos == 0) { currentOptionPos = 1; diff --git a/src/SettingsStringRender.h b/src/SettingsStringRender.h index f7953fd..535c400 100644 --- a/src/SettingsStringRender.h +++ b/src/SettingsStringRender.h @@ -27,10 +27,12 @@ class SettingsStringRender : public IMenuManager, public IGameMenuElement { bool hasSprite; bool isDrawSprite8; bool useColon; + bool disableHorizontalCycling = false; void selectCurrentOptionName(); public: + void setDisableHorizontalCycling(bool disable); SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool var5, Micro* micro, GameMenu* gameMenu, bool useColon); void setFlags(bool hasSprite, bool isDrawSprite8); void setOptionsList(std::vector var1); diff --git a/src/lcdui/Canvas.cpp b/src/lcdui/Canvas.cpp index db42151..778b075 100644 --- a/src/lcdui/Canvas.cpp +++ b/src/lcdui/Canvas.cpp @@ -11,7 +11,7 @@ Canvas::Canvas() { impl = std::make_unique(this); - graphics = std::make_unique(impl->getRenderer()); + graphics = std::make_unique(); } Canvas::~Canvas() @@ -45,10 +45,9 @@ CanvasImpl* Canvas::getCanvasImpl() void Canvas::repaint() { - // IMPROVE two lines below is a hack to make the game work in one thread + impl->clear(); paint(graphics.get()); impl->processEvents(); - impl->repaint(); } @@ -66,7 +65,7 @@ int Canvas::getGameAction(int keyCode) case Keys::FIRE: return keyCode; default: - throw std::runtime_error("getGameAction(" + std::to_string(keyCode) + ") isn't implemented!"); + return -1; } } @@ -103,4 +102,4 @@ void Canvas::pressedEsc() return; } } -} \ No newline at end of file +} diff --git a/src/lcdui/CanvasImpl.cpp b/src/lcdui/CanvasImpl.cpp index 7f1c807..33ffc96 100644 --- a/src/lcdui/CanvasImpl.cpp +++ b/src/lcdui/CanvasImpl.cpp @@ -1,63 +1,27 @@ #include "CanvasImpl.h" - -#include -#include -#include -#include -#include - #include "Canvas.h" +#include "../Micro.h" +#include "psp/glib2d.h" +#include +#include -CanvasImpl::CanvasImpl(Canvas* canvas) +CanvasImpl::CanvasImpl(Canvas* canvas) : canvas(canvas) { - this->canvas = canvas; - - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - throw std::runtime_error(SDL_GetError()); - } - - if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) { - throw std::runtime_error(IMG_GetError()); - } - - if (TTF_Init() == -1) { - throw std::runtime_error(TTF_GetError()); - } - - window = SDL_CreateWindow( - 0, - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - width, height, - SDL_WINDOW_SHOWN); - - if (!window) { - throw std::runtime_error(SDL_GetError()); - } - - renderer = SDL_CreateRenderer( - window, -1, SDL_RENDERER_ACCELERATED); - - if (!renderer) { - throw std::runtime_error(SDL_GetError()); - } - - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderClear(renderer); } CanvasImpl::~CanvasImpl() { - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - IMG_Quit(); - TTF_Quit(); +} + +void CanvasImpl::clear() +{ + g2dClear(WHITE); } void CanvasImpl::repaint() { - SDL_RenderPresent(renderer); + g2dFlip(G2D_VSYNC); + sceDisplayWaitVblankStart(); } int CanvasImpl::getWidth() @@ -70,66 +34,94 @@ int CanvasImpl::getHeight() return height; } -SDL_Renderer* CanvasImpl::getRenderer() -{ - return renderer; -} - void CanvasImpl::processEvents() { - SDL_Event e; - - while (SDL_PollEvent(&e) != 0) { - switch (e.type) { - case SDL_QUIT: - exit(0); // IMPROVE This is a super dumb way to finish the game, but it works - break; - case SDL_KEYDOWN: { - int keyCode = convertKeyCharToKeyCode(e.key.keysym.sym); - // std::cout << "Key pressed: " << keyCode << std::endl; + SceCtrlData pad; + sceCtrlPeekBufferPositive(&pad, 1); + + uint32_t currentButtons = pad.Buttons; + uint32_t pressedButtons = currentButtons & ~lastButtons; + uint32_t releasedButtons = lastButtons & ~currentButtons; + lastButtons = currentButtons; + + bool isMenu = Micro::isInGameMenu; + + auto mapButtonToKey = [isMenu](uint32_t btn) -> int { + switch (btn) { + case PSP_CTRL_UP: return Canvas::Keys::UP; + case PSP_CTRL_DOWN: return Canvas::Keys::DOWN; + case PSP_CTRL_LEFT: return Canvas::Keys::LEFT; + case PSP_CTRL_RIGHT: return Canvas::Keys::RIGHT; + + case PSP_CTRL_CROSS: + return isMenu ? static_cast(Canvas::Keys::FIRE) : static_cast('8'); + case PSP_CTRL_CIRCLE: + return isMenu ? 0 : static_cast('6'); + case PSP_CTRL_SQUARE: + return isMenu ? 0 : static_cast('4'); + case PSP_CTRL_TRIANGLE: + return isMenu ? 0 : static_cast('2'); + + case PSP_CTRL_LTRIGGER: + return isMenu ? 0 : static_cast('1'); + case PSP_CTRL_RTRIGGER: + return isMenu ? 0 : static_cast('3'); + + case PSP_CTRL_START: + return isMenu ? static_cast(Canvas::Keys::FIRE) : 0; + default: + return 0; + } + }; + + uint32_t buttons[] = { + PSP_CTRL_UP, PSP_CTRL_DOWN, PSP_CTRL_LEFT, PSP_CTRL_RIGHT, + PSP_CTRL_CROSS, PSP_CTRL_CIRCLE, PSP_CTRL_SQUARE, PSP_CTRL_TRIANGLE, + PSP_CTRL_LTRIGGER, PSP_CTRL_RTRIGGER, PSP_CTRL_START + }; + + for (uint32_t btn : buttons) { + if (pressedButtons & btn) { + int keyCode = mapButtonToKey(btn); if (keyCode != 0) { canvas->publicKeyPressed(keyCode); } - } break; - case SDL_KEYUP: { - int sdlCode = e.key.keysym.sym; - int keyCode = convertKeyCharToKeyCode(sdlCode); - // std::cout << "Key released: " << keyCode << std::endl; + } + if (releasedButtons & btn) { + int keyCode = mapButtonToKey(btn); if (keyCode != 0) { canvas->publicKeyReleased(keyCode); - } else { - if (sdlCode == SDLK_ESCAPE) { - // std::cout << "ESC released" << std::endl; - canvas->pressedEsc(); - } + } else if ((btn == PSP_CTRL_CIRCLE && isMenu) || (btn == PSP_CTRL_START && !isMenu)) { + canvas->pressedEsc(); } - } break; - default: - break; } } -} -int CanvasImpl::convertKeyCharToKeyCode(SDL_Keycode keyCode) -{ - switch (keyCode) { - case SDLK_RETURN: - return Canvas::Keys::FIRE; - case SDLK_LEFT: - return Canvas::Keys::LEFT; - case SDLK_RIGHT: - return Canvas::Keys::RIGHT; - case SDLK_UP: - return Canvas::Keys::UP; - case SDLK_DOWN: - return Canvas::Keys::DOWN; - default: - std::cout << "unknown keyEvent: " << keyCode << std::endl; - return 0; - } + // Analog stick polling + bool analogLeft = pad.Lx < 40; + bool analogRight = pad.Lx > 216; + bool analogUp = pad.Ly < 40; + bool analogDown = pad.Ly > 216; + + if (analogLeft && !lastAnalogLeft) canvas->publicKeyPressed(Canvas::Keys::LEFT); + if (!analogLeft && lastAnalogLeft) canvas->publicKeyReleased(Canvas::Keys::LEFT); + + if (analogRight && !lastAnalogRight) canvas->publicKeyPressed(Canvas::Keys::RIGHT); + if (!analogRight && lastAnalogRight) canvas->publicKeyReleased(Canvas::Keys::RIGHT); + + if (analogUp && !lastAnalogUp) canvas->publicKeyPressed(Canvas::Keys::UP); + if (!analogUp && lastAnalogUp) canvas->publicKeyReleased(Canvas::Keys::UP); + + if (analogDown && !lastAnalogDown) canvas->publicKeyPressed(Canvas::Keys::DOWN); + if (!analogDown && lastAnalogDown) canvas->publicKeyReleased(Canvas::Keys::DOWN); + + lastAnalogLeft = analogLeft; + lastAnalogRight = analogRight; + lastAnalogUp = analogUp; + lastAnalogDown = analogDown; } void CanvasImpl::setWindowTitle(const std::string& title) { - SDL_SetWindowTitle(window, title.c_str()); -} \ No newline at end of file + (void)title; +} diff --git a/src/lcdui/CanvasImpl.h b/src/lcdui/CanvasImpl.h index 90c4763..a0e98cc 100644 --- a/src/lcdui/CanvasImpl.h +++ b/src/lcdui/CanvasImpl.h @@ -1,10 +1,8 @@ #pragma once #include - -#include -#include -#include +#include +#include class Canvas; @@ -12,23 +10,24 @@ class CanvasImpl { private: Canvas* canvas; - SDL_Window* window; - SDL_Renderer* renderer; - - const int width = 640; - const int height = 480; + const int width = 480; + const int height = 272; - static int convertKeyCharToKeyCode(SDL_Keycode keyCode); + uint32_t lastButtons = 0; + bool lastAnalogLeft = false; + bool lastAnalogRight = false; + bool lastAnalogUp = false; + bool lastAnalogDown = false; public: CanvasImpl(Canvas* canvas); ~CanvasImpl(); + void clear(); void repaint(); int getWidth(); int getHeight(); - SDL_Renderer* getRenderer(); void processEvents(); void setWindowTitle(const std::string& title); -}; \ No newline at end of file +}; diff --git a/src/lcdui/Font.cpp b/src/lcdui/Font.cpp index 547c4ee..13f82e4 100644 --- a/src/lcdui/Font.cpp +++ b/src/lcdui/Font.cpp @@ -1,47 +1,50 @@ #include "Font.h" - -#include - -CMRC_DECLARE(assets); +#include Font::Font(FontStyle style, FontSize pointSize) { - if (!ttfRwOps) { - cmrc::embedded_filesystem internalFs = cmrc::assets::get_filesystem(); - cmrc::file fileData = internalFs.open("FontSansSerif.ttf"); - SDL_RWops* raw = SDL_RWFromConstMem(fileData.begin(), fileData.size()); - if (!raw) { - throw std::runtime_error(SDL_GetError()); + (void)style; + this->scale = getScaleForSize(pointSize); + this->font = intraFontLoadTTF("assets/FontSansSerif.ttf", INTRAFONT_CACHE_MED, 14.0f * this->scale); + if (!this->font) { + this->font = intraFontLoad("flash0:/font/ltn0.pgf", INTRAFONT_CACHE_MED); + } + if (this->font) { + this->height = intraFontTextHeight(this->font); + if (this->height <= 0) { + this->height = static_cast(14.0f * this->scale); } - - ttfRwOps = raw; + } else { + this->height = static_cast(14.0f * this->scale); } - - int realSize = getRealFontSize(pointSize); - TTF_Font* font = TTF_OpenFontRW(ttfRwOps, SDL_TRUE, realSize); - TTF_SetFontStyle(font, style); - this->ttfFont = font; - this->height = realSize; } Font::~Font() { - TTF_CloseFont(ttfFont); + if (this->font) { + intraFontUnload(this->font); + this->font = nullptr; + } } int Font::getBaselinePosition() const { - return height; + return this->height; } int Font::getHeight() const { - return height; + return this->height; +} + +intraFont* Font::getIntraFont() const +{ + return this->font; } -TTF_Font* Font::getTtfFont() const +float Font::getScale() const { - return ttfFont; + return this->scale; } int Font::charWidth(char c) @@ -51,10 +54,8 @@ int Font::charWidth(char c) int Font::stringWidth(const std::string& s) { - int width, height; - if (TTF_SizeText(ttfFont, s.c_str(), &width, &height) == -1) - throw std::runtime_error(TTF_GetError()); - return width; + if (!font) return static_cast(s.length() * 8 * scale); + return static_cast(intraFontMeasureText(font, s.c_str())); } int Font::substringWidth(const std::string& string, int offset, int len) @@ -62,16 +63,16 @@ int Font::substringWidth(const std::string& string, int offset, int len) return stringWidth(string.substr(offset, len)); } -int Font::getRealFontSize(FontSize size) +float Font::getScaleForSize(FontSize size) { switch (size) { case SIZE_LARGE: - return 32; + return 1.2f; case SIZE_MEDIUM: - return 16; + return 0.9f; case SIZE_SMALL: - return 12; + return 0.7f; default: - throw std::runtime_error("unknown font size: " + std::to_string(size)); + return 0.9f; } } diff --git a/src/lcdui/Font.h b/src/lcdui/Font.h index 8d138ba..9b6e3c4 100644 --- a/src/lcdui/Font.h +++ b/src/lcdui/Font.h @@ -1,10 +1,7 @@ #pragma once #include -#include -#include - -#include +#include "psp/intra/intraFont.h" class Font { public: @@ -15,9 +12,9 @@ class Font { }; enum FontStyle { - STYLE_PLAIN = TTF_STYLE_NORMAL, - STYLE_BOLD = TTF_STYLE_BOLD, - STYLE_ITALIC = TTF_STYLE_ITALIC + STYLE_PLAIN = 0, + STYLE_BOLD = 1, + STYLE_ITALIC = 2 }; enum FontFace { @@ -29,15 +26,16 @@ class Font { int getBaselinePosition() const; int getHeight() const; - TTF_Font* getTtfFont() const; + intraFont* getIntraFont() const; + float getScale() const; int charWidth(char c); int stringWidth(const std::string& s); int substringWidth(const std::string& string, int offset, int len); private: - static inline SDL_RWops* ttfRwOps = nullptr; - TTF_Font* ttfFont; - int height; + intraFont* font = nullptr; + float scale = 1.0f; + int height = 14; - static int getRealFontSize(FontSize size); -}; \ No newline at end of file + static float getScaleForSize(FontSize size); +}; diff --git a/src/lcdui/Graphics.cpp b/src/lcdui/Graphics.cpp index bbdf747..d2322bd 100644 --- a/src/lcdui/Graphics.cpp +++ b/src/lcdui/Graphics.cpp @@ -1,39 +1,32 @@ #include "Graphics.h" -#include -Graphics::Graphics(SDL_Renderer* renderer) +Graphics::Graphics() { - this->renderer = renderer; - this->currentColor = { 0, 0, 0, 255 }; + this->currentColor = G2D_RGBA(0, 0, 0, 255); this->font = nullptr; } -void Graphics::drawString(const std::string& s, int x, int y, int anchor) +Graphics::~Graphics() { - SDL_Surface* surfaceMessage = TTF_RenderText_Blended(font->getTtfFont(), s.c_str(), currentColor); - SDL_Texture* message = SDL_CreateTextureFromSurface(renderer, surfaceMessage); +} - int width, height; - if (TTF_SizeText(font->getTtfFont(), s.c_str(), &width, &height) == -1) - throw std::runtime_error(TTF_GetError()); +void Graphics::drawString(const std::string& s, int x, int y, int anchor) +{ + if (!font || !font->getIntraFont()) return; + int width = font->stringWidth(s); + int height = font->getHeight(); x = getAnchorX(x, width, anchor); y = getAnchorY(y, height, anchor); - SDL_Rect dstRect { x, y, width, height }; - SDL_RenderCopy(renderer, message, nullptr, &dstRect); - - SDL_FreeSurface(surfaceMessage); - SDL_DestroyTexture(message); + intraFont* ifont = font->getIntraFont(); + intraFontSetStyle(ifont, font->getScale(), currentColor, 0, 0.0f, INTRAFONT_ALIGN_LEFT); + intraFontPrint(ifont, x, y + height, s.c_str()); } void Graphics::setColor(int r, int g, int b) { - currentColor.r = r; - currentColor.g = g; - currentColor.b = b; - currentColor.a = 255; - SDL_SetRenderDrawColor(renderer, (Uint8)r, (Uint8)g, (Uint8)b, 255); + currentColor = G2D_RGBA(r, g, b, 255); } void Graphics::setFont(std::shared_ptr font) @@ -48,8 +41,19 @@ std::shared_ptr Graphics::getFont() const void Graphics::setClip(int x, int y, int w, int h) { - SDL_Rect clipRect { x, y, w, h }; - SDL_RenderSetClipRect(renderer, &clipRect); + if (w <= 0 || h <= 0 || (x <= 0 && y <= 0 && w >= 480 && h >= 272)) { + g2dResetScissor(); + } else { + int cx = x < 0 ? 0 : x; + int cy = y < 0 ? 0 : y; + int cw = (x + w > 480) ? (480 - cx) : (x + w - cx); + int ch = (y + h > 272) ? (272 - cy) : (y + h - cy); + if (cw > 0 && ch > 0) { + g2dSetScissor(cx, cy, cw, ch); + } else { + g2dSetScissor(0, 0, 0, 0); + } + } } void Graphics::drawChar(char c, int x, int y, int anchor) @@ -59,188 +63,84 @@ void Graphics::drawChar(char c, int x, int y, int anchor) void Graphics::fillRect(int x, int y, int w, int h) { - SDL_Rect rect { x, y, w, h }; - SDL_RenderFillRect(renderer, &rect); -} - -/** - * Draws the outline of a circular or elliptical arc covering the specified rectangle, - * using the current color and stroke style. The resulting arc begins at startAngle - * and extends for arcAngle degrees, using the current color. Angles are interpreted - * such that 0 degrees is at the 3 o'clock position. A positive value indicates a - * counter-clockwise rotation while a negative value indicates a clockwise rotation. - * - * The center of the arc is the center of the rectangle whose origin is (x, y) and - * whose size is specified by the width and height arguments. - * - * The resulting arc covers an area width + 1 pixels wide by height + 1 pixels tall. - * If either width or height is less than zero, nothing is drawn. - * - * The angles are specified relative to the non-square extents of the bounding rectangle - * such that 45 degrees always falls on the line from the center of the ellipse to the - * upper right corner of the bounding rectangle. As a result, if the bounding rectangle - * is noticeably longer in one axis than the other, the angles to the start and end of - * the arc segment will be skewed farther along the longer axis of the bounds. - * - * Parameters: - * x - the x coordinate of the upper-left corner of the arc to be drawn - * y - the y coordinate of the upper-left corner of the arc to be drawn - * width - the width of the arc to be drawn - * height - the height of the arc to be drawn - * startAngle - the beginning angle - * arcAngle - the angular extent of the arc, relative to the start angle - */ -void Graphics::drawArc(int x, int y, int width, int heigth, int startAngle, int arcAngle) -{ - // Draws an elliptical arc left-top at (x, y), with axes given by - // xradius and yradius, traveling from startAngle to endangle. - // Bresenham-based if complete - int xradius = width / 2, yradius = heigth / 2; - x += xradius; - y += yradius; - if (xradius == 0 && yradius == 0) { - return; - } - - // draw complete ellipse if (0, 360) specified - // if (startAngle == 0 && arcAngle == 360) { - // _ellipse(x, y, xradius, yradius); - // return; - // } - - for (int angle = startAngle; angle < startAngle + arcAngle; angle++) { - drawLine(x + int(xradius * cos(angle * PI_CONV)), - y - int(yradius * sin(angle * PI_CONV)), - x + int(xradius * cos((angle + 1) * PI_CONV)), - y - int(yradius * sin((angle + 1) * PI_CONV))); - } + if (w <= 0 || h <= 0) return; + g2dBeginRects(NULL); + g2dSetColor(currentColor); + g2dSetCoordXY(x, y); + g2dSetScaleWH(w, h); + g2dAdd(); + g2dEnd(); } -// void Graphics::fillArc(int x, int y, int w, int h, int startAngle, int arcAngle) { -// // NOTE this impl assumes startAngle < arcAngle -// int _x, _y, // circle centered point -// xx,yy,rr, // x^2,y^2,r^2 -// ux,uy, // u -// vx,vy, // v -// sx,sy; // pixel position - -// arcAngle += startAngle; - -// (void)w; (void)h; -// int r = 30; // TODO -// rr = r*r; -// ux = double(r)*cos(double(startAngle)*PI_CONV); -// uy = double(r)*sin(double(startAngle)*PI_CONV); -// vx = double(r)*cos(double(arcAngle)*PI_CONV); -// vy = double(r)*sin(double(arcAngle)*PI_CONV); - -// if (abs(arcAngle - startAngle) < 180) { // small pie -// for (_y = -r, yy = _y*_y, sy = y + _y; _y <= +r; _y++, yy = _y*_y, sy++) -// for (_x = -r, xx = _x*_x, sx = x + _x; _x <= +r; _x++, xx = _x*_x, sx++) -// if (xx + yy <= rr) // inside circle -// if (((_x*uy) - (_y*ux) <= 0) // x,y is above a0 in clockwise direction -// && ((_x*vy) - (_y*vx) >= 0)) // x,y is below a1 in counter clockwise direction -// _putpixel(sx, sy); -// } else { // big pie -// for (_y = -r, yy = _y*_y, sy = y + _y; _y <= +r; _y++, yy = _y*_y, sy++) -// for (_x = -r, xx = _x*_x, sx = x + _x; _x <= +r; _x++, xx = _x*_x, sx++) -// if (xx + yy <= rr) // inside circle -// if (((_x*uy) - (_y*ux) <= 0) // x,y is above a0 in clockwise direction -// || ((_x*vy) - (_y*vx) >= 0)) // x,y is below a1 in counter clockwise direction -// _putpixel(sx, sy); -// } -// } - -// int norm_ang(int angle) { -// // reduce the angle -// angle = angle % 360; - -// // force it to be the positive remainder, so that 0 <= angle < 360 -// angle = (angle + 360) % 360; - -// // force into the minimum absolute value residue class, so that -180 < angle <= 180 -// if (angle > 180) -// angle -= 360; -// return angle; -// } - -int to_360(int ang) -{ - // if (ang != 0 && ang % 360 == 0) { - // ang = 360; - // } else { - // ang %= 360; - // } - if (ang >= 0 && ang <= 360) { - return ang; +void Graphics::drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) +{ + int rx = width / 2; + int ry = height / 2; + int cx = x + rx; + int cy = y + ry; + if (rx <= 0 && ry <= 0) return; + + g2dBeginLines(G2D_STRIP); + g2dSetColor(currentColor); + + int step = 10; + for (int angle = startAngle; angle <= startAngle + arcAngle; angle += step) { + int a = (angle > startAngle + arcAngle) ? (startAngle + arcAngle) : angle; + float rad = a * PI_CONV; + float px = cx + rx * cos(rad); + float py = cy - ry * sin(rad); + g2dSetCoordXY(px, py); + g2dAdd(); } - if (ang < 0) { - ang += 360; - } - return ang; + g2dEnd(); } void Graphics::fillArc(int x, int y, int w, int h, int startAngle, int arcAngle) { - int endAngle = startAngle + arcAngle; - // startAngle = norm_ang(startAngle); - // endAngle = norm_ang(endAngle); - // SDL_Log("startAngle = %d, endAngle = %d\n", startAngle, endAngle); - double a = w / 2.0, b = h / 2.0; - double e = sqrt(1.0 - (b * b) / (a * a)); - for (int _y = y - b; _y < y + b; _y++) { - for (int _x = x - a; _x < x + a; _x++) { - // _putpixel(_x, _y); - // std::cout << atan2(_y, _x) << "\n"; - // SDL_Log("_y = %d, _x = %d atan2 = %lf\n", _y, _x, atan2(_y - y, _x - x)); - // if (atan2(_y - y, _x - x) >= startAngle*PI_CONV && atan2(_y - y, _x - x) <= endAngle*PI_CONV) { - // _putpixel(_x, _y); - // } - // if (atan2(_y - y, _x - x) < 3.14/2.0) { - // _putpixel(_x, _y); - // } - double ang = atan2(-(_y - y), _x - x); // cuz in screen y grows downwards (in maths y grows upwards) - - // double rad = b/sqrt(1 - e*e*cos(ang)*cos(ang)); - // double dist = sqrt((_x - x)*(_x - x) + (_y - y)*(_y - y)); - double rad = b * b / (1 - e * e * cos(ang) * cos(ang)); - double dist = ((_x - x) * (_x - x) + (_y - y) * (_y - y)); - - int ang2 = to_360(ang / PI_CONV); - - if (ang2 >= to_360(startAngle) - && ang2 <= to_360(endAngle) - && dist <= rad) { - _putpixel(_x, _y); - } - - if (endAngle > 360) { - if (ang2 < endAngle % 360 && dist <= rad) { - _putpixel(_x, _y); - } - } - } + (void)startAngle; (void)arcAngle; + int rx = w / 2; + int ry = h / 2; + int cx = x + rx; + int cy = y + ry; + if (rx <= 0 || ry <= 0) return; + + g2dBeginLines((g2dLine_Mode)0); + g2dSetColor(currentColor); + + for (int dy = -ry; dy <= ry; dy++) { + float dx_max = rx * sqrt(1.0f - (float)(dy * dy) / (float)(ry * ry)); + int x1 = cx - (int)dx_max; + int x2 = cx + (int)dx_max; + int py = cy + dy; + g2dSetCoordXY(x1, py); g2dAdd(); + g2dSetCoordXY(x2, py); g2dAdd(); } -} - -void Graphics::_putpixel(int x, int y) -{ - SDL_RenderDrawPoint(renderer, x, y); + g2dEnd(); } void Graphics::drawLine(int x1, int y1, int x2, int y2) { - SDL_RenderDrawLine(renderer, x1, y1, x2, y2); + g2dBeginLines((g2dLine_Mode)0); + g2dSetColor(currentColor); + g2dSetCoordXY(x1, y1); g2dAdd(); + g2dSetCoordXY(x2, y2); g2dAdd(); + g2dEnd(); } void Graphics::drawImage(Image* const image, int x, int y, int anchor) { - SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, image->getSurface()); - x = getAnchorX(x, image->getWidth(), anchor); - y = getAnchorY(y, image->getHeight(), anchor); - SDL_Rect dstRect { x, y, image->getWidth(), image->getHeight() }; - SDL_RenderCopy(renderer, texture, 0, &dstRect); - SDL_DestroyTexture(texture); + if (!image || !image->getG2DImage()) return; + int w = image->getWidth(); + int h = image->getHeight(); + x = getAnchorX(x, w, anchor); + y = getAnchorY(y, h, anchor); + + g2dBeginRects(image->getG2DImage()); + g2dSetColor(WHITE); + g2dSetCoordXY(x, y); + g2dSetScaleWH(w, h); + g2dAdd(); + g2dEnd(); } int Graphics::getAnchorX(int x, int size, int anchor) @@ -254,7 +154,7 @@ int Graphics::getAnchorX(int x, int size, int anchor) if ((anchor & HCENTER) != 0) { return x - size / 2; } - throw std::runtime_error("unknown xanchor = " + std::to_string(anchor)); + return x; } int Graphics::getAnchorY(int y, int size, int anchor) @@ -268,5 +168,5 @@ int Graphics::getAnchorY(int y, int size, int anchor) if ((anchor & VCENTER) != 0) { return y - size / 2; } - throw std::runtime_error("unknown yanchor = " + std::to_string(anchor)); -} \ No newline at end of file + return y; +} diff --git a/src/lcdui/Graphics.h b/src/lcdui/Graphics.h index 481e68c..d945078 100644 --- a/src/lcdui/Graphics.h +++ b/src/lcdui/Graphics.h @@ -1,29 +1,22 @@ #pragma once #include -#include -#include -#include #include +#include -#include -#include - +#include "psp/glib2d.h" +#include "psp/intra/intraFont.h" #include "Image.h" #include "Font.h" -constexpr auto PI_CONV = 3.1415926 / 180.0; - -class Image; +constexpr auto PI_CONV = 3.14159265358979323846 / 180.0; class Graphics { private: - SDL_Renderer* renderer; std::shared_ptr font; - SDL_Color currentColor; - // void _ellipse(int cx, int cy, int xradius, int yradius); - void _putpixel(int x, int y); + g2dColor currentColor; public: + ~Graphics(); enum Anchors { HCENTER = 1, VCENTER = 2, @@ -33,7 +26,7 @@ class Graphics { BOTTOM = 32, BASELINE = 64 }; - Graphics(SDL_Renderer* renderer); + Graphics(); void drawString(const std::string& s, int x, int y, int anchor); void setColor(int r, int g, int b); void setFont(std::shared_ptr font); @@ -47,4 +40,4 @@ class Graphics { void drawImage(Image* const image, int x, int y, int anchor); static int getAnchorX(int x, int size, int anchor); static int getAnchorY(int y, int size, int anchor); -}; \ No newline at end of file +}; diff --git a/src/lcdui/Image.cpp b/src/lcdui/Image.cpp index 8b81d84..86edfd0 100644 --- a/src/lcdui/Image.cpp +++ b/src/lcdui/Image.cpp @@ -1,65 +1,43 @@ #include "Image.h" - -#include -#include - -#include -#include #include +#include CMRC_DECLARE(assets); Image::Image(int width, int height) { - SDL_Surface* surf = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0); - if (!surf) { - throw std::runtime_error(SDL_GetError()); - } - - this->surface = surf; + this->image = g2dTexCreatePlaceholder(width, height); } Image::Image(const std::string& embeddedPath) { cmrc::embedded_filesystem embeddedFs = cmrc::assets::get_filesystem(); - cmrc::file fileData = embeddedFs.open(embeddedPath); - - SDL_RWops* raw = SDL_RWFromConstMem(fileData.begin(), fileData.size()); - if (!raw) { - throw std::runtime_error(SDL_GetError()); - } - - SDL_Surface* surf = IMG_Load_RW(raw, SDL_TRUE); - if (!surf) { - throw std::runtime_error(IMG_GetError()); - } - - SDL_Surface* surf_conv = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_RGBA32, 0); - SDL_FreeSurface(surf); - - if (!surf_conv) { - throw std::runtime_error(SDL_GetError()); + if (embeddedFs.exists(embeddedPath)) { + cmrc::file fileData = embeddedFs.open(embeddedPath); + this->image = g2dTexLoad(NULL, (unsigned char*)fileData.begin(), fileData.size(), G2D_SWIZZLE); + } else { + this->image = nullptr; } - - this->surface = surf_conv; } Image::~Image() { - SDL_FreeSurface(this->surface); + if (this->image) { + g2dTexFree(&this->image); + } } -int Image::getWidth() const +g2dImage* Image::getG2DImage() const { - return this->surface->w; + return this->image; } -int Image::getHeight() const +int Image::getWidth() const { - return this->surface->h; + return this->image ? this->image->w : 0; } -SDL_Surface* Image::getSurface() const +int Image::getHeight() const { - return this->surface; + return this->image ? this->image->h : 0; } diff --git a/src/lcdui/Image.h b/src/lcdui/Image.h index 779fe0a..ead83ff 100644 --- a/src/lcdui/Image.h +++ b/src/lcdui/Image.h @@ -1,11 +1,11 @@ #pragma once #include -#include +#include "psp/glib2d.h" class Image { private: - SDL_Surface* surface; + g2dImage* image = nullptr; public: Image(const std::string& embeddedPath); @@ -14,5 +14,5 @@ class Image { int getWidth() const; int getHeight() const; - SDL_Surface* getSurface() const; + g2dImage* getG2DImage() const; }; diff --git a/src/main.cpp b/src/main.cpp index ff84b9b..f759bf0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,19 +1,25 @@ #include #include -#include #include #include "Micro.h" +#include "psp/pspSetup.h" + +#if defined(PSP) || defined(__PSP__) +#include +PSP_MODULE_INFO("GravityDefied", 0, 1, 0); +PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU); +PSP_HEAP_SIZE_KB(-1024); +#endif int main(int argc, char** argv) { - try { - std::unique_ptr micro = std::make_unique(); - micro->startApp(argc, argv); - } catch (std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - return EXIT_FAILURE; - } + InitGame(); + + std::unique_ptr micro = std::make_unique(); + micro->startApp(argc, argv); + + ExitGame(); - return EXIT_SUCCESS; -}; + return 0; +} diff --git a/src/psp/glib2d.c b/src/psp/glib2d.c new file mode 100644 index 0000000..0686c12 --- /dev/null +++ b/src/psp/glib2d.c @@ -0,0 +1,1585 @@ +// gLib2D by Geecko - A simple, fast, light-weight 2D graphics library. +// +// This work is licensed under the Creative Commons BY-SA 3.0 Unported License. +// See LICENSE for more details. +// +// Please report bugs at : geecko.dev@free.fr + +#include "glib2d.h" + +#include +#include +#include +#include +#include +#include +#include + +#define PSP_LINE_SIZE (512) +#define PIXEL_SIZE (4) +#define FRAMEBUFFER_SIZE (PSP_LINE_SIZE*G2D_SCR_H*PIXEL_SIZE) +#define MALLOC_STEP (128) +#define TRANSFORM_STACK_MAX (64) +#define SLICE_WIDTH (64.f) +#define M_180_PI (57.29578f) +#define M_PI_180 (0.017453292f) + +#define DEFAULT_SIZE (10) +#define DEFAULT_X (0.f) +#define DEFAULT_Y (0.f) +#define DEFAULT_Z (0.f) +#define DEFAULT_COLOR (WHITE) +#define DEFAULT_ALPHA (0xFF) + +#define CURRENT_OBJ obj_list[obj_list_size-1] +#define CURRENT_TRANSFORM transform_stack[transform_stack_size-1] +#define I_OBJ obj_list[i] + +typedef enum +{ + RECTS, LINES, QUADS, POINTS +} Obj_Type; + +typedef struct +{ + float x, y, z; + float rot, rot_sin, rot_cos; + float scale_w, scale_h; +} Transform; + +typedef struct +{ + float x, y, z; + float rot_x, rot_y; // Rotation center + float rot, rot_sin, rot_cos; + int crop_x, crop_y; + int crop_w, crop_h; + float scale_w, scale_h; + g2dColor color; + g2dAlpha alpha; +} Object; + + +// * Main vars * +static int *list; +static bool init = false, start = false, zclear = true, scissor = false; +static Transform transform_stack[TRANSFORM_STACK_MAX]; +static int transform_stack_size; +static float global_scale = 1.f; +// * Object vars * +static Object *obj_list = NULL, obj; +static Obj_Type obj_type; +static int obj_list_size; +static bool obj_begin = false, obj_line_strip; +static bool obj_use_z, obj_use_vert_color, obj_use_blend, obj_use_rot, +obj_use_tex_linear, obj_use_tex_repeat, obj_use_int; +static int obj_blend_mode; +static int obj_colors_count; +static g2dImage *obj_tex; +static int obj_debug_adds, obj_debug_ends; + +g2dImage g2d_draw_buffer = { 512, 512, G2D_SCR_W, G2D_SCR_H, + (float)G2D_SCR_W / G2D_SCR_H, false, false, + (g2dColor *)FRAMEBUFFER_SIZE }, + g2d_disp_buffer = { 512, 512, G2D_SCR_W, G2D_SCR_H, + (float)G2D_SCR_W / G2D_SCR_H, false, false, + (g2dColor *)0 }; + +// * Internal functions * + +void _g2dStart() { + if (!init) g2dInit(); + + sceKernelDcacheWritebackAll(); + sceGuStart(GU_DIRECT, list); + start = true; +} + + +// Vertex order: [texture uv] [color] [vertex] +void *_g2dSetVertex(void *vp, int i, float vx, float vy) { + short *v_p_short = vp; + g2dColor *v_p_color; + float *v_p_float; + + // Texture + if (obj_tex != NULL) { + *(v_p_short++) = I_OBJ.crop_x + vx * I_OBJ.crop_w; + *(v_p_short++) = I_OBJ.crop_y + vy * I_OBJ.crop_h; + } + + // Color + v_p_color = (g2dColor *)v_p_short; + + if (obj_use_vert_color) { + *(v_p_color++) = I_OBJ.color; + } + + // Coord + v_p_float = (float *)v_p_color; + + v_p_float[0] = I_OBJ.x + (obj_type == RECTS ? vx * I_OBJ.scale_w : 0.f); + v_p_float[1] = I_OBJ.y + (obj_type == RECTS ? vy * I_OBJ.scale_h : 0.f); + + // Then apply the rotation + if (obj_use_rot && obj_type == RECTS) { + float tx = v_p_float[0] - I_OBJ.rot_x, ty = v_p_float[1] - I_OBJ.rot_y; + v_p_float[0] = I_OBJ.rot_x - I_OBJ.rot_sin * ty + I_OBJ.rot_cos * tx, + v_p_float[1] = I_OBJ.rot_y + I_OBJ.rot_cos * ty + I_OBJ.rot_sin * tx; + } + + if (obj_use_int) { + v_p_float[0] = floorf(v_p_float[0]); + v_p_float[1] = floorf(v_p_float[1]); + } + v_p_float[2] = I_OBJ.z; + + v_p_float += 3; + + return (void *)v_p_float; +} + +// * Main functions * + +void g2dInit() { + if (init) return; + + // Display list allocation + list = malloc(512 * 1024); + + // Init & setup GU + sceGuInit(); + sceGuStart(GU_DIRECT, list); + + sceGuDrawBuffer(GU_PSM_8888, g2d_draw_buffer.data, PSP_LINE_SIZE); + sceGuDispBuffer(G2D_SCR_W, G2D_SCR_H, g2d_disp_buffer.data, PSP_LINE_SIZE); + sceGuDepthBuffer((void *)(FRAMEBUFFER_SIZE * 2), PSP_LINE_SIZE); + sceGuOffset(2048 - (G2D_SCR_W / 2), 2048 - (G2D_SCR_H / 2)); + sceGuViewport(2048, 2048, G2D_SCR_W, G2D_SCR_H); + + g2d_draw_buffer.data = vabsptr(g2d_draw_buffer.data); + g2d_disp_buffer.data = vabsptr(g2d_disp_buffer.data); + + g2dResetScissor(); + sceGuDepthRange(65535, 0); + sceGuClearDepth(65535); + sceGuAlphaFunc(GU_GREATER, 0, 0xff); + sceGuDepthFunc(GU_LEQUAL); + sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); + sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA); + sceGuTexFilter(GU_LINEAR, GU_LINEAR); + sceGuShadeModel(GU_SMOOTH); + + sceGuDisable(GU_CULL_FACE); + sceGuDisable(GU_CLIP_PLANES); + sceGuDisable(GU_DITHER); + sceGuEnable(GU_ALPHA_TEST); + sceGuEnable(GU_SCISSOR_TEST); + sceGuEnable(GU_BLEND); + + sceGuFinish(); + sceGuSync(0, 0); + sceDisplayWaitVblankStart(); + sceGuDisplay(GU_TRUE); + + init = true; +} + + +void g2dTerm() { + if (!init) return; + + sceGuTerm(); + + free(list); + + init = false; +} + +void g2dClear(g2dColor color) { + if (!start) _g2dStart(); + + sceGuClearColor(color); + sceGuClear(GU_COLOR_BUFFER_BIT | GU_FAST_CLEAR_BIT + | (zclear ? GU_DEPTH_BUFFER_BIT : 0)); + zclear = false; + obj_debug_adds = 0; + obj_debug_ends = 0; +} + + +void g2dClearZ() { + if (!start) _g2dStart(); + + sceGuClear(GU_DEPTH_BUFFER_BIT | GU_FAST_CLEAR_BIT); + zclear = true; +} + + +void _g2dBeginCommon() { + if (!start) _g2dStart(); + + obj_list_size = 0; + obj_list = realloc(obj_list, MALLOC_STEP * sizeof(Object)); + + obj_use_z = false; + obj_use_vert_color = false; + obj_use_blend = false; + obj_blend_mode = -1; + obj_use_rot = false; + obj_use_int = false; + obj_colors_count = 0; + g2dReset(); + + obj_begin = true; +} + + +void g2dBeginRects(g2dImage *tex) { + if (obj_begin) return; + + obj_type = RECTS; + obj_tex = tex; + _g2dBeginCommon(); +} + + +void g2dBeginLines(g2dLine_Mode mode) { + if (obj_begin) return; + + obj_type = LINES; + obj_tex = NULL; + obj_line_strip = (mode & G2D_STRIP); + _g2dBeginCommon(); +} + + +void g2dBeginQuads(g2dImage *tex) { + if (obj_begin) return; + + obj_type = QUADS; + obj_tex = tex; + _g2dBeginCommon(); +} + + +void g2dBeginPoints() { + if (obj_begin) return; + + obj_type = POINTS; + obj_tex = NULL; + _g2dBeginCommon(); +} + + +void _g2dEndRects() { + // Define vertices properties + int prim = (obj_use_rot ? GU_TRIANGLES : GU_SPRITES), + v_obj_nbr = (obj_use_rot ? 6 : 2), + v_nbr, + v_coord_size = 3, + v_tex_size = (obj_tex != NULL ? 2 : 0), + v_color_size = (obj_use_vert_color ? 1 : 0), + v_size = v_tex_size * sizeof(short) + + v_color_size * sizeof(g2dColor) + + v_coord_size * sizeof(float), + v_type = GU_VERTEX_32BITF | GU_TRANSFORM_2D, + i; + + if (obj_tex != NULL) v_type |= GU_TEXTURE_16BIT; + if (obj_use_vert_color) v_type |= GU_COLOR_8888; + + // Count how many vertices to allocate. + if (obj_tex == NULL || obj_use_rot) // No slicing + { + v_nbr = v_obj_nbr * obj_list_size; + } else // Can use texture slicing for tremendous performance :) + { + for (v_nbr = 0, i = 0; i < obj_list_size; i++) { + v_nbr += v_obj_nbr * ceilf(I_OBJ.crop_w / SLICE_WIDTH); + } + } + + // Allocate vertex list memory + void *v = sceGuGetMemory(v_nbr * v_size), *vi = v; + + // Build the vertex list + for (i = 0; i < obj_list_size; i += 1) { + if (!obj_use_rot) { + if (obj_tex == NULL) { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + vi = _g2dSetVertex(vi, i, 1.f, 1.f); + } else // Use texture slicing + { + float u, step = SLICE_WIDTH / I_OBJ.crop_w; + for (u = 0.f; u < 1.f; u += step) { + vi = _g2dSetVertex(vi, i, u, 0.f); + vi = _g2dSetVertex(vi, i, ((u + step) > 1.f ? 1.f : u + step), 1.f); + } + } + } else // Rotation : draw 2 triangles per obj + { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + vi = _g2dSetVertex(vi, i, 1.f, 0.f); + vi = _g2dSetVertex(vi, i, 0.f, 1.f); + vi = _g2dSetVertex(vi, i, 0.f, 1.f); + vi = _g2dSetVertex(vi, i, 1.f, 0.f); + vi = _g2dSetVertex(vi, i, 1.f, 1.f); + } + } + + // Then put it in the display list. + sceGuDrawArray(prim, v_type, v_nbr, NULL, v); +} + + +void _g2dEndLines() { + // Define vertices properties + int prim = (obj_line_strip ? GU_LINE_STRIP : GU_LINES), + v_obj_nbr = (obj_line_strip ? 1 : 2), + v_nbr = v_obj_nbr * (obj_line_strip ? obj_list_size + : obj_list_size / 2), + v_coord_size = 3, + v_color_size = (obj_use_vert_color ? 1 : 0), + v_size = v_color_size * sizeof(g2dColor) + + v_coord_size * sizeof(float), + v_type = GU_VERTEX_32BITF | GU_TRANSFORM_2D, + i; + + if (obj_use_vert_color) v_type |= GU_COLOR_8888; + + // Allocate vertex list memory + void *v = sceGuGetMemory(v_nbr * v_size), *vi = v; + + // Build the vertex list + if (obj_line_strip) { + vi = _g2dSetVertex(vi, 0, 0.f, 0.f); + for (i = 1; i < obj_list_size; i += 1) { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + } + } else { + for (i = 0; i + 1 < obj_list_size; i += 2) { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + vi = _g2dSetVertex(vi, i + 1, 0.f, 0.f); + } + } + + // Then put it in the display list. + sceGuDrawArray(prim, v_type, v_nbr, NULL, v); +} + + +void _g2dEndQuads() { + // Define vertices properties + int prim = GU_TRIANGLES, + v_obj_nbr = 6, + v_nbr = v_obj_nbr * (obj_list_size / 4), + v_coord_size = 3, + v_tex_size = (obj_tex != NULL ? 2 : 0), + v_color_size = (obj_use_vert_color ? 1 : 0), + v_size = v_tex_size * sizeof(short) + + v_color_size * sizeof(g2dColor) + + v_coord_size * sizeof(float), + v_type = GU_VERTEX_32BITF | GU_TRANSFORM_2D, + i; + + if (obj_tex != NULL) v_type |= GU_TEXTURE_16BIT; + if (obj_use_vert_color) v_type |= GU_COLOR_8888; + + // Allocate vertex list memory + void *v = sceGuGetMemory(v_nbr * v_size), *vi = v; + + // Build the vertex list + for (i = 0; i + 3 < obj_list_size; i += 4) { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + vi = _g2dSetVertex(vi, i + 1, 1.f, 0.f); + vi = _g2dSetVertex(vi, i + 3, 0.f, 1.f); + vi = _g2dSetVertex(vi, i + 3, 0.f, 1.f); + vi = _g2dSetVertex(vi, i + 1, 1.f, 0.f); + vi = _g2dSetVertex(vi, i + 2, 1.f, 1.f); + } + + // Then put it in the display list. + sceGuDrawArray(prim, v_type, v_nbr, NULL, v); +} + + +void _g2dEndPoints() { + // Define vertices properties + int prim = GU_POINTS, + v_obj_nbr = 1, + v_nbr = v_obj_nbr * obj_list_size, + v_coord_size = 3, + v_color_size = (obj_use_vert_color ? 1 : 0), + v_size = v_color_size * sizeof(g2dColor) + + v_coord_size * sizeof(float), + v_type = GU_VERTEX_32BITF | GU_TRANSFORM_2D, + i; + + if (obj_use_vert_color) v_type |= GU_COLOR_8888; + + // Allocate vertex list memory + void *v = sceGuGetMemory(v_nbr * v_size), *vi = v; + + // Build the vertex list + for (i = 0; i < obj_list_size; i += 1) { + vi = _g2dSetVertex(vi, i, 0.f, 0.f); + } + + // Then put it in the display list. + sceGuDrawArray(prim, v_type, v_nbr, NULL, v); +} + + +void g2dEnd() { + if (!obj_begin || obj_list_size <= 0) { + obj_begin = false; + return; + } + + // Manage pspgu extensions + if (obj_use_z) sceGuEnable(GU_DEPTH_TEST); + else sceGuDisable(GU_DEPTH_TEST); + if (obj_use_blend) sceGuEnable(GU_BLEND); + else sceGuDisable(GU_BLEND); + if (obj_blend_mode != -1) { + switch (obj_blend_mode) { //TODO: отрефакторить, заюзать енамы или типа того + case 0: //alpha (дефолт) + sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); + break; + case 1: //add + sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, 0xFFFFFFFF); + break; + case 2: //sub dst - вычесть источник из буфера + //это вроде как дефолтное вычитание, хоть написано и обратное (буквально написано "обратное") + sceGuBlendFunc(GU_REVERSE_SUBTRACT, GU_SRC_ALPHA, GU_FIX, 0, 0xFFFFFFFF); + break; + case 3: //sub src + sceGuBlendFunc(GU_SUBTRACT, GU_SRC_ALPHA, GU_FIX, 0, 0xFFFFFFFF); + break; + } + } + if (obj_use_vert_color) sceGuColor(WHITE); + else sceGuColor(obj_list[0].color); + if (obj_tex == NULL) sceGuDisable(GU_TEXTURE_2D); + else { + sceGuEnable(GU_TEXTURE_2D); + if (obj_use_tex_linear) sceGuTexFilter(GU_LINEAR, GU_LINEAR); + else sceGuTexFilter(GU_NEAREST_MIPMAP_NEAREST, GU_NEAREST_MIPMAP_NEAREST); + if (obj_use_tex_repeat) sceGuTexWrap(GU_REPEAT, GU_REPEAT); + else sceGuTexWrap(GU_CLAMP, GU_CLAMP); + // Load texture + sceGuTexMode(GU_PSM_8888, 0, 0, obj_tex->swizzled); + sceGuTexImage(0, obj_tex->tw, obj_tex->th, obj_tex->tw, obj_tex->data); + } + + switch (obj_type) { + case RECTS: _g2dEndRects(); break; + case LINES: _g2dEndLines(); break; + case QUADS: _g2dEndQuads(); break; + case POINTS: _g2dEndPoints(); break; + } + + sceGuColor(WHITE); + sceGuEnable(GU_BLEND); + if (obj_blend_mode != -1) + sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); + + obj_begin = false; + if (obj_use_z) zclear = true; + obj_debug_ends++; +} + + +void g2dReset() { + g2dResetScale(); + g2dResetColor(); + g2dResetAlpha(); + g2dResetRotation(); + g2dResetCrop(); + g2dResetTex(); +} + + +void g2dFlip(g2dFlip_Mode mode) { + if (scissor) g2dResetScissor(); + + sceGuFinish(); + sceGuSync(0, 0); + if (mode & G2D_VSYNC) sceDisplayWaitVblankStart(); + + g2d_disp_buffer.data = g2d_draw_buffer.data; + g2d_draw_buffer.data = vabsptr(sceGuSwapBuffers()); + + start = false; +} + + +void g2dAdd() { + if (!obj_begin) return; + if (obj.scale_w == 0 || obj.scale_h == 0) return; + + if (!(obj_list_size % MALLOC_STEP)) { + obj_list = realloc(obj_list, (obj_list_size + MALLOC_STEP) * sizeof(Object)); + } + + obj_list_size++; + obj.rot_x = obj.x; + obj.rot_y = obj.y; + CURRENT_OBJ = obj; + + // Alpha stuff + CURRENT_OBJ.color = G2D_MODULATE(CURRENT_OBJ.color, 255, obj.alpha); + obj_debug_adds++; +} + + +void g2dPush() { + if (transform_stack_size >= TRANSFORM_STACK_MAX) return; + transform_stack_size++; + CURRENT_TRANSFORM.x = obj.x; + CURRENT_TRANSFORM.y = obj.y; + CURRENT_TRANSFORM.z = obj.z; + CURRENT_TRANSFORM.rot = obj.rot; + CURRENT_TRANSFORM.rot_sin = obj.rot_sin; + CURRENT_TRANSFORM.rot_cos = obj.rot_cos; + CURRENT_TRANSFORM.scale_w = obj.scale_w; + CURRENT_TRANSFORM.scale_h = obj.scale_h; +} + + +void g2dPop() { + if (transform_stack_size <= 0) return; + obj.x = CURRENT_TRANSFORM.x; + obj.y = CURRENT_TRANSFORM.y; + obj.z = CURRENT_TRANSFORM.z; + obj.rot = CURRENT_TRANSFORM.rot; + obj.rot_sin = CURRENT_TRANSFORM.rot_sin; + obj.rot_cos = CURRENT_TRANSFORM.rot_cos; + obj.scale_w = CURRENT_TRANSFORM.scale_w; + obj.scale_h = CURRENT_TRANSFORM.scale_h; + if (obj.rot != 0.f) obj_use_rot = true; + if (obj.z != 0.f) obj_use_z = true; + transform_stack_size--; +} + + +void g2dGetCoordXYZ(float *x, float *y, float *z) { + if (x != NULL) *x = obj.x; + if (y != NULL) *y = obj.y; + if (z != NULL) *z = obj.z; +} + + +void g2dSetCoordXY(float x, float y) { + obj.x = x * global_scale; + obj.y = y * global_scale; + obj.z = DEFAULT_Z; +} + + +void g2dSetCoordXYZ(float x, float y, float z) { + obj.x = x * global_scale; + obj.y = y * global_scale; + obj.z = z * global_scale; + if (z != 0.f) obj_use_z = true; +} + + +void g2dSetCoordXYRelative(float x, float y) { + float inc_x = x, inc_y = y; + if (obj.rot_cos != 1.f) { + inc_x = -obj.rot_sin * y + obj.rot_cos * x; + inc_y = obj.rot_cos * y + obj.rot_sin * x; + } + obj.x += inc_x * global_scale; + obj.y += inc_y * global_scale; +} + + +void g2dSetCoordXYZRelative(float x, float y, float z) { + g2dSetCoordXYRelative(x, y); + obj.z += z * global_scale; + if (z != 0.f) obj_use_z = true; +} + + +void g2dSetCoordInteger(bool use) { + obj_use_int = use; +} + +// * Scale functions * + +void g2dResetGlobalScale() { + global_scale = 1.f; +} + + +void g2dResetScale() { + if (obj_tex == NULL) { + obj.scale_w = DEFAULT_SIZE; + obj.scale_h = DEFAULT_SIZE; + } else { + obj.scale_w = obj_tex->w; + obj.scale_h = obj_tex->h; + } + + obj.scale_w *= global_scale; + obj.scale_h *= global_scale; +} + + +void g2dGetGlobalScale(float *scale) { + if (scale != NULL) *scale = global_scale; +} + + +void g2dGetScaleWH(float *w, float *h) { + if (w != NULL) *w = obj.scale_w; + if (h != NULL) *h = obj.scale_h; +} + + +void g2dSetGlobalScale(float scale) { + global_scale = scale; +} + + +void g2dSetScale(float w, float h) { + g2dResetScale(); + g2dSetScaleRelative(w, h); +} + + +void g2dSetScaleWH(float w, float h) { + obj.scale_w = w * global_scale; + obj.scale_h = h * global_scale; + // A trick to prevent an unexpected behavior when mirroring with GU_SPRITES. + if (obj.scale_w < 0 || obj.scale_h < 0) obj_use_rot = true; +} + + +void g2dSetScaleRelative(float w, float h) { + obj.scale_w *= w; + obj.scale_h *= h; + + if (obj.scale_w < 0 || obj.scale_h < 0) obj_use_rot = true; +} + + +void g2dSetScaleWHRelative(float w, float h) { + obj.scale_w += w * global_scale; + obj.scale_h += h * global_scale; + + if (obj.scale_w < 0 || obj.scale_h < 0) obj_use_rot = true; +} + +// * Color functions * + +void g2dResetColor() { + obj.color = DEFAULT_COLOR; +} + + +void g2dResetAlpha() { + obj.alpha = DEFAULT_ALPHA; +} + + +void g2dGetAlpha(g2dAlpha *alpha) { + if (alpha != NULL) *alpha = obj.alpha; +} + + +void g2dSetColor(g2dColor color) { + obj.color = color; + if (++obj_colors_count > 1) obj_use_vert_color = true; + if (G2D_GET_A(obj.color) < 255) obj_use_blend = true; +} + + +void g2dSetAlpha(g2dAlpha alpha) { + if (alpha < 0) alpha = 0; + if (alpha > 255) alpha = 255; + obj.alpha = alpha; + if (++obj_colors_count > 1) obj_use_vert_color = true; + if (obj.alpha < 255) obj_use_blend = true; +} + + +void g2dSetAlphaRelative(int alpha) { + g2dSetAlpha(obj.alpha + alpha); +} + +// * Rotations functions * + +void g2dResetRotation() { + obj.rot = 0.f; + obj.rot_sin = 0.f; + obj.rot_cos = 1.f; +} + + +void g2dGetRotationRad(float *radians) { + if (radians != NULL) *radians = obj.rot; +} + + +void g2dGetRotation(float *degrees) { + if (degrees != NULL) *degrees = obj.rot * M_180_PI; +} + + +void g2dSetRotationRad(float radians) { + if (radians == obj.rot) return; + obj.rot = radians; + obj.rot_sin = sinf(radians); + obj.rot_cos = cosf(radians); + if (radians != 0.f) obj_use_rot = true; +} + + +void g2dSetRotation(float degrees) { + g2dSetRotationRad(degrees * M_PI_180); +} + + +void g2dSetRotationRadRelative(float radians) { + g2dSetRotationRad(obj.rot + radians); +} + + +void g2dSetRotationRelative(float degrees) { + g2dSetRotationRadRelative(degrees * M_PI_180); +} + +// * Crop functions * + +void g2dResetCrop() { + if (obj_tex == NULL) return; + obj.crop_x = 0; + obj.crop_y = 0; + obj.crop_w = obj_tex->w; + obj.crop_h = obj_tex->h; +} + + +void g2dGetCropXY(int *x, int *y) { + if (obj_tex == NULL) return; + if (x != NULL) *x = obj.crop_x; + if (y != NULL) *y = obj.crop_y; +} + + +void g2dGetCropWH(int *w, int *h) { + if (obj_tex == NULL) return; + if (w != NULL) *w = obj.crop_w; + if (h != NULL) *h = obj.crop_h; +} + + +void g2dSetCropXY(int x, int y) { + if (obj_tex == NULL) return; + obj.crop_x = x; + obj.crop_y = y; +} + + +void g2dSetCropWH(int w, int h) { + if (obj_tex == NULL) return; + obj.crop_w = w; + obj.crop_h = h; +} + + +void g2dSetCropXYRelative(int x, int y) { + if (obj_tex == NULL) return; + g2dSetCropXY(obj.crop_x + x, obj.crop_y + y); +} + + +void g2dSetCropWHRelative(int w, int h) { + if (obj_tex == NULL) return; + g2dSetCropWH(obj.crop_w + w, obj.crop_h + h); +} + +// * Texture functions * + +void g2dResetTex() { + if (obj_tex == NULL) return; + obj_use_tex_repeat = false; + obj_use_tex_linear = false; + if (obj_tex->can_blend) obj_use_blend = true; +} + + +void g2dSetTexRepeat(bool use) { + if (obj_tex == NULL) return; + obj_use_tex_repeat = use; +} + + +void g2dSetTexBlend(bool use) { + if (obj_tex == NULL) return; + if (!obj_tex->can_blend) return; + obj_use_blend = use; +} + +void g2dSetTexBlendMode(int blendmode) { + obj_blend_mode = blendmode; + obj_use_blend = true; +} + + +void g2dSetTexLinear(bool use) { + if (obj_tex == NULL) return; + obj_use_tex_linear = use; +} + +// * Texture management * + +int _getNextPower2(int n) { + int p = 1; + while ((p <<= 1) < n); + return p; +} + +void _swizzle(unsigned char *dest, unsigned char *source, int width, int height) { + int i, j; + int rowblocks = (width / 16); + int rowblocks_add = (rowblocks - 1) * 128; + unsigned int block_address = 0; + unsigned int *img = (unsigned int *)source; + for (j = 0; j < height; j++, block_address += 16) { + unsigned int *block = (unsigned int *)(dest + block_address); + for (i = 0; i < rowblocks; i++) { + *block++ = *img++; + *block++ = *img++; + *block++ = *img++; + *block++ = *img++; + block += 28; + } + if ((j & 0x7) == 0x7) block_address += rowblocks_add; + } +} + +g2dImage *_g2dTexCreate(int w, int h, bool can_blend) { + g2dImage *tex = malloc(sizeof(g2dImage)); + if (tex == NULL) return NULL; + + tex->tw = _getNextPower2(w); + tex->th = _getNextPower2(h); + tex->w = w; + tex->h = h; + tex->ratio = (float)w / h; + tex->swizzled = false; + tex->can_blend = can_blend; + + tex->data = malloc(tex->tw * tex->th * sizeof(g2dColor)); + if (tex->data == NULL) { free(tex); return NULL; } + memset(tex->data, 0, tex->tw * tex->th * sizeof(g2dColor)); + + return tex; +} + + +void g2dTexFree(g2dImage **tex) { + if (tex == NULL) return; + free((*tex)->data); + free((*tex)); + *tex = NULL; +} + + +#ifdef USE_PNG +g2dImage *_g2dTexLoadPNG(FILE *fp) { + png_structp png_ptr; + png_infop info_ptr; + unsigned int sig_read = 0; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + u32 x, y, *line; + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_set_error_fn(png_ptr, NULL, NULL, NULL); + info_ptr = png_create_info_struct(png_ptr); + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, sig_read); + png_read_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_type, NULL, NULL); + png_set_strip_16(png_ptr); + png_set_packing(png_ptr); + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + g2dImage *tex = _g2dTexCreate(width, height, true); + line = malloc(width * 4); + for (y = 0; y < height; y++) { + png_read_row(png_ptr, (u8 *)line, NULL); + for (x = 0; x < width; x++) { + u32 color = line[x]; + tex->data[x + y * tex->tw] = color; + } + } + free(line); + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + return tex; +} + +void readData(png_structp png_ptr, png_bytep data, png_size_t length) { + CIMGContext *ctx = (CIMGContext *)png_get_io_ptr(png_ptr); + + if (ctx->pos + length > ctx->size) { + png_error(png_ptr, "Read beyond end of data"); + } + + memcpy(data, ctx->data + ctx->pos, length); + ctx->pos += length; +} + +g2dImage *_g2dTexLoadPNGfromC(const unsigned char *data, size_t size) { + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + u32 x, y, *line; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return NULL; + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return NULL; + } + + CIMGContext ctx = { data, size, 0 }; + png_set_read_fn(png_ptr, &ctx, readData); + png_set_sig_bytes(png_ptr, 0); + png_read_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); + png_set_strip_16(png_ptr); + png_set_packing(png_ptr); + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + + g2dImage *tex = _g2dTexCreate(width, height, true); + line = malloc(width * 4); + for (y = 0; y < height; y++) { + png_read_row(png_ptr, (u8 *)line, NULL); + for (x = 0; x < width; x++) { + u32 color = line[x]; + tex->data[x + y * tex->tw] = color; + } + } + free(line); + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + return tex; +} +#endif + +#ifdef USE_JPEG +g2dImage *_g2dTexLoadJPEG(FILE *fp) { + struct jpeg_decompress_struct dinfo; + struct jpeg_error_mgr jerr; + dinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&dinfo); + jpeg_stdio_src(&dinfo, fp); + jpeg_read_header(&dinfo, TRUE); + int width = dinfo.image_width; + int height = dinfo.image_height; + jpeg_start_decompress(&dinfo); + g2dImage *tex = _g2dTexCreate(width, height, false); + u8 *line = (u8 *)malloc(width * 3); + if (dinfo.jpeg_color_space == JCS_GRAYSCALE) { + while (dinfo.output_scanline < dinfo.output_height) { + int y = dinfo.output_scanline, x; + jpeg_read_scanlines(&dinfo, &line, 1); + for (x = 0; x < width; x++) { + g2dColor c = line[x]; + tex->data[x + tex->tw * y] = c | (c << 8) | (c << 16) | 0xff000000; + } + } + } else { + while (dinfo.output_scanline < dinfo.output_height) { + int y = dinfo.output_scanline, x; + jpeg_read_scanlines(&dinfo, &line, 1); + u8 *linePointer = line; + for (x = 0; x < width; x++) { + g2dColor c = *(linePointer++); + c |= (*(linePointer++)) << 8; + c |= (*(linePointer++)) << 16; + tex->data[x + tex->tw * y] = c | 0xff000000; + } + } + } + jpeg_finish_decompress(&dinfo); + jpeg_destroy_decompress(&dinfo); + free(line); + + return tex; +} + +void _jpegReadFromMemory(j_decompress_ptr cinfo, CIMGContext *ctx) { + struct jpeg_source_mgr *src = cinfo->src; + + if (ctx->pos >= ctx->size) { + // EOF + src->next_input_byte = NULL; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte = ctx->data + ctx->pos; + src->bytes_in_buffer = ctx->size - ctx->pos; + ctx->pos = ctx->size; + } +} + +g2dImage *_g2dTexLoadJPEGfromC(const unsigned char *data, size_t size) { + struct jpeg_decompress_struct dinfo; + struct jpeg_error_mgr jerr; + + dinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&dinfo); + jpeg_mem_src(&dinfo, (unsigned char *)data, size); + jpeg_read_header(&dinfo, TRUE); + + int width = dinfo.image_width; + int height = dinfo.image_height; + + jpeg_start_decompress(&dinfo); + + g2dImage *tex = _g2dTexCreate(width, height, false); + if (!tex) { + jpeg_destroy_decompress(&dinfo); + return NULL; + } + + u8 *line = (u8 *)malloc(width * 3); + if (!line) { + g2dTexFree(&tex); + jpeg_destroy_decompress(&dinfo); + return NULL; + } + + if (dinfo.jpeg_color_space == JCS_GRAYSCALE) { + while (dinfo.output_scanline < dinfo.output_height) { + int y = dinfo.output_scanline; + jpeg_read_scanlines(&dinfo, &line, 1); + + for (int x = 0; x < width; x++) { + g2dColor c = line[x]; + tex->data[x + tex->tw * y] = c | (c << 8) | (c << 16) | 0xff000000; + } + } + } else { + while (dinfo.output_scanline < dinfo.output_height) { + int y = dinfo.output_scanline; + jpeg_read_scanlines(&dinfo, &line, 1); + u8 *linePointer = line; + + for (int x = 0; x < width; x++) { + g2dColor c = *(linePointer++); + c |= (*(linePointer++)) << 8; + c |= (*(linePointer++)) << 16; + tex->data[x + tex->tw * y] = c | 0xff000000; + } + } + } + + free(line); + jpeg_finish_decompress(&dinfo); + jpeg_destroy_decompress(&dinfo); + + return tex; +} +#endif + +#ifdef USE_BMP +g2dImage *_g2dTexLoadBMP(FILE *fp) { + BMPHeader header; + fread(&header, sizeof(BMPHeader), 1, fp); + + if (header.signature != 0x4D42) + return NULL; + + + if (header.bitsPerPixel != 24 && header.bitsPerPixel != 32) + return NULL; + + + g2dImage *tex = _g2dTexCreate(header.width, abs(header.height), true); + if (!tex) return NULL; + + fseek(fp, header.dataOffset, SEEK_SET); + + int rowSize = ((header.width * header.bitsPerPixel + 31) / 32) * 4; + u8 *rowData = malloc(rowSize); + int y; + + for (y = 0; y < abs(header.height); y++) { + fread(rowData, 1, rowSize, fp); + u8 *pixel = rowData; + int x; + + for (x = 0; x < header.width; x++) { + u32 color = (pixel[0] << 16) | (pixel[1] << 8) | pixel[2] | 0xFF000000; + if (header.bitsPerPixel == 32) + pixel += 4; + else // 24-bit + pixel += 3; + + int targetY = (header.height > 0) ? (header.height - 1 - y) : y; + tex->data[x + targetY * tex->tw] = color; + } + } + + free(rowData); + return tex; +} + +g2dImage *_g2dTexLoadBMPfromC(const unsigned char *data, size_t size) { + CIMGContext ctx = { data, size, 0 }; + + if (ctx.size - ctx.pos < 54) return NULL; + + u16 signature = *(u16 *)(ctx.data + ctx.pos); + ctx.pos += 2; + if (signature != 0x4D42) return NULL; + + ctx.pos += 8; + + u32 dataOffset = *(u32 *)(ctx.data + ctx.pos); + ctx.pos += 4; + + u32 headerSize = *(u32 *)(ctx.data + ctx.pos); + ctx.pos += 4; + if (headerSize < 40) return NULL; + + u32 width = *(u32 *)(ctx.data + ctx.pos); + ctx.pos += 4; + u32 height = *(u32 *)(ctx.data + ctx.pos); + ctx.pos += 4; + + u16 planes = *(u16 *)(ctx.data + ctx.pos); + ctx.pos += 2; + if (planes != 1) return NULL; + + u16 bitsPerPixel = *(u16 *)(ctx.data + ctx.pos); + ctx.pos += 2; + if (bitsPerPixel != 24 && bitsPerPixel != 32) return NULL; + + ctx.pos = 54; + + g2dImage *tex = _g2dTexCreate(width, abs(height), true); + if (!tex) return NULL; + + ctx.pos = dataOffset; + + int rowSize = ((width * bitsPerPixel + 31) / 32) * 4; + int y; + + for (y = 0; y < abs(height); y++) { + if (ctx.pos + rowSize > ctx.size) { + g2dTexFree(&tex); + return NULL; + } + + const u8 *rowData = ctx.data + ctx.pos; + ctx.pos += rowSize; + + const u8 *pixel = rowData; + int x; + + for (x = 0; x < width; x++) { + u32 color = (pixel[0] << 16) | (pixel[1] << 8) | pixel[2] | 0xFF000000; + if (bitsPerPixel == 32) + pixel += 4; + else // 24-bit + pixel += 3; + + int targetY = (height > 0) ? (height - 1 - y) : y; + tex->data[x + targetY * tex->tw] = color; + } + } + + return tex; +} +#endif + +#ifdef USE_TGA +g2dImage *_g2dTexLoadTGA(FILE *fp) { + TGAHeader header; + if (fread(&header, sizeof(TGAHeader), 1, fp) != 1) return NULL; + + if (header.imageType != 2 && header.imageType != 10) return NULL; + + if (header.bpp != 24 && header.bpp != 32) return NULL; + + fseek(fp, header.idLength, SEEK_CUR); + + g2dImage *tex = _g2dTexCreate(header.width, header.height, true); + if (!tex) return NULL; + + int pixelSize = header.bpp / 8; + int imageSize = header.width * header.height * pixelSize; + u8 *imageData = malloc(imageSize); + if (!imageData) { + g2dTexFree(&tex); + return NULL; + } + + if (header.imageType == 2) + fread(imageData, 1, imageSize, fp); + else if (header.imageType == 10) { // RLE-сжатие + u8 *ptr = imageData; + int remaining = imageSize; + + while (remaining > 0) { + u8 chunkHeader; + if (fread(&chunkHeader, 1, 1, fp) != 1) break; + + int chunkSize = (chunkHeader & 0x7F) + 1; + int chunkBytes = chunkSize * pixelSize; + + if (chunkHeader & 0x80) { // RLE-пакет + u8 pixel[4]; + if (fread(pixel, 1, pixelSize, fp) != pixelSize) break; + + for (int i = 0; i < chunkSize; i++) { + if (remaining < pixelSize) break; + memcpy(ptr, pixel, pixelSize); + ptr += pixelSize; + remaining -= pixelSize; + } + } else { // RAW-пакет + int readBytes = chunkBytes < remaining ? chunkBytes : remaining; + if (fread(ptr, 1, readBytes, fp) != readBytes) break; + ptr += readBytes; + remaining -= readBytes; + } + } + } + + // Обработка пиксельных данных + u8 *pixel = imageData; + for (int y = 0; y < header.height; y++) { + int targetY = (header.descriptor & 0x20) ? y : (header.height - 1 - y); + + for (int x = 0; x < header.width; x++) { + u32 color; + if (header.bpp == 32) + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | (pixel[3] << 24); + else // 24-bit + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | 0xFF000000; + + tex->data[x + targetY * tex->tw] = color; + pixel += pixelSize; + } + } + + free(imageData); + return tex; +} + +g2dImage *_g2dTexLoadTGAfromC(const unsigned char *data, size_t size) { + CIMGContext ctx = { data, size, 0 }; + + if (ctx.size - ctx.pos < 18) return NULL; + + u8 idLength = ctx.data[ctx.pos++]; + u8 imageType = ctx.data[ctx.pos++]; + + ctx.pos += 5; + + u16 width = *(u16 *)(ctx.data + ctx.pos); + ctx.pos += 2; + u16 height = *(u16 *)(ctx.data + ctx.pos); + ctx.pos += 2; + + u8 bpp = ctx.data[ctx.pos++]; + u8 descriptor = ctx.data[ctx.pos++]; + + if (imageType != 2 && imageType != 10) return NULL; + + if (bpp != 24 && bpp != 32) return NULL; + + ctx.pos += idLength; + + g2dImage *tex = _g2dTexCreate(width, height, true); + if (!tex) return NULL; + + int pixelSize = bpp / 8; + int imageSize = width * height * pixelSize; + + if (imageType == 2) { + if (ctx.pos + imageSize > ctx.size) { + g2dTexFree(&tex); + return NULL; + } + + const u8 *imageData = ctx.data + ctx.pos; + ctx.pos += imageSize; + + const u8 *pixel = imageData; + for (int y = 0; y < height; y++) { + int targetY = (descriptor & 0x20) ? y : (height - 1 - y); + + for (int x = 0; x < width; x++) { + u32 color; + if (bpp == 32) + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | (pixel[3] << 24); + else // 24-bit + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | 0xFF000000; + + tex->data[x + targetY * tex->tw] = color; + pixel += pixelSize; + } + } + } else if (imageType == 10) { + u8 *imageData = malloc(imageSize); + if (!imageData) { + g2dTexFree(&tex); + return NULL; + } + + u8 *ptr = imageData; + int remaining = imageSize; + + while (remaining > 0 && ctx.pos < ctx.size) { + if (ctx.pos >= ctx.size) break; + + u8 chunkHeader = ctx.data[ctx.pos++]; + int chunkSize = (chunkHeader & 0x7F) + 1; + int chunkBytes = chunkSize * pixelSize; + + if (chunkHeader & 0x80) { + if (ctx.pos + pixelSize > ctx.size) break; + + u8 pixel[4]; + memcpy(pixel, ctx.data + ctx.pos, pixelSize); + ctx.pos += pixelSize; + + for (int i = 0; i < chunkSize; i++) { + if (remaining < pixelSize) break; + memcpy(ptr, pixel, pixelSize); + ptr += pixelSize; + remaining -= pixelSize; + } + } else { + int readBytes = (chunkBytes < remaining) ? chunkBytes : remaining; + if (ctx.pos + readBytes > ctx.size) break; + + memcpy(ptr, ctx.data + ctx.pos, readBytes); + ptr += readBytes; + ctx.pos += readBytes; + remaining -= readBytes; + } + } + + + u8 *pixel = imageData; + for (int y = 0; y < height; y++) { + int targetY = (descriptor & 0x20) ? y : (height - 1 - y); + + for (int x = 0; x < width; x++) { + u32 color; + if (bpp == 32) + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | (pixel[3] << 24); + else // 24-bit + color = pixel[2] | (pixel[1] << 8) | (pixel[0] << 16) | 0xFF000000; + + tex->data[x + targetY * tex->tw] = color; + pixel += pixelSize; + } + } + + free(imageData); + } + + return tex; +} +#endif + +g2dImage *g2dTexLoad(const char *path, unsigned char *data, size_t size, g2dTex_Mode mode) { + if (path == NULL && data == NULL && size == 0) return NULL; + + g2dImage *tex = NULL; + + if (path == NULL) { + if (size >= 8 && memcmp(data, "\x89PNG\r\n\x1a\n", 8) == 0) { +#ifdef USE_PNG + tex = _g2dTexLoadPNGfromC(data, size); +#endif + } else if (size >= 2 && data[0] == 0xFF && data[1] == 0xD8) { +#ifdef USE_JPEG + tex = _g2dTexLoadJPEGfromC(data, size); +#endif + } else if (size >= 2 && data[0] == 0x42 && data[1] == 0x4D) { +#ifdef USE_BMP + tex = _g2dTexLoadBMPfromC(data, size); +#endif + } else if (size >= 18) { + // Check for TGA (no magic number, check fields) + u8 imageType = data[2]; + if (imageType == 2 || imageType == 10) { // Uncompressed true-color +#ifdef USE_TGA + tex = _g2dTexLoadTGAfromC(data, size); +#endif + } + } + } else { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) return NULL; + + const char *ext = strrchr(path, '.'); + if (ext != NULL) { + ext++; // Skip the dot +#ifdef USE_PNG + if (strcasecmp(ext, "png") == 0) { + tex = _g2dTexLoadPNG(fp); + } +#endif + +#ifdef USE_JPEG + if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0) { + tex = _g2dTexLoadJPEG(fp); + } +#endif + +#ifdef USE_BMP + if (strcasecmp(ext, "bmp") == 0) { + tex = _g2dTexLoadBMP(fp); + } +#endif + +#ifdef USE_TGA + if (strcasecmp(ext, "tga") == 0) { + tex = _g2dTexLoadTGA(fp); + } +#endif + } + + fclose(fp); + } + + if (tex == NULL) return NULL; + + if (tex->data == NULL) { + g2dTexFree(&tex); + return NULL; + } + + if (tex->w > 512 || tex->h > 512) { + g2dTexFree(&tex); + return NULL; + } + + sceKernelDcacheWritebackAll(); + + if ((mode & G2D_SWIZZLE) && (tex->w >= 16 || tex->h >= 16)) { + u8 *tmp = malloc(tex->tw * tex->th * PIXEL_SIZE); + _swizzle(tmp, (u8 *)tex->data, tex->tw * PIXEL_SIZE, tex->th); + free(tex->data); + tex->data = (g2dColor *)tmp; + tex->swizzled = true; + sceKernelDcacheWritebackAll(); + } else tex->swizzled = false; + + return tex; +} + +g2dImage *g2dTexCreatePlaceholder(int width, int height) { + // Create a 64x64 image with alpha blending support + g2dImage *tex = _g2dTexCreate(width, height, true); + if (tex == NULL) return NULL; + + // Define the colors + g2dColor dark_gray = DARKGRAY; // 0xFF3F3F3F + g2dColor light_gray = LITEGRAY; // 0xFFBFBFBF + + // Size of each checkerboard square + int square_size = 8; + + // Fill the texture with checkerboard pattern + for (int y = 0; y < tex->h; y++) { + for (int x = 0; x < tex->w; x++) { + // Determine which square we're in + int square_x = x / square_size; + int square_y = y / square_size; + + // Use alternating colors based on square position + // Creates a chessboard pattern + if ((square_x + square_y) % 2 == 0) { + tex->data[x + y * tex->tw] = dark_gray; + } else { + tex->data[x + y * tex->tw] = light_gray; + } + } + } + + // Write back to cache for performance + sceKernelDcacheWritebackAll(); + + return tex; +} + +// * Scissor functions * + +void g2dResetScissor() { + g2dSetScissor(0, 0, G2D_SCR_W, G2D_SCR_H); + scissor = false; +} + + +void g2dSetScissor(int x, int y, int w, int h) { + sceGuScissor(x, y, w, h); + scissor = true; +} + +void set_pixel(g2dImage *tex, int x, int y, g2dColor color) { + if (x >= 0 && x < tex->w && y >= 0 && y < tex->h) { + tex->data[x + y * tex->tw] = color; + } +} + +g2dColor get_pixel(g2dImage *tex, int x, int y) { + if (x >= 0 && x < tex->w && y >= 0 && y < tex->h) { + return tex->data[x + y * tex->tw]; + } + return 0; // или какое-то значение по умолчанию +} + +void draw_circle(g2dImage *tex, int x, int y, int w, int h, g2dColor color) { + for (int i = 0; i < w; i++) { + set_pixel(tex, x + i, y, color); // верхняя линия + set_pixel(tex, x + i, y + h - 1, color); // нижняя линия + } + for (int i = 0; i < h; i++) { + set_pixel(tex, x, y + i, color); // левая линия + set_pixel(tex, x + w - 1, y + i, color); // правая линия + } + sceKernelDcacheWritebackAll(); +} + +// * Debug functions * + +int g2dDebugGetAdds() { + return obj_debug_adds; +} + +int g2dDebugGetEnds() { + return obj_debug_ends; +} + +// EOF diff --git a/src/psp/glib2d.h b/src/psp/glib2d.h new file mode 100644 index 0000000..1f21d80 --- /dev/null +++ b/src/psp/glib2d.h @@ -0,0 +1,887 @@ +/** \mainpage gLib2D Documentation + * + * \section intro Introduction + * + * gLib2D by Geecko - A simple, fast, light-weight 2D graphics library. \n\n + * This library has been designed to replace the old graphics.c library + * and to simplify the use of pspgu.\n + * The goals : keep it simple, keep it small, keep it fast. + * + * \section limits Known limitations + * + * - Draw & display buffers can't actually be used as real textures. Just a way + * to get the vram pointer. + * - No support for multiples contexts (e.g. sharing coordinates beetween + * textures using some gBegin calls at a time). + * - Manipulating textures (clear, get pixel info...) is not possible. + * - When some 512*512 rotated, colorized and scaled textures are rendered + * at a time, the framerate *could* go under 60 fps. + * + * \section install Installation + * + * - Simply put glib2d.c and glib2d.h in your source directory. \n + * - Then add glib2d.o and link "-lpng -ljpeg -lz -lpspgu -lm -lpspvram" + * in your Makefile. + * - You're done ! + * + * \section copyright License + * + * This work is licensed under the Creative Commons BY-SA 3.0 License. \n + * See http://creativecommons.org/licenses/by-sa/3.0/ or the LICENSE file + * for more details. \n + * You can support the library by marking your homebrew with + * "Using gLib2D by Geecko". + * + * \section contact Contact + * + * Please report bugs or submit ideas at : \n geecko.dev@free.fr \n\n + * Get the full documentation on : \n http://geecko.dev.free.fr \n\n + * Also stay tuned on... \n + * https://github.com/GeeckoDev (contributors would be a plus!) \n + * http://twitter.com/GeeckoDev + */ + + + /** + * \file glib2d.h + * \brief gLib2D Header + * \version Beta 5 + */ + +#ifndef GLIB2D_H +#define GLIB2D_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#include +#include +#include +#include + + /** + * \def USE_PNG + * \brief Choose if the PNG support is enabled. + * + * Otherwise, this part will be not compiled to gain some space. + * Enable this to get PNG support, disable to avoid compilation errors + * when libpng is not linked in the Makefile. + */ + /** + * \def USE_JPEG + * \brief Choose if the JPEG support is enabled. + * + * Otherwise, this part will be not compiled to gain some space. + * Enable this to get JPEG support, disable to avoid compilation errors + * when libjpeg is not linked in the Makefile. + */ + /** + * \def USE_VFPU + * \brief Choose if the VFPU support is enabled. + * + * Otherwise, this part will be not compiled to use the standard math library. + * Enable this to greatly improve performance with 2d rotations. You SHOULD use + * PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU) to avoid crashes. + */ +#define USE_PNG +#define USE_JPEG +#define USE_BMP +#define USE_TGA + +#define USE_VFPU + +#ifdef USE_PNG +#include +#endif + +#ifdef USE_JPEG +#include +#endif + + /** + * \def G2D_SCR_W + * \brief Screen width constant, in pixels. + */ + /** + * \def G2D_SCR_H + * \brief Screen height constant, in pixels. + */ + /** + * \def G2D_VOID + * \brief Generic constant, equals to 0 (do nothing). + */ +#define G2D_SCR_W (480) +#define G2D_SCR_H (272) +#define G2D_VOID 0 + + /** + * \def G2D_RGBA(r,g,b,a) + * \brief Create a g2dColor. + * + * This macro creates a g2dColor from 4 values, red, green, blue and alpha. + * Input range is from 0 to 255. + */ +#define G2D_RGBA(r,g,b,a) ((r)|((g)<<8)|((b)<<16)|((a)<<24)) + + /** + * \def G2D_GET_R(color) + * \brief Get red channel value from a g2dColor. + */ + /** + * \def G2D_GET_G(color) + * \brief Get green channel value from a g2dColor. + */ + /** + * \def G2D_GET_B(color) + * \brief Get blue channel value from a g2dColor. + */ + /** + * \def G2D_GET_A(color) + * \brief Get alpha channel value from a g2dColor. + */ +#define G2D_GET_R(color) (((color) ) & 0xFF) +#define G2D_GET_G(color) (((color)>>8 ) & 0xFF) +#define G2D_GET_B(color) (((color)>>16) & 0xFF) +#define G2D_GET_A(color) (((color)>>24) & 0xFF) + + /** + * \def G2D_MODULATE(color,luminance,alpha) + * \brief g2dColor modulation. + * + * This macro modulates the luminance & alpha of a g2dColor. + * Input range is from 0 to 255. + */ +#define G2D_MODULATE(color,luminance,alpha) \ +G2D_RGBA((int)(luminance)*G2D_GET_R(color)/255, \ + (int)(luminance)*G2D_GET_G(color)/255, \ + (int)(luminance)*G2D_GET_B(color)/255, \ + (int)(alpha )*G2D_GET_A(color)/255) + + /** + * \enum g2dColors + * \brief Colors enumeration. + * + * Primary, secondary, tertiary and grayscale colors are defined. + */ + enum g2dColors + { + // Primary colors + RED = 0xFF0000FF, + GREEN = 0xFF00FF00, + BLUE = 0xFFFF0000, + // Secondary colors + CYAN = 0xFFFFFF00, + MAGENTA = 0xFFFF00FF, + YELLOW = 0xFF00FFFF, + // Tertiary colors + AZURE = 0xFFFF7F00, + VIOLET = 0xFFFF007F, + ROSE = 0xFF7F00FF, + ORANGE = 0xFF007FFF, + CHARTREUSE = 0xFF00FF7F, + SPRING_GREEN = 0xFF7FFF00, + // Grayscale + WHITE = 0xFFFFFFFF, + LITEGRAY = 0xFFBFBFBF, + GRAY = 0xFF7F7F7F, + DARKGRAY = 0xFF3F3F3F, + BLACK = 0xFF000000 + }; + + /** + * \enum g2dLine_Mode + * \brief Line modes enumeration. + * + * Change line draw properties. + * Can only be used with g2dBeginLines. + */ + /** + * \enum g2dFlip_Mode + * \brief Flip modes enumeration. + * + * Change flip properties. + * Can only be used with g2dFlip. + */ + /** + * \enum g2dTex_Mode + * \brief Texture modes enumeration. + * + * Change texture properties. + * Can only be used with g2dTexLoad. + */ + typedef enum + { + G2D_STRIP = 1 /**< Make a line strip. */ + } g2dLine_Mode; + typedef enum + { + G2D_VSYNC = 1 /**< Limit the FPS to 60 (synchronized with the screen). + Better image quality and less power consumption. */ + } g2dFlip_Mode; + typedef enum + { + G2D_SWIZZLE = 1 /**< Recommended. Use it to get *more* rendering speed. */ + } g2dTex_Mode; + + /** + * \var g2dAlpha + * \brief Alpha type. + */ + /** + * \var g2dColor + * \brief Color type. + */ + typedef int g2dAlpha; + typedef unsigned int g2dColor; + + /** + * \struct g2dImage + * \brief Image structure. + */ + typedef struct + { + int tw; /**< Real texture width. A power of two. */ + int th; /**< Real texture height. A power of two. */ + int w; /**< Texture width, as seen when drawing. */ + int h; /**< Texture height, as seen when drawing. */ + float ratio; /**< Width/height ratio. */ + bool swizzled; /**< Is the texture swizzled ? */ + bool can_blend; /**< Can the texture blend ? */ + g2dColor *data; /**< Pointer to raw data. */ + } g2dImage; + +#ifdef USE_BMP +#pragma pack(push, 1) + typedef struct + { + u16 signature; + u32 fileSize; + u32 reserved; + u32 dataOffset; + u32 headerSize; + u32 width; + u32 height; + u16 planes; + u16 bitsPerPixel; + u32 compression; + u32 imageSize; + u32 xPixelsPerMeter; + u32 yPixelsPerMeter; + u32 colorsUsed; + u32 colorsImportant; + } BMPHeader; + +#pragma pack(pop) +#endif + +#ifdef USE_TGA +#pragma pack(push, 1) + typedef struct + { + u8 idLength; + u8 colorMapType; + u8 imageType; + u16 colorMapFirst; + u16 colorMapLength; + u8 colorMapDepth; + u16 xOrigin; + u16 yOrigin; + u16 width; + u16 height; + u8 bpp; + u8 descriptor; + } TGAHeader; +#pragma pack(pop) +#endif + + typedef struct + { + const u8 *data; + size_t size; + size_t pos; + } CIMGContext; + + /** + * \var g2d_draw_buffer + * \brief The current draw buffer as a texture. + */ + /** + * \var g2d_disp_buffer + * \brief The current display buffer as a texture. + */ + extern g2dImage g2d_draw_buffer; + extern g2dImage g2d_disp_buffer; + + g2dImage *_g2dTexCreate(int w, int h, bool can_blend); + + /** + * \brief Initializes the library. + * + * This function will create a GU context and setup the display buffers. + * Automatically called by the other functions. + */ + void g2dInit(); + + /** + * \brief Shutdowns the library. + * + * This function will destroy the GU context. + */ + void g2dTerm(); + + /** + * \brief Clears screen & depth buffer. + * @param color Screen clear color + * + * This function clears the screen, and clears the zbuffer if depth coordinate + * is used in the loop. Will automatically init the GU if needed. + */ + void g2dClear(g2dColor color); + + /** + * \brief Clears depth buffer. + * + * This function clears the zbuffer to zero (z range 0-65535). + * Will automatically init the GU if needed. + */ + void g2dClearZ(); + + /** + * \brief Begins rectangles rendering. + * @param tex Pointer to a texture, pass NULL to get a colored rectangle. + * + * This function begins object rendering. Resets all properties. + * One g2dAdd() call per object. + * Only one texture can be used, but multiple objects can be rendered at a time. + * g2dBegin*() / g2dEnd() couple can be called multiple times in the loop, + * to render multiple textures. + */ + void g2dBeginRects(g2dImage *tex); + + /** + * \brief Begins lines rendering. + * @param line_mode A g2dLine_Mode constant. + * + * This function begins object rendering. Calls g2dReset(). + * Two g2dAdd() calls per object. + * Pass G2D_LINE_STRIP to make a line strip (two calls, then one per object). + */ + void g2dBeginLines(g2dLine_Mode mode); + + /** + * \brief Begins quads rendering. + * @param tex Pointer to a texture, pass NULL to get a colored quad. + * + * This function begins object rendering. Resets all properties. + * Four g2dAdd() calls per object, first for the up left corner, then clockwise. + * Only one texture can be used, but multiple objects can be rendered at a time. + * g2dBegin*() / g2dEnd() couple can be called multiple times in the loop, + * to render multiple textures. + */ + void g2dBeginQuads(g2dImage *tex); + + /** + * \brief Begins points rendering. + * + * This function begins object rendering. Resets all properties. + * One g2dAdd() call per object. + */ + void g2dBeginPoints(); + + /** + * \brief Ends object rendering. + * + * This function ends object rendering. Must be called after g2dBegin*() to add + * objects to the display list. Automatically adapts pspgu functionnalities + * to get the best performance possible. + */ + void g2dEnd(); + + /** + * \brief Resets current transformation and attribution. + * + * This function must be called during object rendering. + * Calls g2dResetCoord(), g2dResetRotation(), g2dResetScale(), + * g2dResetColor(), g2dResetAlpha(), g2dResetCrop() and g2dResetTex(). + */ + void g2dReset(); + + /** + * \brief Flips the screen. + * @param flip_mode A g2dFlip_Mode constant. + * + * This function must be called at the end of the loop. + * Renders the whole display list to the draw buffer. + * Inverts framebuffers to display the whole thing. + */ + void g2dFlip(g2dFlip_Mode mode); + + /** + * \brief Pushes the current transformation & attribution to a new object. + * + * This function must be called during object rendering. + */ + void g2dAdd(); + + /** + * \brief Saves the current transformation to stack. + * + * This function must be called during object rendering. + * The stack is 64 saves high. + * Use it like the OpenGL one. + */ + void g2dPush(); + + /** + * \brief Restore the current transformation from stack. + * + * This function must be called during object rendering. + * The stack is 64 saves high. + * Use it like the OpenGL one. + */ + void g2dPop(); + + /** + * \brief Frees an image & set its pointer to NULL. + * @param tex Pointer to the variable which contains the image pointer. + * + * This function is used to gain memory when an image is useless. + * Must pass the pointer to the variable which contains the pointer, + * to set it to NULL (passing NULL to a g2dBegin* function is safe). + */ + void g2dTexFree(g2dImage **tex); + + /** + * \brief Loads an image. + * @param path Path to the file. + * @param tex_mode A g2dTex_Mode constant. + * @returns Pointer to the image. + * + * This function loads an image file. There is support for PNG & JPEG files + * (if USE_PNG and USE_JPEG are defined). Swizzling is enabled only for 16*16+ + * textures (useless on small textures), pass G2D_SWIZZLE to enable it. + * Image support up to 512*512 only (hardware limitation). + */ + g2dImage *g2dTexLoad(const char *path, unsigned char *data, size_t size, g2dTex_Mode mode); + + /** + * \brief Creates a placeholder image with a checkerboard pattern. + * @returns Pointer to the created image. + * + * This function creates a 64x64 placeholder image with alternating + * dark and light gray squares, commonly used as a placeholder texture. + * The pattern consists of 8x8 pixel squares in a chessboard arrangement. + */ + g2dImage *g2dTexCreatePlaceholder(int width, int height); + + /** + * \brief Gets the current position. + * @param x Pointer to save the current x (in pixels). + * @param y Pointer to save the current y (in pixels). + * @param z Pointer to save the current z (in pixels). + * + * This function must be called during object rendering. + * Parameters are pointers to float, not int ! + * Pass NULL if not needed. + */ + void g2dGetCoordXYZ(float *x, float *y, float *z); + + /** + * \brief Sets the new position. + * @param x New x, in pixels. + * @param y New y, in pixels. + * + * This function must be called during object rendering. + */ + void g2dSetCoordXY(float x, float y); + + /** + * \brief Sets the new position, with depth support. + * @param x New x, in pixels. + * @param y New y, in pixels. + * @param z New z, in pixels. (front 0-65535 back) + * + * This function must be called during object rendering. + */ + void g2dSetCoordXYZ(float x, float y, float z); + + /** + * \brief Sets the new position, relative to the current. + * @param x New x increment, in pixels. + * @param y New y increment, in pixels. + * + * This function must be called during object rendering. + */ + void g2dSetCoordXYRelative(float x, float y); + + /** + * \brief Sets the new position, with depth support, relative to the current. + * @param x New x increment, in pixels. + * @param y New y increment, in pixels. + * @param z New z increment, in pixels. + * + * This function must be called during object rendering. + */ + void g2dSetCoordXYZRelative(float x, float y, float z); + + /** + * \brief Use integer coordinates. + * @param use false to desactivate (better look, by default), + true to activate (can be useful when you have glitches). + * + * This function must be called during object rendering. + */ + void g2dSetCoordInteger(bool use); + + /** + * \brief Resets the global scale. + * + * This function resets the global scale to 1.f. + * Translations and scales are multiplied by this factor. + */ + void g2dResetGlobalScale(); + + /** + * \brief Resets the current scale. + * + * This function must be called during object rendering. + * Sets the scale to the current image size or (10,10). + */ + void g2dResetScale(); + + /** + * \brief Gets the global scale. + * @param scale Pointer to save the global scale (factor). + * + * Pass NULL if not needed. + */ + void g2dGetGlobalScale(float *scale); + + /** + * \brief Gets the current scale. + * @param w Pointer to save the current width (in pixels). + * @param h Pointer to save the current height (in pixels). + * + * This function must be called during object rendering. + * Parameters are pointers to float, not int ! + * Pass NULL if not needed. + */ + void g2dGetScaleWH(float *w, float *h); + + /** + * \brief Sets the global scale. + * + * Translations and scales are multiplied by this factor. + */ + void g2dSetGlobalScale(float scale); + + /** + * \brief Sets the new scale. + * @param w Width scale factor. + * @param h Height scale factor. + * + * This function must be called during object rendering. + * g2dResetScale() is called, then width & height scale are + * multiplied by these values. + * Negative values can be passed to invert the image. + */ + void g2dSetScale(float w, float h); + + /** + * \brief Sets the new scale, in pixels. + * @param w New width, in pixels. + * @param h New height, in pixels. + * + * This function must be called during object rendering. + * Negative values can be passed to invert the image. + */ + void g2dSetScaleWH(float w, float h); + + /** + * \brief Sets the new scale, relative to the current. + * @param w Width scale factor. + * @param h Height scale factor. + * + * This function must be called during object rendering. + * Current width & height scale are multiplied by these values. + * Negative values can be passed to invert the image. + */ + void g2dSetScaleRelative(float w, float h); + + /** + * \brief Sets the new scale, in pixels, relative to the current. + * @param w New width to increment, in pixels. + * @param h New height to increment, in pixels. + * + * This function must be called during object rendering. + * Negative values can be passed to invert the image. + */ + void g2dSetScaleWHRelative(float w, float h); + + /** + * \brief Resets the current color. + * + * This function must be called during object rendering. + * Sets g2dSetColor() to WHITE. + */ + void g2dResetColor(); + + /** + * \brief Resets the current alpha. + * + * This function must be called during object rendering. + * Sets g2dSetAlpha() to 255. + */ + void g2dResetAlpha(); + + /** + * \brief Gets the current alpha. + * @param alpha Pointer to save the current alpha (0-255). + * + * This function must be called during object rendering. + * Pass NULL if not needed. + */ + void g2dGetAlpha(g2dAlpha *alpha); + + /** + * \brief Sets the new color. + * @param color The new color. + * + * This function must be called during object rendering. + * Can be used to colorize any object. + */ + void g2dSetColor(g2dColor color); + + /** + * \brief Sets the new alpha. + * @param alpha The new alpha (0-255). + * + * This function must be called during object rendering. + * Can be used to make any object transparent. + */ + void g2dSetAlpha(g2dAlpha alpha); + + /** + * \brief Sets the new alpha, relative to the current alpha. + * @param alpha The new alpha increment. + * + * This function must be called during object rendering. + * Can be used to make any object transparent. + */ + void g2dSetAlphaRelative(int alpha); + + /** + * \brief Resets the current rotation. + * + * This function must be called during object rendering. + * Sets g2dSetRotation() to 0°. + */ + void g2dResetRotation(); + + /** + * \brief Gets the current rotation, in radians. + * @param radians Pointer to save the current rotation. + * + * This function must be called during object rendering. + * Pass NULL if not needed. + */ + void g2dGetRotationRad(float *radians); + + /** + * \brief Gets the current rotation, in degrees. + * @param degrees Pointer to save the current rotation. + * + * This function must be called during object rendering. + * Pass NULL if not needed. + */ + void g2dGetRotation(float *degrees); + + /** + * \brief Sets the new rotation, in radians. + * @param radians The new angle. + * + * This function must be called during object rendering. + * The rotation center is the actual coordinates. + */ + void g2dSetRotationRad(float radians); + + /** + * \brief Sets the new rotation, in degrees. + * @param degrees The new angle. + * + * This function must be called during object rendering. + * The rotation center is the actual coordinates. + */ + void g2dSetRotation(float degrees); + + /** + * \brief Sets the new rotation, relative to the current, in radians. + * @param radians The new angle increment. + * + * This function must be called during object rendering. + * The rotation center is the actual coordinates. + */ + void g2dSetRotationRadRelative(float radians); + + /** + * \brief Sets the new rotation, relative to the current, in degrees. + * @param degrees The new angle increment. + * + * This function must be called during object rendering. + * The rotation center is the actual coordinates. + */ + void g2dSetRotationRelative(float degrees); + + /** + * \brief Resets the current crop. + * + * This function must be called during object rendering. + * Sets g2dSetCropXY() to (0;0) and g2dSetCropWH() to (tex->w,tex->h). + */ + void g2dResetCrop(); + + /** + * \brief Gets the current crop position. + * @param x Pointer to save the current crop x. + * @param y Pointer to save the current crop y. + * + * This function must be called during object rendering. + * Pass NULL if not needed. + */ + void g2dGetCropXY(int *x, int *y); + + /** + * \brief Gets the current crop scale. + * @param w Pointer to save the current crop width. + * @param h Pointer to save the current crop height. + * + * This function must be called during object rendering. + * Pass NULL if not needed. + */ + void g2dGetCropWH(int *w, int *h); + + /** + * \brief Sets the new crop position. + * @param x New x, in pixels. + * @param y New y, in pixels. + * + * This function must be called during object rendering. Defines crop position. + * If the rectangle is larger or next to the image, texture will be repeated + * when g2dSetTexRepeat is enabled. Useful for a tileset. + */ + void g2dSetCropXY(int x, int y); + + /** + * \brief Sets the new crop size. + * @param w New width, in pixels. + * @param h New height, in pixels. + * + * This function must be called during object rendering. Defines crop size. + * If the rectangle is larger or next to the image, texture will be repeated + * when g2dSetTexRepeat is enabled. Useful for a tileset. + */ + void g2dSetCropWH(int w, int h); + + /** + * \brief Sets the new crop position, relative to the current. + * @param x New x increment, in pixels. + * @param y New y increment, in pixels. + * + * This function must be called during object rendering. Defines crop position. + * If the rectangle is larger or next to the image, texture will be repeated + * when g2dSetTexRepeat is enabled. Useful for a tileset. + */ + void g2dSetCropXYRelative(int x, int y); + + /** + * \brief Sets the new crop size, relative to the current. + * @param w New width increment, in pixels. + * @param h New height increment, in pixels. + * + * This function must be called during object rendering. Defines crop size. + * If the rectangle is larger or next to the image, texture will be repeated + * when g2dSetTexRepeat is enabled. Useful for a tileset. + */ + void g2dSetCropWHRelative(int w, int h); + + /** + * \brief Resets texture properties. + * + * This function must be called during object rendering. + */ + void g2dResetTex(); + + /** + * \brief Set texture wrap. + * @param use true to repeat, false to clamp (by default). + * + * This function must be called during object rendering. + */ + void g2dSetTexRepeat(bool use); + + /** + * \brief Use alpha blending with the texture. + * @param use true to activate (better look, by default), + false to desactivate (better performance). + * + * This function must be called during object rendering. + * Automaticaly disabled when g2dImage::can_blend is set to false. + */ + void g2dSetTexBlend(bool use); + + /** + * \brief ? + * + * ? + */ + void g2dSetTexBlendMode(int blendmode); + + /** + * \brief Use the bilinear filter with the texture. + * @param use true to activate (better look, by default). + false to desactivate (better performance). + * + * This function must be called during object rendering. + * Only useful when scaling. + */ + void g2dSetTexLinear(bool use); + + /** + * \brief Resets the draw zone to the entire screen. + * + * This function can be called everywhere in the loop. + */ + void g2dResetScissor(); + + /** + * \brief Sets the draw zone. + * @param x New x position. + * @param y New y position. + * @param w New width. + * @param h New height. + * + * This function can be called everywhere in the loop. + * Pixel draw will be skipped outside this rectangle. + */ + void g2dSetScissor(int x, int y, int w, int h); + + // * Debug functions * + int g2dDebugGetAdds(); + int g2dDebugGetEnds(); + + void set_pixel(g2dImage *tex, int x, int y, g2dColor color); + + void draw_circle(g2dImage *tex, int x, int y, int w, int h, g2dColor color); + + g2dColor get_pixel(g2dImage *tex, int x, int y); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/psp/intra/intraFont.c b/src/psp/intra/intraFont.c new file mode 100644 index 0000000..9604e82 --- /dev/null +++ b/src/psp/intra/intraFont.c @@ -0,0 +1,1409 @@ +/* + * intraFont.c + * This file is used to display the PSP's internal font (pgf and bwfon firmware files) + * intraFont Version 0.31 by BenHur - http://www.psp-programming.com/benhur + * + * Uses parts of pgeFont by InsertWittyName - http://insomniac.0x89.org + * G-spec code by Geecko + * + * This work is licensed under the Creative Commons Attribution-Share Alike 3.0 License. + * See LICENSE for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intraFont.h" + +static unsigned int __attribute__((aligned(16))) clut[16]; + +unsigned long intraFontGetV(unsigned long n, unsigned char *p, unsigned long *b) { + unsigned long i, v = 0; + for (i = 0;i < n;i++) { + v += ((p[(*b) / 8] >> ((*b) % 8)) & 1) << i; + (*b)++; + } + return v; +} + +unsigned long *intraFontGetTable(FILE *file, unsigned long n_elements, unsigned long bp_element) { + unsigned long len_table = ((n_elements * bp_element + 31) / 32) * 4; + unsigned char *raw_table = (unsigned char *)malloc(len_table * sizeof(unsigned char)); + if (raw_table == NULL) return NULL; + if (fread(raw_table, len_table * sizeof(unsigned char), 1, file) != 1) { + free(raw_table); + return NULL; + } + unsigned long *table = (unsigned long *)malloc(n_elements * sizeof(unsigned long)); + if (table == NULL) { + free(raw_table); + return NULL; + } + unsigned long i, j = 0; + for (i = 0;i < n_elements;i++) { + table[i] = intraFontGetV(bp_element, raw_table, &j); + } + free(raw_table); + return table; +} + +int intraFontGetBMP(intraFont *font, unsigned short id, unsigned char glyphtype) { + if (!font) return 0; + if (font->options & INTRAFONT_CACHE_ASCII) return 0; //swizzeled texture + + Glyph *glyph; + if (font->fileType == FILETYPE_PGF) { + if (glyphtype & PGF_CHARGLYPH) { + glyph = &(font->glyph[id]); + } else { + return 0; + } + } else { //FILETYPE_BWFON + if (glyphtype & PGF_CHARGLYPH) { + glyph = &(font->glyph[0]); + glyph->flags = font->glyphBW[id].flags | PGF_BMP_H_ROWS; + } else { + return 0; + } + glyph->ptr = ((unsigned long)id) * 36; //36 bytes/char + } + + if (glyph->flags & PGF_CACHED) return 1; + unsigned long b = glyph->ptr * 8; + + if (glyph->width > 0 && glyph->height > 0) { + if (!(glyph->flags & PGF_BMP_H_ROWS) != !(glyph->flags & PGF_BMP_V_ROWS)) { //H_ROWS xor V_ROWS (real glyph, no overlay) + if ((font->texX + glyph->width + 1) > font->texWidth) { + font->texY += font->texYSize + 1; + font->texX = 1; + } + if ((font->texY + glyph->height + 1) > font->texHeight) { + font->texY = 1; + font->texX = 1; + } + glyph->x = font->texX; + glyph->y = font->texY; + + //draw bmp + int i = 0, j, xx, yy; + unsigned char nibble, value = 0; + if (font->fileType == FILETYPE_PGF) { //for compressed pgf format + while (i < (glyph->width * glyph->height)) { + nibble = intraFontGetV(4, font->fontdata, &b); + if (nibble < 8) value = intraFontGetV(4, font->fontdata, &b); + for (j = 0; (j <= ((nibble < 8) ? (nibble) : (15 - nibble))) && (i < (glyph->width * glyph->height)); j++) { + if (nibble >= 8) value = intraFontGetV(4, font->fontdata, &b); + if (glyph->flags & PGF_BMP_H_ROWS) { + xx = i % glyph->width; + yy = i / glyph->width; + } else { + xx = i / glyph->height; + yy = i % glyph->height; + } + if ((font->texX + xx) & 1) { + font->texture[((font->texX + xx) + (font->texY + yy) * font->texWidth) >> 1] &= 0x0F; + font->texture[((font->texX + xx) + (font->texY + yy) * font->texWidth) >> 1] |= (value << 4); + } else { + font->texture[((font->texX + xx) + (font->texY + yy) * font->texWidth) >> 1] &= 0xF0; + font->texture[((font->texX + xx) + (font->texY + yy) * font->texWidth) >> 1] |= (value); + } + i++; + } + } + } else { //for uncompressed bwfon format (could use some optimizations...) + for (yy = 0; yy < glyph->height; yy++) { + for (xx = 0; xx < glyph->width; xx++) { + if (glyphtype & PGF_CHARGLYPH) { + value = intraFontGetV(1, font->fontdata, &b) * 0x0f; //scale 1 bit/pix to 4 bit/pix + if ((font->texX + (7 - (xx & 7) + (xx & 248))) & 1) { + font->texture[((font->texX + (7 - (xx & 7) + (xx & 248))) + (font->texY + yy) * font->texWidth) >> 1] &= 0x0F; + font->texture[((font->texX + (7 - (xx & 7) + (xx & 248))) + (font->texY + yy) * font->texWidth) >> 1] |= (value << 4); + } else { + font->texture[((font->texX + (7 - (xx & 7) + (xx & 248))) + (font->texY + yy) * font->texWidth) >> 1] &= 0xF0; + font->texture[((font->texX + (7 - (xx & 7) + (xx & 248))) + (font->texY + yy) * font->texWidth) >> 1] |= (value); + } + } else { //PGF_SHADOWGLYPH + + } + + } + } + } + + //erase border around glyph + for (i = font->texX / 2; i < (font->texX + glyph->width + 1) / 2; i++) { + font->texture[i + (font->texY - 1) * font->texWidth / 2] = 0; + font->texture[i + (font->texY + glyph->height) * font->texWidth / 2] = 0; + } + for (i = font->texY - 1; i < (font->texY + glyph->height + 1); i++) { + font->texture[((font->texX - 1) + (i * font->texWidth)) >> 1] &= (font->texX & 1) ? 0xF0 : 0x0F; + font->texture[((font->texX + glyph->width) + (i * font->texWidth)) >> 1] &= ((font->texX + glyph->width) & 1) ? 0x0F : 0xF0; + } + font->texX += glyph->width + 1; //add empty gap to prevent interpolation artifacts from showing + + //mark dirty glyphs as uncached + if (font->fileType == FILETYPE_PGF) { //...for PGF glyphs + for (i = 0; i < font->n_chars; i++) { + if ((font->glyph[i].flags & PGF_CACHED) && (font->glyph[i].y == glyph->y)) { + if ((font->glyph[i].x + font->glyph[i].width + 1) > glyph->x && font->glyph[i].x < (glyph->x + glyph->width + 1)) { + font->glyph[i].flags -= PGF_CACHED; + } + } + } + } else { //...for BWFON glyphs + for (i = 0; i < font->n_chars; i++) { + if ((font->glyphBW[i].flags & PGF_CACHED) && (font->glyphBW[i].y == glyph->y)) { + if ((font->glyphBW[i].x + font->glyph[0].width + 1) > glyph->x && font->glyphBW[i].x < (glyph->x + glyph->width + 1)) { + font->glyphBW[i].flags -= PGF_CACHED; + } + } + } + } + + } else return 0; //transposition=0 or overlay glyph + } else { + glyph->x = 0; + glyph->y = 0; + } + glyph->flags |= PGF_CACHED; + + if ((font->fileType == FILETYPE_BWFON) && (glyphtype & PGF_CHARGLYPH)) { //write properties back to BW-glyphs + font->glyphBW[id].x = glyph->x; + font->glyphBW[id].y = glyph->y; + font->glyphBW[id].flags = glyph->flags; + } + + return 1; //texture has changed +} + +int intraFontGetGlyph(unsigned char *data, unsigned long *b, unsigned char glyphtype, signed long *advancemap, Glyph *glyph) { + if (glyphtype & PGF_CHARGLYPH) { + (*b) += 14; //skip offset pos value of shadow + } else { + (*b) += intraFontGetV(14, data, b) * 8 + 14; //skip to shadow + } + glyph->width = intraFontGetV(7, data, b); + glyph->height = intraFontGetV(7, data, b); + glyph->left = intraFontGetV(7, data, b); + if (glyph->left >= 64) glyph->left -= 128; + glyph->top = intraFontGetV(7, data, b); + if (glyph->top >= 64) glyph->top -= 128; + glyph->flags = intraFontGetV(6, data, b); + if (glyph->flags & PGF_CHARGLYPH) { + (*b) += 7; //skip magic number + intraFontGetV(9, data, b); + (*b) += 24 + ((glyph->flags & PGF_NO_EXTRA1) ? 0 : 56) + ((glyph->flags & PGF_NO_EXTRA2) ? 0 : 56) + ((glyph->flags & PGF_NO_EXTRA3) ? 0 : 56); //skip to 6bits before end of header + glyph->advance = advancemap[intraFontGetV(8, data, b) * 2] / 16; + } else { + glyph->advance = 0; + } + glyph->ptr = (*b) / 8; + return 1; +} + +unsigned short intraFontGetID(intraFont *font, cccUCS2 ucs) { + unsigned short j, id = 0; + char found = 0; + for (j = 0; j < font->charmap_compr_len && !found; j++) { + if ((ucs >= font->charmap_compr[j * 2]) && (ucs < (font->charmap_compr[j * 2] + font->charmap_compr[j * 2 + 1]))) { + id += ucs - font->charmap_compr[j * 2]; + found = 1; + } else { + id += font->charmap_compr[j * 2 + 1]; + } + } + if (!found) return 65535; //char not in charmap + if (font->fileType == FILETYPE_PGF) id = font->charmap[id]; //BWFON has right id already + if (id >= font->n_chars) return 65535; //char not in fontdata or not in ASCII-cache + return id; +} + +static int intraFontSwizzle(intraFont *font) { + int height = font->texHeight; + int byteWidth = font->texWidth >> 1; + int textureSize = byteWidth * height; + + int rowBlocks = (byteWidth >> 4); + int rowBlocksAdd = (rowBlocks - 1) << 7; + unsigned int blockAddress = 0; + unsigned int *src = (unsigned int *)font->texture; + static unsigned char *tData; + + tData = (unsigned char *)memalign(16, textureSize); + if (!tData) return 0; + + int i, j; + for (j = 0; j < height; j++, blockAddress += 16) { + unsigned int *block = ((unsigned int *)&tData[blockAddress]); + for (i = 0; i < rowBlocks; i++) { + *block++ = *src++; + *block++ = *src++; + *block++ = *src++; + *block++ = *src++; + block += 28; + } + if ((j & 0x7) == 0x7) blockAddress += rowBlocksAdd; + } + + free(font->texture); + font->texture = tData; + font->options |= INTRAFONT_CACHE_ASCII; + + return 1; +} + +int intraFontPreCache(intraFont *font, unsigned int options) { + if (!font) return 0; + if (!(options & INTRAFONT_CACHE_ASCII)) return 0; //no precaching requested + if (font->options & INTRAFONT_CACHE_ASCII) return 0; //already prechached? + + //cache all glyphs + int i, y; + font->texX = 1; + font->texY = 1; + font->texYSize = 0; + for (i = 0; i < font->n_chars; i++) { + y = font->texY; + intraFontGetBMP(font, i, PGF_CHARGLYPH); + if (font->texY > y || font->texYSize < font->glyph[i].height) font->texYSize = font->glyph[i].height; //minimize ysize after newline in cache(only valid for precached glyphs) + if (font->texY < y) return 0; //char did not fit into cache -> abort precache (should reset cache and glyph.flags) + } + font->texHeight = ((font->texY) + (font->texYSize) + 7) & ~7; + if (font->texHeight > font->texWidth) font->texHeight = font->texWidth; + + //reduce fontdata + int index = 0, j; + for (i = 0; i < font->n_chars; i++) { + if ((font->glyph[i].flags & PGF_BMP_H_ROWS) && (font->glyph[i].flags & PGF_BMP_V_ROWS)) { + for (j = 0; j < 6; j++, index++) { + font->fontdata[index] = font->fontdata[(font->glyph[i].ptr) + j]; + } + font->glyph[i].ptr = index - j; + } + } + if (index == 0) { + free(font->fontdata); + font->fontdata = NULL; + } else { + font->fontdata = (unsigned char *)realloc(font->fontdata, index * sizeof(unsigned char)); + } + + //swizzle texture + sceKernelDcacheWritebackAll(); + intraFontSwizzle(font); + sceKernelDcacheWritebackAll(); + + return 1; +} + +intraFont *intraFontLoad(const char *filename, unsigned int options) { + unsigned long i, j; + static Glyph bw_glyph = { 0, 0, 16, 18, 0, 15, PGF_BMP_H_ROWS, 64, 0 }; + static const unsigned short bw_charmap_compr[] = { 0x00a4, 1, 0x00a7, 2, 0x00b0, 2, 0x00b7, 1, 0x00d7, 1, 0x00e0, 2, 0x00e8, 3, 0x00ec, 2, + 0x00f2, 2, 0x00f7, 1, 0x00f9, 2, 0x00fc, 1, 0x0101, 1, 0x0113, 1, 0x011b, 1, 0x012b, 1, + 0x0144, 1, 0x0148, 1, 0x014d, 1, 0x016b, 1, 0x01ce, 1, 0x01d0, 1, 0x01d2, 1, 0x01d4, 1, + 0x01d6, 1, 0x01d8, 1, 0x01da, 1, 0x01dc, 1, 0x0251, 1, 0x0261, 1, 0x02c7, 1, 0x02c9, 3, + 0x02d9, 1, 0x0391, 17, 0x03a3, 7, 0x03b1, 17, 0x03c3, 7, 0x0401, 1, 0x0410, 64, 0x0451, 1, + 0x2010, 1, 0x2013, 4, 0x2018, 2, 0x201c, 2, 0x2025, 2, 0x2030, 1, 0x2032, 2, 0x2035, 1, + 0x203b, 1, 0x20ac, 1, 0x2103, 1, 0x2105, 1, 0x2109, 1, 0x2116, 1, 0x2121, 1, 0x2160, 12, + 0x2170, 10, 0x2190, 4, 0x2196, 4, 0x2208, 1, 0x220f, 1, 0x2211, 1, 0x2215, 1, 0x221a, 1, + 0x221d, 4, 0x2223, 1, 0x2225, 1, 0x2227, 5, 0x222e, 1, 0x2234, 4, 0x223d, 1, 0x2248, 1, + 0x224c, 1, 0x2252, 1, 0x2260, 2, 0x2264, 4, 0x226e, 2, 0x2295, 1, 0x2299, 1, 0x22a5, 1, + 0x22bf, 1, 0x2312, 1, 0x2460, 10, 0x2474, 40, 0x2500, 76, 0x2550, 36, 0x2581, 15, 0x2593, 3, + 0x25a0, 2, 0x25b2, 2, 0x25bc, 2, 0x25c6, 2, 0x25cb, 1, 0x25ce, 2, 0x25e2, 4, 0x2605, 2, + 0x2609, 1, 0x2640, 1, 0x2642, 1, 0x2e81, 1, 0x2e84, 1, 0x2e88, 1, 0x2e8b, 2, 0x2e97, 1, + 0x2ea7, 1, 0x2eaa, 1, 0x2eae, 1, 0x2eb3, 1, 0x2eb6, 2, 0x2ebb, 1, 0x2eca, 1, 0x2ff0, 12, + 0x3000, 4, 0x3005, 19, 0x301d, 2, 0x3021, 9, 0x303e, 1, 0x3041, 83, 0x309b, 4, 0x30a1, 86, + 0x30fc, 3, 0x3105, 37, 0x3220, 10, 0x3231, 1, 0x32a3, 1, 0x338e, 2, 0x339c, 3, 0x33a1, 1, + 0x33c4, 1, 0x33ce, 1, 0x33d1, 2, 0x33d5, 1,0x3400,6582,0x4e00,20902,0xe78d, 10, 0xe7c7, 2, + 0xe816, 3, 0xe81e, 1, 0xe826, 1, 0xe82b, 2, 0xe831, 2, 0xe83b, 1, 0xe843, 1, 0xe854, 2, + 0xe864, 1, 0xf92c, 1, 0xf979, 1, 0xf995, 1, 0xf9e7, 1, 0xf9f1, 1, 0xfa0c, 4, 0xfa11, 1, + 0xfa13, 2, 0xfa18, 1, 0xfa1f, 3, 0xfa23, 2, 0xfa27, 3, 0xfe30, 2, 0xfe33, 18, 0xfe49, 10, + 0xfe54, 4, 0xfe59, 14, 0xfe68, 4, 0xff01, 94, 0xffe0, 6 }; + static const unsigned char bw_shadow[] = { 0x10, 0x11, 0x11, 0x01, 0x10, 0x22, 0x22, 0x01, 0x21, 0x43, 0x34, 0x12, 0x31, 0x75, 0x57, 0x13, + 0x31, 0x86, 0x68, 0x13, 0x31, 0x86, 0x68, 0x13, 0x31, 0x75, 0x57, 0x13, 0x21, 0x43, 0x34, 0x12, + 0x10, 0x22, 0x22, 0x01, 0x10, 0x11, 0x11, 0x01 }; + + //create font structure + intraFont *font = (intraFont *)malloc(sizeof(intraFont)); + if (!font) return NULL; + + //open pgf file and get file size + FILE *file = fopen(filename, "rb"); /* read from the file in binary mode */ + if (!file) return NULL; + fseek(file, 0, SEEK_END); + unsigned long filesize = ftell(file); + fseek(file, 0, SEEK_SET); + + //read pgf header + static PGF_Header header; + if (fread(&header, sizeof(PGF_Header), 1, file) != 1) { + fclose(file); + return NULL; + } + + //pgf header tests: valid and known pgf file? + if ((strncmp(header.pgf_id, "PGF0", 4) == 0) && (header.version == 6) && (header.revision == 2 || header.revision == 3) && + (header.charmap_len <= 65535) && (header.charptr_len <= 65535) && (header.charmap_bpe <= 32) && (header.charptr_bpe <= 32) && + (header.charmap_min <= header.charmap_max) && (header.shadowmap_len <= 511) && (header.shadowmap_bpe <= 16)) { + font->fileType = FILETYPE_PGF; + } else if (filesize == 1023372) { //only known size for bwfon -> refuse to work for modified sizes + font->fileType = FILETYPE_BWFON; + } else { + fclose(file); + return NULL; + } + + //intitialize font structure + if (font->fileType == FILETYPE_PGF) { + font->n_chars = (unsigned short)header.charptr_len; + font->charmap_compr_len = (header.revision == 3) ? 7 : 1; + font->texYSize = 0; + font->advancex = header.fixedsize[0] / 16; + font->advancey = header.fixedsize[1] / 16; + font->glyph = (Glyph *)malloc(font->n_chars * sizeof(Glyph)); + font->glyphBW = NULL; + font->charmap_compr = (unsigned short *)malloc(font->charmap_compr_len * sizeof(unsigned short) * 2); + font->charmap = (unsigned short *)malloc(header.charmap_len * sizeof(unsigned short)); + if (!font->glyph || !font->charmap_compr || !font->charmap) { + fclose(file); + intraFontUnload(font); + return NULL; + } + memset(font->glyph, 0, font->n_chars * sizeof(Glyph)); //make sure glyphs are initialized with 0 + } else { //FILETYPE_BWFON + font->n_chars = filesize / 36; //36 bytes/char + font->charmap_compr_len = 165; //entries in the compressed charmap + font->texYSize = 18; + font->advancex = 16 * 4; + font->advancey = 18 * 4; + font->glyph = &bw_glyph; + font->glyphBW = (GlyphBW *)malloc(font->n_chars * sizeof(GlyphBW)); + font->charmap_compr = (unsigned short *)bw_charmap_compr; //static for bwfon + font->charmap = NULL; //not needed for bwfon + if (!font->glyphBW) { + fclose(file); + intraFontUnload(font); + return NULL; + } + memset(font->glyphBW, 0, font->n_chars * sizeof(GlyphBW)); //make sure glyphBWs are initialized with 0 + } + font->texWidth = (options & INTRAFONT_CACHE_LARGE) ? (512) : (256); + font->texHeight = font->texWidth; + font->texX = 1; + font->texY = 1; + font->options = options; + if ((options & INTRAFONT_CACHE_ASCII)) font->options -= INTRAFONT_CACHE_ASCII; //pre-cached texture enabled at the end of font-load + font->size = 1.0f; //default size + font->color = 0xFFFFFFFF; //non-transparent white + font->angle = 0.f; //non-rotated + font->Rsin = 0.f; + font->Rcos = 1.f; + font->isRotated = 0; + font->altFont = NULL; //no alternative font + font->filename = (char *)malloc((strlen(filename) + 1) * sizeof(char)); + font->texture = (unsigned char *)memalign(16, font->texWidth * font->texHeight >> 1); + if (!font->filename || !font->texture) { + fclose(file); + intraFontUnload(font); + return NULL; + } + strcpy(font->filename, filename); + + //read in file (tables / fontdata) and populate font structure + if (font->fileType == FILETYPE_PGF) { + + //read advance table + fseek(file, header.header_len + (header.table1_len + header.table2_len + header.table3_len) * 8, SEEK_SET); + signed long *advancemap = (signed long *)malloc(header.advance_len * sizeof(signed long) * 2); + if (!advancemap) { + fclose(file); + intraFontUnload(font); + return NULL; + } + if (fread(advancemap, header.advance_len * sizeof(signed long) * 2, 1, file) != 1) { + free(advancemap); + fclose(file); + intraFontUnload(font); + return NULL; + } + + //SKIP SHADOWMAP + unsigned long shadow_table_size = ((header.shadowmap_len * header.shadowmap_bpe + 31) / 32) * 4; + fseek(file, shadow_table_size, SEEK_CUR); + + //version 6.3 charmap compression + if (header.revision == 3) { + if (fread(font->charmap_compr, font->charmap_compr_len * sizeof(unsigned short) * 2, 1, file) != 1) { + free(advancemap); + //free(ucs_shadowmap); + fclose(file); + intraFontUnload(font); + return NULL; + } + } else { + font->charmap_compr[0] = header.charmap_min; + font->charmap_compr[1] = header.charmap_len; + } + + //read charmap + if (header.charmap_bpe == 16) { //read directly from file... + if (fread(font->charmap, header.charmap_len * sizeof(unsigned short), 1, file) != 1) { + free(advancemap); + //free(ucs_shadowmap); + fclose(file); + intraFontUnload(font); + return NULL; + } + } else { + unsigned long *id_charmap = intraFontGetTable(file, header.charmap_len, header.charmap_bpe); + if (id_charmap == NULL) { + free(advancemap); + //free(ucs_shadowmap); + fclose(file); + intraFontUnload(font); + return NULL; + } + for (i = 0;i < header.charmap_len;i++) { + font->charmap[i] = (unsigned short)((id_charmap[i] < font->n_chars) ? id_charmap[i] : 65535); + } + free(id_charmap); + } + + //read charptr + unsigned long *charptr = intraFontGetTable(file, header.charptr_len, header.charptr_bpe); + if (charptr == NULL) { + free(advancemap); + //free(ucs_shadowmap); + fclose(file); + intraFontUnload(font); + return NULL; + } + + //read raw fontdata + unsigned long start_fontdata = ftell(file); + unsigned long len_fontdata = filesize - start_fontdata; + font->fontdata = (unsigned char *)malloc(len_fontdata * sizeof(unsigned char)); + if (font->fontdata == NULL) { + free(advancemap); + //free(ucs_shadowmap); + free(charptr); + fclose(file); + intraFontUnload(font); + return NULL; + } + if (fread(font->fontdata, len_fontdata * sizeof(unsigned char), 1, file) != 1) { + free(advancemap); + //free(ucs_shadowmap); + free(charptr); + fclose(file); + intraFontUnload(font); + return NULL; + } + + //close file + fclose(file); + + //count ascii chars and reduce mem required + if ((options & PGF_CACHE_MASK) == INTRAFONT_CACHE_ASCII) { + font->n_chars = 1; //assume there's at least one char (needed for intraFontGetID to work properly) + for (i = 0; i < 128; i++) { + if (intraFontGetID(font, i) < 65535) (font->n_chars)++; + } + (font->n_chars)--; //correct the above assumption + if (font->n_chars == 0) { //no ASCII chars in fontfile -> abort + free(advancemap); + //free(ucs_shadowmap); + free(charptr); + intraFontUnload(font); + return NULL; + } + font->glyph = (Glyph *)realloc(font->glyph, font->n_chars * sizeof(Glyph)); + font->charmap_compr[1] = 128 - font->charmap_compr[0]; + font->charmap = (unsigned short *)realloc(font->charmap, font->charmap_compr[1] * sizeof(unsigned short)); + } + + //populate chars and count space used in cache to prebuffer all chars + int x = 1, y = 1, ysize = 0; + for (i = 0; i < font->n_chars; i++) { + j = charptr[i] * 4 * 8; + if (!intraFontGetGlyph(font->fontdata, &j, PGF_CHARGLYPH, advancemap, &(font->glyph[i]))) { + free(advancemap); + //free(ucs_shadowmap); + free(charptr); + intraFontUnload(font); + return NULL; + } + if (!(font->glyph[i].flags & PGF_BMP_H_ROWS) != !(font->glyph[i].flags & PGF_BMP_V_ROWS)) { //H_ROWS xor V_ROWS (real glyph, not overlay) + if (font->glyph[i].height > font->texYSize) font->texYSize = font->glyph[i].height; //find max glyph height + if ((x + font->glyph[i].width) > font->texWidth) { + y += ysize + 1; + x = 1; + ysize = 0; + } + if (font->glyph[i].height > ysize) ysize = font->glyph[i].height; + x += font->glyph[i].width + 1; + } + } + + //free temp stuff + free(advancemap); + //free(ucs_shadowmap); + free(charptr); + + //cache chars, swizzle texture and free unneeded stuff (if INTRAFONT_CACHE_ALL or _ASCII) + sceKernelDcacheWritebackAll(); + if ((options & INTRAFONT_CACHE_ASCII) && ((y + ysize + 1) <= font->texHeight)) { //cache all and does it fit into cache? + if (!intraFontPreCache(font, options)) { + intraFontUnload(font); + return NULL; + } + } + + } else { //FILETYPE_BWFON + + //read raw fontdata + fseek(file, 0, SEEK_SET); + font->fontdata = (unsigned char *)malloc((filesize + 40) * sizeof(unsigned char)); + if (font->fontdata == NULL) { + fclose(file); + intraFontUnload(font); + return NULL; + } + if (fread(font->fontdata, filesize * sizeof(unsigned char), 1, file) != 1) { + fclose(file); + intraFontUnload(font); + return NULL; + } + memcpy(font->fontdata + filesize, bw_shadow, 40); + + //close file + fclose(file); + + //count ascii chars and reduce mem required: no ascii chars in bwfon -> abort + if ((options & PGF_CACHE_MASK) == INTRAFONT_CACHE_ASCII) { + intraFontUnload(font); + return NULL; + } + + //populate chars and count space used in cache to prebuffer all chars: not needed/available -> skip + //populate shadows and count space (only for chars needed): not needed/available -> skip + //free temp stuff: not needed -> skip + //cache chars, swizzle texture and free unneeded stuff (if INTRAFONT_CACHE_ALL or _ASCII): not available ->skip + + } + + sceKernelDcacheWritebackAll(); + + return font; +} + +void intraFontUnload(intraFont *font) { + if (!font) return; + + if (font->filename) free(font->filename); + if (font->fontdata) free(font->fontdata); + if (font->texture) free(font->texture); + + if (font->fileType == FILETYPE_PGF) { + if (font->charmap_compr) free(font->charmap_compr); + if (font->charmap) free(font->charmap); + if (font->glyph) free(font->glyph); + //if (font->shadowGlyph) free(font->shadowGlyph); + } else { //FILETYPE_BWFON + if (font->glyphBW) free(font->glyphBW); + } + + if (font) free(font); + font = NULL; +} + +int intraFontInit(void) { + int n; + for (n = 0; n < 16; n++) + clut[n] = ((n * 17) << 24) | 0xffffff; + return 1; +} + +void intraFontShutdown(void) { + //Nothing yet +} + +void intraFontActivate(intraFont *font, bool linear) { + if (!font) return; + if (!font->texture) return; + + sceGuClutMode(GU_PSM_8888, 0, 255, 0); + sceGuClutLoad(2, clut); + + sceGuEnable(GU_TEXTURE_2D); + sceGuTexMode(GU_PSM_T4, 0, 0, (font->options & INTRAFONT_CACHE_ASCII) ? 1 : 0); + sceGuTexImage(0, font->texWidth, font->texWidth, font->texWidth, font->texture); + sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA); + sceGuTexEnvColor(0x0); + sceGuTexOffset(0.f, 0.f); + sceGuTexWrap(GU_CLAMP, GU_CLAMP); + if (linear == true) sceGuTexFilter(GU_LINEAR, GU_LINEAR); + else sceGuTexFilter(GU_NEAREST, GU_NEAREST); + font->options |= INTRAFONT_ACTIVE; +} + +void intraFontSetStyle(intraFont *font, float size, unsigned int color, unsigned int shadowColor, float angle, unsigned int options) { + if (!font) return; + font->size = size; + font->color = color; + //font->shadowColor = shadowColor; + if (font->angle != angle) // Avoid recomputations + { + font->angle = angle; + if (angle == 0.f) { + font->Rsin = 0.f; + font->Rcos = 1.f; + } else { + font->Rsin = sinf(angle * GU_PI / 180.f); + font->Rcos = cosf(angle * GU_PI / 180.f); + } + font->isRotated = !(font->Rsin == 0.f && font->Rcos == 1.f); + } + font->options = (options & PGF_OPTIONS_MASK) | (font->options & PGF_STRING_MASK) | (font->options & PGF_CACHE_MASK); + if ((font->options & PGF_WIDTH_MASK) == 0) font->options |= ((font->advancex / 8) & PGF_WIDTH_MASK); +} + +void intraFontSetEncoding(intraFont *font, unsigned int options) { + if (!font) return; + font->options = (font->options & PGF_OPTIONS_MASK) | (options & PGF_STRING_MASK) | (font->options & PGF_CACHE_MASK); +} + +void intraFontSetAltFont(intraFont *font, intraFont *altFont) { + if (!font) return; + intraFont *nextFont; + nextFont = altFont; + while (nextFont) { + if ((nextFont->altFont) == font) return; //it must not point at itself + nextFont = nextFont->altFont; + } + font->altFont = altFont; +} + +float intraFontPrintf(intraFont *font, float x, float y, const char *text, ...) { + if (!font) return x; + + char buffer[256]; + va_list ap; + + va_start(ap, text); + vsnprintf(buffer, 256, text, ap); + va_end(ap); + buffer[255] = 0; + + return intraFontPrint(font, x, y, buffer); +} + +float intraFontPrint(intraFont *font, float x, float y, const char *text) { + if (!font) return x; + int length = cccStrlenCode((cccCode *)text, font->options / 0x00010000); + return intraFontPrintColumnEx(font, x, y, 0.f, text, length); +} + +float intraFontPrintEx(intraFont *font, float x, float y, const char *text, int length) { + return intraFontPrintColumnEx(font, x, y, 0.f, text, length); +} + +float intraFontPrintColumn(intraFont *font, float x, float y, float width, const char *text) { + if (!font) return x; + int length = cccStrlenCode((cccCode *)text, font->options / 0x00010000); + return intraFontPrintColumnEx(font, x, y, width, text, length); +} + +float intraFontPrintColumnEx(intraFont *font, float x, float y, float column, const char *text, int length) { + if (!text || length <= 0 || !font) return x; + +#define LOCAL_BUFFER_LENGTH 256 + cccUCS2 buffer[LOCAL_BUFFER_LENGTH]; + cccUCS2 *ucs2_text = buffer; + + // Allocate a temporary buffer if too long + if (length > LOCAL_BUFFER_LENGTH) ucs2_text = (cccUCS2 *)malloc(length * sizeof(cccUCS2)); + if (!ucs2_text) return x; + + //->UCS2 conversion + length = cccCodetoUCS2(ucs2_text, length, (cccCode *)text, font->options / 0x00010000); + + //for scrolling: if text contains '\n', replace with spaces + int i; + if (font->options & INTRAFONT_SCROLL_LEFT) { + for (i = 0; i < length; i++) { + if (ucs2_text[i] == '\n') ucs2_text[i] = ' '; + } + } + + if (column >= 0) { + x = intraFontPrintColumnUCS2Ex(font, x, y, column, ucs2_text, length); + } else { //negative column prevents from drawing -> measure text + x = intraFontMeasureTextUCS2Ex(font, ucs2_text, length); //(hack to share local buffer between intraFontPrint and intraFontMeasure) + } + + // Free temporary buffer if allocated + if (ucs2_text != buffer) free(ucs2_text); + + return x; +} + +float intraFontPrintUCS2(intraFont *font, float x, float y, const cccUCS2 *text) { + return intraFontPrintColumnUCS2Ex(font, x, y, 0.f, text, cccStrlenUCS2(text)); +} + +float intraFontPrintUCS2Ex(intraFont *font, float x, float y, const cccUCS2 *text, int length) { + return intraFontPrintColumnUCS2Ex(font, x, y, 0.f, text, length); +} + +float intraFontPrintColumnUCS2(intraFont *font, float x, float y, float column, const cccUCS2 *text) { + return intraFontPrintColumnUCS2Ex(font, x, y, 0.f, text, cccStrlenUCS2(text)); +} + +float intraFontPrintColumnUCS2Ex(intraFont *font, float x, float y, float column, const cccUCS2 *text, int length) { + if (!text || length <= 0 || !font) return x; + + //for scrolling: if text contains '\n', replace with spaces and call intraFontColumnUCS2Ex again + int i; + if (font->options & INTRAFONT_SCROLL_LEFT) { + for (i = 0; i < length; i++) { + if (text[i] == '\n') { + cccUCS2 *ucs2_text = (cccUCS2 *)malloc(length * sizeof(cccUCS2)); + if (!ucs2_text) return x; + for (i = 0; i < length; i++) ucs2_text[i] = (text[i] == '\n') ? ' ' : text[i]; + x = intraFontPrintColumnUCS2Ex(font, x, y, column, ucs2_text, length); + free(ucs2_text); + return x; + } + } + } + + unsigned int color = font->color; + float glyphscale = font->size; + float width = 0.f, height = font->advancey * glyphscale / 4.0; + float left = x, top = y - 2 * height; + int eol = -1, n_spaces = -1, scroll = 0, textwidth; + float fill = 0.f; + float xl, xr, yu, yd, ul, ur, vu, vd; + + typedef struct + { + float u, v; + unsigned int c; + float x, y, z; + } fontVertex; + fontVertex *v, *v0, *v1, *v2, *v3, *v4, *v5;//, *s0, *s1, *s2, *s3, *s4, *s5; + + //count number of glyphs to draw and cache BMPs + int j, n_glyphs, n_sglyphs, changed, count = 0; + unsigned short char_id, subucs2, glyph_id, glyph_ptr; + do { + changed = 0; + n_glyphs = 0; + n_sglyphs = 0; + for (i = 0; i < length; i++) { + + char_id = intraFontGetID(font, text[i]); //char + if (char_id < font->n_chars) { + if (font->fileType == FILETYPE_PGF) { //PGF-file + if ((font->glyph[char_id].flags & PGF_BMP_OVERLAY) == PGF_BMP_OVERLAY) { //overlay glyph? + for (j = 0; j < 3; j++) { + subucs2 = font->fontdata[(font->glyph[char_id].ptr) + j * 2] + font->fontdata[(font->glyph[char_id].ptr) + j * 2 + 1] * 256; + if (subucs2) { + glyph_id = intraFontGetID(font, subucs2); + if (glyph_id < font->n_chars) { + n_glyphs++; + if (!(font->glyph[glyph_id].flags & PGF_CACHED)) { + if (intraFontGetBMP(font, glyph_id, PGF_CHARGLYPH)) changed = 1; + } + } + } + } + } else { + n_glyphs++; + if (!(font->glyph[char_id].flags & PGF_CACHED)) { + if (intraFontGetBMP(font, char_id, PGF_CHARGLYPH)) changed = 1; + } + } + + } else { //BWFON-file + n_glyphs++; + if (!(font->glyphBW[char_id].flags & PGF_CACHED)) { + if (intraFontGetBMP(font, char_id, PGF_CHARGLYPH)) changed = 1; + } + } + } + + } + count++; + } while (changed && count <= length); + if (changed) return x; //not all chars fit into texture -> abort (better solution: split up string and call intraFontPrintUCS2 twice) + + //reserve memory in displaylist (switch between GU_TRIANGLES and GU_SPRITES) + v = sceGuGetMemory((font->isRotated ? 6 : 2) * (n_glyphs + n_sglyphs) * sizeof(fontVertex)); + + int c_index = n_sglyphs;//s_index = 0, last_c_index = n_sglyphs; // index for shadow and character/overlay glyphs + for (i = 0; i < length; i++) { + + //calculate left, height and possibly fill for character placement + if ((i == 0) || (text[i] == '\n') || ((column > 0.f) && (i >= eol) && (text[i] != 32))) { //newline + + if (column > 0.f) { + if (font->options & INTRAFONT_SCROLL_LEFT) { //scrolling (any direction) requested + eol = length; + scroll = 1; + left = ((float)((int)x)); + union { int i; float f; } ux, uleft; + ux.f = x; + uleft.f = left; + count = ux.i - uleft.i; + textwidth = intraFontMeasureTextUCS2Ex(font, text + i, length - i) + 1; + if (textwidth > column) { //scrolling really required + + switch (font->options & PGF_SCROLL_MASK) { + + case INTRAFONT_SCROLL_LEFT: //scroll left + sceGuScissor(left - 2, 0, left + column + 4, 272); //limit to column width + if (count < 60) { + //show initial text for 1s + } else if (count < (textwidth + 90)) { + left -= (count - 60); //scroll left for (textwidth/60) s (speed = 1 pix/frame) and disappear for 0.5s + } else if (count < (textwidth + 120)) { + color = (color & 0x00FFFFFF) | ((((color >> 24) * (count - textwidth - 90)) / 30) << 24); //fade-in in 0.5s + } else { + ux.f = left; //reset counter + } + break; + + case INTRAFONT_SCROLL_SEESAW: //scroll left and right + sceGuScissor(left - column / 2 - 2, 0, left + column / 2 + 4, 272); //limit to column width + textwidth -= column; + if (count < 60) { + left -= column / 2; //show initial text (left side) for 1s + } else if (count < (textwidth + 60)) { + left -= column / 2 + (count - 60); //scroll left + } else if (count < (textwidth + 120)) { + left -= column / 2 + textwidth; //show right side for 1s + } else if (count < (2 * textwidth + 120)) { + left -= column / 2 + 2 * textwidth - count + 120; //scroll right + } else { + ux.f = left; //reset counter + left -= column / 2; + } + break; + + case INTRAFONT_SCROLL_RIGHT: //scroll right + sceGuScissor(left - column - 2, 0, left + 4, 272); //limit to column width + if (count < 60) { + left -= textwidth; //show initial text for 1s + } else if (count < (textwidth + 90)) { + left -= textwidth - count + 60; //scroll right for (textwidth/60) s (speed = 1 pix/frame) and disappear for 0.5s + } else if (count < (textwidth + 120)) { + left -= textwidth; + color = (color & 0x00FFFFFF) | ((((color >> 24) * (count - textwidth - 90)) / 30) << 24); //fade-in in 0.5s + } else { + ux.f = left; //reset counter + left -= textwidth; + } + break; + + case INTRAFONT_SCROLL_THROUGH: //scroll through + sceGuScissor(left - 2, 0, left + column + 4, 272); //limit to column width + if (count < (textwidth + column + 30)) { + left += column + 4 - count; //scroll through + } else { + ux.f = left; //reset counter + left += column + 4; + } + break; + + } + ux.i++; //increase counter + x = ux.f; //copy back to original var + sceGuEnable(GU_SCISSOR_TEST); + } + } else { //automatic line-break required + n_spaces = -1; + eol = -1; + fill = 0.f; + for (j = i; j < length; j++) { + if (text[j] == '\n') { //newline reached -> no auto-line-break + eol = j; + break; + } + if (text[j] == ' ') { //space found for padding or eol + n_spaces++; + eol = j; + } + if (intraFontMeasureTextUCS2Ex(font, text + i, j + 1 - i) > column) { //line too long -> line-break + if (eol < 0) eol = j; //break in the middle of the word + if (n_spaces > 0) fill = (column - intraFontMeasureTextUCS2Ex(font, text + i, eol - i)) / ((float)n_spaces); + break; + } + } + if (j == length) { + eol = length; //last line + while ((text[eol - 1] == ' ') && (eol > 1)) eol--; //do not display trailing spaces + } + + left = x; + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_RIGHT) left -= intraFontMeasureTextUCS2Ex(font, text + i, eol - i); + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_CENTER) left -= intraFontMeasureTextUCS2Ex(font, text + i, eol - i) / 2.0; + + } + } else { //no column boundary -> display everything + left = x; + if (text[i] == '\n') { + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_RIGHT) left -= intraFontMeasureTextUCS2Ex(font, text + i + 1, length - i - 1); + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_CENTER) left -= intraFontMeasureTextUCS2Ex(font, text + i + 1, length - i - 1) / 2.0; + } else { + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_RIGHT) left -= intraFontMeasureTextUCS2Ex(font, text + i, length - i); + if ((font->options & PGF_ALIGN_MASK) == INTRAFONT_ALIGN_CENTER) left -= intraFontMeasureTextUCS2Ex(font, text + i, length - i) / 2.0; + } + } + + width = 0.f; + height += font->advancey * glyphscale * 0.25; + + } + + char_id = intraFontGetID(font, text[i]); + if (char_id < font->n_chars) { + + glyph_ptr = (font->fileType == FILETYPE_PGF) ? char_id : 0; //for fields not covered in GlyphBW (advance,...) + //shadowGlyph_ptr = (font->fileType == FILETYPE_PGF) ? font->glyph[glyph_ptr].shadowID : 0; + + //center glyphs for monospace + if (font->options & INTRAFONT_WIDTH_FIX) { + width += (((float)(font->options & PGF_WIDTH_MASK)) / 2.0f - ((float)font->glyph[glyph_ptr].advance) / 8.0f) * glyphscale; + } + + //add vertices for subglyphs + for (j = 0; j < 3; j++) { + if (font->fileType == FILETYPE_PGF) { + if ((font->glyph[char_id].flags & PGF_BMP_OVERLAY) == PGF_BMP_OVERLAY) { + subucs2 = font->fontdata[(font->glyph[char_id].ptr) + j * 2] + font->fontdata[(font->glyph[char_id].ptr) + j * 2 + 1] * 256; + glyph_id = intraFontGetID(font, subucs2); + } else { + glyph_id = char_id; + j = 2; + } + } else { //FILETYPE_BWFON + glyph_id = 0; + j = 2; + } + + if (glyph_id < font->n_chars) { + Glyph *glyph = &(font->glyph[glyph_id]); + if (font->fileType == FILETYPE_BWFON) { + glyph->x = font->glyphBW[char_id].x; + glyph->y = font->glyphBW[char_id].y; + } + + // Screen coords + xl = left + width + glyph->left * glyphscale; + xr = xl + glyph->width * glyphscale; + yu = top + height - glyph->top * glyphscale; + yd = yu + glyph->height * glyphscale; + // Tex coords + ul = glyph->x - 0.25f; + ur = glyph->x + glyph->width + 0.25f; + vu = glyph->y - 0.25f; + vd = glyph->y + glyph->height + 0.25f; + + if (font->isRotated) { + v0 = &v[c_index * 6]; + v1 = v0 + 1; + v2 = v1 + 1; + v3 = v2 + 1; + v4 = v3 + 1; + v5 = v4 + 1; + + // Up-left + v0->u = ul, v0->v = vu; + v0->c = color; + v0->x = xl, v0->y = yu; + + // Up-right + v1->u = ur, v1->v = vu; + v1->c = color; + v1->x = xr, v1->y = yu; + + // Down-right + v2->u = ur, v2->v = vd; + v2->c = color; + v2->x = xr, v2->y = yd; + + // Down-left + v3->u = ul, v3->v = vd; + v3->c = color; + v3->x = xl, v3->y = yd; + + // Apply rotation to each vertex + // x' = x cos θ - y sin θ + // y' = x sin θ + y cos θ + + float vx, vy; // Temp vars are necessary to avoid wrong computations + + vx = x + (v0->x - x) * font->Rcos - (v0->y - y) * font->Rsin; + vy = y + (v0->x - x) * font->Rsin + (v0->y - y) * font->Rcos; + v0->x = vx, v0->y = vy; + vx = x + (v1->x - x) * font->Rcos - (v1->y - y) * font->Rsin; + vy = y + (v1->x - x) * font->Rsin + (v1->y - y) * font->Rcos; + v1->x = vx, v1->y = vy; + vx = x + (v2->x - x) * font->Rcos - (v2->y - y) * font->Rsin; + vy = y + (v2->x - x) * font->Rsin + (v2->y - y) * font->Rcos; + v2->x = vx, v2->y = vy; + vx = x + (v3->x - x) * font->Rcos - (v3->y - y) * font->Rsin; + vy = y + (v3->x - x) * font->Rsin + (v3->y - y) * font->Rcos; + v3->x = vx, v3->y = vy; + + *v4 = *v0; + *v5 = *v2; + } else { + v0 = &v[c_index << 1]; // faster than *2 + v1 = v0 + 1; + + // Up-left + v0->u = ul, v0->v = vu; + v0->c = color; + v0->x = xl, v0->y = yu; + + // Down-right + v1->u = ur, v1->v = vd; + v1->c = color; + v1->x = xr, v1->y = yd; + } + + c_index++; + } + } + + // advance + if (font->options & INTRAFONT_WIDTH_FIX) { + width += (((float)(font->options & PGF_WIDTH_MASK)) / 2.0f + ((float)font->glyph[glyph_ptr].advance) / 8.0f) * glyphscale; + } else { + width += font->glyph[glyph_ptr].advance * glyphscale * 0.25; + } + + if ((text[i] == 32) && ((font->options & INTRAFONT_ALIGN_FULL) == INTRAFONT_ALIGN_FULL)) width += fill; + + } else { + if (font->altFont) { + unsigned int altOptions = (font->altFont)->options; + (font->altFont)->options = altOptions & (PGF_WIDTH_MASK + PGF_CACHE_MASK); + width += intraFontPrintColumnUCS2Ex(font->altFont, left + width, top + height, 0.f, text + i, 1) - (left + width); + (font->altFont)->options = altOptions; + } + } + } + + //finalize and activate texture (if not already active or has been changed) + sceKernelDcacheWritebackAll(); + if (!(font->options & INTRAFONT_ACTIVE)) intraFontActivate(font, true); + + sceGuDisable(GU_DEPTH_TEST); + sceGuDrawArray((font->isRotated ? GU_TRIANGLES : GU_SPRITES), GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_2D, (n_glyphs + n_sglyphs) * (font->isRotated ? 6 : 2), 0, v); + if (font->fileType == FILETYPE_BWFON) //draw chars again without shadows for improved readability + sceGuDrawArray((font->isRotated ? GU_TRIANGLES : GU_SPRITES), GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_2D, n_glyphs * (font->isRotated ? 6 : 2), 0, v + (n_sglyphs * (font->isRotated ? 6 : 2))); + sceGuEnable(GU_DEPTH_TEST); + + if (scroll == 1) { + sceGuScissor(0, 0, 480, 272); //reset window to whole screen (test was previously enabled) + return x; //for scrolling return x-pos (including updated counter) + } else { + return left + width; //otherwise return x-pos at end of text + } +} + +float intraFontMeasureText(intraFont *font, const char *text) { + if (!font) return 0; + int length = cccStrlenCode((cccCode *)text, font->options / 0x00010000); + return intraFontMeasureTextEx(font, text, length); +} + +float intraFontMeasureTextEx(intraFont *font, const char *text, int length) { + return intraFontPrintColumnEx(font, 0.f, 0.f, -1.0f, text, length); //explanation: intraFontPrintColumnEx does the String -> UCS2 conversation, + //but a negative column width triggers measurement without drawing +} + +float intraFontMeasureTextUCS2(intraFont *font, const cccUCS2 *text) { + return intraFontMeasureTextUCS2Ex(font, text, cccStrlenUCS2(text)); +} + +float intraFontMeasureTextUCS2Ex(intraFont *font, const cccUCS2 *text, int length) { + if (!text || length <= 0 || !font) return 0.f; + + int i; + float x = 0.f; + + for (i = 0; (i < length) && (text[i] != '\n'); i++) { //measure until end of string or first newline char + unsigned short char_id = intraFontGetID(font, text[i]); + if (char_id < font->n_chars) { + unsigned short glyph_ptr = (font->fileType == FILETYPE_PGF) ? char_id : 0; //for fields not covered in GlyphBW (advance,...) + x += (font->options & INTRAFONT_WIDTH_FIX) ? (font->options & PGF_WIDTH_MASK) * font->size : (font->glyph[glyph_ptr].advance) * font->size * 0.25f; + } else { + x += intraFontMeasureTextUCS2Ex(font->altFont, text + i, 1); //try alternative font if char does not exist in current font + } + } + + return x; +} + +int intraFontTextHeight(intraFont *font) { + return ceil((font->glyph[1].height) * font->size); +} + +intraFont *intraFontLoadTTF(const char *filename, unsigned int options, float size) { + FT_Library library; + FT_Face face; + intraFont *font = NULL; + + // Validate size + if (size <= 0.0f || size > 30.0f) { + return NULL; + } + + // Initialize FreeType with more error checking + if (FT_Init_FreeType(&library)) { + return NULL; + } + + // Load face with better error handling + if (FT_New_Face(library, filename, 0, &face)) { + FT_Done_FreeType(library); + return NULL; + } + + // Set char size with proper scaling + FT_F26Dot6 fixedSize = (FT_F26Dot6)(size * 64); + if (FT_Set_Char_Size(face, 0, fixedSize, 72, 72)) { + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + + // Allocate font structure with proper alignment + font = (intraFont *)memalign(16, sizeof(intraFont)); + if (!font) { + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + memset(font, 0, sizeof(intraFont)); + + // Initialize basic fields + font->filename = strdup(filename); + if (!font->filename) { + intraFontUnload(font); + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + + font->fileType = FILETYPE_PGF; + font->options = options; + font->size = 1.0f; + font->color = 0xFFFFFFFF; + //font->shadowColor = 0xFF000000; + font->angle = 0.0f; + font->Rsin = 0.0f; + font->Rcos = 1.0f; + font->isRotated = 0; + font->altFont = NULL; + + // Use larger texture for better glyph support + font->texWidth = 512; + font->texHeight = 512; + font->texX = 1; + font->texY = 1; + font->texYSize = 0; + + // Set advance based on actual font metrics + font->advancex = (int)face->size->metrics.max_advance >> 6; + font->advancey = (int)(face->size->metrics.ascender - face->size->metrics.descender) >> 6; + + // Character set handling + font->n_chars = 0x1000; + font->charmap_compr_len = 10; // Увеличили для дополнительных диапазонов + + // Allocate texture memory with proper alignment + int textureSize = (font->texWidth * font->texHeight) >> 1; + font->texture = (unsigned char *)memalign(16, textureSize); + if (!font->texture) { + intraFontUnload(font); + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + memset(font->texture, 0, textureSize); + + // Allocate glyph arrays with proper alignment + font->glyph = (Glyph *)memalign(16, font->n_chars * sizeof(Glyph)); + if (!font->glyph) { + intraFontUnload(font); + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + memset(font->glyph, 0, font->n_chars * sizeof(Glyph)); + + // Character map compression info + font->charmap_compr = (unsigned short *)memalign(16, font->charmap_compr_len * 2 * sizeof(unsigned short)); + if (!font->charmap_compr) { + intraFontUnload(font); + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + + // Define supported character ranges with additional symbols + static const unsigned short ranges[] = { + 32, 126, // Basic ASCII (включая пунктуацию) + 0xA0, 96, // Latin-1 Supplement + 0x400, 256, // Cyrillic + 0x2010, 50, // General Punctuation (тире, кавычки, многоточие и т.д.) + 0x20AC, 1, // € Euro symbol + 0x2116, 1, // № Numero sign + 0x2122, 1, // ™ Trade mark + 0x490, 2, // Ґ, ґ (Ukrainian Ge) + 0x406, 2, // І, і (Ukrainian I) + 0x407, 2, // Ї, ї (Ukrainian Yi) + 0x404, 2 // Є, є (Ukrainian Ye) + }; + memcpy(font->charmap_compr, ranges, sizeof(ranges)); + + // Character map - ВАЖНО: сохраняем индексы для корректной работы intraFontGetID + font->charmap = (unsigned short *)memalign(16, font->n_chars * sizeof(unsigned short)); + if (!font->charmap) { + intraFontUnload(font); + FT_Done_Face(face); + FT_Done_FreeType(library); + return NULL; + } + + // Load glyphs with proper mapping + int loaded_chars = 0; + int mapped_chars = 0; // Отдельный счетчик для маппинга + FT_Int32 load_flags = FT_LOAD_RENDER | FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_NORMAL; + + // Создаем маппинг: char_code -> index в glyph массиве + for (int range = 0; range < font->charmap_compr_len && mapped_chars < font->n_chars; range++) { + unsigned short first_char = font->charmap_compr[range * 2]; + unsigned short num_chars = font->charmap_compr[range * 2 + 1]; + + for (int i = 0; i < num_chars && mapped_chars < font->n_chars; i++) { + unsigned short char_code = first_char + i; + + // Skip control characters (кроме пробела) + if (char_code < 32 && char_code != 32) continue; + + // Всегда добавляем символ в маппинг, даже если он не поддерживается + font->charmap[mapped_chars] = mapped_chars; + + // Проверяем, поддерживается ли символ шрифтом + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + if (glyph_index == 0) { + // Символ не поддерживается - пропускаем добавление в спрайтлист + mapped_chars++; + continue; + } + + if (FT_Load_Char(face, char_code, load_flags)) { + mapped_chars++; + continue; + } + + FT_GlyphSlot slot = face->glyph; + + // Дополнительная проверка: если глиф пустой или имеет нулевые размеры - пропускаем + if (char_code != 32 && (slot->bitmap.width == 0 || slot->bitmap.rows == 0)) { + mapped_chars++; + continue; + } + + Glyph *glyph = &font->glyph[mapped_chars]; + + glyph->width = slot->bitmap.width; + glyph->height = slot->bitmap.rows; + glyph->top = (short)slot->bitmap_top; + glyph->left = (short)slot->bitmap_left; + glyph->advance = (short)slot->advance.x >> 4; + glyph->flags = PGF_BMP_H_ROWS | PGF_CACHED; + //glyph->shadowID = 0; + glyph->ptr = 0; + + // Only cache glyph if it fits in texture + if (glyph->width > 0 && glyph->height > 0) { + // Check if glyph fits in current row + if ((font->texX + glyph->width + 2) > font->texWidth) { + font->texY += font->texYSize + 1; + font->texX = 1; + font->texYSize = 0; + } + + // Check if glyph fits in texture at all + if ((font->texY + glyph->height + 2) > font->texHeight) { + glyph->flags &= ~PGF_CACHED; + mapped_chars++; + continue; + } + + glyph->x = font->texX; + glyph->y = font->texY; + + // Copy glyph bitmap to texture + unsigned char *buffer = slot->bitmap.buffer; + unsigned int pitch = slot->bitmap.pitch; + + for (unsigned int y = 0; y < glyph->height; y++) { + for (unsigned int x = 0; x < glyph->width; x++) { + unsigned int tex_pos = ((font->texX + x) + (font->texY + y) * font->texWidth) >> 1; + unsigned char value = buffer[y * pitch + x] >> 4; + + if ((font->texX + x) & 1) { + font->texture[tex_pos] = (font->texture[tex_pos] & 0x0F) | (value << 4); + } else { + font->texture[tex_pos] = (font->texture[tex_pos] & 0xF0) | value; + } + } + } + + font->texX += glyph->width + 1; + if (glyph->height > font->texYSize) { + font->texYSize = glyph->height; + } + + loaded_chars++; + } + + mapped_chars++; + } + } + + // Update actual number of mapped characters + font->n_chars = mapped_chars; + + // Cleanup FreeType + FT_Done_Face(face); + FT_Done_FreeType(library); + + // Swizzle texture + if (!intraFontSwizzle(font)) { + intraFontUnload(font); + return NULL; + } + + // Flush cache + sceKernelDcacheWritebackAll(); + + return font; +} diff --git a/src/psp/intra/intraFont.h b/src/psp/intra/intraFont.h new file mode 100644 index 0000000..7750264 --- /dev/null +++ b/src/psp/intra/intraFont.h @@ -0,0 +1,314 @@ +/* + * intraFont.h + * This file is used to display the PSP's internal font (pgf and bwfon firmware files) + * intraFont Version 0.31 by BenHur - http://www.psp-programming.com/benhur + * + * Uses parts of pgeFont by InsertWittyName - http://insomniac.0x89.org + * G-spec code by Geecko + * + * This work is licensed under the Creative Commons Attribution-Share Alike 3.0 License. + * See LICENSE for more details. + * + */ + +#ifndef __INTRAFONT_H__ +#define __INTRAFONT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libccc.h" +#include +#include +#include FT_FREETYPE_H + + /** @defgroup intraFont Font Library + * @{ + */ + +#define INTRAFONT_ADVANCE_H 0x00000000 //default: advance horizontaly from one char to the next +#define INTRAFONT_ADVANCE_V 0x00000100 +#define INTRAFONT_ALIGN_LEFT 0x00000000 //default: left-align the text +#define INTRAFONT_ALIGN_CENTER 0x00000200 +#define INTRAFONT_ALIGN_RIGHT 0x00000400 +#define INTRAFONT_ALIGN_FULL 0x00000600 //full justify text to width set by intraFontSetTextWidth() +#define INTRAFONT_SCROLL_LEFT 0x00002000 //in intraFontPrintColumn if text does not fit text is scrolled to the left + //(requires redrawing at ~60 FPS with x position returned by previous call to intraFontPrintColumn()) +#define INTRAFONT_SCROLL_SEESAW 0x00002200 //in intraFontPrintColumn if text does not fit text is scrolled left and right +#define INTRAFONT_SCROLL_RIGHT 0x00002400 //in intraFontPrintColumn if text does not fit text is scrolled to the right +#define INTRAFONT_SCROLL_THROUGH 0x00002600 //in intraFontPrintColumn if text does not fit text is scrolled through (to the left) +#define INTRAFONT_WIDTH_VAR 0x00000000 //default: variable-width +#define INTRAFONT_WIDTH_FIX 0x00000800 //set your custom fixed witdh to 24 pixels: INTRAFONT_WIDTH_FIX | 24 + //(max is 255, set to 0 to use default fixed width, this width will be scaled by size) +#define INTRAFONT_ACTIVE 0x00001000 //assumes the font-texture resides inside sceGuTex already, prevents unecessary reloading -> very small speed-gain +#define INTRAFONT_CACHE_MED 0x00000000 //default: 256x256 texture (enough to cache about 100 chars) +#define INTRAFONT_CACHE_LARGE 0x00004000 //512x512 texture(enough to cache all chars of ltn0.pgf or ... or ltn15.pgf or kr0.pgf) +#define INTRAFONT_CACHE_ASCII 0x00008000 //try to cache all ASCII chars during fontload (uses less memory and is faster to draw text, but slower to load font) + //if it fails: (because the cache is too small) it will automatically switch to chache on-the-fly with a medium texture + //if it succeeds: (all chars and shadows fit into chache) it will free some now unneeded memory +#define INTRAFONT_CACHE_ALL 0x0000C000 //try to cache all chars during fontload (uses less memory and is faster to draw text, but slower to load font) + //if it fails: (because the cache is too small) it will automatically switch to chache on-the-fly with a large texture + //if it succeeds: (all chars and shadows fit into chache) it will free some now unneeded memory +#define INTRAFONT_STRING_ASCII (0x00010000*CCC_CP000) //default: interpret strings as ascii text (ISO/IEC 8859-1) +#define INTRAFONT_STRING_CP437 (0x00010000*CCC_CP437) //interpret strings as ascii text (codepage 437) +#define INTRAFONT_STRING_CP850 (0x00010000*CCC_CP850) //interpret strings as ascii text (codepage 850) +#define INTRAFONT_STRING_CP866 (0x00010000*CCC_CP866) //interpret strings as ascii text (codepage 866) +#define INTRAFONT_STRING_SJIS (0x00010000*CCC_CP932) //interpret strings as shifted-jis (used for japanese) +#define INTRAFONT_STRING_GBK (0x00010000*CCC_CP936) //interpret strings as GBK (used for simplified chinese) +#define INTRAFONT_STRING_KOR (0x00010000*CCC_CP949) //interpret strings as Korean codepage 949 +#define INTRAFONT_STRING_BIG5 (0x00010000*CCC_CP950) //interpret strings as BIG5 (used for traditional chinese) +#define INTRAFONT_STRING_CP1251 (0x00010000*CCC_CP1251) //interpret strings as ascii text (codepage windows-1251) +#define INTRAFONT_STRING_CP1252 (0x00010000*CCC_CP1252) //interpret strings as ascii text (codepage windows-1252) +#define INTRAFONT_STRING_UTF8 (0x00010000*CCC_CPUTF8) //interpret strings as UTF-8 + +/** @note The following definitions are used internally by ::intraFont and have no other relevance.*/ +#define FILETYPE_PGF 0x00 +#define FILETYPE_BWFON 0x01 +#define PGF_BMP_H_ROWS 0x01 +#define PGF_BMP_V_ROWS 0x02 +#define PGF_BMP_OVERLAY 0x03 +#define PGF_NO_EXTRA1 0x04 +#define PGF_NO_EXTRA2 0x08 +#define PGF_NO_EXTRA3 0x10 +#define PGF_CHARGLYPH 0x20 +#define PGF_SHADOWGLYPH 0x40 //warning: this flag is not contained in the metric header flags and is only provided for simpler call to intraFontGetGlyph - ONLY check with (flags & PGF_CHARGLYPH) +#define PGF_CACHED 0x80 +#define PGF_WIDTH_MASK 0x000000FF +#define PGF_OPTIONS_MASK 0x00003FFF +#define PGF_ALIGN_MASK 0x00000600 +#define PGF_SCROLL_MASK 0x00002600 +#define PGF_CACHE_MASK 0x0000C000 +#define PGF_STRING_MASK 0x00FF0000 + + +/** + * A Glyph struct + * @note This is used internally by ::intraFont and has no other relevance. + */ + typedef struct + { + unsigned short x; //in pixels + unsigned short y; //in pixels + unsigned char width; //in pixels + unsigned char height; //in pixels + short left; //in pixels + short top; //in pixels + unsigned char flags; + short advance; //in quarterpixels + unsigned long ptr; //offset + } Glyph; + + typedef struct + { + unsigned short x; //in pixels + unsigned short y; //in pixels + unsigned char flags; + } GlyphBW; + + /** + * A PGF_Header struct + * @note This is used internally by ::intraFont and has no other relevance. + */ + typedef struct + { + unsigned short header_start; + unsigned short header_len; + char pgf_id[4]; + unsigned long revision; + unsigned long version; + unsigned long charmap_len; + unsigned long charptr_len; + unsigned long charmap_bpe; + unsigned long charptr_bpe; + unsigned char junk00[21]; + unsigned char family[64]; + unsigned char style[64]; + unsigned char junk01[1]; + unsigned short charmap_min; + unsigned short charmap_max; + unsigned char junk02[50]; + unsigned long fixedsize[2]; + unsigned char junk03[14]; + unsigned char table1_len; + unsigned char table2_len; + unsigned char table3_len; + unsigned char advance_len; + unsigned char junk04[102]; + unsigned long shadowmap_len; + unsigned long shadowmap_bpe; + unsigned char junk05[4]; + unsigned long shadowscale[2]; + //currently no need ; + } PGF_Header; + + /** + * A Font struct + */ + typedef struct intraFont + { + char *filename; + unsigned char fileType; /**< FILETYPE_PGF or FILETYPE_BWFON */ + unsigned char *fontdata; + + unsigned char *texture; /**< The bitmap data */ + unsigned int texWidth; /**< Texture size (power2) */ + unsigned int texHeight; /**< Texture height (power2) */ + unsigned short texX; + unsigned short texY; + unsigned short texYSize; + + unsigned short n_chars; + char advancex; /**< in quarterpixels */ + char advancey; /**< in quarterpixels */ + unsigned char charmap_compr_len; /**< length of compression info */ + unsigned short *charmap_compr; /**< Compression info on compressed charmap */ + unsigned short *charmap; /**< Character map */ + Glyph *glyph; /**< Character glyphs */ + GlyphBW *glyphBW; + + float size; + unsigned int color; + float angle, Rsin, Rcos; /**< For rotation */ + short isRotated; + unsigned int options; + + struct intraFont *altFont; + } intraFont; + + + /** + * Initialise the Font library + * @returns 1 on success. + */ + int intraFontInit(void); + + /** + * Shutdown the Font library + */ + void intraFontShutdown(void); + + /** + * Load a pgf font. + * @param filename - Path to the font + * @param options - INTRAFONT_XXX flags as defined above including flags related to CACHE (ored together) + * @returns A ::intraFont struct + */ + intraFont *intraFontLoad(const char *filename, unsigned int options); + + intraFont *intraFontLoadTTF(const char *filename, unsigned int options, float size); + + /** + * Free the specified font. + * @param font - A valid ::intraFont + */ + void intraFontUnload(intraFont *font); + + /** + * Activate the specified font. + * @param font - A valid ::intraFont + * + * @param linear - smoth letters + */ + void intraFontActivate(intraFont *font, bool linear); + + /** + * Set font style + * @param font - A valid ::intraFont + * @param size - Text size + * @param color - Text color + * @param angle - Text angle (in degrees) + * @param shadowColor - Shadow color (use 0 for no shadow) + * @param options - INTRAFONT_XXX flags as defined above except flags related to CACHE (ored together) + */ + void intraFontSetStyle(intraFont *font, float size, unsigned int color, unsigned int shadowColor, float angle, unsigned int options); + + /** + * Set type of string encoding to be used in intraFontPrint[f] + * @param font - A valid ::intraFont + * @param options - INTRAFONT_STRING_XXX flags as defined above except flags related to CACHE (ored together) + */ + void intraFontSetEncoding(intraFont *font, unsigned int options); + + /** + * Set alternative font + * @param font - A valid ::intraFont + * @param altFont - A valid ::intraFont that's to be used if font does not contain a character + */ + void intraFontSetAltFont(intraFont *font, intraFont *altFont); + + /** + * Draw UCS-2 encoded text along the baseline starting at x, y. + * @param font - A valid ::intraFont + * @param x - X position on screen + * @param y - Y position on screen + * @param width - column width for automatic line breaking (intraFontPrintColumn... versions only) + * @param text - UCS-2 encoded text to draw + * @param length - char length of text to draw (...Ex versions only) + * @returns The x position after the last char + */ + float intraFontPrintUCS2(intraFont *font, float x, float y, const unsigned short *text); + float intraFontPrintUCS2Ex(intraFont *font, float x, float y, const unsigned short *text, int length); + float intraFontPrintColumnUCS2(intraFont *font, float x, float y, float width, const unsigned short *text); + float intraFontPrintColumnUCS2Ex(intraFont *font, float x, float y, float width, const unsigned short *text, int length); + + /** + * Draw text along the baseline starting at x, y. + * @param font - A valid ::intraFont + * @param x - X position on screen + * @param y - Y position on screen + * @param width - column width for automatic line breaking (intraFontPrintColumn... versions only) + * @param text - Text to draw (ASCII & extended ASCII, S-JIS or UTF-8 encoded) + * @param length - char length of text to draw (...Ex versions only) + * @returns The x position after the last char + */ + float intraFontPrint(intraFont *font, float x, float y, const char *text); + float intraFontPrintEx(intraFont *font, float x, float y, const char *text, int length); + float intraFontPrintColumn(intraFont *font, float x, float y, float width, const char *text); + float intraFontPrintColumnEx(intraFont *font, float x, float y, float width, const char *text, int length); + + /** + * Draw text along the baseline starting at x, y (with formatting). + * @param font - A valid ::intraFont + * @param x - X position on screen + * @param y - Y position on screen + * @param width - column width for automatic line breaking (intraFontPrintfColumn... versions only) + * @param text - Text to draw (ASCII & extended ASCII, S-JIS or UTF-8 encoded) + * @param length - char length of text to draw (...Ex versions only) + * @returns The x position after the last char + */ + float intraFontPrintf(intraFont *font, float x, float y, const char *text, ...); + //the following functions might be implemented in a future version of intraFont + //float intraFontPrintfEx (intraFont *font, float x, float y, const char *text, int length, ...); + //float intraFontPrintfColumn (intraFont *font, float x, float y, float width, const char *text, ...); + //float intraFontPrintfColumnEx(intraFont *font, float x, float y, float width, const char *text, int length, ...); + + /** + * Measure a length of text if it were to be drawn + * @param font - A valid ::intraFont + * @param text - Text to measure (ASCII & extended ASCII, S-JIS or UTF-8 encoded) + * @param length - char length of text to measure (...Ex version only) + * @returns The total width of the text (until the first newline char) + */ + float intraFontMeasureText(intraFont *font, const char *text); + float intraFontMeasureTextEx(intraFont *font, const char *text, int length); + + /** + * Measure a length of UCS-2 encoded text if it were to be drawn + * @param font - A valid ::intraFont + * @param text - UCS-2 encoded text to measure + * @param length - char length of text to measure (...Ex version only) + * @returns The total width of the text (until the first newline char) + */ + float intraFontMeasureTextUCS2(intraFont *font, const unsigned short *text); + float intraFontMeasureTextUCS2Ex(intraFont *font, const unsigned short *text, int length); + + int intraFontTextHeight(intraFont *font); + + /** @} */ + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // __INTRAFONT_H__ diff --git a/src/psp/intra/libccc.c b/src/psp/intra/libccc.c new file mode 100644 index 0000000..02eda6a --- /dev/null +++ b/src/psp/intra/libccc.c @@ -0,0 +1,539 @@ +/* + * libccc.c + * Character Code Conversion Library + * Version 0.31 by BenHur - http://www.psp-programming.com/benhur + * + * This work is licensed under the Creative Commons Attribution-Share Alike 3.0 License. + * See LICENSE for more details. + * + */ + +#include +#include +#include +#include + +#include "libccc.h" + +static unsigned char cccInitialized = 0; +static void* __table_ptr__[CCC_N_CP]; +static void* __table_end__[CCC_N_CP]; +static unsigned char __table_dyn__[CCC_N_CP]; +static cccUCS2 __error_char_ucs2__ = 0x0000U; + +/* the following code is adapted from libLZR 0.11 (see http://www.psp-programming.com/benhur) */ + +void cccLZRFillBuffer(unsigned int *test_mask, unsigned int *mask, unsigned int *buffer, unsigned char **next_in) { + /* if necessary: fill up in buffer and shift mask */ + if (*test_mask <= 0x00FFFFFFu) { + (*buffer) = ((*buffer) << 8) + *(*next_in)++; + *mask = *test_mask << 8; + } +} + +char cccLZRNextBit(unsigned char *buf_ptr1, int *number, unsigned int *test_mask, unsigned int *mask, unsigned int *buffer, unsigned char **next_in) { + /* extract and return next bit of information from in stream, update buffer and mask */ + cccLZRFillBuffer(test_mask, mask, buffer, next_in); + unsigned int value = (*mask >> 8) * (*buf_ptr1); + if (test_mask != mask) *test_mask = value; + *buf_ptr1 -= *buf_ptr1 >> 3; + if (number) (*number) <<= 1; + if (*buffer < value) { + *mask = value; + *buf_ptr1 += 31; + if (number) (*number)++; + return 1; + } else { + *buffer -= value; + *mask -= value; + return 0; + } +} + +int cccLZRGetNumber(signed char n_bits, unsigned char *buf_ptr, char inc, char *flag, unsigned int *mask, unsigned int *buffer, unsigned char **next_in) { + /* extract and return a number (consisting of n_bits bits) from in stream */ + int number = 1; + if (n_bits >= 3) { + cccLZRNextBit(buf_ptr+3*inc, &number, mask, mask, buffer, next_in); + if (n_bits >= 4) { + cccLZRNextBit(buf_ptr+3*inc, &number, mask, mask, buffer, next_in); + if (n_bits >= 5) { + cccLZRFillBuffer(mask, mask, buffer, next_in); + for (; n_bits >= 5; n_bits--) { + number <<= 1; + (*mask) >>= 1; + if (*buffer < *mask) number++; else (*buffer) -= *mask; + } + } + } + } + *flag = cccLZRNextBit(buf_ptr, &number, mask, mask, buffer, next_in); + if (n_bits >= 1) { + cccLZRNextBit(buf_ptr+inc, &number, mask, mask, buffer, next_in); + if (n_bits >= 2) { + cccLZRNextBit(buf_ptr+2*inc, &number, mask, mask, buffer, next_in); + } + } + return number; +} + +int cccLZRDecompress(void *out, unsigned int out_capacity, void *in, void *in_end) { + unsigned char **next_in, *tmp, *next_out, *out_end, *next_seq, *seq_end, *buf_ptr1, *buf_ptr2; + unsigned char last_char = 0; + int seq_len, seq_off, n_bits, buf_off = 0, i, j; + unsigned int mask = 0xFFFFFFFF, test_mask; + char flag; + + signed char type = *(signed char*)in; + unsigned int buffer = ((unsigned int)*(unsigned char*)(in+1) << 24) + + ((unsigned int)*(unsigned char*)(in+2) << 16) + + ((unsigned int)*(unsigned char*)(in+3) << 8) + + ((unsigned int)*(unsigned char*)(in+4) ); + next_in = (in_end) ? in_end : &tmp; //use user provided counter if available + *next_in = in + 5; + next_out = out; + out_end = out + out_capacity; + + if (type < 0) { + + /* copy from stream without decompression */ + + seq_end = next_out + buffer; + if (seq_end > out_end) return CCC_ERROR_BUFFER_SIZE; + while (next_out < seq_end) { + *next_out++ = *(*next_in)++; + } + (*next_in)++; //skip 1 byte padding + return next_out - (unsigned char*)out; + + } + + /* create and init buffer */ + unsigned char *buf = (unsigned char*)malloc(2800); + if (!buf) return CCC_ERROR_MEM_ALLOC; + for (i = 0; i < 2800; i++) buf[i] = 0x80; + + while (1) { + + buf_ptr1 = buf + buf_off + 2488; + if (!cccLZRNextBit(buf_ptr1, 0, &mask, &mask, &buffer, next_in)) { + + /* single new char */ + + if (buf_off > 0) buf_off--; + if (next_out == out_end) return CCC_ERROR_BUFFER_SIZE; + buf_ptr1 = buf + (((((((int)(next_out - (unsigned char*)out)) & 0x07) << 8) + last_char) >> type) & 0x07) * 0xFF - 0x01; + for (j = 1; j <= 0xFF; ) { + cccLZRNextBit(buf_ptr1+j, &j, &mask, &mask, &buffer, next_in); + } + *next_out++ = j; + + } else { + + /* sequence of chars that exists in out stream */ + + /* find number of bits of sequence length */ + test_mask = mask; + n_bits = -1; + do { + buf_ptr1 += 8; + flag = cccLZRNextBit(buf_ptr1, 0, &test_mask, &mask, &buffer, next_in); + n_bits += flag; + } while ((flag != 0) && (n_bits < 6)); + + /* find sequence length */ + buf_ptr2 = buf + n_bits + 2033; + j = 64; + if ((flag != 0) || (n_bits >= 0)) { + buf_ptr1 = buf + (n_bits << 5) + (((((int)(next_out - (unsigned char*)out)) << n_bits) & 0x03) << 3) + buf_off + 2552; + seq_len = cccLZRGetNumber(n_bits, buf_ptr1, 8, &flag, &mask, &buffer, next_in); + if (seq_len == 0xFF) return next_out - (unsigned char*)out; //end of data stream + if ((flag != 0) || (n_bits > 0)) { + buf_ptr2 += 56; + j = 352; + } + } else { + seq_len = 1; + } + + /* find number of bits of sequence offset */ + i = 1; + do { + n_bits = (i << 4) - j; + flag = cccLZRNextBit(buf_ptr2 + (i << 3), &i, &mask, &mask, &buffer, next_in); + } while (n_bits < 0); + + /* find sequence offset */ + if (flag || (n_bits > 0)) { + if (!flag) n_bits -= 8; + seq_off = cccLZRGetNumber(n_bits/8, buf+n_bits+2344, 1, &flag, &mask, &buffer, next_in); + } else { + seq_off = 1; + } + + /* copy sequence */ + next_seq = next_out - seq_off; + if (next_seq < (unsigned char*)out) return CCC_ERROR_INPUT_STREAM; + seq_end = next_out + seq_len + 1; + if (seq_end > out_end) return CCC_ERROR_BUFFER_SIZE; + buf_off = ((((int)(seq_end - (unsigned char*)out))+1) & 0x01) + 0x06; + do { + *next_out++ = *next_seq++; + } while (next_out < seq_end); + + } + last_char = *(next_out-1); + } +} + +/* end of code adapted from libLZR 0.11 (see http://www.psp-programming.com/benhur) */ + +int cccSetTable(void* table, unsigned int bytesize, unsigned char cp, unsigned char dyn) { + if (cp < CCC_N_CP) { + if (__table_dyn__[cp] && __table_ptr__[cp]) + free(__table_ptr__[cp]); + __table_ptr__[cp] = table; + __table_end__[cp] = table+bytesize; + __table_dyn__[cp] = dyn; + return CCC_SUCCESS; + } else + return CCC_ERROR_UNSUPPORTED; +} + +int cccLoadTable(const char *filename, unsigned char cp) { + if (cp >= CCC_N_CP) return CCC_ERROR_UNSUPPORTED; + + /* read in (compressed) table_data */ + SceUID fd = sceIoOpen(filename, PSP_O_RDONLY, 0777); + if (fd < 0) return CCC_ERROR_FILE_READ; + unsigned int filesize = sceIoLseek(fd, 0, SEEK_END); + sceIoLseek(fd, 0, SEEK_SET); + void* table_data = (void*)malloc(filesize); + if (!table_data) { + sceIoClose(fd); + return CCC_ERROR_MEM_ALLOC; + } + if (sceIoRead(fd, table_data, filesize) != filesize) { + sceIoClose(fd); + free(table_data); + return CCC_ERROR_FILE_READ; + } + sceIoClose(fd); + + /* decompress requested tables */ + unsigned int *header = (unsigned int*)table_data; + while (header[0]) { + if ((cp == 0) || (cp == header[0])) { + void* table = (void*)malloc(header[4]); + if (!table) { + free(table_data); + return CCC_ERROR_MEM_ALLOC; + } + int ret = cccLZRDecompress(table, header[4], table_data+header[2], NULL); + if (ret < 0) { + free(table_data); + free(table); + return ret; + } + cccSetTable(table, header[4], header[0], 1); + } + header += 8; + } + free(table_data); + return CCC_SUCCESS; +} + +void cccInit(void) { + if (!cccInitialized) { + int cp; + for (cp = 0; cp < CCC_N_CP; cp++) { + __table_ptr__[cp] = NULL; + __table_end__[cp] = NULL; + __table_dyn__[cp] = 0; + } + //cccLoadTable("flash0:/vsh/etc/cptbl.dat", 0); //this would load all tables available, but it's done on demand + cccInitialized = 1; + } +} + +void cccShutDown(void) { + if (cccInitialized) { + //free dynamically loaded tables + int cp; + for (cp = 0; cp < CCC_N_CP; cp++) + cccSetTable(NULL, 0, cp, 0); + cccInitialized = 0; + } +} + +cccUCS2 cccSetErrorCharUCS2(cccUCS2 code) { + cccUCS2 old = __error_char_ucs2__; + __error_char_ucs2__ = code; + return old; +} + +int cccStrlen(cccCode const * str) { + if (!str) return 0; + + return strlen((char*)str); +} + +int cccStrlenSJIS(cccCode const * str) { + if (!str) return 0; + + int i = 0, length = 0; + while (str[i]) { + length++; + i += (str[i] <= 0x80 || (str[i] >= 0xA0 && str[i] <= 0xDF) || str[i] >= 0xFD) ? 1 : 2; //single or double byte + } + return length; +} + +int cccStrlenGBK(cccCode const * str) { + if (!str) return 0; + + int i = 0, length = 0; + while (str[i]) { + length++; + i += (str[i] <= 0x80 || str[i] == 0xFF) ? 1 : 2; //single or double byte + } + return length; +} + +int cccStrlenKOR(cccCode const * str) { + return cccStrlenGBK(str); +} + +int cccStrlenBIG5(cccCode const * str) { + return cccStrlenGBK(str); +} + +int cccStrlenUTF8(cccCode const * str) { + if (!str) return 0; + + int i = 0, length = 0; + while (str[i]) { + if (str[i] <= 0x7F) { i++; length++; } //ASCII + else if (str[i] <= 0xC1) { i++; } //part of multi-byte or overlong encoding ->ignore + else if (str[i] <= 0xDF) { i += 2; length++; } //2-byte + else if (str[i] <= 0xEF) { i += 3; length++; } //3-byte + else { i++; } //4-byte, restricted or invalid range ->ignore + } + return length; +} + +int cccStrlenCode(cccCode const * str, unsigned char cp) { + if (!str) return 0; + + int length = 0; + switch (cp) { + /* multi byte character sets */ + case CCC_CP932: length = cccStrlenSJIS(str); break; + case CCC_CP936: length = cccStrlenGBK (str); break; + case CCC_CP949: length = cccStrlenKOR (str); break; + case CCC_CP950: length = cccStrlenBIG5(str); break; + case CCC_CPUTF8: length = cccStrlenUTF8(str); break; + /* single byte character sets */ + default: length = cccStrlen(str); break; + } + return length; +} + +int cccStrlenUCS2(cccUCS2 const * str) { + if (!str) return 0; + + int length = 0; + while (str[length]) length++; + return length; +} + + +int cccSJIStoUCS2(cccUCS2 * dst, size_t count, cccCode const * str) { + if (!str || !dst) return 0; + if (!cccInitialized) cccInit(); + if (!(__table_ptr__[CCC_CP932])) cccLoadTable("flash0:/vsh/etc/cptbl.dat", CCC_CP932); + + int i = 0, length = 0, j, code, id; + if (__table_ptr__[CCC_CP932]) { //table is present + unsigned short *header = (unsigned short*)(__table_ptr__[CCC_CP932]); + cccUCS2 *SJIStoUCS2 = (cccUCS2*)header+header[2]*3+3; + while (str[i] && length < count) { + code = str[i]; + id = -1; + for (j = 1; (j <= header[2]) && (id < 0); j++) { + if ((code >= header[j*3]) && (code <= header[j*3+1])) { + id = header[j*3+2] + code - header[j*3]; + } else { + if (j == 2) code = 0x0200 * str[i] - 0xE100 - ((str[i] >= 0xE0) ? 0x8000 : 0) + str[i+1] + ((str[i+1] <= 0x7E) ? -0x1F : ((str[i+1] >= 0x9F) ? 0x82 : -0x20) ); + } + } + dst[length++] = (id < 0) ? __error_char_ucs2__ : SJIStoUCS2[id]; + i += (str[i] <= 0x80 || (str[i] >= 0xA0 && str[i] <= 0xDF) || str[i] >= 0xFD) ? 1 : 2; //single or double byte + } + } else { //table not present + while (str[i] && length < count) { + dst[length++] = __error_char_ucs2__; + i += (str[i] <= 0x80 || (str[i] >= 0xA0 && str[i] <= 0xDF) || str[i] >= 0xFD) ? 1 : 2; //single or double byte + } + } + return length; +} + +int cccGBKtoUCS2(cccUCS2 * dst, size_t count, cccCode const * str) { + if (!str || !dst) return 0; + if (!cccInitialized) cccInit(); + if (!(__table_ptr__[CCC_CP936])) cccLoadTable("flash0:/vsh/etc/cptbl.dat", CCC_CP936); + + unsigned char* entry; + unsigned short code; + int i = 0, length = 0; + while (str[i] && length < count) { + if (str[i] <= 0x7f) { + dst[length] = (cccUCS2)str[i]; + } else if (str[i] <= 0x80) { + dst[length] = 0x20ac; + } else if (str[i] <= 0xfe) { + if (__table_ptr__[CCC_CP936]) { //table is present + code = 0x0100 * str[i] + str[i+1]; + for (entry = (unsigned char*)(__table_ptr__[CCC_CP936]); (entry < (unsigned char*)(__table_end__[CCC_CP936])) && ((entry[0]+0x100*entry[1] + entry[4]) <= code); entry += 5); + if ((entry >= (unsigned char*)(__table_end__[CCC_CP936])) || (code < entry[0]+0x100*entry[1])) { + dst[length] = __error_char_ucs2__; + } else { + dst[length] = entry[2]+0x100*entry[3] + (code - entry[0]-0x100*entry[1]); + } + } else { + dst[length] = __error_char_ucs2__; + } + } else { + dst[length] = __error_char_ucs2__; + } + length++; + i += (str[i] <= 0x80 || str[i] == 0xFF) ? 1 : 2; //single or double byte + } + return length; +} + +int cccKORtoUCS2(cccUCS2 * dst, size_t count, cccCode const * str) { + if (!str || !dst) return 0; + if (!cccInitialized) cccInit(); + if (!(__table_ptr__[CCC_CP949])) cccLoadTable("flash0:/vsh/etc/cptbl.dat", CCC_CP949); + + unsigned char* entry; + unsigned short code; + int i = 0, length = 0; + while (str[i] && length < count) { + if (str[i] <= 0x7f) { + dst[length] = (cccUCS2)str[i]; + } else if (str[i] <= 0x80) { + dst[length] = __error_char_ucs2__; + } else if (str[i] <= 0xfd) { + if (__table_ptr__[CCC_CP949]) { //table is present + code = 0x0100 * str[i] + str[i+1]; + for (entry = (unsigned char*)(__table_ptr__[CCC_CP949]); (entry < (unsigned char*)(__table_end__[CCC_CP949])) && ((entry[0]+0x100*entry[1] + entry[4]) <= code); entry += 5); + if ((entry >= (unsigned char*)(__table_end__[CCC_CP949])) || (code < entry[0]+0x100*entry[1])) { + dst[length] = __error_char_ucs2__; + } else { + dst[length] = entry[2]+0x100*entry[3] + (code - entry[0]-0x100*entry[1]); + } + } else { + dst[length] = __error_char_ucs2__; + } + } else { + dst[length] = __error_char_ucs2__; + } + length++; + i += (str[i] <= 0x80 || str[i] == 0xFF) ? 1 : 2; //single or double byte + } + return length; +} + +int cccBIG5toUCS2(cccUCS2 * dst, size_t count, cccCode const * str) { + if (!str || !dst) return 0; + if (!cccInitialized) cccInit(); + if (!(__table_ptr__[CCC_CP950])) cccLoadTable("flash0:/vsh/etc/cptbl.dat", CCC_CP950); + + typedef struct { + unsigned short code; + cccUCS2 ucs2; + } cccTblEntry; + cccTblEntry* entry; + unsigned short code; + int i = 0, length = 0; + while (str[i] && length < count) { + if (str[i] <= 0x7f) { + dst[length] = (cccUCS2)str[i]; + } else if (str[i] <= 0xa0) { + dst[length] = __error_char_ucs2__; + } else if (str[i] <= 0xf9) { + if (__table_ptr__[CCC_CP950]) { //table is present + code = 0x0100 * str[i] + str[i+1]; + for (entry = (cccTblEntry*)(__table_ptr__[CCC_CP950]); (entry < (cccTblEntry*)(__table_end__[CCC_CP950])) && (entry->code < code); entry++); + if ((entry >= (cccTblEntry*)(__table_end__[CCC_CP950])) || (code < entry->code)) { + dst[length] = __error_char_ucs2__; + } else { + dst[length] = entry->ucs2; + } + } else { + dst[length] = __error_char_ucs2__; + } + } else { + dst[length] = __error_char_ucs2__; + } + length++; + i += (str[i] <= 0x80 || str[i] == 0xFF) ? 1 : 2; //single or double byte + } + return length; +} + +int cccUTF8toUCS2(cccUCS2 * dst, size_t count, cccCode const * str) { + if (!str || !dst) return 0; + + int i = 0, length = 0; + while (str[i] && length < count) { + if (str[i] <= 0x7FU) { //ASCII + dst[length] = (cccUCS2)str[i]; + i++; length++; + } else if (str[i] <= 0xC1U) { //part of multi-byte or overlong encoding ->ignore + i++; + } else if (str[i] <= 0xDFU) { //2-byte + dst[length] = ((str[i]&0x001fu)<<6) | (str[i+1]&0x003fu); + i += 2; length++; + } else if (str[i] <= 0xEFU) { //3-byte + dst[length] = ((str[i]&0x001fu)<<12) | ((str[i+1]&0x003fu)<<6) | (str[i+2]&0x003fu); + i += 3; length++; + } else i++; //4-byte, restricted or invalid range ->ignore + } + return length; +} + +int cccCodetoUCS2(cccUCS2 * dst, size_t count, cccCode const * str, unsigned char cp) { + if (!str || !dst) return 0; + + int length = 0; + switch (cp) { + //multi-byte character sets + case CCC_CP932: length = cccSJIStoUCS2(dst, count, str); break; + case CCC_CP936: length = cccGBKtoUCS2(dst, count, str); break; + case CCC_CP949: length = cccKORtoUCS2(dst, count, str); break; + case CCC_CP950: length = cccBIG5toUCS2(dst, count, str); break; + case CCC_CPUTF8: length = cccUTF8toUCS2(dst, count, str); break; + //single-byte character sets + default: + if (cp < CCC_N_CP) { //codepage in range? + if (!cccInitialized) cccInit(); + if (!(__table_ptr__[cp]) && (cp > 0)) cccLoadTable("flash0:/vsh/etc/cptbl.dat", cp); + while (str[length] && length < count) { //conversion: ASCII (if ASCII) or LUT-value (if LUT exists) or error_char (if LUT doesn't exist) + if (str[length] < 0x80) { + dst[length] = (cccUCS2)str[length]; + } else { + dst[length] = ((__table_ptr__[cp]) ? ((cccUCS2*)(__table_ptr__[cp]))[str[length]-0x80] : __error_char_ucs2__); + if (!dst[length]) dst[length] = __error_char_ucs2__; + } + length++; + } + } + break; + } + return length; +} + diff --git a/src/psp/intra/libccc.h b/src/psp/intra/libccc.h new file mode 100644 index 0000000..5ec214a --- /dev/null +++ b/src/psp/intra/libccc.h @@ -0,0 +1,108 @@ +/* + * libccc.h + * Character Code Conversion Library + * Version 0.31 by BenHur - http://www.psp-programming.com/benhur + * + * This work is licensed under the Creative Commons Attribution-Share Alike 3.0 License. + * See LICENSE for more details. + * + */ + +#ifndef __LIBCCC_H__ +#define __LIBCCC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @defgroup character code conversion library + * @{ + */ + +typedef unsigned short cccUCS2; +typedef unsigned char cccCode; + +/* supported codepages */ +#define CCC_CP000 0x00 //ASCII +#define CCC_CP437 0x01 //US +#define CCC_CP850 0x05 //Multilingual Latin I +#define CCC_CP866 0x0b //Russian +#define CCC_CP932 0x0d //S-JIS +#define CCC_CP936 0x0e //GBK +#define CCC_CP949 0x0f //Korean +#define CCC_CP950 0x10 //Big5 +#define CCC_CP1251 0x12 //Cyrillic +#define CCC_CP1252 0x13 //Latin II +#define CCC_CPUTF8 0xff //UTF-8 +#define CCC_N_CP 0x14 //number of codepages (for array definition) + +/* error codes */ +#define CCC_SUCCESS 0x00000000 +#define CCC_ERROR_BUFFER_SIZE 0x80000104 +#define CCC_ERROR_INPUT_STREAM 0x80000108 +#define CCC_ERROR_MEM_ALLOC 0x800200D9 +#define CCC_ERROR_FILE_READ 0x80020130 +#define CCC_ERROR_UNSUPPORTED 0x80020325 + + +/** + * Character counting + * + * @param str - zero terminated string + * + * @param cp - codepage + * + * @returns number of characters in the string + */ +int cccStrlen(cccCode const * str); //for single byte character sets +int cccStrlenSJIS(cccCode const * str); +int cccStrlenGBK(cccCode const * str); +int cccStrlenKOR(cccCode const * str); +int cccStrlenBIG5(cccCode const * str); +int cccStrlenUTF8(cccCode const * str); +int cccStrlenCode(cccCode const * str, unsigned char cp); +int cccStrlenUCS2(cccUCS2 const * str); + +/** + * Character code conversion + * + * @param dst - output string + * + * @param count - size of output buffer + * + * @param str - input string + * + * @param cp - codepage + * + * @returns number of converted character codes + */ +int cccSJIStoUCS2(cccUCS2 * dst, size_t count, cccCode const * str); +int cccGBKtoUCS2 (cccUCS2 * dst, size_t count, cccCode const * str); +int cccKORtoUCS2 (cccUCS2 * dst, size_t count, cccCode const * str); +int cccBIG5toUCS2(cccUCS2 * dst, size_t count, cccCode const * str); +int cccUTF8toUCS2(cccUCS2 * dst, size_t count, cccCode const * str); +int cccCodetoUCS2(cccUCS2 * dst, size_t count, cccCode const * str, unsigned char cp); + +/** + * Set error character (character that's used for code points where conversion failed) + * + * @param code - new error character (default: 0) + * + * @returns previous error character + */ +cccUCS2 cccSetErrorCharUCS2(cccUCS2 code); + +/** + * Shutdown the Character Code Conversion Library + */ +void cccShutDown(void); + +/** @} */ + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // __LIBCCC_H__ diff --git a/src/psp/pspSetup.cpp b/src/psp/pspSetup.cpp new file mode 100644 index 0000000..a61c29b --- /dev/null +++ b/src/psp/pspSetup.cpp @@ -0,0 +1,44 @@ +#include "pspSetup.h" +#include "glib2d.h" +#include "intra/intraFont.h" +#include +#include +#include +#include "../Micro.h" + +int exit_callback(int arg1, int arg2, void *common) { + (void)arg1; (void)arg2; (void)common; + Micro::field_249 = false; + return 0; +} + +int CallbackThread(SceSize args, void *argp) { + (void)args; (void)argp; + int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); + sceKernelRegisterExitCallback(cbid); + sceKernelSleepThreadCB(); + return 0; +} + +int SetupCallbacks(void) { + int thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0); + if(thid >= 0) { + sceKernelStartThread(thid, 0, 0); + } + return thid; +} + +void InitGame(void) { + SetupCallbacks(); + scePowerSetClockFrequency(333, 333, 166); + sceCtrlSetSamplingCycle(0); + sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG); + g2dInit(); + intraFontInit(); +} + +void ExitGame(void) { + intraFontShutdown(); + g2dTerm(); + sceKernelExitGame(); +} diff --git a/src/psp/pspSetup.h b/src/psp/pspSetup.h new file mode 100644 index 0000000..b91d5ab --- /dev/null +++ b/src/psp/pspSetup.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void InitGame(void); +void ExitGame(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/rms/RecordEnumerationImpl.cpp b/src/rms/RecordEnumerationImpl.cpp index 33880a0..b92af0f 100644 --- a/src/rms/RecordEnumerationImpl.cpp +++ b/src/rms/RecordEnumerationImpl.cpp @@ -22,7 +22,10 @@ int RecordEnumerationImpl::numRecords() std::vector RecordEnumerationImpl::nextRecord() { - return data[currentPos++]; + if (currentPos < static_cast(data.size())) { + return data[currentPos++]; + } + return std::vector(); } int RecordEnumerationImpl::addRecord(std::vector bytes) @@ -33,10 +36,11 @@ int RecordEnumerationImpl::addRecord(std::vector bytes) void RecordEnumerationImpl::setRecord(int index, std::vector bytes) { - if (static_cast(data.size()) <= index) { - throw RecordStoreException(); + if (index >= 0 && index < static_cast(data.size())) { + data[index] = bytes; + } else { + data.push_back(bytes); } - data[index] = bytes; } void RecordEnumerationImpl::reset() @@ -46,10 +50,10 @@ void RecordEnumerationImpl::reset() int RecordEnumerationImpl::nextRecordId() { - if (currentPos >= static_cast(data.size())) { - throw RecordStoreException(); + if (currentPos > 0 && currentPos <= static_cast(data.size())) { + return currentPos - 1; } - return currentPos; + return -1; } void RecordEnumerationImpl::destroy() diff --git a/src/rms/RecordEnumerationImpl.h b/src/rms/RecordEnumerationImpl.h index 8db27f1..2e42f50 100644 --- a/src/rms/RecordEnumerationImpl.h +++ b/src/rms/RecordEnumerationImpl.h @@ -24,6 +24,10 @@ class RecordEnumerationImpl : public RecordEnumeration { int nextRecordId(); void destroy(); + int getCurrentPos() const { return currentPos; } + void setCurrentPos(int pos) { currentPos = pos; } + const std::vector>& getData() const { return data; } + void serialize(FileStream* outStream); void deserialize(FileStream* inStream); diff --git a/src/rms/RecordStore.cpp b/src/rms/RecordStore.cpp index 8237ee5..a19e4c4 100644 --- a/src/rms/RecordStore.cpp +++ b/src/rms/RecordStore.cpp @@ -3,22 +3,25 @@ #include #include #include -#include -#include #include +#include +#include +#include +#include -#ifdef WIN32 -#include -#else #include -#include +#ifdef PSP +#include +#elif defined(_WIN32) +#include +#else +#include #endif -#include "RecordStoreException.h" #include "../utils/FileStream.h" #include "../utils/String.h" -RecordStore::RecordStore(std::filesystem::path filePath, RecordEnumerationImpl* records) +RecordStore::RecordStore(std::string filePath, RecordEnumerationImpl* records) { this->filePath = filePath; this->records.reset(records); @@ -35,7 +38,7 @@ RecordEnumeration* RecordStore::enumerateRecords(RecordFilter* filter, RecordCom void RecordStore::closeRecordStore() { - // nothing + save(); } int RecordStore::addRecord(std::vector arr, int offset, int numBytes) @@ -56,55 +59,197 @@ void RecordStore::setRecord(int recordId, std::vector arr, int offset, i save(); } +std::string RecordStore::getCurrentPackDir() +{ + std::string pack = packPrefix; + if (pack.empty()) { + pack = "original"; + } else { + if (pack.back() == '_') { + pack.pop_back(); + } + } + std::string clean; + for (char c : pack) { + if (std::isalnum(static_cast(c)) || c == '_' || c == '-') { + clean += c; + } else if (c == ' ') { + clean += '_'; + } + } + if (clean.empty()) { + clean = "original"; + } + return clean; +} + +void RecordStore::makeDir(const std::string& path) +{ + if (path.empty()) return; + std::string dirPath = path; + while (dirPath.length() > 1 && (dirPath.back() == '/' || dirPath.back() == '\\')) { + dirPath.pop_back(); + } +#ifdef PSP + sceIoMkdir(dirPath.c_str(), 0777); +#elif defined(_WIN32) + _mkdir(dirPath.c_str()); +#else + mkdir(dirPath.c_str(), 0777); +#endif +} + void RecordStore::save() { - FileStream outStream(filePath, std::ios::out | std::ios::binary); - records->serialize(&outStream); + if (!records) { + return; + } + + const auto& data = records->getData(); + if (data.empty()) { + return; + } + + makeDir(rootSaveDir); + makeDir(rootSaveDir + getCurrentPackDir()); + + FILE* f = fopen(filePath.c_str(), "wb"); + if (!f) { + log("Failed to open file for writing: " + filePath); + return; + } + + int32_t currentPos = static_cast(records->getCurrentPos()); + uint32_t numRecords = static_cast(data.size()); + + fwrite(¤tPos, sizeof(int32_t), 1, f); + fwrite(&numRecords, sizeof(uint32_t), 1, f); + + for (size_t i = 0; i < data.size(); ++i) { + uint32_t recSize = static_cast(data[i].size()); + fwrite(&recSize, sizeof(uint32_t), 1, f); + if (recSize > 0) { + fwrite(data[i].data(), 1, recSize, f); + } + } + + fflush(f); + fclose(f); + log("Saved " + std::to_string(numRecords) + " records to " + filePath); +} + +void RecordStore::saveAllOpened() +{ + for (auto& pair : opened) { + if (pair.second) { + pair.second->save(); + } + } } -RecordEnumerationImpl* RecordStore::load(std::filesystem::path filePath) +RecordEnumerationImpl* RecordStore::load(const std::string& filePath) { RecordEnumerationImpl* temp = new RecordEnumerationImpl(); - FileStream inStream(filePath, std::ios::in | std::ios::binary); - temp->deserialize(&inStream); + FILE* f = fopen(filePath.c_str(), "rb"); + if (!f) { + return temp; + } + + fseek(f, 0, SEEK_END); + long fileSize = ftell(f); + fseek(f, 0, SEEK_SET); + + if (fileSize <= 0) { + fclose(f); + return temp; + } + + int32_t currentPos = 0; + uint32_t numRecords = 0; + if (fread(¤tPos, sizeof(int32_t), 1, f) == 1 && + fread(&numRecords, sizeof(uint32_t), 1, f) == 1) { + + temp->setCurrentPos(currentPos); + for (uint32_t i = 0; i < numRecords; ++i) { + uint32_t recSize = 0; + if (fread(&recSize, sizeof(uint32_t), 1, f) == 1) { + if (recSize > 0) { + std::vector buf(recSize); + if (fread(buf.data(), 1, recSize, f) == recSize) { + temp->addRecord(buf); + } + } else { + temp->addRecord(std::vector()); + } + } + } + } + + fclose(f); return temp; } +void RecordStore::setPackPrefix(const std::string& prefix) +{ + packPrefix = prefix; +} + RecordStore* RecordStore::openRecordStore(std::string name, bool createIfNecessary) { - if (opened.find(name) == opened.end()) { - opened[name] = createRecordStore(name, createIfNecessary); + std::string prefixedKey = packPrefix + name; + if (opened.find(prefixedKey) == opened.end()) { + auto store = createRecordStore(name, createIfNecessary); + if (!store) { + return nullptr; + } + opened[prefixedKey] = std::move(store); } - return opened[name].get(); + return opened[prefixedKey].get(); } std::unique_ptr RecordStore::createRecordStore(std::string name, bool createIfNecessary) { log("createRecordStore(" + name + ", " + std::to_string(createIfNecessary) + ")"); - std::filesystem::path filePath = recordStoreDir / std::filesystem::path(name); + std::string packDir = rootSaveDir + getCurrentPackDir() + "/"; + makeDir(rootSaveDir); + makeDir(rootSaveDir + getCurrentPackDir()); + + std::string filePath = packDir + name; - if (std::filesystem::exists(filePath)) { - return std::unique_ptr(new RecordStore(filePath, load(filePath))); + FILE* f = fopen(filePath.c_str(), "rb"); + if (f != nullptr) { + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fclose(f); + + if (sz > 0) { + return std::unique_ptr(new RecordStore(filePath, load(filePath))); + } } if (createIfNecessary) { - std::filesystem::create_directories(filePath.parent_path()); - std::unique_ptr rs(new RecordStore(filePath, new RecordEnumerationImpl())); - rs->save(); return rs; } else { - throw RecordStoreException(); + return nullptr; } } std::vector RecordStore::listRecordStores() { std::vector result; - - for (const auto& entry : recordStoreDir) - result.push_back(entry.filename().string()); + std::string packDir = rootSaveDir + getCurrentPackDir() + "/"; + DIR* dir = opendir(packDir.c_str()); + if (dir != nullptr) { + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) { + result.push_back(entry->d_name); + } + } + closedir(dir); + } log("listRecordStores() = {" + String::join(result, ", ") + "}"); @@ -114,7 +259,13 @@ std::vector RecordStore::listRecordStores() void RecordStore::deleteRecordStore(std::string name) { log("deleteRecordStore(" + name + ")"); - throw std::runtime_error("deleteRecordStore is not implemented"); + std::string packDir = rootSaveDir + getCurrentPackDir() + "/"; + std::string filePath = packDir + name; +#ifdef PSP + sceIoRemove(filePath.c_str()); +#else + std::remove(filePath.c_str()); +#endif } void RecordStore::log(std::string s) @@ -122,19 +273,45 @@ void RecordStore::log(std::string s) std::cout << s << std::endl; } -void RecordStore::setRecordStoreDir([[maybe_unused]] const char* progName) +void RecordStore::setRecordStoreDir(const char* progName) { -#ifdef WIN32 - const char* base = dirname(strdup(progName)); - recordStoreDir = std::filesystem::path(base) / "recordStore"; + char basePath[256] = {0}; + if (progName != nullptr && progName[0] != '\0') { + const char* lastSlash = std::strrchr(progName, '/'); + if (!lastSlash) lastSlash = std::strrchr(progName, '\\'); + if (lastSlash != nullptr) { + size_t len = lastSlash - progName + 1; + if (len < sizeof(basePath)) { + std::strncpy(basePath, progName, len); + basePath[len] = '\0'; + } + } + } + + if (basePath[0] == '\0') { +#ifdef PSP + if (getcwd(basePath, sizeof(basePath) - 1) != nullptr && basePath[0] != '\0') { + size_t len = std::strlen(basePath); + if (basePath[len - 1] != '/' && basePath[len - 1] != '\\') { + std::strcat(basePath, "/"); + } + } else { + std::strncpy(basePath, "ms0:/PSP/GAME/GravityDefied/", sizeof(basePath) - 1); + } #else - const char* homeDir = getenv("HOME"); - if (!homeDir) - homeDir = getpwuid(getuid())->pw_dir; + if (getcwd(basePath, sizeof(basePath) - 1) != nullptr && basePath[0] != '\0') { + size_t len = std::strlen(basePath); + if (basePath[len - 1] != '/' && basePath[len - 1] != '\\') { + std::strcat(basePath, "/"); + } + } else { + std::strncpy(basePath, "./", sizeof(basePath) - 1); + } +#endif + } - if (!homeDir) - throw std::system_error(errno, std::system_category(), "Error getting home directory"); + rootSaveDir = std::string(basePath) + "save/"; - recordStoreDir = std::filesystem::path(homeDir) / ".GravityDefied"; -#endif + makeDir(rootSaveDir); + makeDir(rootSaveDir + getCurrentPackDir()); } diff --git a/src/rms/RecordStore.h b/src/rms/RecordStore.h index 821c532..1b06dc9 100644 --- a/src/rms/RecordStore.h +++ b/src/rms/RecordStore.h @@ -1,9 +1,9 @@ #pragma once #include -#include #include #include +#include #include "RecordEnumerationImpl.h" @@ -12,20 +12,26 @@ class RecordComparator; class RecordStore { private: - RecordStore(std::filesystem::path filePath, RecordEnumerationImpl* records); + RecordStore(std::string filePath, RecordEnumerationImpl* records); void save(); - static RecordEnumerationImpl* load(std::filesystem::path filePath); + static RecordEnumerationImpl* load(const std::string& filePath); static std::unique_ptr createRecordStore(std::string name, bool createIfNecessary); static void log(std::string s); - inline static std::filesystem::path recordStoreDir; + inline static std::string rootSaveDir; inline static std::unordered_map> opened; - std::filesystem::path filePath; + std::string filePath; std::unique_ptr records; + static std::string getCurrentPackDir(); + static void makeDir(const std::string& path); + public: + inline static std::string packPrefix = ""; + static void setPackPrefix(const std::string& prefix); static void setRecordStoreDir(const char* progName); static RecordStore* openRecordStore(std::string name, bool createIfNecessary); + static void saveAllOpened(); void closeRecordStore(); static void deleteRecordStore(std::string name); static std::vector listRecordStores(); diff --git a/src/utils/EmbedFileStream.cpp b/src/utils/EmbedFileStream.cpp index b92aa13..6994d79 100644 --- a/src/utils/EmbedFileStream.cpp +++ b/src/utils/EmbedFileStream.cpp @@ -1,7 +1,7 @@ #include "EmbedFileStream.h" #include -#include +#include CMRC_DECLARE(assets); @@ -26,7 +26,7 @@ void EmbedFileStream::read_impl(char* s, std::streamsize n) void EmbedFileStream::write_impl([[maybe_unused]] char* s, [[maybe_unused]] std::streamsize n) { - throw std::runtime_error("Write to buffer no supported!"); + std::cerr << "Write to EmbedFileStream not supported!" << std::endl; } bool EmbedFileStream::isOpen() diff --git a/src/utils/FileStream.h b/src/utils/FileStream.h index 74fac88..d2b1aa6 100644 --- a/src/utils/FileStream.h +++ b/src/utils/FileStream.h @@ -2,7 +2,7 @@ #include #include -#include +#include class FileStream : std::fstream { public: @@ -11,8 +11,8 @@ class FileStream : std::fstream { { } - FileStream(const std::filesystem::path& file, std::ios::openmode mode) - : std::fstream(file, mode) + FileStream(const std::string& file, std::ios::openmode mode) + : std::fstream(file.c_str(), mode) { } diff --git a/src/utils/Time.cpp b/src/utils/Time.cpp index d8c2c4d..65ef6be 100644 --- a/src/utils/Time.cpp +++ b/src/utils/Time.cpp @@ -1,8 +1,13 @@ -#include "time.h" +#include "Time.h" -#include #include +#if defined(PSP) || defined(__PSP__) +#include +#else +#include +#endif + namespace Time { int64_t currentTimeMillis() { @@ -12,6 +17,10 @@ int64_t currentTimeMillis() void sleep(int64_t ms) { - SDL_Delay(ms); +#if defined(PSP) || defined(__PSP__) + sceKernelDelayThread(ms * 1000); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +#endif +} } -} \ No newline at end of file diff --git a/winres/icon.ico b/winres/icon.ico deleted file mode 100755 index f762e77..0000000 Binary files a/winres/icon.ico and /dev/null differ diff --git a/winres/res.rc b/winres/res.rc deleted file mode 100755 index a1ec328..0000000 --- a/winres/res.rc +++ /dev/null @@ -1 +0,0 @@ -1 ICON "icon.ico"