From 674b9da02b443786c8776b121be3e3e1adcced04 Mon Sep 17 00:00:00 2001 From: Viet Dinh <36768030+Desdaemon@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:11:45 -0500 Subject: [PATCH] Merge with EasyRPG upstream (#49) * Emscripten: Sanitize (Sound)fonts Disable feature when not available. * FluidSynth: Change SF when the MIDI changes Live SF change is too much complexity, not working properly. * FtFont: Improve error handling - Game will not terminate anymore when a glyph fails to render - Fix crash when a font fails to load * Settings: Add Spanish pangram * Maniac Patch: Added separate Width/Height Values for pictures Added: - Separate Width and Height value as per .scale2 parameter in ManiacPatch 211010 - Set Height to Width when using picture effects (as per ManiacPatch) - Set both Width and Height to Magnify when using .scale parameter Bugfix: - Fix ManiacPatch picture rotation not fully working * Maniac Patch: Fix String Vars with ShowPicture Added the following: - Check if we're using a string variable with com.parameters[17] - Set the name parameter to the String Variable Fixed the following: - When using a String Var with ShowPicture, it no longers crash the game with picture ID -1 * Maniac Patch: Added Var and Var Index support for Picture Tint While testing, I found out the following things: - You cannot mix multiple Variable types for each parameters - When using Variables for Picture Tint, parameter 17 is set to 4096 (0x100), - When using Var Indexes for Picture Tint, parameter 17 is set to 8192 (0x200), - Before, parameters for red, green, blue and saturation were always constants. - Surprisingly this feature doesn't work with ShowStringPicture Added: - Variable and VarIndex support for Tinting using ShowPicture and MovePicture using bitfields * New Command: 2002 - Activate Event At [x,y] `@raw 2002, "", UseVarX,x UseVarY,y` Activate an event in map remotely, based on its x and y coordinates. Command made by @MackValentine, I only refactored it to fit the player specifications. * 3DS: properly support 3dslink * NX: fix copy/paste error in custom logging func * Simple implementation for AntiLagSwitch * Implemented experimental "map event caches" to drastically increase performance when doing a lot of operations with game_switches & game_variables * Fix crash when MovePicture is used on an erased picture with a non-default origin. Fix #3189 * Settings: Only show "Open folder" on Windows, Linux, macOS * Settings: Support ".soundfont" WiiU: Disable Pause when focus lost * Emscripten: Do not lowercase the --game argument (regression fix) * Title scene: Allow accessing the load scene for file upload in emscripten with Shift * Emscripten: Fix canvas focus when embedded * Change Sprite Association: Fix incorrect offset for String Picture * Use individual cli options for patches. Useful for the anti lag switch and further patches that require an argument. Update documentation. * Improve MapEventCache data structure. Get rid of the unnecessary pointer. * Ignore invalid skills when leveling up. They are caused by stale database data and are harmless. Fix #2977 * Enable animated RPG2k3e spritesheets when using RM2k engine with 'RPG2k3Commands=1' * Update Gradle * Update Android SDL to 2.30.1 Minimize diff * CMake: Add WiiU preset * CMake Presets: Remove windows-x86 and windows-x64 and replace it with windows. Ninja does not support setting of the machine type. Instead the appropriate VS setup script must be executed beforehand. VS2022 targets are kept as they support this. * CMake Presets: The Prefix path is now appended, not overwritten * Resolve flathub linter warnings * Add former unsupported URL types * Update flatpak manifest from flathub package Build liblcf out-of-tree * 3DS: Fix crash by increasing linear heap memory * 3DS: Make rendering more robust - Should work with any resolution now (e.g. wide mode or double) - Fix bug where the LCD toggles endlessly when keeping button touched - Draw bottom screen UI in proper layers, not first to last * Party: Change the startup sanity checks from Warning to Debug Fix #3008 * metainfo: add 0.8 release * Rename ActivateEventAt to TriggerEventAt as discussed * Add "Wait For Single Movement" Event command by @jetrotal * Redo the X/Y shifting (Sprite clones) on looping maps. The change doubles the amount of sprites on looping maps but this fixes all the rendering issues, especially with big charsets ($). Most of the sprites are culled because they are out of bounds on larger maps, so the performance implications are small. Fix #3149 * Sprite_Character/AirshipShadow: Move functions that are only used by Sprite from Update to Draw. This is similiar how Pictures do it since years. Avoids some unnecessary function calls when Player is frame-skipping as less draw related code is processed. * Image API: Refactor. Return original bit depth of the image. * Add EasyRPG Patch Flag and lock some extensions behind it. Is mostly for features that can break games by accident. Currently behind it is: - Our custom event commands - Large charset - 32 bit image support. Reporting 32 bit images is useful because this prevents that games tested with EasyRPG do not run in RPG_RT by accident. 32 bit is also enabled when: A translation is active or Maniac Patch is used. * Fix Finger (Touch) Input on Windows On some devices the ID is not 0-4 but counts upwards. Introduced a new finger ID field to resolve this. * Remove __WINRT__ (OS is not developed anymore by Microsoft) * Windows 11: Disable Window rounding This looses pixels in the edges of the game. No error check. Will silently fail when unsupported. * Fix: Add inih to workflow (liblcf dep) * New Command 2056 - Spawn Map Event Command that clones Events from any map inside the current map. Co-Authored-By: Primekick * Spawn Map Event - Handling cases where it clones events from its current map * Spawn Map Event - Overwrite paramater Updates on DestroyMapEvent You can also replace an older event with a new one with this optional parameter * Maniac Patch: Add flag "2" to disable the annoying/game-breaking variable range adjustment. * String Variable's ToFile - Relax File extension and path When saving a file, maniacs forces the File in Text/ folder with .txt extension. Through this PR, if '*' is at the end of filename, the file extension is relaxed. It also better handles the creation of nested directories. * Guard more extensions behind the flag * Generalize ToHero functions to be ToCharacter instead. Based on work of @jetrotal * WaitForSingleMovement: Similiar to other waiting commands preserve the state. Is required to prevent that the variable changes across waits and causes issues. * Flatpak: add libinih Fix git revision, in "detached head" state is no prefix * New Commands: 2053 SetInterpreterFlag Usages: 2053, "flag Name", on_off_Value_IsVariable, on_off_Value 2053, "dynrpg", 0, 1 // dynrpg patch * Implemented DirectMenuPatch * Scene/Window refactor: Instead of actor_id most scenes accept now an instance of Game_Actor Scenes that can handle multiple actors expect now an vector of actors. Makes it more flexible. * Scene_Debug; Minor menueing inconsistency: Play Cursor SE when moving between tabs of the range window * Scene_Debug: Implemented new debug view for viewing Maniac strings * Scene_Debug: Implemented new debug view for displaying current interpreter stack states * Window_StringView: Fixed line numbering for Auto-Linebreak * Updated Makefile.am * Use the pixel-based Game_Message::WordWrap function for the new stringview debug window (code assumes monospace font) * Scene_Debug: Minor refactoring + UX improvement * Made Game_Interpreter::GetState() return a const reference and moved the actual logic for retrieving the save data to a new method "GetSaveState()" * Scene_Debug: Fix a small copypaste error * Introduce "Choices" window for Scene_Debug & refactor a bit to remain compatible with ScopedVars branch * Interpreter window fix: window would become unselectable due to wrong index. just always set it to 0 on PushUiInterpreterView; no need to remember the index here. * EasyRpg Flag: Move bmp.reset call to the correct location * Add nlohmann_json to the buildsystem (required for Emscripten) Is enabled for all platforms because this will be used by a JSON event command. (also dropped rang from the license screen, we forgot to delete it earlier) * Emscripten: Migrate from picojson to nlohmann_json * Android App: Chinese (Simplified) translation updated Currently translated at 100.0% (119 of 119 strings) * New translation (Portuguese) added Android App: Portuguese (Brazil) translation updated Currently translated at 70.5% (84 of 119 strings) * Android App: Lithuanian translation updated Currently translated at 53.7% (64 of 119 strings) Android App: Lithuanian translation updated Currently translated at 30.2% (36 of 119 strings) New translation (Lithuanian) added * Android Metadata: Czech translation updated Currently translated at 100.0% (3 of 3 strings) Android App: Czech translation updated Currently translated at 100.0% (119 of 119 strings) Android Metadata: Czech translation updated Currently translated at 100.0% (3 of 3 strings) Android App: Czech translation updated Currently translated at 100.0% (119 of 119 strings) Android App: Czech translation updated Currently translated at 96.6% (115 of 119 strings) Android App: Czech translation updated Currently translated at 95.7% (114 of 119 strings) Android App: Czech translation updated Currently translated at 94.9% (113 of 119 strings) Android App: Czech translation updated Currently translated at 94.1% (112 of 119 strings) Android App: Czech translation updated Currently translated at 93.2% (111 of 119 strings) Android App: Czech translation updated Currently translated at 89.9% (107 of 119 strings) Android App: Czech translation updated Currently translated at 89.0% (106 of 119 strings) Android App: Czech translation updated Currently translated at 88.2% (105 of 119 strings) Android App: Czech translation updated Currently translated at 85.7% (102 of 119 strings) Android App: Czech translation updated Currently translated at 84.0% (100 of 119 strings) Android App: Czech translation updated Currently translated at 83.1% (99 of 119 strings) Android App: Czech translation updated Currently translated at 81.5% (97 of 119 strings) Android App: Czech translation updated Currently translated at 80.6% (96 of 119 strings) Android App: Czech translation updated Currently translated at 78.9% (94 of 119 strings) Android App: Czech translation updated Currently translated at 77.3% (92 of 119 strings) Android App: Czech translation updated Currently translated at 76.4% (91 of 119 strings) Android App: Czech translation updated Currently translated at 75.6% (90 of 119 strings) Android App: Czech translation updated Currently translated at 74.7% (89 of 119 strings) Android App: Czech translation updated Currently translated at 73.1% (87 of 119 strings) Android App: Czech translation updated Currently translated at 72.2% (86 of 119 strings) Android App: Czech translation updated Currently translated at 71.4% (85 of 119 strings) Android App: Czech translation updated Currently translated at 70.5% (84 of 119 strings) Android App: Czech translation updated Currently translated at 68.0% (81 of 119 strings) Android App: Czech translation updated Currently translated at 67.2% (80 of 119 strings) Android App: Czech translation updated Currently translated at 66.3% (79 of 119 strings) Android App: Czech translation updated Currently translated at 65.5% (78 of 119 strings) Android App: Czech translation updated Currently translated at 64.7% (77 of 119 strings) Android App: Czech translation updated Currently translated at 59.6% (71 of 119 strings) Android App: Czech translation updated Currently translated at 51.2% (61 of 119 strings) Android App: Czech translation updated Currently translated at 31.0% (37 of 119 strings) New translation (Czech) added * Android Metadata: Japanese translation updated Currently translated at 33.3% (1 of 3 strings) * Android Metadata: Japanese translation updated Currently translated at 66.6% (2 of 3 strings) Android App: Japanese translation updated Currently translated at 74.7% (89 of 119 strings) Android Metadata: Chinese (Traditional) translation updated Currently translated at 33.3% (1 of 3 strings) Android Metadata: Indonesian translation updated Currently translated at 100.0% (3 of 3 strings) Android App: Russian translation updated Currently translated at 96.6% (115 of 119 strings) Android App: Japanese translation updated Currently translated at 72.2% (86 of 119 strings) Android App: Indonesian translation updated Currently translated at 100.0% (119 of 119 strings) Android App: Greek translation updated Currently translated at 43.6% (52 of 119 strings) * Android App: Portuguese (Brazil) translation updated Currently translated at 84.0% (100 of 119 strings) * Android Metadata: French translation updated Currently translated at 100.0% (3 of 3 strings) Android App: French translation updated Currently translated at 100.0% (119 of 119 strings) Android App: French translation updated Currently translated at 100.0% (119 of 119 strings) * Android App: Portuguese (Brazil) translation updated Currently translated at 99.1% (118 of 119 strings) Android Metadata: Portuguese (Brazil) translation updated Currently translated at 66.6% (2 of 3 strings) Android App: Portuguese (Brazil) translation updated Currently translated at 95.7% (114 of 119 strings) Android App: Portuguese (Brazil) translation updated Currently translated at 94.9% (113 of 119 strings) * Android Metadata: Chinese (Traditional) translation updated Currently translated at 100.0% (3 of 3 strings) Android Metadata: Chinese (Simplified) translation updated Currently translated at 100.0% (3 of 3 strings) Android App: Chinese (Traditional) translation updated Currently translated at 100.0% (119 of 119 strings) * Android App: Portuguese translation updated Currently translated at 54.6% (65 of 119 strings) * Android Metadata: Dutch translation updated Currently translated at 100.0% (3 of 3 strings) Android App: Dutch translation updated Currently translated at 100.0% (119 of 119 strings) New translation (Dutch) added * Android Metadata: Portuguese (Brazil) translation updated Currently translated at 66.6% (2 of 3 strings) Android Metadata: French translation updated Currently translated at 100.0% (3 of 3 strings) Android App: French translation updated Currently translated at 100.0% (119 of 119 strings) * MidiOut: Support volume settings Fix #3083 * FluidLite: Use upstream target name Show FluidLite in settings instead of FluidSynth * Settings: Reset font size of bottom help window and font settings when game is changed * Settings: The font size can now be altered directly with left/right while a font is selected. Add cursor animation option to Window class. * Audio: Add Api to get the shared Midi Out Instance Solves errors and lag in the audio settings when the device is opened multiple times * Config: Namespace all the enums to reduce global scope pollution * Config: Unify FPS show settings. The options are now: - OFF (Do not show) - ON (in titlebar when windowed, inside when fullscreen) - Overlay (show always inside the window) Last option is disabled when fullscreen option is disabled. * Font: Unify the rendering functions for shaped and non-shaped fonts * Font: Resize the mask when the glyph is too large The mask looks brighter than in Maniac Patch but at least it does not look corrupted anymore. * Do transparent blitting of the system graphic frame Fix #2961 * MIDI: Only call seq->play in the Update function. Otherwise can cause a race condition and silence the track. Fix #3186 * Unlock the font settings when returning to the Game Browser * Audio Generic: memset was not clearing all the data (works on bytes, not floats) Use std::fill now which is not this error prone. Solves audio glitches ("pop") when having no BGM (or MidiOut) and a sound effect ends. * Settings: Do not play Decision SE twice * Android: Hardcode the read only strings Reduces confusion in Weblate due to the Read only strings. * Android: Remove FastForward button by default Can be added in the Layout editor. * FileFinder: Add a helper function to recursively find a game To be used by the Android GameBrowser * Android: Fix Saf Write test * Android: Refactor Player Filesystem API The JNIEnv-Pointer is now set from the outside. Makes it possible to call these functions without having SDL initialized. Remove DeleteLocalRef, as these references are garbage collected automatically when the function returns. * Android: Use native FileFinder for the game detection of the GameBrowser. Allows to drop most of the Java scanning code and makes maintanance easier. * Android: Add progress information to the scanning process * Implemented SaveEventExecFrame fields "maniac_event_id" (not reset on map change) & "maniac_event_page_id" + extended interpreter debug view * Window_Interpreter fixes: - Base frame for CommonEvent interpreters was always shown with Id '0' - Encountering a "CallEvent" command while traversing up the stack, always set the flag "is_calling_ev_ce", even if it was a map event * Remove patch-flag checks for debugging info * ZipFs: Reuse the initial stream (LzhFs already does this) * Android: Multiple games are now found in subdirectories This also allows stuff like multiple games in a single zip, zip inside zip etc. * Android: Sync the FF-Multiplier setting with the INI setting * Android: Update translations to mention LZH support and lack of 7z-support * Android: Pass in the title through JNI, cleanup * Android: Try to load the title image in three ways 1. If only one title image: Use this one 2. By parsing the database 3. By taking the first image in the title folder This approach is surprisingly fast and the caching skips the scan after the first time. * Android: Fetch the game name from the INI The title is then reencoded based on the users encoding setting. * Android: Add option to rename the game * Android: Provide soundfont path via command line. Fix typos * Android: Make it possible to configure the Font Also minor Soundfont code fixes * Settings: Hide Font settings when Freetype is missing Hide Soundfont and Font settings on Android in general (thx SAF) * Android: Improve font scene, show preview * Android: Improve wording of the folder buttons * Android: Remove GameScanner suffix from the cpp files Originally they were generated by a Header generator but by now Android Studio handles this which is more flexible. * Add custom error display for platforms Implemented currently for 3DS and SDL2. Switch and Vita are possible, however an Error report might be generated on Switch (bad, could get send to big N) and for Vita one would need to match framebuffer format and such when dealing with GXM for dialog display. This is not worth currently, since we likely migrate away from vita2d library in the future. Go through platform teardown code. While this may not work for every case (e.g. out of memory on Wii), this should be cleaner. * Android: Add RTP status to the Folders activity Fix #1845 * Android: Add "Toggle FPS" and "Reset Game" to the game drawer Related #3103 * Regression fix of the Scene refactor: Battle skills were not listed correctly Somehow the compiler does not complain when you pass an integer to a parameter of type "const Game_Actor&". * Fix warnings * Wii U: Bundle Icons and Splash screens * CMakeLists.txt: Simplify Support bundling a game for Wii U * Wii U: Handle main loop Support shipped games Better CWD handling * SDL: use system format for audio warn if unsupported * Wii U: use full paths for consistency * Wii U: Manually handle ProcUI without the WHB wrapper The ProcUI API is very unstable and querying it crashes when a shutdown was requested. For more control use it directly. * Wii U: Fix MakeDirectory logic Consider "/vol" a valid path * Wii U: Disable settings that do not work * ZipFS: Replace the scan backward for determining if it is a ZIP with a single read Was the only agressive use of "end" in our code. * Add a custom buffered filebuf that uses a file descriptor Implemented for the WiiU because IO through std::filebuf appears to be unbuffered. But can maybe help on other platforms. Can be toggled by setting USE_CUSTOM_FILE_READBUF to the prefered buffer size. * Wii U: Put wildmidi config and config.ini in "data" With the upcoming audiocfg branch it will create "Soundfont" and "Font" folders on startup. Looks ugly when in the game directory as they are shown. * Wii U: Update assets Thanks @jetrotal * Use custom readbuf on our homebrew platforms * Make our life on GitHub easier: - Automatically label pull requests - Hilight some warnings in the output - Keep actions updated * ttyp0 changes (remove Thai and Hebrew), generator fix Fix #2999 * Spawn Map Event - Support renaming an event * Refactored the AntiLag "MapCache" structure & moved it to its own namespace * Refactored several Interpreter related code segments: - Implemented template method "DecodeTargetEvaluationMode". (This logic is shared between ControlVariables, ControlSwitches & ControlStrings but the actual implementation for several special access modes differs between implementations. Support for these modes is evaluated at compile time.) - Moved some common helper functions into a new file "game_interpreter_shared.cpp" (CheckOperator, ValueOrVariable / CommandStringOrVariable & variants) - Refactored "ValueOrVariable" & "ValueOrVariableBitfield" into template methods - Moved some commonly used member function declarations which are needed for certain command argument evaluation methods from Game_Interpreter into a new abstract base class (namespace "Game_Interpreter_Shared" is agnostic to the actual implementation of the interpreter) * Refactor: Moved a few other helper functions from Game_Interpreter to Game_Interpreter_Shared * game_interpreter_shared: Moved explicit instantiations to .cpp & moved definition of "Game_BaseInterpreterContext" to the bottom of header file * Refactor explicit template instantiations: Looks like MSVC does some implicit instantiations here while GCC fails * Rebase: Re-apply warning fixes for moved code segments * Remove scaling restrictions when using special effects From Maniac Patch version 240423: [RPG_RT.exe] Support for different magnification rates for special drawing of pictures (rotation, waveform, angle). + Added check if sprite's width/height is at zero. If so, skip the draw routine (helps avoid a glitchy sprite at the top-left when rotating an infinitely thin picture) - Removed check for effect mode when setting zoom or width/height. One thing to note when comparing Maniac and EasyRPG: the former doesn't seem to properly clip the sprite in the corners, when rotating the sprite. * Maniacs Patch - Call System Functions In Maniacs, CommandOpenSaveMenu is renamed to "Call System Function". I implemented what is closer to our code. Also expanded the code to cover our custom submenus (Cases 8 to 12). Update game_interpreter_map.cpp * WaitForSingleMovement: Guard with EasyRpg Extension Check * Proper casing for LoadMapFile and make RequestMap request always important * SpawnMapEvent: Add Async handling, Cleanup * SpawnMapEvent: Suspend the interpreter using Async to avoid crashes when the event vector is altered. Renamed to CloneMapEvent * DestroyMapEvent: Implemented * DestoryMapEvent: Set the main interpreter event ID to 0 when the event running is destroyed. Prevents triggering the sanity check about an invalid event on the main interpreter. Fix operator= of Game_CharacterDataStorage. * Call System Functions: Move EasyRPG Extensions to a higher ID to prevent conflicts Minor enhancements * Local Settings - First Commit If you have a copy of config.ini saved at the same folder as the player app, it will load the game settings from it, instead of the global folder. ------------------------------------- Update game_config.cpp * Fix compiling OpenDingux and SDL 1.2 Otherwise, it will complain about undefined references to GetSdlAxis. * Refactor: Always check isMessageActive() at the beginning in game_Interpreter _map. * Refactor: check out_of_bounds before calc map_draw_xy * Refactor: remove GetEventsXY() since the code is broken and never been used now. * Android: Use different save paths when multiple games are in one archive * Android: Bump to SDK 34 * Android: Update deps and gradle to 8.10 * When loading a save remove active BattleAnimations They can reference stale event data and crash. Fix #3255 * Equipment: Fix another missed change from actor.GetId to actor. Detected by ASAN, somehow does not crash without. Fix #3235 * Emscripten: Expose FS Module again. Was lost when moving to MODULARIZE. Fix #3243 * Fix compilation errors when resampling is disabled * SpawnMapEvent: Update BattleAnimation reference Prevents crashes... Had to add multiple player includes as a header was removed. * SpawnMapEvent: Directly insert sorted. Make FindEventById the same as GetEvent but for lcf::rpg::Event. * Android: Replace assets with SVG. Works since API 21 which we require. Also add asset for "Visit Website" and "Fonts". * Android: Bump SDL2 to 2.30.6 * SDL2: Swap pixel format with a faster one for pixman on Big Endian Fix WiiU texture copy by manually doing a format conversion. * Custom Filebuf: Also use it for writing Has the same performance issues * Wii U: Move teardown code to atexit * CMake: Revert the Wii SDL part because it does not work (linker errors) * WiiU: Address review comments - ProdUi renamed - Fixed save path for bundled games - Moved ProcessProcUi call to the Ui * WiiU: Map the buttons according to an Xbox-Controller and then swap them with the swap abxy setting * Add a bool to ProcessEvents to signal program exit. Calling Player::Exit in the Ui can crash because the Ui is deleted by it. Also the normal teardown codepath is too slow on WiiU and crashes the console when the home button is used. * Fix flickering in Yume2kki on map 3D Underworld (ID 1884). The map uses a MoveRoute with a jump and SetVehicleLocation for party movement in a tight loop which causes heavy flickering in our Player. This fix does not appear to be completely correct as RPG_RT does not reset the jump flag here but the "damage" is reduced because SetVehicleLocation -1 cannot happen without patching the game. Fix #3254 Co-Authored-By: Viet Dinh <54ckb0y789@gmail.com> * Effects: Use the filename for the cache, not the bitmap pointer The pointer can be reused by a new bitmap and cause wrong animations. For individual tiles the filename is lost. "filename" in bitmap was renamed to "id" and gets now filename+tileid in that case. Fix #3256 Co-Authored-By: Viet Dinh <54ckb0y789@gmail.com> * Fluidsynth: Exhaust the internal synth buffer to prevent playing of old samples after long pauses Fix #3135 (this time for real) * Android: Forgot to commit SDLActivity.java for the SDL2 2.30.6 bump * Android: Make title encoding detection more robust and filter out UTF16 * Improve debug image size warnings (#3260) debug size warning tweaks * Android: Remove deprecated package= option from Manifest * CMake: Replace the verbose warning when nlohmann is not found with a one-liner This verbose warning appears when a library has no find module. * Fix typo ObservervedVarOps -> ObservedVarOps * Fix 3ds build (wrong var type in WaitForSingleMovement) * Cast the string constants to char* when tremor is used. Fixes warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings] Tremor never writes to the strings so this is safe to do. * Switch: Fix build (Add missing return value) * The Sacred Tears: TRUE uses a modified map tree Detect this and apply a shift of 1 to all bytes in the map tree to make it work. * Add transparency to the SpriteEffect key, matching the behaviour of the normal sprite cache. Fixes a black screen when entering the bedroom in Yume 2kki's Nocturnal Grove (Regression) Fix #3274 * ManiacGetSaveInfo: Set transparency and apply correct defaults Fix #3272 * ManiacControlGlobalSave: Fix command not working the first time when using Copy From/To without calling Open. Bug was that the function returned when the lgs file was missing. Emscripten: Update the filesystem state Fix #3270 * Load: Fix Async loading of the map file when command ManiacLoad is used Fix #3271 * ZIP: Improve encoding detection by only feeding the filename to ICU Before the string contained full paths, breaking the detection logic because most paths are ASCII * DestroyMapEvent: Remove the event from the cache * Clone/DestroyMapEvent: Silence warning when deletion fails while cloning Add documentation * Reset UI on WebGL context loss On Emscripten, the WebGL context may be loss for any reason, for example when restoring the application from the background, the GPU force- refreshed the application to reclaim memory, etc. In these cases, canvas data is lost and this commit accommodates that situation by reinitiali- zing the UI when the browser has restored the context. * Fix compile errors from merge --------- Co-authored-by: Ghabry Co-authored-by: Tool Man Co-authored-by: MackValentine Co-authored-by: Carsten Teibes Co-authored-by: florianessl Co-authored-by: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Co-authored-by: Primekick Co-authored-by: XWX1 Co-authored-by: Daniel Paim de Mattos Oliveira Co-authored-by: AidasKar15 Co-authored-by: Sadver Co-authored-by: otya Co-authored-by: Gabriel Co-authored-by: crazep Co-authored-by: Carbonara Co-authored-by: Jesse5800 Co-authored-by: pissolato Co-authored-by: lemtom Co-authored-by: Dzmitry Kushnarou Co-authored-by: fdelapena Co-authored-by: gameblabla Co-authored-by: xiaodao Co-authored-by: lumiscosity --- .gitattributes | 5 +- .github/dependabot.yml | 12 + .github/gcc_comment_matcher.json | 17 + .github/labeler.yml | 98 + .github/workflows/pr_labels.yml | 27 + .github/workflows/stable-compilation.yml | 10 +- CMakeLists.txt | 160 +- CMakePresets.json | 248 +- Makefile.am | 11 +- README.md | 4 +- builds/android/app/build.gradle | 12 +- .../app/src/gamebrowser/CMakeLists.txt | 9 +- .../org_easyrpg_player_game_browser.cpp | 521 + .../org_easyrpg_player_game_browser.h | 48 + ...asyrpg_player_game_browser_GameScanner.cpp | 216 - ..._easyrpg_player_game_browser_GameScanner.h | 21 - .../android/app/src/main/AndroidManifest.xml | 15 +- .../main/java/org/easyrpg/player/Helper.java | 55 +- .../java/org/easyrpg/player/InitActivity.java | 3 +- .../player/button_mapping/InputLayout.java | 2 - .../org/easyrpg/player/game_browser/Game.java | 273 +- .../game_browser/GameBrowserActivity.java | 40 +- .../game_browser/GameBrowserHelper.java | 117 +- .../player/game_browser/GameScanner.java | 401 +- .../player/player/EasyRpgPlayerActivity.java | 39 +- .../org/easyrpg/player/settings/IniFile.java | 4 +- .../settings/SettingsAudioActivity.java | 12 +- .../easyrpg/player/settings/SettingsEnum.java | 11 +- .../player/settings/SettingsFontActivity.java | 308 + .../settings/SettingsGamesFolderActivity.java | 55 + .../settings/SettingsInputActivity.java | 6 +- .../player/settings/SettingsMainActivity.java | 4 + .../player/settings/SettingsManager.java | 152 +- .../java/org/libsdl/app/HIDDeviceManager.java | 16 +- .../app/src/main/java/org/libsdl/app/SDL.java | 14 +- .../main/java/org/libsdl/app/SDLActivity.java | 16 +- .../org/libsdl/app/SDLControllerManager.java | 16 +- .../main/java/org/libsdl/app/SDLSurface.java | 4 +- .../ic_action_action_autorenew.png | Bin 970 -> 0 bytes .../ic_action_action_settings.png | Bin 803 -> 0 bytes .../drawable-hdpi/ic_action_content_clear.png | Bin 568 -> 0 bytes .../ic_action_favorite_off_black.png | Bin 1209 -> 0 bytes .../ic_action_favorite_off_white.png | Bin 1353 -> 0 bytes .../ic_action_favorite_on_black.png | Bin 852 -> 0 bytes .../ic_action_favorite_on_white.png | Bin 941 -> 0 bytes .../ic_action_settings_black.png | Bin 1074 -> 0 bytes .../ic_action_settings_white.png | Bin 813 -> 0 bytes .../res/drawable-hdpi/ic_clear_black_24dp.png | Bin 207 -> 0 bytes .../ic_folder_open_black_36dp.png | Bin 164 -> 0 bytes .../drawable-hdpi/ic_gamepad_black_36dp.png | Bin 218 -> 0 bytes .../drawable-hdpi/ic_refresh_white_24dp.png | Bin 387 -> 0 bytes .../drawable-hdpi/ic_settings_white_24dp.png | Bin 460 -> 0 bytes .../ic_video_label_black_36dp.png | Bin 202 -> 0 bytes .../drawable-hdpi/ic_volume_up_black_36dp.png | Bin 500 -> 0 bytes .../ic_action_action_autorenew.png | Bin 592 -> 0 bytes .../ic_action_action_settings.png | Bin 484 -> 0 bytes .../drawable-mdpi/ic_action_content_clear.png | Bin 308 -> 0 bytes .../ic_action_favorite_off_black.png | Bin 1032 -> 0 bytes .../ic_action_favorite_off_white.png | Bin 1135 -> 0 bytes .../ic_action_favorite_on_black.png | Bin 757 -> 0 bytes .../ic_action_favorite_on_white.png | Bin 779 -> 0 bytes .../ic_action_settings_black.png | Bin 696 -> 0 bytes .../ic_action_settings_white.png | Bin 700 -> 0 bytes .../res/drawable-mdpi/ic_clear_black_24dp.png | Bin 164 -> 0 bytes .../ic_folder_open_black_36dp.png | Bin 138 -> 0 bytes .../drawable-mdpi/ic_gamepad_black_36dp.png | Bin 167 -> 0 bytes .../drawable-mdpi/ic_refresh_white_24dp.png | Bin 254 -> 0 bytes .../drawable-mdpi/ic_settings_white_24dp.png | Bin 326 -> 0 bytes .../ic_video_label_black_36dp.png | Bin 151 -> 0 bytes .../drawable-mdpi/ic_volume_up_black_36dp.png | Bin 364 -> 0 bytes .../ic_action_action_autorenew.png | Bin 1297 -> 0 bytes .../ic_action_action_settings.png | Bin 1109 -> 0 bytes .../ic_action_content_clear.png | Bin 618 -> 0 bytes .../ic_action_favorite_off_black.png | Bin 2165 -> 0 bytes .../ic_action_favorite_off_white.png | Bin 2362 -> 0 bytes .../ic_action_favorite_on_black.png | Bin 1456 -> 0 bytes .../ic_action_favorite_on_white.png | Bin 1544 -> 0 bytes .../ic_action_settings_black.png | Bin 1484 -> 0 bytes .../ic_action_settings_white.png | Bin 1509 -> 0 bytes .../drawable-xhdpi/ic_clear_black_24dp.png | Bin 235 -> 0 bytes .../ic_folder_open_black_36dp.png | Bin 242 -> 0 bytes .../drawable-xhdpi/ic_gamepad_black_36dp.png | Bin 224 -> 0 bytes .../drawable-xhdpi/ic_refresh_white_24dp.png | Bin 509 -> 0 bytes .../drawable-xhdpi/ic_settings_white_24dp.png | Bin 562 -> 0 bytes .../ic_video_label_black_36dp.png | Bin 194 -> 0 bytes .../ic_volume_up_black_36dp.png | Bin 626 -> 0 bytes .../ic_action_action_autorenew.png | Bin 1909 -> 0 bytes .../ic_action_action_settings.png | Bin 1992 -> 0 bytes .../ic_action_content_clear.png | Bin 1300 -> 0 bytes .../ic_action_favorite_off_black.png | Bin 2626 -> 0 bytes .../ic_action_favorite_off_white.png | Bin 2918 -> 0 bytes .../ic_action_favorite_on_black.png | Bin 1823 -> 0 bytes .../ic_action_favorite_on_white.png | Bin 1955 -> 0 bytes .../ic_action_settings_black.png | Bin 2292 -> 0 bytes .../ic_action_settings_white.png | Bin 2022 -> 0 bytes .../drawable-xxhdpi/ic_clear_black_24dp.png | Bin 309 -> 0 bytes .../ic_folder_open_black_36dp.png | Bin 283 -> 0 bytes .../drawable-xxhdpi/ic_gamepad_black_36dp.png | Bin 315 -> 0 bytes .../drawable-xxhdpi/ic_refresh_white_24dp.png | Bin 734 -> 0 bytes .../ic_settings_white_24dp.png | Bin 843 -> 0 bytes .../ic_video_label_black_36dp.png | Bin 308 -> 0 bytes .../ic_volume_up_black_36dp.png | Bin 955 -> 0 bytes .../ic_action_action_autorenew.png | Bin 2827 -> 0 bytes .../ic_action_action_settings.png | Bin 2952 -> 0 bytes .../ic_action_content_clear.png | Bin 1416 -> 0 bytes .../ic_action_favorite_off_black.png | Bin 5636 -> 0 bytes .../ic_action_favorite_off_white.png | Bin 4949 -> 0 bytes .../ic_action_favorite_on_black.png | Bin 3840 -> 0 bytes .../ic_action_favorite_on_white.png | Bin 3653 -> 0 bytes .../ic_action_settings_black.png | Bin 3812 -> 0 bytes .../ic_action_settings_white.png | Bin 4454 -> 0 bytes .../drawable-xxxhdpi/ic_clear_black_24dp.png | Bin 377 -> 0 bytes .../ic_folder_open_black_36dp.png | Bin 470 -> 0 bytes .../ic_gamepad_black_36dp.png | Bin 326 -> 0 bytes .../ic_refresh_white_24dp.png | Bin 967 -> 0 bytes .../ic_settings_white_24dp.png | Bin 1074 -> 0 bytes .../ic_video_label_black_36dp.png | Bin 387 -> 0 bytes .../ic_volume_up_black_36dp.png | Bin 1236 -> 0 bytes .../main/res/drawable/ic_action_autorenew.xml | 9 + .../drawable/ic_action_favorite_off_black.xml | 9 + .../drawable/ic_action_favorite_off_white.xml | 9 + .../drawable/ic_action_favorite_on_black.xml | 9 + .../drawable/ic_action_favorite_on_white.xml | 9 + .../main/res/drawable/ic_article_black.xml | 9 + .../res/drawable/ic_folder_open_black.xml | 9 + .../main/res/drawable/ic_gamepad_black.xml | 9 + .../src/main/res/drawable/ic_home_dark.xml | 9 + .../app/src/main/res/drawable/ic_info.xml | 9 + .../main/res/drawable/ic_refresh_white.xml | 9 + .../main/res/drawable/ic_settings_black.xml | 9 + .../main/res/drawable/ic_settings_white.xml | 9 + .../res/drawable/ic_video_label_black.xml | 9 + .../main/res/drawable/ic_volume_up_black.xml | 9 + .../res/layout/activity_settings_audio.xml | 5 - .../activity_settings_easyrpg_folders.xml | 53 +- .../res/layout/activity_settings_font.xml | 163 + .../res/layout/activity_settings_inputs.xml | 6 +- .../res/layout/activity_settings_main.xml | 16 +- .../layout/browser_game_card_landscape.xml | 2 +- .../res/layout/browser_game_card_portrait.xml | 2 +- .../res/layout/game_browser_item_list.xml | 2 +- .../app/src/main/res/layout/loading_panel.xml | 23 +- .../settings_input_layout_item_list.xml | 2 +- .../app/src/main/res/menu/game_browser.xml | 4 +- .../src/main/res/menu/game_browser_drawer.xml | 6 +- .../android/app/src/main/res/menu/player.xml | 4 + .../app/src/main/res/values-ca/strings.xml | 15 +- .../app/src/main/res/values-cs/strings.xml | 140 + .../app/src/main/res/values-de/strings.xml | 13 +- .../app/src/main/res/values-el/strings.xml | 43 + .../app/src/main/res/values-es/strings.xml | 15 +- .../app/src/main/res/values-fr/strings.xml | 31 +- .../app/src/main/res/values-in/strings.xml | 87 +- .../app/src/main/res/values-it/strings.xml | 12 +- .../app/src/main/res/values-ja/strings.xml | 6 +- .../app/src/main/res/values-lt/strings.xml | 79 + .../app/src/main/res/values-nl/strings.xml | 140 + .../app/src/main/res/values-pl/strings.xml | 9 +- .../src/main/res/values-pt-rBR/strings.xml | 78 +- .../app/src/main/res/values-pt/strings.xml | 80 + .../app/src/main/res/values-ru/strings.xml | 41 +- .../src/main/res/values-zh-rTW/strings.xml | 64 +- .../app/src/main/res/values-zh/strings.xml | 13 +- .../app/src/main/res/values/strings.xml | 36 +- builds/android/build.gradle | 10 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- builds/android/gradlew | 41 +- builds/android/gradlew.bat | 35 +- .../metadata/cs-CZ/full_description.txt | 7 + .../metadata/cs-CZ/short_description.txt | 1 + builds/android/metadata/cs-CZ/title.txt | 1 + .../metadata/fr-FR/full_description.txt | 4 +- .../android/metadata/id/full_description.txt | 10 +- .../android/metadata/id/short_description.txt | 2 +- builds/android/metadata/id/title.txt | 2 +- .../metadata/ja-JP/full_description.txt | 8 +- .../metadata/ja-JP/short_description.txt | 2 +- builds/android/metadata/ja-JP/title.txt | 1 + .../metadata/nl-NL/full_description.txt | 7 + .../metadata/nl-NL/short_description.txt | 1 + builds/android/metadata/nl-NL/title.txt | 1 + .../metadata/pt-BR/full_description.txt | 2 +- .../metadata/pt-BR/short_description.txt | 2 +- builds/android/metadata/pt-BR/title.txt | 2 +- .../metadata/zh-CN/full_description.txt | 4 +- .../metadata/zh-CN/short_description.txt | 2 +- builds/android/metadata/zh-CN/title.txt | 1 + .../metadata/zh-TW/full_description.txt | 4 +- .../metadata/zh-TW/short_description.txt | 2 +- builds/android/metadata/zh-TW/title.txt | 1 + builds/cmake/CMakePresets.json.template | 40 +- builds/cmake/Modules/FindFluidLite.cmake | 8 +- builds/cmake/Modules/PlayerFindPackage.cmake | 26 +- builds/cmake/gen-cmake-presets.py | 0 builds/flatpak/org.easyrpg.player.yml | 99 +- configure.ac | 2 + docs/BUILDING.md | 11 +- resources/emscripten/emscripten-post.js | 74 +- resources/emscripten/emscripten-pre.js | 7 +- resources/emscripten/emscripten-shell.html | 5 +- resources/shinonome/generate_cxx_font.rb | 10 +- resources/ttyp0/font.bit | 11851 ++++++---------- resources/ttyp0/font_hebrew.bit | 1751 +++ resources/ttyp0/font_thai.bit | 1694 +++ resources/unix/bash-completion/easyrpg-player | 2 +- resources/unix/easyrpg-player.6.adoc | 104 +- resources/unix/easyrpg-player.metainfo.xml | 21 +- resources/wiiu/icon.png | Bin 0 -> 4087 bytes resources/wiiu/splash-drc.png | Bin 0 -> 147110 bytes resources/wiiu/splash-tv.png | Bin 0 -> 25796 bytes src/async_handler.cpp | 53 +- src/async_op.h | 95 +- src/audio.cpp | 54 + src/audio.h | 22 + src/audio_decoder_midi.cpp | 32 +- src/audio_decoder_midi.h | 1 + src/audio_generic.cpp | 33 +- src/audio_generic.h | 2 + src/audio_generic_midiout.cpp | 22 +- src/audio_generic_midiout.h | 4 +- src/audio_midi.cpp | 60 +- src/audio_midi.h | 23 + src/baseui.cpp | 9 +- src/baseui.h | 66 +- src/battle_animation.cpp | 12 +- src/battle_animation.h | 3 +- src/bitmap.cpp | 75 +- src/bitmap.h | 41 +- src/cache.cpp | 54 +- src/cmdline_parser.h | 20 +- src/config_param.h | 66 +- src/decoder_fluidsynth.cpp | 127 +- src/decoder_fluidsynth.h | 13 +- src/decoder_oggvorbis.cpp | 14 +- src/decoder_wildmidi.cpp | 16 +- src/decoder_wildmidi.h | 2 +- src/dynrpg_textplugin.cpp | 4 +- src/exe_reader.cpp | 4 +- src/external/picojson.h | 1204 -- src/filefinder.cpp | 39 +- src/filefinder.h | 18 + src/filefinder_rtp.cpp | 7 +- src/filesystem_hook.cpp | 147 + src/filesystem_hook.h | 60 + src/filesystem_native.cpp | 35 +- src/filesystem_stream.cpp | 124 + src/filesystem_stream.h | 31 + src/filesystem_zip.cpp | 62 +- src/filesystem_zip.h | 1 + src/font.cpp | 247 +- src/font.h | 8 + src/game_actor.cpp | 9 +- src/game_character.cpp | 72 +- src/game_character.h | 71 +- src/game_commonevent.cpp | 2 +- src/game_commonevent.h | 2 + src/game_config.cpp | 155 +- src/game_config.h | 97 +- src/game_config_game.cpp | 132 +- src/game_config_game.h | 8 +- src/game_event.cpp | 42 +- src/game_event.h | 9 +- src/game_interpreter.cpp | 517 +- src/game_interpreter.h | 49 +- src/game_interpreter_control_variables.cpp | 3 +- src/game_interpreter_control_variables.h | 4 +- src/game_interpreter_map.cpp | 264 +- src/game_interpreter_map.h | 5 + src/game_interpreter_shared.cpp | 255 + src/game_interpreter_shared.h | 172 + src/game_map.cpp | 266 +- src/game_map.h | 157 +- src/game_party.cpp | 4 +- src/game_pictures.cpp | 19 +- src/game_pictures.h | 3 +- src/game_player.cpp | 12 +- src/game_player.h | 4 +- src/game_playerother.h | 6 +- src/game_screen.cpp | 15 + src/game_screen.h | 7 +- src/game_strings.cpp | 22 +- src/game_strings.h | 34 +- src/game_vehicle.cpp | 4 +- src/game_vehicle.h | 2 +- src/game_windows.cpp | 2 + src/generated/bitmapfont_ttyp0.h | 177 +- src/image_bmp.cpp | 21 +- src/image_bmp.h | 5 +- src/image_png.cpp | 42 +- src/image_png.h | 7 +- src/image_xyz.cpp | 22 +- src/image_xyz.h | 5 +- src/input_source.cpp | 9 + src/input_source.h | 9 +- src/main_data.cpp | 4 + src/main_data.h | 1 + src/maniac_patch.cpp | 6 +- src/maniac_patch.h | 4 +- src/multiplayer/messages.h | 5 +- src/multiplayer/packet.h | 30 +- src/options.h | 12 - src/output.cpp | 33 +- src/platform.cpp | 7 + src/platform/3ds/main.cpp | 104 +- src/platform/3ds/ui.cpp | 171 +- src/platform/3ds/ui.h | 4 +- src/platform/android/android.cpp | 5 + src/platform/android/android.h | 6 +- src/platform/android/filesystem_apk.cpp | 10 +- src/platform/android/filesystem_saf.cpp | 54 +- ...pg_player_player_EasyRpgPlayerActivity.cpp | 24 +- ...yrpg_player_player_EasyRpgPlayerActivity.h | 52 +- src/platform/emscripten/interface.cpp | 80 + src/platform/emscripten/interface.h | 6 +- src/platform/emscripten/main.cpp | 3 +- src/platform/libretro/midiout_device.cpp | 6 +- src/platform/libretro/midiout_device.h | 2 +- src/platform/libretro/ui.cpp | 6 +- src/platform/libretro/ui.h | 2 +- src/platform/linux/midiout_device_alsa.cpp | 17 +- src/platform/linux/midiout_device_alsa.h | 2 +- .../macos/midiout_device_coreaudio.cpp | 6 +- src/platform/macos/midiout_device_coreaudio.h | 2 +- .../opendingux/opendingux_input_buttons.cpp | 8 + src/platform/psvita/main.cpp | 10 +- src/platform/psvita/ui.cpp | 12 +- src/platform/psvita/ui.h | 6 +- src/platform/sdl/main.cpp | 27 +- src/platform/sdl/sdl2_ui.cpp | 197 +- src/platform/sdl/sdl2_ui.h | 6 +- src/platform/sdl/sdl_audio.cpp | 3 +- src/platform/sdl/sdl_ui.cpp | 12 +- src/platform/sdl/sdl_ui.h | 2 +- src/platform/switch/main.cpp | 42 +- src/platform/switch/ui.cpp | 6 +- src/platform/switch/ui.h | 2 +- src/platform/wii/main.cpp | 3 +- src/platform/wiiu/input_buttons.cpp | 25 +- src/platform/wiiu/main.cpp | 163 + src/platform/wiiu/main.h | 18 + src/platform/windows/midiout_device_win32.cpp | 4 +- src/platform/windows/midiout_device_win32.h | 2 +- src/player.cpp | 142 +- src/player.h | 14 +- src/rtp.cpp | 38 +- src/rtp.h | 15 +- src/scene_battle_rpg2k.cpp | 2 +- src/scene_battle_rpg2k3.cpp | 2 +- src/scene_debug.cpp | 473 +- src/scene_debug.h | 58 +- src/scene_equip.cpp | 35 +- src/scene_equip.h | 10 +- src/scene_item.cpp | 1 + src/scene_logo.cpp | 4 +- src/scene_map.cpp | 8 + src/scene_menu.cpp | 6 +- src/scene_order.cpp | 1 + src/scene_settings.cpp | 86 +- src/scene_settings.h | 5 + src/scene_skill.cpp | 22 +- src/scene_skill.h | 7 +- src/scene_status.cpp | 27 +- src/scene_status.h | 7 +- src/scene_title.cpp | 15 +- src/sprite_airshipshadow.cpp | 28 +- src/sprite_airshipshadow.h | 13 +- src/sprite_character.cpp | 66 +- src/sprite_character.h | 17 +- src/sprite_picture.cpp | 8 + src/spriteset_map.cpp | 35 +- src/system.h | 8 + src/text.cpp | 1 - src/tilemap_layer.cpp | 6 +- src/utils.cpp | 6 + src/utils.h | 1 - src/window.cpp | 24 +- src/window.h | 42 +- src/window_actorinfo.cpp | 8 +- src/window_actorinfo.h | 4 +- src/window_actorstatus.cpp | 29 +- src/window_actorstatus.h | 4 +- src/window_battlecommand.cpp | 13 +- src/window_battlecommand.h | 4 +- src/window_equip.cpp | 9 +- src/window_equip.h | 7 +- src/window_equipitem.cpp | 10 +- src/window_equipitem.h | 8 +- src/window_equipstatus.cpp | 17 +- src/window_equipstatus.h | 9 +- src/window_face.cpp | 8 +- src/window_face.h | 4 +- src/window_help.cpp | 53 +- src/window_help.h | 30 + src/window_interpreter.cpp | 211 + src/window_interpreter.h | 66 + src/window_message.cpp | 20 +- src/window_message.h | 2 + src/window_paramstatus.cpp | 14 +- src/window_paramstatus.h | 6 +- src/window_settings.cpp | 279 +- src/window_settings.h | 56 +- src/window_skill.cpp | 11 +- src/window_skill.h | 6 +- src/window_skillstatus.cpp | 22 +- src/window_skillstatus.h | 6 +- src/window_stringview.cpp | 147 + src/window_stringview.h | 50 + src/window_varlist.cpp | 26 +- src/window_varlist.h | 6 +- tests/cmdline_parser.cpp | 17 + tests/game_character.cpp | 14 - 412 files changed, 17225 insertions(+), 12197 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/gcc_comment_matcher.json create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/pr_labels.yml create mode 100644 builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp create mode 100644 builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h delete mode 100644 builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp delete mode 100644 builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h create mode 100644 builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_black.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_white.png delete mode 100755 builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_gamepad_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_video_label_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_action_autorenew.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_action_settings.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_content_clear.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_black.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_white.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_black.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png delete mode 100755 builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_black.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_white.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_video_label_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_white.png delete mode 100755 builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_folder_open_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_settings.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png delete mode 100755 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxhdpi/ic_volume_up_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_settings.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_content_clear.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png delete mode 100755 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_gamepad_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png delete mode 100644 builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png create mode 100644 builds/android/app/src/main/res/drawable/ic_action_autorenew.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_article_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_folder_open_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_gamepad_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_home_dark.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_info.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_refresh_white.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_settings_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_settings_white.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_video_label_black.xml create mode 100644 builds/android/app/src/main/res/drawable/ic_volume_up_black.xml create mode 100644 builds/android/app/src/main/res/layout/activity_settings_font.xml create mode 100644 builds/android/app/src/main/res/values-cs/strings.xml create mode 100644 builds/android/app/src/main/res/values-lt/strings.xml create mode 100644 builds/android/app/src/main/res/values-nl/strings.xml create mode 100644 builds/android/app/src/main/res/values-pt/strings.xml create mode 100644 builds/android/metadata/cs-CZ/full_description.txt create mode 100644 builds/android/metadata/cs-CZ/short_description.txt create mode 100644 builds/android/metadata/cs-CZ/title.txt create mode 100644 builds/android/metadata/ja-JP/title.txt create mode 100644 builds/android/metadata/nl-NL/full_description.txt create mode 100644 builds/android/metadata/nl-NL/short_description.txt create mode 100644 builds/android/metadata/nl-NL/title.txt create mode 100644 builds/android/metadata/zh-CN/title.txt create mode 100644 builds/android/metadata/zh-TW/title.txt mode change 100644 => 100755 builds/cmake/gen-cmake-presets.py create mode 100644 resources/ttyp0/font_hebrew.bit create mode 100644 resources/ttyp0/font_thai.bit create mode 100644 resources/wiiu/icon.png create mode 100644 resources/wiiu/splash-drc.png create mode 100644 resources/wiiu/splash-tv.png delete mode 100644 src/external/picojson.h create mode 100644 src/filesystem_hook.cpp create mode 100644 src/filesystem_hook.h create mode 100644 src/game_interpreter_shared.cpp create mode 100644 src/game_interpreter_shared.h create mode 100644 src/platform/android/android.cpp create mode 100644 src/platform/wiiu/main.cpp create mode 100644 src/platform/wiiu/main.h create mode 100644 src/window_interpreter.cpp create mode 100644 src/window_interpreter.h create mode 100644 src/window_stringview.cpp create mode 100644 src/window_stringview.h diff --git a/.gitattributes b/.gitattributes index 38bc2e14d..80cf0c50e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,10 +15,7 @@ src/external/* -diff linguist-vendored src/midiprogram.h -diff linguist-vendored src/midisequencer.* -diff linguist-vendored src/midisynth.* -diff linguist-vendored -src/picojson.h -diff linguist-vendored CMakePresets.json -diff linguist-generated -builds/android/app/src/main/java/org/libsdl/app/SDL*.java -diff linguist-vendored +builds/android/app/src/main/java/org/libsdl/app/*.java -diff linguist-vendored builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java diff -builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java diff -builds/android/app/src/main/java/org/libsdl/app/HID*.java -diff linguist-vendored src/generated/* -diff linguist-generated diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..202e28ba0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + labels: + - "Building" + #reviewers: + # - carstene1ns + commit-message: + prefix: "CI" diff --git a/.github/gcc_comment_matcher.json b/.github/gcc_comment_matcher.json new file mode 100644 index 000000000..81995b459 --- /dev/null +++ b/.github/gcc_comment_matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "gcc-problem-matcher", + "pattern": [ + { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] + } + ] +} diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..972b28d35 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,98 @@ +# +# See https://github.com/actions/labeler +# and workflows/pr_labels.yml for reference +# + +Building: +- changed-files: + - any-glob-to-any-file: + - .github/** + - CMakeLists.txt + - builds/** + - '!builds/android/app/**' + - Makefile.am + - configure.ac + +Documentation: +- changed-files: + - any-glob-to-any-file: + - docs/** + - '**/*.md' + - '**/*.adoc' + - src/docmain.h + - '**/Doxyfile*' + - resources/unix/*.metainfo.xml + +Tests: +- changed-files: + - any-glob-to-any-file: [ tests/** ] + +Window/Scenes: +- changed-files: + - any-glob-to-any-file: [ src/**/window_*, src/**/scene_* ] + +# misc + +Audio: +- changed-files: + - any-glob-to-any-file: [ src/**/*audio* ] + +FileFinder: +- changed-files: + - any-glob-to-any-file: [ src/**/filefinder*, src/**/filesystem* ] + +Fonts: +- changed-files: + - any-glob-to-any-file: + - resources/exfont.png + - resources/ttyp0/** + - resources/shinonome/** + - resources/wenquanyi/** + - src/**/*font* + - src/generated/bitmapfont_* + +MIDI: +- changed-files: + - any-glob-to-any-file: [ src/**/*midi* ] + +# platforms + +3DS: +- changed-files: + - any-glob-to-any-file: [ src/platform/3ds/**, resources/3ds/** ] + +Android: +- changed-files: + - any-glob-to-any-file: [ src/platform/android/**, builds/android/app/** ] + +Emscripten: +- changed-files: + - any-glob-to-any-file: [ src/platform/emscripten/**, resources/emscripten/** ] + +libretro: +- changed-files: + - any-glob-to-any-file: [ src/platform/libretro/** ] + +macOS: +- changed-files: + - any-glob-to-any-file: [ src/platform/macos/**, resources/macos/** ] + +PSVita: +- changed-files: + - any-glob-to-any-file: [ src/platform/psvita/**, resources/psvita/** ] + +Switch: +- changed-files: + - any-glob-to-any-file: [ src/platform/switch/**, resources/switch/** ] + +Wii: +- changed-files: + - any-glob-to-any-file: [ src/platform/wii/**, resources/wii/** ] + +WiiU: +- changed-files: + - any-glob-to-any-file: [ src/platform/wiiu/**, resources/wiiu/** ] + +Win32: +- changed-files: + - any-glob-to-any-file: [ src/platform/windows/**, resources/windows/** ] diff --git a/.github/workflows/pr_labels.yml b/.github/workflows/pr_labels.yml new file mode 100644 index 000000000..e4d84c449 --- /dev/null +++ b/.github/workflows/pr_labels.yml @@ -0,0 +1,27 @@ +name: "Label Pull Requests" +on: + pull_request_target: + types: [opened, ready_for_review] + +jobs: + update: + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + # pull_request_target is run under forks, use main repo source + fetch-depth: 0 + repository: EasyRPG/Player + ref: master + + - name: Update labels + uses: actions/labeler@v5 + with: + sync-labels: true + dot: true diff --git a/.github/workflows/stable-compilation.yml b/.github/workflows/stable-compilation.yml index 5971edd83..53908fec3 100644 --- a/.github/workflows/stable-compilation.yml +++ b/.github/workflows/stable-compilation.yml @@ -42,9 +42,10 @@ jobs: apt-get update apt-get install -yqq --no-install-recommends --no-install-suggests \ ca-certificates build-essential cmake ninja-build git \ - libicu-dev libexpat1-dev libsdl2-dev libpng-dev libpixman-1-dev \ - libfmt-dev libfreetype6-dev libharfbuzz-dev libmpg123-dev \ - libsndfile-dev libvorbis-dev libopusfile-dev libspeexdsp-dev \ + libicu-dev libexpat1-dev libinih-dev nlohmann-json3-dev \ + libsdl2-dev libpng-dev libpixman-1-dev libfmt-dev \ + libfreetype6-dev libharfbuzz-dev libmpg123-dev libsndfile-dev \ + libvorbis-dev libopusfile-dev libspeexdsp-dev \ libdrm-dev libgbm-dev # only needed for sdl2 on debian 11 - name: Clone Repository @@ -56,6 +57,9 @@ jobs: with: ref: ${{ github.event.inputs.git-ref }} + - name: Use gcc problem matcher + run: echo "::add-matcher::.github/gcc_comment_matcher.json" + - name: Compile run: | VER="(GA, `date +%Y-%m-%d`)" diff --git a/CMakeLists.txt b/CMakeLists.txt index 40b0b2faf..1d31d47e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ include(PlayerBuildType) include(PlayerMisc) include(GetGitRevisionDescription) +# Dependencies provided by CMake Presets +list(APPEND CMAKE_PREFIX_PATH "${PLAYER_PREFIX_PATH_APPEND}") + # C++17 is required set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -120,6 +123,8 @@ add_library(${PROJECT_NAME} OBJECT src/fileext_guesser.h src/filesystem.cpp src/filesystem.h + src/filesystem_hook.cpp + src/filesystem_hook.h src/filesystem_lzh.cpp src/filesystem_lzh.h src/filesystem_native.cpp @@ -174,6 +179,8 @@ add_library(${PROJECT_NAME} OBJECT src/game_interpreter.h src/game_interpreter_map.cpp src/game_interpreter_map.h + src/game_interpreter_shared.cpp + src/game_interpreter_shared.h src/game_map.cpp src/game_map.h src/game_message.cpp @@ -419,6 +426,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_input_settings.h src/window_item.cpp src/window_item.h + src/window_interpreter.cpp + src/window_interpreter.h src/window_keyboard.cpp src/window_keyboard.h src/window_menustatus.cpp @@ -453,6 +462,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_skill.h src/window_skillstatus.cpp src/window_skillstatus.h + src/window_stringview.cpp + src/window_stringview.h src/window_targetstatus.cpp src/window_targetstatus.h src/window_teleport.cpp @@ -522,12 +533,12 @@ if(NOT GIT_TAG) list(GET GIT_DESCRIPTION 2 GIT_HASH) string(APPEND GIT_MESSAGE "${GIT_COMMITS} commits since tag \"${GIT_TAG}\", ") string(PREPEND GIT_COMMITS "+") + # strip the g prefix + string(SUBSTRING ${GIT_HASH} 1 -1 GIT_HASH) else() # no tags found, only hash list(GET GIT_DESCRIPTION 0 GIT_HASH) endif() - # strip the g prefix - string(SUBSTRING ${GIT_HASH} 1 -1 GIT_HASH) set(PLAYER_VERSION_GIT "git${GIT_COMMITS}@${GIT_HASH}") string(APPEND GIT_MESSAGE "object hash is ${GIT_HASH}") git_local_changes(GIT_DIRTY) @@ -554,8 +565,22 @@ set_property(SOURCE src/version.cpp PROPERTY COMPILE_DEFINITIONS ) # Platform setup -set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1 libretro psvita 3ds switch wii amigaos4") -set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1 libretro psvita 3ds switch wii amigaos4) +if(NINTENDO_3DS) + set(PLAYER_TARGET_PLATFORM "3ds" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_SWITCH) + set(PLAYER_TARGET_PLATFORM "switch" CACHE STRING "Platform to compile for.") +elseif(VITA) + set(PLAYER_TARGET_PLATFORM "psvita" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_WII) + set(PLAYER_TARGET_PLATFORM "wii" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_WIIU) + set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for.") +elseif(AMIGA) + set(PLAYER_TARGET_PLATFORM "SDL1" CACHE STRING "Platform to compile for.") +else() + set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1 libretro") + set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1 libretro) +endif() set(PLAYER_BUILD_EXECUTABLE ON) set(PLAYER_TEST_LIBRARIES ${PROJECT_NAME}) @@ -588,15 +613,19 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") endif() if(ANDROID) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/android/app/src/gamebrowser) set(PLAYER_BUILD_EXECUTABLE OFF) endif() if(NINTENDO_WIIU) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) target_sources(${PROJECT_NAME} PRIVATE + src/platform/wiiu/main.h src/platform/wiiu/input_buttons.cpp) endif() + + if(WIN32) + target_link_libraries(${PROJECT_NAME} "Dwmapi") + endif() elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") target_sources(${PROJECT_NAME} PRIVATE src/platform/sdl/sdl_audio.cpp @@ -613,9 +642,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/libretro) target_link_libraries(${PROJECT_NAME} retro_common) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") - if(NOT NINTENDO_3DS) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/3DS.cmake' option.") - endif() target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=CtrUi PLAYER_NINTENDO) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after devkitarm ships newer gcc # generate gfx assets @@ -646,9 +672,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") src/platform/3ds/ui.h) target_link_libraries(${PROJECT_NAME} 3ds-assets) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") - if(NOT VITA) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake' option.") - endif() include("$ENV{VITASDK}/share/vita.cmake" REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=Psp2Ui) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after vitasdk ships newer gcc @@ -661,9 +684,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") src/platform/psvita/ui.cpp src/platform/psvita/ui.h) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") - if(NOT NINTENDO_SWITCH) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake' option.") - endif() target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=NxUi PLAYER_NINTENDO) find_package(OpenGL CONFIG REQUIRED) find_library(GLAD glad REQUIRED) @@ -683,9 +703,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") src/platform/switch/ui.h) target_link_libraries(${PROJECT_NAME} switch-assets) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") - if(NOT NINTENDO_WII) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake' option.") - endif() find_package(SDL REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1 PLAYER_NINTENDO) target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) @@ -698,19 +715,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") src/platform/sdl/axis.h src/platform/sdl/sdl_ui.cpp src/platform/sdl/sdl_ui.h) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "amigaos4") - if(NOT AMIGAOS4) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=path/to/ppc-amigaos.cmake' option.") - endif() - find_package(SDL REQUIRED) - target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1) - target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) - target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h - src/platform/sdl/axis.h - src/platform/sdl/sdl_ui.cpp - src/platform/sdl/sdl_ui.h) else() message(FATAL_ERROR "Invalid target platform") endif() @@ -722,13 +726,17 @@ if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|psvita|switch|wii)$" OR NINTENDO_WII set(PLAYER_ENABLE_TESTS OFF) option(PLAYER_VERSIONED_PACKAGES "Create zip packages with versioned name (for internal use)" ON) endif() -# Make romfs available -if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch)$") - option(PLAYER_ROMFS "Embedd a directory in the executable" OFF) - set(PLAYER_ROMFS_PATH "romfs" CACHE PATH "Directory to include in executable as romfs:/ path") - set(ROMFS_ARG "NO_ROMFS_IGNORE_ME") - if(PLAYER_ROMFS) - set(ROMFS_ARG "ROMFS") +# Make content available (romfs/wuhb bundle) +if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch)$" OR NINTENDO_WIIU) + option(PLAYER_BUNDLE "Embed a directory in the executable" OFF) + set(PLAYER_BUNDLE_PATH "content" CACHE PATH "Directory to include in executable") + set(BUNDLE_ARG "_IGNORE_ME") + if(PLAYER_BUNDLE) + if(NINTENDO_WIIU) + set(BUNDLE_ARG "CONTENT") + else() + set(BUNDLE_ARG "ROMFS") + endif() endif() endif() @@ -798,7 +806,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") set(PLAYER_JS_GAME_URL "games/" CACHE STRING "Game URL/directory where the web player searches for games") set(PLAYER_JS_OUTPUT_NAME "easyrpg-player" CACHE STRING "Output name of the js, html and wasm files") set_property(SOURCE src/async_handler.cpp APPEND PROPERTY COMPILE_DEFINITIONS "EM_GAME_URL=\"${PLAYER_JS_GAME_URL}\"") - target_sources(${PROJECT_NAME} PRIVATE src/external/picojson.h) endif() # Endianess check @@ -899,6 +906,23 @@ player_find_package(NAME lhasa TARGET LHASA::liblhasa ) +# json support +if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + player_find_package(NAME nlohmann_json + DEFINITION HAVE_NLOHMANN_JSON + TARGET nlohmann_json::nlohmann_json + REQUIRED + ) +else() + option(PLAYER_WITH_NLOHMANN_JSON "Support processing of JSON files" ON) + player_find_package(NAME nlohmann_json + CONDITION PLAYER_WITH_NLOHMANN_JSON + DEFINITION HAVE_NLOHMANN_JSON + TARGET nlohmann_json::nlohmann_json + ONLY_CONFIG + ) +endif() + # Sound system to use if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") @@ -911,21 +935,17 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) else() + # Assuming that all platforms not targeting SDL have only one audio backend set(PLAYER_AUDIO_BACKEND "Default" CACHE STRING "Audio system to use. Options: Default OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS Default OFF) - - if(${PLAYER_AUDIO_BACKEND} STREQUAL "Default") - # Assuming that all platforms not targeting SDL have only one audio backend - set(PLAYER_AUDIO_BACKEND ${PLAYER_TARGET_PLATFORM}) - endif() endif() # Configure Audio backends -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|amigaos4)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") set(PLAYER_HAS_AUDIO ON) target_compile_definitions(${PROJECT_NAME} PUBLIC SUPPORT_AUDIO=1) - if(${PLAYER_AUDIO_BACKEND} STREQUAL "libretro") + if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") if (WIN32 OR UNIX OR APPLE) set(SUPPORT_NATIVE_MIDI ON) endif() @@ -980,7 +1000,7 @@ CMAKE_DEPENDENT_OPTION(PLAYER_WITH_FLUIDLITE "Play MIDI audio with fluidlite" ON CMAKE_DEPENDENT_OPTION(PLAYER_WITH_XMP "Play MOD audio with libxmp" ON "PLAYER_HAS_AUDIO" OFF) CMAKE_DEPENDENT_OPTION(PLAYER_ENABLE_DRWAV "Play WAV audio with dr_wav (built-in). Unsupported files are played by libsndfile." ON "PLAYER_HAS_AUDIO" OFF) -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|amigaos4)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") set(PLAYER_AUDIO_RESAMPLER "Auto" CACHE STRING "Audio resampler to use. Options: Auto speexdsp samplerate OFF") set_property(CACHE PLAYER_AUDIO_RESAMPLER PROPERTY STRINGS Auto speexdsp samplerate OFF) @@ -1067,7 +1087,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|a player_find_package(NAME FluidLite CONDITION PLAYER_WITH_FLUIDLITE DEFINITION HAVE_FLUIDLITE - TARGET FluidLite::fluidlite) + TARGET "fluidlite::fluidlite;fluidlite::fluidlite-static") endif() # xmp (lite) @@ -1096,7 +1116,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|a endif() # Executable -if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL(1|2)$" AND NOT NINTENDO_WIIU) +if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL[12]$" AND NOT NINTENDO_WIIU) if(APPLE) set(EXE_NAME "EasyRPG-Player.app") set_source_files_properties(${${PROJECT_NAME}_BUNDLE_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") @@ -1149,6 +1169,7 @@ if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL(1|2)$" -sEXIT_RUNTIME --bind --pre-js ${PLAYER_JS_PREJS} --post-js ${PLAYER_JS_POSTJS} \ -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$autoResumeAudioContext','$dynCall'] \ -sEXPORTED_FUNCTIONS=_main,_malloc,_free \ + -sEXPORTED_RUNTIME_METHODS=['FS'] \ -sGL_ENABLE_GET_PROC_ADDRESS") set_source_files_properties("src/platform/sdl/main.cpp" PROPERTIES OBJECT_DEPENDS "${PLAYER_JS_PREJS};${PLAYER_JS_POSTJS};${PLAYER_JS_SHELL}") @@ -1214,7 +1235,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/3ds/icon.png) ctr_create_3dsx(easyrpg-player SMDH easyrpg-player.smdh - ${ROMFS_ARG} ${PLAYER_ROMFS_PATH}) + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.3dsx resources/3ds/easyrpg-player.xml # consider removing, *hax² is mostly obsolete DESTINATION easyrpg-player COMPONENT 3ds) @@ -1230,7 +1251,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO nx_create_nro(easyrpg-player NACP easyrpg-player.nacp ICON "${PROJECT_SOURCE_DIR}/resources/switch/icon.jpg" - ${ROMFS_ARG} ${PLAYER_ROMFS_PATH}) + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.nro DESTINATION easyrpg-player COMPONENT switch) elseif(NINTENDO_WII) @@ -1251,14 +1272,17 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO ${CMAKE_CURRENT_BINARY_DIR}/resources/wii/meta.xml DESTINATION easyrpg-player COMPONENT wii) elseif(NINTENDO_WIIU) - add_executable(easyrpg-player src/platform/sdl/main.cpp) + add_executable(easyrpg-player src/platform/wiiu/main.cpp) target_link_libraries(easyrpg-player ${PROJECT_NAME}) wut_create_rpx(easyrpg-player) wut_create_wuhb(easyrpg-player NAME "EasyRPG Player ${PLAYER_VERSION_FULL}" - SHORT_NAME "Player" + SHORTNAME "EasyRPG Player" AUTHOR "EasyRPG Team" - ) # todo icon, splashs) + ICON resources/wiiu/icon.png + TVSPLASH resources/wiiu/splash-tv.png + DRCSPLASH resources/wiiu/splash-drc.png + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.rpx ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.wuhb DESTINATION . COMPONENT wiiu) @@ -1314,12 +1338,6 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO set(CPACK_ARCHIVE_DEBUG_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${CPACK_PLATFORM}-debug") set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) # we do this manually include(CPack) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "amigaos4") - add_executable(easyrpg-player "src/platform/sdl/main.cpp") - target_link_libraries(easyrpg-player - ${PROJECT_NAME} - ${SDL_LIBRARIES}) - # FIXME: packaging? else() # library if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") add_library(easyrpg_libretro @@ -1345,6 +1363,7 @@ else() # library endif() elseif(ANDROID AND ${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") add_library(easyrpg_android + src/platform/android/android.cpp src/platform/android/android.h src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.cpp src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.h @@ -1355,6 +1374,7 @@ else() # library src/platform/sdl/main.cpp) target_link_libraries(easyrpg_android ${PROJECT_NAME}) set_target_properties(easyrpg_android PROPERTIES DEBUG_POSTFIX "") + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/android/app/src/gamebrowser) set(PLAYER_TEST_LIBRARIES "easyrpg_android") else() message(FATAL_ERROR "Unsupported library target platform ${PLAYER_TARGET_PLATFORM}") @@ -1490,9 +1510,15 @@ endif() # Print summary message(STATUS "") -message(STATUS "Target system: ${PLAYER_TARGET_PLATFORM}") -if(PLAYER_ROMFS) - message(STATUS "RomFS: Embedding directory \"${PLAYER_ROMFS_PATH}\"") +set(TARGET_STATUS "${PLAYER_TARGET_PLATFORM}") +if(NINTENDO_WIIU) + set(TARGET_STATUS "Wii U (SDL2)") +elseif(AMIGA) + set(TARGET_STATUS "Amiga (SDL1)") +endif() +message(STATUS "Target system: ${TARGET_STATUS}") +if(PLAYER_BUNDLE) + message(STATUS "Embedding directory \"${PLAYER_BUNDLE_PATH}\"") endif() message(STATUS "") @@ -1502,7 +1528,7 @@ if(PLAYER_BUILD_LIBLCF) endif() message(STATUS "Audio backend: ${PLAYER_AUDIO_BACKEND}") -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") message(STATUS "") set(WAV_LIBS) @@ -1523,7 +1549,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii)$ if(TARGET FluidSynth::libfluidsynth) list(APPEND MIDI_LIBS "FluidSynth") endif() - if(TARGET FluidLite::fluidlite) + if(TARGET fluidlite::fluidlite OR TARGET fluidlite::fluidlite-static) list(APPEND MIDI_LIBS "FluidLite") endif() if(TARGET WildMidi::libwildmidi OR TARGET WildMidi::libwildmidi-static) @@ -1593,6 +1619,12 @@ else() message(STATUS "LZH archive support: No") endif() +if(TARGET nlohmann_json::nlohmann_json) + message(STATUS "JSON support: nlohmann_json") +else() + message(STATUS "JSON support: No") +endif() + message(STATUS "") message(STATUS "Manual page: ${MANUAL_STATUS}") diff --git a/CMakePresets.json b/CMakePresets.json index ff2cde875..53ff62878 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -76,7 +76,7 @@ "name": "linux-parent", "toolchainFile": "${sourceDir}/builds/cmake/LinuxToolchain.cmake", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/linux-static" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/linux-static" }, "hidden": true, "inherits": "base-user" @@ -133,61 +133,61 @@ ] }, { - "name": "windows-x86-parent", + "name": "windows-parent", "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x86-windows-static" + "VCPKG_TARGET_TRIPLET": "$env{VSCMD_ARG_TGT_ARCH}-windows-static" }, "inherits": "win-user", "hidden": true }, { - "name": "windows-x86-debug", - "displayName": "Windows (x86) (Debug)", + "name": "windows-debug", + "displayName": "Windows (Debug)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-debug" ] }, { - "name": "windows-x86-relwithdebinfo", - "displayName": "Windows (x86) (RelWithDebInfo)", + "name": "windows-relwithdebinfo", + "displayName": "Windows (RelWithDebInfo)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-relwithdebinfo" ] }, { - "name": "windows-x86-release", - "displayName": "Windows (x86) (Release)", + "name": "windows-release", + "displayName": "Windows (Release)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-release" ] }, { - "name": "windows-x86-libretro-debug", - "displayName": "Windows (x86) (libretro core) (Debug)", + "name": "windows-libretro-debug", + "displayName": "Windows (libretro core) (Debug)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-debug" ] }, { - "name": "windows-x86-libretro-relwithdebinfo", - "displayName": "Windows (x86) (libretro core) (RelWithDebInfo)", + "name": "windows-libretro-relwithdebinfo", + "displayName": "Windows (libretro core) (RelWithDebInfo)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-relwithdebinfo" ] }, { - "name": "windows-x86-libretro-release", - "displayName": "Windows (x86) (libretro core) (Release)", + "name": "windows-libretro-release", + "displayName": "Windows (libretro core) (Release)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-release" ] }, @@ -252,66 +252,6 @@ "type-release" ] }, - { - "name": "windows-x64-parent", - "architecture": "x64", - "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x64-windows-static" - }, - "inherits": "win-user", - "hidden": true - }, - { - "name": "windows-x64-debug", - "displayName": "Windows (x64) (Debug)", - "inherits": [ - "windows-x64-parent", - "type-debug" - ] - }, - { - "name": "windows-x64-relwithdebinfo", - "displayName": "Windows (x64) (RelWithDebInfo)", - "inherits": [ - "windows-x64-parent", - "type-relwithdebinfo" - ] - }, - { - "name": "windows-x64-release", - "displayName": "Windows (x64) (Release)", - "inherits": [ - "windows-x64-parent", - "type-release" - ] - }, - { - "name": "windows-x64-libretro-debug", - "displayName": "Windows (x64) (libretro core) (Debug)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-debug" - ] - }, - { - "name": "windows-x64-libretro-relwithdebinfo", - "displayName": "Windows (x64) (libretro core) (RelWithDebInfo)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-relwithdebinfo" - ] - }, - { - "name": "windows-x64-libretro-release", - "displayName": "Windows (x64) (libretro core) (Release)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-release" - ] - }, { "name": "windows-x64-vs2022-parent", "generator": "Visual Studio 17 2022", @@ -376,7 +316,7 @@ { "name": "macos-parent", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/osx", + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/osx", "CMAKE_OSX_DEPLOYMENT_TARGET": "10.9" }, "condition": { @@ -442,7 +382,7 @@ "name": "emscripten-parent", "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/emscripten/emsdk-portable/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "CMAKE_FIND_ROOT_PATH": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "PLAYER_JS_BUILD_SHELL": "ON" }, @@ -477,8 +417,7 @@ "name": "3ds-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/3DS.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "3ds", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/3ds" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/3ds" }, "inherits": "dkp-user", "hidden": true @@ -538,8 +477,7 @@ "name": "switch-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/Switch.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "switch", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/switch" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/switch" }, "inherits": "dkp-user", "hidden": true @@ -599,8 +537,7 @@ "name": "wii-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/Wii.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "wii", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/wii" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wii" }, "inherits": "dkp-user", "hidden": true @@ -656,11 +593,70 @@ "type-release" ] }, + { + "name": "wiiu-parent", + "toolchainFile": "$env{DEVKITPRO}/cmake/WiiU.cmake", + "cacheVariables": { + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wiiu" + }, + "inherits": "dkp-user", + "hidden": true + }, + { + "name": "wiiu-debug", + "displayName": "Nintendo WiiU (Debug)", + "inherits": [ + "wiiu-parent", + "type-debug" + ] + }, + { + "name": "wiiu-relwithdebinfo", + "displayName": "Nintendo WiiU (RelWithDebInfo)", + "inherits": [ + "wiiu-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wiiu-release", + "displayName": "Nintendo WiiU (Release)", + "inherits": [ + "wiiu-parent", + "type-release" + ] + }, + { + "name": "wiiu-libretro-debug", + "displayName": "Nintendo WiiU (libretro core) (Debug)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-debug" + ] + }, + { + "name": "wiiu-libretro-relwithdebinfo", + "displayName": "Nintendo WiiU (libretro core) (RelWithDebInfo)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wiiu-libretro-release", + "displayName": "Nintendo WiiU (libretro core) (Release)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-release" + ] + }, { "name": "psvita-parent", "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/vita/vitasdk/share/vita.toolchain.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "psvita", "BUILD_SHARED_LIBS": "OFF" }, "hidden": true, @@ -768,28 +764,28 @@ "configurePreset": "linux-libretro-release" }, { - "name": "windows-x86-debug", - "configurePreset": "windows-x86-debug" + "name": "windows-debug", + "configurePreset": "windows-debug" }, { - "name": "windows-x86-relwithdebinfo", - "configurePreset": "windows-x86-relwithdebinfo" + "name": "windows-relwithdebinfo", + "configurePreset": "windows-relwithdebinfo" }, { - "name": "windows-x86-release", - "configurePreset": "windows-x86-release" + "name": "windows-release", + "configurePreset": "windows-release" }, { - "name": "windows-x86-libretro-debug", - "configurePreset": "windows-x86-libretro-debug" + "name": "windows-libretro-debug", + "configurePreset": "windows-libretro-debug" }, { - "name": "windows-x86-libretro-relwithdebinfo", - "configurePreset": "windows-x86-libretro-relwithdebinfo" + "name": "windows-libretro-relwithdebinfo", + "configurePreset": "windows-libretro-relwithdebinfo" }, { - "name": "windows-x86-libretro-release", - "configurePreset": "windows-x86-libretro-release" + "name": "windows-libretro-release", + "configurePreset": "windows-libretro-release" }, { "name": "windows-x86-vs2022-debug", @@ -815,30 +811,6 @@ "name": "windows-x86-vs2022-libretro-release", "configurePreset": "windows-x86-vs2022-libretro-release" }, - { - "name": "windows-x64-debug", - "configurePreset": "windows-x64-debug" - }, - { - "name": "windows-x64-relwithdebinfo", - "configurePreset": "windows-x64-relwithdebinfo" - }, - { - "name": "windows-x64-release", - "configurePreset": "windows-x64-release" - }, - { - "name": "windows-x64-libretro-debug", - "configurePreset": "windows-x64-libretro-debug" - }, - { - "name": "windows-x64-libretro-relwithdebinfo", - "configurePreset": "windows-x64-libretro-relwithdebinfo" - }, - { - "name": "windows-x64-libretro-release", - "configurePreset": "windows-x64-libretro-release" - }, { "name": "windows-x64-vs2022-debug", "configurePreset": "windows-x64-vs2022-debug" @@ -971,6 +943,30 @@ "name": "wii-libretro-release", "configurePreset": "wii-libretro-release" }, + { + "name": "wiiu-debug", + "configurePreset": "wiiu-debug" + }, + { + "name": "wiiu-relwithdebinfo", + "configurePreset": "wiiu-relwithdebinfo" + }, + { + "name": "wiiu-release", + "configurePreset": "wiiu-release" + }, + { + "name": "wiiu-libretro-debug", + "configurePreset": "wiiu-libretro-debug" + }, + { + "name": "wiiu-libretro-relwithdebinfo", + "configurePreset": "wiiu-libretro-relwithdebinfo" + }, + { + "name": "wiiu-libretro-release", + "configurePreset": "wiiu-libretro-release" + }, { "name": "psvita-debug", "configurePreset": "psvita-debug" diff --git a/Makefile.am b/Makefile.am index c05af3518..942640420 100644 --- a/Makefile.am +++ b/Makefile.am @@ -103,6 +103,8 @@ libeasyrpg_player_a_SOURCES = \ src/fileext_guesser.h \ src/filesystem.cpp \ src/filesystem.h \ + src/filesystem_hook.cpp \ + src/filesystem_hook.h \ src/filesystem_lzh.cpp \ src/filesystem_lzh.h \ src/filesystem_native.cpp \ @@ -157,6 +159,8 @@ libeasyrpg_player_a_SOURCES = \ src/game_interpreter_control_variables.h \ src/game_interpreter_map.cpp \ src/game_interpreter_map.h \ + src/game_interpreter_shared.cpp \ + src/game_interpreter_shared.h \ src/game_map.cpp \ src/game_map.h \ src/game_message.cpp \ @@ -398,6 +402,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_input_settings.h \ src/window_item.cpp \ src/window_item.h \ + src/window_interpreter.cpp \ + src/window_interpreter.h \ src/window_keyboard.cpp \ src/window_keyboard.h \ src/window_menustatus.cpp \ @@ -432,6 +438,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_skill.h \ src/window_skillstatus.cpp \ src/window_skillstatus.h \ + src/window_stringview.cpp \ + src/window_stringview.h \ src/window_targetstatus.cpp \ src/window_targetstatus.h \ src/window_teleport.cpp \ @@ -513,7 +521,6 @@ EXTRA_DIST += \ bench/text.cpp \ bench/utils.cpp \ bench/variables.cpp \ - src/external/picojson.h \ src/platform/3ds/audio.cpp \ src/platform/3ds/audio.h \ src/platform/3ds/clock.cpp \ @@ -579,6 +586,7 @@ libeasyrpg_player_a_CXXFLAGS = \ $(FREETYPE_CFLAGS) \ $(HARFBUZZ_CFLAGS) \ $(LHASA_CFLAGS) \ + $(NLOHMANN_JSON_CFLAGS) \ $(SDL_CFLAGS) \ $(PNG_CFLAGS) \ $(ZLIB_CFLAGS) \ @@ -632,6 +640,7 @@ easyrpg_player_LDADD = libeasyrpg-player.a libplayer-version.a \ $(FREETYPE_LIBS) \ $(HARFBUZZ_LIBS) \ $(LHASA_LIBS) \ + $(NLOHMANN_JSON_LIBS) \ $(SDL_LIBS) \ $(PNG_LIBS) \ $(ZLIB_LIBS) \ diff --git a/README.md b/README.md index d28fe0726..d9b746e1a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org - libxmp for tracker music support. - SpeexDSP or libsamplerate for proper audio resampling. - lhasa for LHA (.lzh) archive support. +- nlohmann_json for processing JSON files (required when targetting Emscripten) The older SDL version 1.2 is still supported, but deprecated. Please do not add new platform code for this library. @@ -134,8 +135,6 @@ EasyRPG Player makes use of the following 3rd party software: (Yoshio Uno), provided under the (3-clause) BSD license * [dr_wav] WAV audio loader and writer - Copyright (c) David Reid, provided under public domain or MIT-0 -* [PicoJSON] JSON parser/serializer - Copyright (c) 2009-2010 Cybozu Labs, Inc. - Copyright (c) 2011-2015 Kazuho Oku, provided under the (2-clause) BSD license ### 3rd party resources @@ -160,7 +159,6 @@ EasyRPG Player makes use of the following 3rd party software: [Logo2]: resources/logo2.png [FMMidi]: http://unhaut.epizy.com/fmmidi [dr_wav]: https://github.com/mackron/dr_libs -[PicoJSON]: https://github.com/kazuho/picojson [baekmuk]: https://kldp.net/baekmuk [Shinonome]: http://openlab.ring.gr.jp/efont/shinonome [ttyp0]: https://people.mpi-inf.mpg.de/~uwe/misc/uw-ttyp0 diff --git a/builds/android/app/build.gradle b/builds/android/app/build.gradle index 4c227e130..b00b9396a 100644 --- a/builds/android/app/build.gradle +++ b/builds/android/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 33 - buildToolsVersion '33.0.0' + namespace "org.easyrpg.player" ndkVersion '21.4.7075529' assetPacks = [":assets"] defaultConfig { applicationId "org.easyrpg.player" + compileSdk 34 minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionName VERSION_NAME versionCode Integer.parseInt(VERSION_CODE) } @@ -74,10 +74,10 @@ allprojects { } dependencies { - implementation 'androidx.appcompat:appcompat:1.6.0' - implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'org.ini4j:ini4j:0.5.4' diff --git a/builds/android/app/src/gamebrowser/CMakeLists.txt b/builds/android/app/src/gamebrowser/CMakeLists.txt index d578ec7b9..79017e5aa 100644 --- a/builds/android/app/src/gamebrowser/CMakeLists.txt +++ b/builds/android/app/src/gamebrowser/CMakeLists.txt @@ -3,12 +3,13 @@ cmake_minimum_required(VERSION 3.7) project(easyrpg_android VERSION 1.0 LANGUAGES CXX) add_library(gamebrowser - org_easyrpg_player_game_browser_GameScanner.cpp - org_easyrpg_player_game_browser_GameScanner.h + org_easyrpg_player_game_browser.cpp + org_easyrpg_player_game_browser.h ) -find_package(PNG REQUIRED) -target_link_libraries(gamebrowser PNG::PNG) +set_target_properties(gamebrowser PROPERTIES DEBUG_POSTFIX "") + +target_link_libraries(gamebrowser easyrpg_android) if(BUILD_SHARED_LIBS) set_property(TARGET gamebrowser PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp new file mode 100644 index 000000000..15e1954f3 --- /dev/null +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp @@ -0,0 +1,521 @@ +/* + * This file is part of EasyRPG Player + * + * Copyright (c) EasyRPG Project. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "org_easyrpg_player_game_browser.h" + +#include +#include +#include +#include +#include +#include + +#include "filefinder.h" +#include "utils.h" +#include "string_view.h" +#include "platform/android/android.h" +#include "bitmap.h" +#include "font.h" +#include "cache.h" +#include "rtp.h" + +#include +#include +#include +#include + +// via https://stackoverflow.com/q/1821806 +static void custom_png_write_func(png_structp png_ptr, png_bytep data, png_size_t length) { + std::vector *p = reinterpret_cast*>(png_get_io_ptr(png_ptr)); + p->insert(p->end(), data, data + length); +} + +jbyteArray readXyz(JNIEnv *env, std::istream& stream) { + char header[4]; + + stream.read(header, 4); + if(memcmp(header, "XYZ1", 4) != 0) { + return nullptr; + } + + unsigned short width; + unsigned short height; + stream.read((char*) &width, 2); + stream.read((char*) &height, 2); + + constexpr int buffer_incr = 8192; + std::vector compressed_xyz_data; + do { + compressed_xyz_data.resize(compressed_xyz_data.size() + buffer_incr); + stream.read(compressed_xyz_data.data() + compressed_xyz_data.size() - buffer_incr, buffer_incr); + } while (stream.gcount() == buffer_incr); + compressed_xyz_data.resize(compressed_xyz_data.size() - buffer_incr + stream.gcount()); + + uLongf xyz_size = 768 + (width * height); + std::vector xyz_data( + xyz_size); + + int status = uncompress(&xyz_data.front(), + &xyz_size, reinterpret_cast(compressed_xyz_data.data()), + compressed_xyz_data.size()); + + if(status != Z_OK) { + return nullptr; + } + + png_structp png_ptr; + png_infop info_ptr; + + // Create PNG write structure + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, + NULL, NULL); + if(png_ptr == NULL) + { + return nullptr; + } + + // Create PNG info structure + info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) + { + png_destroy_write_struct(&png_ptr, NULL); + return nullptr; + } + + // Init I/O functions + if(setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + + std::vector png_outbuf; + + png_set_write_fn(png_ptr, &png_outbuf, custom_png_write_func, nullptr); + + // Set compression parameters + png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); + png_set_compression_mem_level(png_ptr, MAX_MEM_LEVEL); + png_set_compression_buffer_size(png_ptr, 1024 * 1024); + + // Write header + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // Write palette + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + png_colorp palette = (png_colorp) png_malloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))); + + for(int i = 0; i < PNG_MAX_PALETTE_LENGTH; i++) + { + palette[i].red = xyz_data[i * 3]; + palette[i].green = xyz_data[i * 3 + 1]; + palette[i].blue = xyz_data[i * 3 + 2]; + } + png_set_PLTE(png_ptr, info_ptr, palette, + PNG_MAX_PALETTE_LENGTH); + + png_write_info(png_ptr, info_ptr); + + png_bytep* row_pointers = new png_bytep[height]; + for(int i = 0; i < height; i++) { + row_pointers[i] = + &xyz_data[768 + width * i]; + } + png_write_image(png_ptr, row_pointers); + delete[] row_pointers; + + png_write_end(png_ptr, info_ptr); + + png_free(png_ptr, palette); + palette = NULL; + + jbyteArray result = env->NewByteArray(png_outbuf.size()); + + env->SetByteArrayRegion(result, 0, png_outbuf.size(), reinterpret_cast(png_outbuf.data())); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + return result; +} + +std::string jstring_to_string(JNIEnv* env, jstring j_str) { + if (!j_str) { + return {}; + } + const char* chars = env->GetStringUTFChars(j_str, NULL); + std::string str(chars); + env->ReleaseStringUTFChars(j_str, chars); + return str; +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass, jstring jpath, jstring jmain_dir_name) { + EpAndroid::env = env; + + // jpath is the SAF path to the game, is converted to FilesystemView "root" + std::string spath = jstring_to_string(env, jpath); + auto root = FileFinder::Root().Create(spath); + root.ClearCache(); + + std::vector fs_list = FileFinder::FindGames(root); + + jclass jgame_class = env->FindClass("org/easyrpg/player/game_browser/Game"); + jobjectArray jgame_array = env->NewObjectArray(fs_list.size(), jgame_class, nullptr); + + if (fs_list.empty()) { + // No games found + return jgame_array; + } + + jmethodID jgame_constructor = env->GetMethodID(jgame_class, "", "(Ljava/lang/String;Ljava/lang/String;[B)V"); + + std::string root_path = FileFinder::GetFullFilesystemPath(root); + bool game_in_main_dir = false; + if (fs_list.size() == 1) { + if (root_path == FileFinder::GetFullFilesystemPath(fs_list[0])) { + game_in_main_dir = true; + } + } + + for (size_t i = 0; i < fs_list.size(); ++i) { + auto& fs = fs_list[i]; + + std::string full_path = FileFinder::GetFullFilesystemPath(fs); + std::string game_dir_name; + if (game_in_main_dir) { + // The main dir is URI encoded, the human readable name is in jmain_dir_name + game_dir_name = jstring_to_string(env, jmain_dir_name); + } else { + // In all other cases the folder name is "clean" and can be used + game_dir_name = std::get<1>(FileFinder::GetPathAndFilename(fs.GetFullPath())); + } + + std::string save_path; + if (!fs.IsFeatureSupported(Filesystem::Feature::Write)) { + // Is an archive and needs a redirected save path + // Get archive name + save_path = jstring_to_string(env, jmain_dir_name); + + // Compatibility with original GameScanner Java code + // Everything after the extension is removed + size_t ext = save_path.find_last_of('.'); + if (ext != std::string::npos) { + save_path = save_path.substr(0, ext); + } + + // Append subdirectory when the archive contains more than one game + if (fs_list.size() > 1) { + save_path += FileFinder::GetFullFilesystemPath(fs).substr(root_path.size()); + } + + // Recursion is annoying in SAF so flatten the path + // SAF already does this replacement but better do not rely on this implementation detail + save_path = Utils::ReplaceAll(save_path, "/", "_"); + } + + /* Obtaining of the game_dir_name image */ + + // 1. When the game_dir_name directory contains only one image: Load it + // 2. Attempt to fetch it from the database + // 3. If this fails grab the first from the game_dir_name folder + jbyteArray title_image = nullptr; + + auto load_image = [&](Filesystem_Stream::InputStream& stream) { + if (!stream) { + return; + } + + if (stream.GetName().ends_with(".xyz")) { + title_image = readXyz(env, stream); + } else if (stream.GetName().ends_with(".png") || stream.GetName().ends_with(".bmp")) { + auto vec = Utils::ReadStream(stream); + title_image = env->NewByteArray(vec.size()); + env->SetByteArrayRegion(title_image, 0, vec.size(), reinterpret_cast(vec.data())); + } + }; + + // 1. When the game_dir_name directory contains only one image: Load it + auto title_fs = fs.Subtree("Title"); + if (title_fs) { + auto& content = *title_fs.ListDirectory(); + if (content.size() == 1 && content[0].second.type == DirectoryTree::FileType::Regular) { + auto is = title_fs.OpenInputStream(content[0].second.name); + if (!is) { + // When opening of the image fails it is in an unsupported archive format + // Skip this game + continue; + } + load_image(is); + } + } + + // 2. Attempt to fetch it from the database + if (!title_image) { + std::string db_file = fs.FindFile("RPG_RT.ldb"); + if (!db_file.empty()) { + // This can fail when the database file is renamed, is not an error condition + auto is = fs.OpenInputStream(db_file); + if (!is) { + // When opening of the db fails it is in an unsupported archive format + // Skip this game + continue; + } else { + auto db = lcf::LDB_Reader::Load(is); + if (!db) { + // Database corrupted? Skip + continue; + } + + if (!db->system.title_name.empty()) { + auto encodings = lcf::ReaderUtil::DetectEncodings(*db); + for (auto &enc: encodings) { + if (lcf::Encoder encoder(enc); encoder.IsOk()) { + std::string title_name = lcf::ToString(db->system.title_name); + encoder.Encode(title_name); + auto title_is = fs.OpenFile("Title", title_name, FileFinder::IMG_TYPES); + // Title image was found -> Load it + load_image(title_is); + } + } + } + } + } + } + + // 3. Simply grab the first from the game_dir_name folder + if (!title_image) { + // No image loaded yet: Grab the first from the game_dir_name folder + if (title_fs) { + for (auto &[name, entry]: *title_fs.ListDirectory()) { + if (entry.type == DirectoryTree::FileType::Regular) { + auto is = title_fs.OpenInputStream(entry.name); + load_image(is); + if (title_image) { + break; + } + } + } + } + } + + /* Setting the game title */ + // By default it is just the name of the directory + std::string title = game_dir_name; + bool title_from_ini = false; + + // Try to grab a title from the INI file + if (auto ini_is = fs.OpenFile("RPG_RT.ini"); ini_is) { + if (lcf::INIReader ini(ini_is); !ini.ParseError()) { + if (std::string ini_title = ini.GetString("RPG_RT", "GameTitle", ""); !ini_title.empty()) { + title = ini_title; + title_from_ini = true; + } + } + } + + /* Create an instance of "Game" */ + jstring jgame_path = env->NewStringUTF(("content://" + full_path).c_str()); + jstring jsave_path = env->NewStringUTF(save_path.c_str()); + jobject jgame_object = env->NewObject(jgame_class, jgame_constructor, jgame_path, jsave_path, title_image); + + if (title_from_ini) { + // Store the raw string in the Game instance so it can be reencoded later via user setting + jbyteArray jtitle_raw = env->NewByteArray(title.size()); + env->SetByteArrayRegion(jtitle_raw, 0, title.size(), reinterpret_cast(title.data())); + jfieldID jtitle_raw_field = env->GetFieldID(jgame_class, "titleRaw", "[B"); + env->SetObjectField(jgame_object, jtitle_raw_field, jtitle_raw); + Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(env, jgame_object); + } else { + // Use the folder name as the title + jstring jtitle = env->NewStringUTF(title.c_str()); + jmethodID jset_title_method = env->GetMethodID(jgame_class, "setTitle", "(Ljava/lang/String;)V"); + env->CallVoidMethod(jgame_object, jset_title_method, jtitle); + } + + env->SetObjectArrayElement(jgame_array, i, jgame_object); + } + + // Some fields of the Array can be NULL when a game was skipped due to an error + // This is sanitized on the Java site + return jgame_array; +} + +extern "C" +JNIEXPORT void JNICALL +Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(JNIEnv *env, jobject thiz) { + jclass jgame_class = env->GetObjectClass(thiz); + + // Fetch the raw title string (result will be at the end in "title" variable) + jfieldID jtitle_raw_field = env->GetFieldID(jgame_class, "titleRaw", "[B"); + jbyteArray jtitle_raw = reinterpret_cast(env->GetObjectField(thiz, jtitle_raw_field)); + + if (!jtitle_raw) { + return; + } + + jbyte* title_data = env->GetByteArrayElements(jtitle_raw, NULL); + jsize title_len = env->GetArrayLength(jtitle_raw); + std::string title(reinterpret_cast(title_data), title_len); + env->ReleaseByteArrayElements(jtitle_raw, title_data, 0); + + // Obtain the encoding + jmethodID jget_encoding_method = env->GetMethodID(jgame_class, "getEncodingCode", "()Ljava/lang/String;"); + jstring jencoding = (jstring)env->CallObjectMethod(thiz, jget_encoding_method); + std::string encoding = jstring_to_string(env, jencoding); + if (encoding == "auto") { + auto det_encodings = lcf::ReaderUtil::DetectEncodings(title); + for (auto &det_enc: det_encodings) { + if (det_enc == "UTF-16BE" || det_enc == "UTF-16LE") { + // Skip obviously wrong title encodings + continue; + } + + if (lcf::Encoder encoder(det_enc); encoder.IsOk()) { + encoder.Encode(title); + break; + } + } + } else { + lcf::Encoder enc(encoding); + enc.Encode(title); + } + + if (title.empty()) { + // Something failed, do not set a new title + return; + } + + // Set the new title after reencoding + jstring jtitle = env->NewStringUTF(title.c_str()); + jmethodID jset_title_method = env->GetMethodID(jgame_class, "setTitle", "(Ljava/lang/String;)V"); + env->CallVoidMethod(thiz, jset_title_method, jtitle); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_org_easyrpg_player_settings_SettingsFontActivity_DrawText(JNIEnv *env, jclass, jstring jfont, jint jsize, jboolean jfirst_font) { + EpAndroid::env = env; + + std::string font = jstring_to_string(env, jfont); + + FontRef font_file; + FontRef def_bitmap = Font::DefaultBitmapFont(!jfirst_font); + + if (font.empty()) { + // Option "Built-in Font" selected + font_file = def_bitmap; + } else { + auto is = FileFinder::Root().OpenInputStream(font); + if (!is) { + return nullptr; + } + + font_file = Font::CreateFtFont(std::move(is), jsize, false, false); + if (!font_file) { + return nullptr; + } + + font_file->SetFallbackFont(def_bitmap); + } + + int width = MESSAGE_BOX_WIDTH - 16; + int height = 16 * 6; + + jbyteArray buffer_array = env->NewByteArray(width * height * 4); + jbyte* buffer_raw = env->GetByteArrayElements(buffer_array, 0); + + Bitmap::SetFormat(Bitmap::ChooseFormat(format_R8G8B8A8_a().format())); + auto sys = Cache::System(CACHE_DEFAULT_BITMAP); + BitmapRef draw_area = Bitmap::Create(reinterpret_cast(buffer_raw), width, height, 0, format_R8G8B8A8_a().format()); + draw_area->Fill(Color(0, 0, 0, 255)); + + Text::Draw(*draw_area, 0, 16 * 0 + 2, *font_file, *sys, Font::ColorDefault, "TheQuickBrownFoxJumpsOverTheLazyDog.!?1234567890=&#%"); + Text::Draw(*draw_area, 0, 16 * 1 + 2, *font_file, *sys, Font::ColorDefault, "色は匂えど散りぬるを我が世誰ぞ常ならん有為の奥山今#日越えて浅き夢見じ酔いもせず"); + Text::Draw(*draw_area, 0, 16 * 2 + 2, *font_file, *sys, Font::ColorDefault, "天地玄黃宇宙洪荒日月盈昃辰宿列張寒來暑往秋收冬藏閏#餘成歲律呂調陽"); + Text::Draw(*draw_area, 0, 16 * 3 + 2, *font_file, *sys, Font::ColorDefault, "키스의고유조건은입술끼리만나야하고특별한기술은필요#치않다"); + Text::Draw(*draw_area, 0, 16 * 4 + 2, *font_file, *sys, Font::ColorDefault, "(+)[-]{*}ÀÁÂÃÄÅÆÇАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬ#ЭЮЯ"); + Text::Draw(*draw_area, 0, 16 * 5 + 2, *font_file, *sys, Font::ColorDefault, "ÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúû#üýþÿ"); + + return buffer_array; +} + +extern "C" +JNIEXPORT void JNICALL +Java_org_easyrpg_player_settings_SettingsGamesFolderActivity_DetectRtp(JNIEnv *env, jobject, jstring jpath, jobject hit_info, int version) { + EpAndroid::env = env; + + + std::string path = jstring_to_string(env, jpath); + + auto fs = FileFinder::Root().Create(path); + if (!fs) { + return; + } + + fs.ClearCache(); + + auto hits = RTP::Detect(fs, version); + + if (hits.empty()) { + return; + } + + // Find RTP with highest hit rate + double best_rate = 0; + int best_index = 0; + + for (size_t i = 0; i < hits.size(); ++i) { + double rate = static_cast(hits[i].hits) / hits[i].max; + if (rate > best_rate) { + best_index = i; + best_rate = rate; + } + } + + RTP::RtpHitInfo& best_hit = hits[best_index]; + + jclass hit_info_cls = env->GetObjectClass(hit_info); + jfieldID name_field = env->GetFieldID(hit_info_cls, "name", "Ljava/lang/String;"); + jfieldID version_field = env->GetFieldID(hit_info_cls, "version", "I"); + jfieldID hits_field = env->GetFieldID(hit_info_cls, "hits", "I"); + jfieldID max_field = env->GetFieldID(hit_info_cls, "max", "I"); + + jstring jname = env->NewStringUTF(best_hit.name.c_str()); + env->SetObjectField(hit_info, name_field, jname); + + env->SetIntField(hit_info, version_field, best_hit.version); + env->SetIntField(hit_info, hits_field, best_hit.hits); + env->SetIntField(hit_info, max_field, best_hit.max); +} diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h new file mode 100644 index 000000000..a9a059492 --- /dev/null +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h @@ -0,0 +1,48 @@ +/* + * This file is part of EasyRPG Player + * + * Copyright (c) EasyRPG Project. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#ifndef _Included_org_easyrpg_player_game_browser_GameScanner +#define _Included_org_easyrpg_player_game_browser_GameScanner +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jobject JNICALL +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, jstring path, jstring jmain_dir_name); + +JNIEXPORT void JNICALL +Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(JNIEnv *env, jobject thiz); + +JNIEXPORT jbyteArray JNICALL +Java_org_easyrpg_player_settings_SettingsFontActivity_DrawText(JNIEnv *env, jclass clazz, jstring font, jint size, jboolean first_font); + +JNIEXPORT void JNICALL +Java_org_easyrpg_player_settings_SettingsGamesFolderActivity_DetectRtp(JNIEnv *env, jobject thiz, jstring path, jobject hit_info, int version); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp deleted file mode 100644 index 6a61c6e5a..000000000 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * This file is part of EasyRPG Player - * - * Copyright (c) 2021 EasyRPG Project. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "org_easyrpg_player_game_browser_GameScanner.h" - -#include -#include -#include -#include -#include -#include - -class FdStreamBufIn : public std::streambuf { -public: - FdStreamBufIn(int fd) : std::streambuf(), fd(fd) { - setg(buffer.data(), buffer.data() + buffer.size(), buffer.data() + buffer.size()); - } - - ~FdStreamBufIn() override { - close(fd); - } - - int underflow() override { - ssize_t res = read(fd, buffer.data(), buffer.size()); - if (res <= 0) { - return traits_type::eof(); - } - setg(buffer.data(), buffer.data(), buffer.data() + res); - return traits_type::to_int_type(*gptr()); - } - -private: - int fd = 0; - std::array buffer; -}; - -class BufferStreamBufIn : public std::streambuf { -public: - BufferStreamBufIn(char* buffer, jsize size) : std::streambuf(), buffer(buffer), size(size) { - setg(buffer, buffer, buffer + size); - } - -private: - char* buffer; - jsize size; - jsize index = 0; -}; - -// via https://stackoverflow.com/q/1821806 -static void custom_png_write_func(png_structp png_ptr, png_bytep data, png_size_t length) { - std::vector *p = reinterpret_cast*>(png_get_io_ptr(png_ptr)); - p->insert(p->end(), data, data + length); -} - -jbyteArray readXyz(JNIEnv *env, std::istream& stream); - -extern "C" -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZbuffer( - JNIEnv *env, jclass, jbyteArray buffer) { - jbyte* elements = env->GetByteArrayElements(buffer, nullptr); - jsize size = env->GetArrayLength(buffer); - - std::istream stream(new BufferStreamBufIn(reinterpret_cast(elements), size)); - jbyteArray array = readXyz(env, stream); - - env->ReleaseByteArrayElements(buffer, elements, 0); - - return array; -} - -extern "C" -JNIEXPORT jbyteArray JNICALL Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZfd - (JNIEnv * env, jclass, jint fd) { - std::istream stream(new FdStreamBufIn(fd)); - return readXyz(env, stream); -} - -jbyteArray readXyz(JNIEnv *env, std::istream& stream) { - char header[4]; - - stream.read(header, 4); - if(memcmp(header, "XYZ1", 4) != 0) { - return nullptr; - } - - unsigned short width; - unsigned short height; - stream.read((char*) &width, 2); - stream.read((char*) &height, 2); - - constexpr int buffer_incr = 8192; - std::vector compressed_xyz_data; - do { - compressed_xyz_data.resize(compressed_xyz_data.size() + buffer_incr); - stream.read(compressed_xyz_data.data() + compressed_xyz_data.size() - buffer_incr, buffer_incr); - } while (stream.gcount() == buffer_incr); - compressed_xyz_data.resize(compressed_xyz_data.size() - buffer_incr + stream.gcount()); - - uLongf xyz_size = 768 + (width * height); - std::vector xyz_data( - xyz_size); - - int status = uncompress(&xyz_data.front(), - &xyz_size, reinterpret_cast(compressed_xyz_data.data()), - compressed_xyz_data.size()); - - if(status != Z_OK) { - return nullptr; - } - - png_structp png_ptr; - png_infop info_ptr; - - // Create PNG write structure - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, - NULL, NULL); - if(png_ptr == NULL) - { - return nullptr; - } - - // Create PNG info structure - info_ptr = png_create_info_struct(png_ptr); - if(info_ptr == NULL) - { - png_destroy_write_struct(&png_ptr, NULL); - return nullptr; - } - - // Init I/O functions - if(setjmp(png_jmpbuf(png_ptr))) - { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - - std::vector png_outbuf; - - png_set_write_fn(png_ptr, &png_outbuf, custom_png_write_func, nullptr); - - // Set compression parameters - png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); - png_set_compression_mem_level(png_ptr, MAX_MEM_LEVEL); - png_set_compression_buffer_size(png_ptr, 1024 * 1024); - - // Write header - if(setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - png_set_IHDR(png_ptr, info_ptr, width, height, 8, - PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - // Write palette - if(setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - png_colorp palette = (png_colorp) png_malloc(png_ptr, - PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))); - - for(int i = 0; i < PNG_MAX_PALETTE_LENGTH; i++) - { - palette[i].red = xyz_data[i * 3]; - palette[i].green = xyz_data[i * 3 + 1]; - palette[i].blue = xyz_data[i * 3 + 2]; - } - png_set_PLTE(png_ptr, info_ptr, palette, - PNG_MAX_PALETTE_LENGTH); - - png_write_info(png_ptr, info_ptr); - - png_bytep* row_pointers = new png_bytep[height]; - for(int i = 0; i < height; i++) { - row_pointers[i] = - &xyz_data[768 + width * i]; - } - png_write_image(png_ptr, row_pointers); - delete[] row_pointers; - - png_write_end(png_ptr, info_ptr); - - png_free(png_ptr, palette); - palette = NULL; - - jbyteArray result = env->NewByteArray(png_outbuf.size()); - - env->SetByteArrayRegion(result, 0, png_outbuf.size(), reinterpret_cast(png_outbuf.data())); - - png_destroy_write_struct(&png_ptr, &info_ptr); - - return result; -} diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h deleted file mode 100644 index 893b67271..000000000 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_easyrpg_player_game_browser_GameScanner */ - -#ifndef _Included_org_easyrpg_player_game_browser_GameScanner -#define _Included_org_easyrpg_player_game_browser_GameScanner -#ifdef __cplusplus -extern "C" { -#endif - -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZfd(JNIEnv *env, jclass clazz, jint fd); - -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZbuffer(JNIEnv *env, jclass clazz, - jbyteArray buffer); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/builds/android/app/src/main/AndroidManifest.xml b/builds/android/app/src/main/AndroidManifest.xml index 9ef5c3332..6b69aa1ae 100644 --- a/builds/android/app/src/main/AndroidManifest.xml +++ b/builds/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,10 @@ + android:versionName="1.0" + android:installLocation="auto"> + @@ -38,6 +38,10 @@ android:label="@string/app_name" android:theme="@style/AppTheme"> + + + + + + diff --git a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java index 41acd3c00..b01272a5c 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; @@ -29,7 +30,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; -import java.util.Set; public class Helper { /** @@ -112,6 +112,7 @@ public static void createEasyRPGFolders(Context context, Uri easyRPGFolderURI){ createFolder(context, easyRPGFolder, SettingsManager.GAMES_FOLDER_NAME); createFolder(context, easyRPGFolder, SettingsManager.SOUND_FONTS_FOLDER_NAME); createFolder(context, easyRPGFolder, SettingsManager.SAVES_FOLDER_NAME); + createFolder(context, easyRPGFolder, SettingsManager.FONTS_FOLDER_NAME); // The .nomedia file (avoid media app to scan games and RTP folders) if (Helper.findFile(context, easyRPGFolder.getUri(), ".nomedia") == null) { @@ -178,7 +179,7 @@ public static GameBrowserHelper.SafError testContentProvider(Context context, Ur return GameBrowserHelper.SafError.BAD_CONTENT_PROVIDER_READ; } - try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(testFile.getUri(), "r")) { + try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(testFile.getUri(), "w")) { } catch (IOException | IllegalArgumentException e) { return GameBrowserHelper.SafError.BAD_CONTENT_PROVIDER_WRITE; } @@ -215,17 +216,21 @@ public static List listChildrenDocumentID(Context context, Uri folderUri return filesList; } - /** List files (with DOCUMENT_ID and MIME_TYPE) in the folder pointed by "folderURI" */ - public static List listChildrenDocumentIDAndType(Context context, Uri folderUri){ + /** + * List files in the folder pointed by "folderURI" + * @return Array of Document ID, mimeType, display name (filename) + */ + public static List listChildrenDocuments(Context context, Uri folderUri){ final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); List filesList = new ArrayList<>(); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); String mimeType = c.getString(1); - filesList.add(new String[] {documentID, mimeType}); + String fileName = c.getString(2); + filesList.add(new String[] {documentID, mimeType, fileName}); } c.close(); } catch (Exception e) { @@ -238,10 +243,10 @@ public static Uri findFileUri(Context context, Uri folderUri, String fileNameToF final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.equals(fileNameToFind)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); c.close(); @@ -261,10 +266,10 @@ public static List findFileUriWithRegex(Context context, Uri folderUri, Str final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.matches(regex)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); uriList.add(uri); @@ -282,13 +287,6 @@ public static DocumentFile findFile(Context context, Uri folderUri, String fileN return getFileFromURI(context, uri); } - public static String getFileNameFromDocumentID(String documentID) { - if (documentID != null) { - return documentID.substring(documentID.lastIndexOf('/') + 1); - } - return ""; - } - public static DocumentFile getFileFromURI (Context context, Uri fileURI) { try { return DocumentFile.fromTreeUri(context, fileURI); @@ -331,4 +329,27 @@ public static double getTouchScale(Context context) { return Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels) / (float) outLargestSize.x; } + + public static Bitmap createBitmapFromRGBA(byte[] rgba, int width, int height) { + if (rgba == null || rgba.length != width * height * 4) { + throw new IllegalArgumentException("Invalid RGBA array length"); + } + + int[] pixels = new int[width * height]; + + for (int i = 0; i < width * height; i++) { + int r = rgba[i * 4] & 0xFF; + int g = rgba[i * 4 + 1] & 0xFF; + int b = rgba[i * 4 + 2] & 0xFF; + int a = rgba[i * 4 + 3] & 0xFF; + + pixels[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + return bitmap; + } + } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java index 2f4966681..b3aab24ca 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java @@ -125,7 +125,8 @@ private void startGameStandalone() { String saveDir = getExternalFilesDir(null).getAbsolutePath() + "/Save"; new File(saveDir).mkdirs(); - Game project = new Game(gameDir, saveDir); + Game project = new Game(gameDir, saveDir, null); + project.setStandalone(true); GameBrowserHelper.launchGame(this, project); finish(); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java b/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java index 15b4412d1..d6fcff175 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java @@ -79,7 +79,6 @@ public String toStringForSave(Activity activity) { private static LinkedList getDefaultHorizontalButtonList(Activity activity) { LinkedList l = new LinkedList<>(); l.add(new MenuButton(activity, 0.01, 0.01, 90)); - l.add(VirtualButton.Create(activity, VirtualButton.KEY_FAST_FORWARD, 0.9, 0.01, 90)); l.add(new VirtualCross(activity, 0.01, 0.4, 100)); l.add(VirtualButton.Create(activity, VirtualButton.ENTER, 0.80, 0.55, 100)); l.add(VirtualButton.Create(activity, VirtualButton.CANCEL, 0.90, 0.45, 100)); @@ -90,7 +89,6 @@ private static LinkedList getDefaultHorizontalButtonList(Activity private static LinkedList getDefaultVerticalButtonList(Activity activity) { LinkedList l = new LinkedList<>(); l.add(new MenuButton(activity, 0.01, 0.5, 90)); - l.add(VirtualButton.Create(activity, VirtualButton.KEY_FAST_FORWARD, 0.70, 0.5, 90)); l.add(new VirtualCross(activity, 0.05, 0.65, 100)); l.add(VirtualButton.Create(activity, VirtualButton.ENTER, 0.60, 0.75, 100)); l.add(VirtualButton.Create(activity, VirtualButton.CANCEL, 0.70, 0.65, 100)); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java index a408e495a..fb2286582 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java @@ -5,6 +5,7 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Base64; +import android.util.Log; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; @@ -15,54 +16,140 @@ public class Game implements Comparable { final static char escapeCode = '\u0001'; - private final String title; + /** The title shown in the Game Browser */ + private String title; + /** Bytes of the title string in an unspecified encoding */ + private byte[] titleRaw = null; + /** Path to the game folder (forwarded via --project-path */ private final String gameFolderPath; - private String savePath; - private boolean isFavorite; - private final DocumentFile gameFolder; - private Bitmap titleScreen; - /** Path to the game folder inside of the zip */ - private String zipInnerPath; - - private Game(DocumentFile gameFolder) { - this.gameFolder = gameFolder; - this.title = gameFolder.getName(); - Uri folderURI = gameFolder.getUri(); - this.gameFolderPath = folderURI.toString(); - this.savePath = gameFolderPath; + /** Relative path to the save directory, made absolute by launchGame (forwarded via --save-path) */ + private String savePath = ""; + /** Whether the game was tagged as a favourite */ + private boolean isFavorite; + /** Title image shown in the Game Browser */ + private Bitmap titleScreen = null; + /** Game is launched from the APK via standalone mode */ + private boolean standalone = false; + + public Game(String gameFolderPath, String saveFolder, byte[] titleScreen) { + this.gameFolderPath = gameFolderPath; + + // is only relative here, launchGame will put this in the "saves" directory + if (!saveFolder.isEmpty()) { + savePath = saveFolder; + } + + if (titleScreen != null) { + this.titleScreen = BitmapFactory.decodeByteArray(titleScreen, 0, titleScreen.length); + }; this.isFavorite = isFavoriteFromSettings(); - } + } + + public String getTitle() { + String customTitle = getCustomTitle(); + if (!customTitle.isEmpty()) { + return customTitle; + } + + return title; + } + + public void setTitle(String title) { + this.title = title; + } - public Game(DocumentFile gameFolder, Bitmap titleScreen) { - this(gameFolder); - this.titleScreen = titleScreen; + public native void reencodeTitle(); + + public String getCustomTitle() { + return SettingsManager.getCustomGameTitle(this); + } + + public void setCustomTitle(String customTitle) { + SettingsManager.setCustomGameTitle(this, customTitle); + } + + public String getGameFolderPath() { + return gameFolderPath; + } + + public String getSavePath() { + return savePath; + } + + public void setSavePath(String path) { + savePath = path; + } + + public boolean isFavorite() { + return isFavorite; + } + + public void setFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; + if(isFavorite){ + SettingsManager.addFavoriteGame(this); + } else { + SettingsManager.removeAFavoriteGame(this); + } + } + + private boolean isFavoriteFromSettings() { + return SettingsManager.getFavoriteGamesList().contains(this.getKey()); + } + + @Override + public int compareTo(Game game) { + if (this.isFavorite() && !game.isFavorite()) { + return -1; + } + if (!this.isFavorite() && game.isFavorite()) { + return 1; + } + return this.getTitle().compareTo(game.getTitle()); } /** - * Constructor for standalone mode + * Returns a unique key to be used for storing settings related to the game. * - * @param gameFolder - * @param saveFolder + * @return unique key */ - public Game(String gameFolder, String saveFolder) { - this.title = "Standalone"; - this.gameFolderPath = gameFolder; - this.savePath = saveFolder; - this.gameFolder = null; - this.isFavorite = false; + public String getKey() { + return gameFolderPath.replaceAll("[/ ]", "_"); } - private Game(DocumentFile gameFolder, String pathInZip, Bitmap titleScreen) { - this(gameFolder, titleScreen); - zipInnerPath = pathInZip; + public Encoding getEncoding() { + return SettingsManager.getGameEncoding(this); } - public static Game fromZip(DocumentFile gameFolder, String pathInZip, String saveFolder, Bitmap titleScreen) { - Game game = new Game(gameFolder, pathInZip, titleScreen); - // is only relative here, launchGame will put this in the "saves" directory - game.setSavePath(saveFolder); - return game; + public void setEncoding(Encoding encoding) { + SettingsManager.setGameEncoding(this, encoding); + reencodeTitle(); + } + + /** + * @return The encoding number or "auto" when not configured (for use via JNI) + */ + public String getEncodingCode() { + return getEncoding().getRegionCode(); + } + + public Bitmap getTitleScreen() { + return titleScreen; + } + + public boolean isStandalone() { + return standalone; + } + + public void setStandalone(boolean standalone) { + this.standalone = standalone; + } + + @NonNull + @Override + public String toString() { + return getTitle(); } public static Game fromCacheEntry(Context context, String cache) { @@ -78,99 +165,45 @@ public static Game fromCacheEntry(Context context, String cache) { return null; } - boolean isZip = Boolean.parseBoolean(entries[2]); - String zipInnerPath = null; + String title = entries[2]; - if (isZip) { - zipInnerPath = entries[3]; + byte[] titleRaw = null; + if (!entries[3].equals("null")) { + titleRaw = Base64.decode(entries[3], 0); } - Bitmap titleScreen = null; + byte[] titleScreen = null; if (!entries[4].equals("null")) { - byte[] decodedByte = Base64.decode(entries[4], 0); - titleScreen = BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length); - } - - if (isZip) { - return fromZip(gameFolder, zipInnerPath, savePath, titleScreen); - } else { - return new Game(gameFolder, titleScreen); + titleScreen = Base64.decode(entries[4], 0); } - } - - public String getTitle() { - return title; - } - public String getGameFolderPath() { - return gameFolderPath; - } + Game g = new Game(entries[1], savePath, titleScreen); + g.setTitle(title); + g.titleRaw = titleRaw; - public String getSavePath() { - return savePath; - } + if (g.titleRaw != null) { + g.reencodeTitle(); + } - public void setSavePath(String path) { - savePath = path; - } - - public boolean isFavorite() { - return isFavorite; - } - - public void setFavorite(boolean isFavorite) { - this.isFavorite = isFavorite; - if(isFavorite){ - SettingsManager.addFavoriteGame(this); - } else { - SettingsManager.removeAFavoriteGame(this); - } - } - - private boolean isFavoriteFromSettings() { - return SettingsManager.getFavoriteGamesList().contains(this.title); - } - - @Override - public int compareTo(Game game) { - if (this.isFavorite() && !game.isFavorite()) { - return -1; - } - if (!this.isFavorite() && game.isFavorite()) { - return 1; - } - return this.title.compareTo(game.title); - } - - public Encoding getEncoding() { - return SettingsManager.getGameEncoding(this); - } - - public void setEncoding(Encoding encoding) { - SettingsManager.setGameEncoding(this, encoding); - } - - @NonNull - @Override - public String toString() { - return getTitle(); + return g; } public String toCacheEntry() { StringBuilder sb = new StringBuilder(); + // Cache structure: savePath | gameFolderPath | title | titleRaw | titleScreen sb.append(savePath); sb.append(escapeCode); - - sb.append(gameFolder.getUri()); + sb.append(gameFolderPath); sb.append(escapeCode); - - sb.append(isZipArchive()); + sb.append(title); sb.append(escapeCode); - - sb.append(zipInnerPath); + if (titleRaw != null) { + sb.append(Base64.encodeToString(titleRaw, Base64.NO_WRAP)); + } else { + sb.append("null"); + } sb.append(escapeCode); - if (titleScreen != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); titleScreen.compress(Bitmap.CompressFormat.PNG, 90, baos); @@ -182,24 +215,4 @@ public String toCacheEntry() { return sb.toString(); } - - public DocumentFile getGameFolder() { - return gameFolder; - } - - public Bitmap getTitleScreen() { - return titleScreen; - } - - public Boolean isStandalone() { - return gameFolder == null; - } - - public Boolean isZipArchive() { - return zipInnerPath != null; - } - - public String getZipInnerPath() { - return zipInnerPath; - } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java index 7f9d2514e..f6fa1cb5d 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; +import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; @@ -16,6 +17,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; @@ -35,6 +37,7 @@ import org.easyrpg.player.R; import org.easyrpg.player.settings.SettingsManager; +import org.libsdl.app.SDL; import java.util.Collections; import java.util.List; @@ -57,13 +60,17 @@ protected void onCreate(Bundle savedInstanceState) { if (!libraryLoaded) { try { + System.loadLibrary("easyrpg_android"); System.loadLibrary("gamebrowser"); libraryLoaded = true; } catch (UnsatisfiedLinkError e) { - Log.e("EasyRPG Player", "Couldn't load libgamebrowser. XYZ parsing will be unavailable: " + e.getMessage()); + Log.e("EasyRPG Player", "Couldn't load libgamebrowser: " + e.getMessage()); + throw e; } } + SDL.setContext(getApplicationContext()); + setContentView(R.layout.activity_games_browser); // Configure the toolbar @@ -311,6 +318,7 @@ public void onBindViewHolder(final ViewHolder holder, final int position) { holder.settingsButton.setOnClickListener(v -> { String[] choices_list = { activity.getResources().getString(R.string.select_game_region), + activity.getResources().getString(R.string.game_rename), activity.getResources().getString(R.string.launch_debug_mode) }; @@ -319,8 +327,10 @@ public void onBindViewHolder(final ViewHolder holder, final int position) { .setTitle(R.string.settings) .setItems(choices_list, (dialog, which) -> { if (which == 0) { - chooseRegion(activity, gameList.get(position)); + chooseRegion(activity, holder, gameList.get(position)); } else if (which == 1) { + renameGame(activity, holder, gameList.get(position)); + } else if (which == 2) { launchGame(position, true); } }); @@ -363,7 +373,7 @@ public void updateFavoriteButton(ViewHolder holder, Game game){ holder.favoriteButton.setImageResource(buttonImageResource); } - public void chooseRegion(final Context context, final Game game) { + public void chooseRegion(final Context context, final ViewHolder holder, final Game game) { // The list of region choices String[] region_array = Encoding.getEncodingDescriptions(context); @@ -381,12 +391,36 @@ public void chooseRegion(final Context context, final Game game) { if (!selectedEncoding.equals(encoding)) { game.setEncoding(selectedEncoding); + holder.title.setText(game.getTitle()); } }) .setNegativeButton(R.string.cancel, null); builder.show(); } + public void renameGame(final Context context, final ViewHolder holder, final Game game) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + + // Set up text input + final EditText input = new EditText(context); + input.setInputType(InputType.TYPE_CLASS_TEXT); + input.setText(holder.title.getText()); + builder.setView(input); + + builder + .setTitle(R.string.game_rename) + .setPositiveButton(R.string.ok, (dialog, id) -> { + game.setCustomTitle(input.getText().toString()); + holder.title.setText(game.getTitle()); + }) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.revert, (dialog, id) -> { + game.setCustomTitle(""); + holder.title.setText(game.getTitle()); + }); + builder.show(); + } + public static class ViewHolder extends RecyclerView.ViewHolder { public TextView title; public ImageView titleScreen; diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java index e20b51837..dcb6d3246 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java @@ -8,7 +8,6 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.util.Log; -import android.widget.Toast; import androidx.documentfile.provider.DocumentFile; @@ -50,74 +49,62 @@ public static void launchGame(Context context, Game game) { public static void launchGame(Context context, Game game, boolean debugMode) { String path = game.getGameFolderPath(); - // Test again in case somebody messed with the file system - boolean valid = game.isStandalone() || - (game.isZipArchive() && game.getGameFolder().canRead()) || - (game.getGameFolder().isDirectory() && game.getGameFolder().canRead()); + Intent intent = new Intent(context, EasyRpgPlayerActivity.class); + ArrayList args = new ArrayList<>(); - if (valid) { - Intent intent = new Intent(context, EasyRpgPlayerActivity.class); - ArrayList args = new ArrayList<>(); + // Command line passed via intent "command_line" + args.add("--project-path"); + args.add(path); - // Command line passed via intent "command_line" - String savePath; + String savePath = path; + if (!game.getSavePath().isEmpty()) { + DocumentFile saveFolder = Helper.createFolderInSave(context, game.getSavePath()); - if (game.isZipArchive()) { - // Create the redirected save folder - DocumentFile saveFolder = Helper.createFolderInSave(context, game.getSavePath()); - - args.add("--project-path"); - args.add(path + "/" + game.getZipInnerPath()); - - // In error case the native code will try to put a save folder next to the zip - if (saveFolder != null) { - savePath = saveFolder.getUri().toString(); - args.add("--save-path"); - args.add(savePath); - } else { - savePath = path; - } - } else { - args.add("--project-path"); - args.add(path); - - savePath = game.getSavePath(); + // In error case the native code will try to put a save folder next to the zip + if (saveFolder != null) { + savePath = saveFolder.getUri().toString(); args.add("--save-path"); args.add(savePath); } + } - Encoding enc = game.getEncoding(); - if (enc.getIndex() > 0) { - // 0 = Auto, in that case let the Player figure it out - args.add("--encoding"); - args.add(enc.getRegionCode()); - } + Encoding enc = game.getEncoding(); + if (enc.getIndex() > 0) { + // 0 = Auto, in that case let the Player figure it out + args.add("--encoding"); + args.add(enc.getRegionCode()); + } - args.add("--config-path"); - args.add(context.getExternalFilesDir(null).getAbsolutePath()); + args.add("--config-path"); + args.add(context.getExternalFilesDir(null).getAbsolutePath()); - // Soundfont - Uri soundfontUri = SettingsManager.getSoundFountFileURI(context); - if (soundfontUri != null) { - args.add("--soundfont"); - args.add(soundfontUri.toString()); - } - - if (debugMode) { - args.add("--test-play"); - } + /* FIXME: Currently disabled because the built-in scene cannot handle URI-encoded paths + // Sound Font Folder path (used by the settings scene) + Uri soundFontFolderUri = SettingsManager.getSoundFontsFolderURI(context); + if (soundFontFolderUri != null) { + args.add("--soundfont-path"); + args.add(soundFontFolderUri.toString()); + } - intent.putExtra(EasyRpgPlayerActivity.TAG_SAVE_PATH, savePath); - intent.putExtra(EasyRpgPlayerActivity.TAG_COMMAND_LINE, args.toArray(new String[0])); - intent.putExtra(EasyRpgPlayerActivity.TAG_STANDALONE, game.isStandalone()); + // Font Folder path (used by the settings scene) + Uri fontFolderUri = SettingsManager.getFontsFolderURI(context); + if (fontFolderUri != null) { + args.add("--font-path"); + args.add(fontFolderUri.toString()); + } + */ - Log.i("EasyRPG", "Start EasyRPG Player with following arguments : " + args); - Log.i("EasyRPG", "The RTP folder is : " + SettingsManager.getRTPFolderURI(context)); - context.startActivity(intent); - } else { - String msg = context.getString(R.string.not_valid_game).replace("$PATH", game.getTitle()); - Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + if (debugMode) { + args.add("--test-play"); } + + intent.putExtra(EasyRpgPlayerActivity.TAG_SAVE_PATH, savePath); + intent.putExtra(EasyRpgPlayerActivity.TAG_COMMAND_LINE, args.toArray(new String[0])); + intent.putExtra(EasyRpgPlayerActivity.TAG_STANDALONE, game.isStandalone()); + + Log.i("EasyRPG", "Start EasyRPG Player with following arguments : " + args); + Log.i("EasyRPG", "The RTP folder is : " + SettingsManager.getRTPFolderURI(context)); + context.startActivity(intent); } public static void openSettingsActivity(Context context) { @@ -199,7 +186,7 @@ public static SafError dealAfterFolderSelected(Activity activity, int requestCod return SafError.BAD_CONTENT_PROVIDER_BASE_FOLDER_NOT_FOUND; } - List items = Helper.listChildrenDocumentIDAndType(activity, folder.getUri()); + List items = Helper.listChildrenDocuments(activity, folder.getUri()); int item_count = 0; for (String[] item: items) { if (item[0] == null || Helper.isDirectoryFromMimeType(item[1]) || item[0].endsWith(".nomedia")) { @@ -242,31 +229,31 @@ public static void showErrorMessage(Context context, SafError error) { break; case BAD_CONTENT_PROVIDER_CREATE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_create); + errorMsg += "File creation failed."; break; case BAD_CONTENT_PROVIDER_READ: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_read); + errorMsg += "Read operation failed."; break; case BAD_CONTENT_PROVIDER_WRITE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_write); + errorMsg += "Write operation failed."; break; case BAD_CONTENT_PROVIDER_DELETE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_delete); + errorMsg += "File deletion failed."; break; case BAD_CONTENT_PROVIDER_FILENAME_IGNORED: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_filename_ignored); + errorMsg += "Provided filename ignored."; break; case BAD_CONTENT_PROVIDER_BASE_FOLDER_NOT_FOUND: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_base_folder_not_found); + errorMsg += "Selected folder not found."; break; case BAD_CONTENT_PROVIDER_FILE_ACCESS: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_file_access); + errorMsg += "A file was successfully created but cannot be accessed."; break; case FOLDER_NOT_ALMOST_EMPTY: errorMsg = context.getString(R.string.error_saf_folder_not_empty); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java index f10e6be13..7fddba236 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java @@ -1,37 +1,22 @@ package org.easyrpg.player.game_browser; -import android.content.ContentResolver; +import android.app.Activity; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.provider.MediaStore; import android.util.Log; +import android.widget.TextView; import androidx.documentfile.provider.DocumentFile; import org.easyrpg.player.Helper; import org.easyrpg.player.R; import org.easyrpg.player.settings.SettingsManager; +import org.libsdl.app.SDL; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; public class GameScanner { // We use a singleton pattern to allow further optimizations @@ -51,7 +36,7 @@ private GameScanner() { this.errorList = new ArrayList<>(); } - public static GameScanner getInstance(Context context) { + public static GameScanner getInstance(Activity activity) { // Singleton pattern if (GameScanner.instance == null) { synchronized(GameScanner.class) { @@ -62,12 +47,14 @@ public static GameScanner getInstance(Context context) { } //Scan the folder - instance.scanGames(context); + instance.scanGames(activity); return instance; } - private void scanGames(Context context){ + private void scanGames(Activity activity){ + Context context = activity.getApplicationContext(); + gameList.clear(); errorList.clear(); @@ -107,7 +94,7 @@ private void scanGames(Context context){ } } - if (gameList.size() > 0) { + if (!gameList.isEmpty()) { Log.i("EasyRPG", gameList.size() + " game(s) found in cache."); return; @@ -118,7 +105,7 @@ private void scanGames(Context context){ } // Scan the games folder - scanFolderRecursive(context, gamesFolder.getUri(), GAME_SCANNING_DEPTH); + scanRootFolder(activity, gamesFolder.getUri()); // If the scan brings nothing in this folder : we notify the errorList if (gameList.size() <= 0) { @@ -140,7 +127,8 @@ private void scanGames(Context context){ private int scanFolderHash(Context context, Uri folderURI) { StringBuilder sb = new StringBuilder(); - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { + sb.append("2"); // Bump this when the cache layout changes + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { sb.append(array[0]); sb.append(array[1]); } @@ -148,289 +136,47 @@ private int scanFolderHash(Context context, Uri folderURI) { return sb.toString().hashCode(); } - private void scanFolderRecursive(Context context, Uri folderURI, int depth) { - if (depth > 0) { - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { - String fileDocumentID = array[0]; - String fileDocumentType = array[1]; - - String name = Helper.getFileNameFromDocumentID(fileDocumentID); - if (name.equals("")) { - continue; - } - if (!name.startsWith(".")) { - boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); - if (isDirectory) { - // Is the file/folder a RPG Maker game? - Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); - Game game = isAGame(context, fileURI); - if (game != null) { - gameList.add(game); - } - else if (depth > 1) { - // Not a RPG2k Game but a directory -> recurse - // (We don't check if it's a directory or if its readable because of slow - // Android SAF calls and scanFolder(...) already check that) - scanFolderRecursive(context, fileURI, depth - 1); - } - } else { - String nameLower = name.toLowerCase(Locale.ROOT); - if (nameLower.endsWith(".zip") || nameLower.endsWith(".easyrpg")) { - Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); - Game game = isAGameZipped(context, fileURI, true); - if (game != null) { - gameList.add(game); - } - } - } - } - } - } - } - - /** Return a game if "folder" is a game folder, or return null. - * This method is designed to reduce the number of sys calls */ - public static Game isAGame(Context context, Uri uri) { - Game game = null; - Uri titleFolderURI = null; - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - int rpgRtCount = 0; - boolean databaseFound = false; - boolean treemapFound = false; - boolean isARpgGame = false; - - for (String filePath : Helper.listChildrenDocumentID(context, uri)) { - String fileName = Helper.getFileNameFromDocumentID(filePath); - String fileNameLower = fileName.toLowerCase(Locale.ROOT); - - if (!databaseFound && fileName.equals(DATABASE_NAME)) { - databaseFound = true; - } else if (!treemapFound && fileName.equals(TREEMAP_NAME)) { - treemapFound = true; - } - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - if (fileNameLower.startsWith("rpg_rt.")) { - if (!(fileName.equals(INI_FILE) || fileName.equals(EXE_FILE))) { - rpgRtCount += 1; - } - } - - if ((databaseFound && treemapFound) || rpgRtCount == 2) { - isARpgGame = true; - } - - // If we encounter a Title folder, we keep it for the title screen - // We do that here in order to avoid syscalls - if (fileNameLower.equals("title")) { - titleFolderURI = DocumentsContract.buildDocumentUriUsingTree(uri, filePath); - } - } - - if (isARpgGame) { - Bitmap titleScreen = GameScanner.extractTitleScreenImage(context, titleFolderURI); - game = new Game(Helper.getFileFromURI(context, uri), titleScreen); - } - - return game; - } - - static class ZipFoundStats { - int rpgRtCount = 0; - boolean databaseFound = false; - boolean treemapFound = false; - boolean isARpgGame = false; - byte[] titleImage = null; - - ZipFoundStats() { - } - } - - /** Return a game if "folder" is a game folder, or return null. - * This method is designed to reduce the number of sys calls */ - public static Game isAGameZipped(Context context, Uri zipUri, boolean unicode) { - ContentResolver resolver = context.getContentResolver(); - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - Map games = new HashMap<>(); - - try (InputStream zipIStream = resolver.openInputStream(zipUri)) { - ZipInputStream zipStream; - if (unicode) { - zipStream = new ZipInputStream(zipIStream); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // arbitrary encoding that does not crash - zipStream = new ZipInputStream(zipIStream, StandardCharsets.ISO_8859_1); - } else { - return null; - } - - ZipEntry entry; - - while ((entry = zipStream.getNextEntry()) != null) { - if (entry.isDirectory()) { - continue; - } - - String fullPath = entry.getName(); - String fileName; - - if (fullPath.isEmpty()) { - continue; - } - - int slash = fullPath.lastIndexOf('/'); - if (slash == -1) { - slash = fullPath.lastIndexOf('\\'); - } - - if (slash == -1) { - fileName = fullPath; - } else if (slash == fullPath.length() - 1) { - continue; - } else { - fileName = fullPath.substring(slash + 1); - } - - String fileNameLower = fileName.toLowerCase(Locale.ROOT); - - String gameDirectory; - if (slash <= 0) { - gameDirectory = ""; - } else { - gameDirectory = fullPath.substring(0, slash); - } - - String gameDirectoryLower = gameDirectory.toLowerCase(Locale.ROOT); - if (gameDirectoryLower.endsWith("/title") || gameDirectoryLower.endsWith("\\title")) { - // Check for a title image - ZipFoundStats stats = games.get(gameDirectory.substring(0, gameDirectory.length() - "/title".length())); - - if (stats != null) { - if (fileNameLower.endsWith(".xyz") || fileNameLower.endsWith(".png") || fileNameLower.endsWith(".bmp")) { - int count; - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - byte[] buffer = new byte[16 * 1024]; - while ((count = zipStream.read(buffer)) != -1) - out.write(buffer, 0, count); - stats.titleImage = out.toByteArray(); - if (stats.isARpgGame) { - break; - } - } - } - } - - continue; - } - - ZipFoundStats stats = games.get(gameDirectory); - - if (stats == null) { - stats = new ZipFoundStats(); - games.put(gameDirectory, stats); - } + private void scanRootFolder(Activity activity, Uri folderURI) { + Context context = activity.getApplicationContext(); + SDL.setContext(context); - if (!stats.databaseFound && fileNameLower.equals(DATABASE_NAME)) { - stats.databaseFound = true; - } else if (!stats.treemapFound && fileNameLower.equalsIgnoreCase(TREEMAP_NAME)) { - stats.treemapFound = true; - } + final ArrayList names = new ArrayList<>(); + final ArrayList fileURIs = new ArrayList<>(); - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - if (fileNameLower.startsWith("rpg_rt.")) { - if (!(fileNameLower.equals(INI_FILE) || fileNameLower.equals(EXE_FILE))) { - stats.rpgRtCount += 1; - } - } + // Precalculate how many folders are to be scanned + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { + String fileDocumentID = array[0]; + String name = array[2]; - if ((stats.databaseFound && stats.treemapFound) || stats.rpgRtCount == 2) { - stats.isARpgGame = true; - if (stats.titleImage != null) { - break; - } - } - } - } catch (IOException e) { - return null; - } catch (IllegalArgumentException e) { - if (unicode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return isAGameZipped(context, zipUri, false); + if (name.isEmpty()) { + continue; } - return null; - } - for (Map.Entry entry : games.entrySet()) { - if (entry.getValue().isARpgGame) { - String name = new File(zipUri.getPath()).getName(); - String saveFolder = name.substring(0, name.lastIndexOf(".")); - Bitmap titleScreen = extractTitleScreenImage(context, entry.getValue().titleImage); - String key = entry.getKey(); - if (!unicode) { - // FIXME: This will launch the built-in Game Browser when the ZIP archive contains more than one folder in the root - // But the game can be at least launched this way. - key = ""; - } - return Game.fromZip(Helper.getFileFromURI(context, zipUri), key, saveFolder, titleScreen); + if (!name.startsWith(".")) { + Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); + names.add(name); + fileURIs.add(fileURI); } } - return null; - } - - /** The VERY SLOW way of testing if a folder is a RPG2k Game. (contains DATABASE_NAME and TREEMAP_NAME) - * It shouldn't be use unless Google forces the use of DocumentFile over ContentResolver - * @param dir The directory to test - * @return true if RPG2k game - */ - @Deprecated - public static boolean isAGameSlowWay(DocumentFile dir) { - if (!dir.isDirectory() || !dir.canRead()) { - return false; - } - - boolean databaseFound = false; - boolean treemapFound = false; - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - int rpgRtCount = 0; - - for (DocumentFile entry : dir.listFiles()) { - if (entry.isFile() && entry.canRead()) { - String entryName = entry.getName(); - if (entryName == null) { - continue; - } - - if (!databaseFound && entryName.equalsIgnoreCase(DATABASE_NAME)) { - databaseFound = true; - } else if (!treemapFound && entryName.equalsIgnoreCase(TREEMAP_NAME)) { - treemapFound = true; - } + // Scan all the folders and show the current scanning progress + for (int i = 0; i < names.size(); ++i) { + final String name = names.get(i); // only "final" variables can be passed to lambdas + final int j = i; + activity.runOnUiThread(() -> { + // Update Ui progress + TextView myTextView = activity.findViewById(R.id.progressText); + myTextView.setText(String.format("%s (%d/%d)", name, j + 1, names.size())); + }); - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - if (entryName.toLowerCase().startsWith("rpg_rt.")) { - if (!(entryName.equalsIgnoreCase(INI_FILE) || entryName.equalsIgnoreCase(EXE_FILE))) { - rpgRtCount += 1; - } - } + Game[] candidates = findGames(fileURIs.get(i).toString(), names.get(i)); - if (databaseFound && treemapFound) { - return true; + for (Game candidate: candidates) { + if (candidate != null) { + gameList.add(candidate); } } } - - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - return rpgRtCount == 2; } public List getGameList() { @@ -445,70 +191,5 @@ public boolean hasError() { return !errorList.isEmpty(); } - /** Return the game title screen, in a dumb way following last Enterbrain conventions */ - public static Bitmap extractTitleScreenImage(Context context, byte[] titleScreenBuffer) { - if (titleScreenBuffer == null) { - return null; - } - - Bitmap bmp = BitmapFactory.decodeByteArray(titleScreenBuffer, 0, titleScreenBuffer.length); - - if (bmp == null) { - byte[] xyz = decodeXYZbuffer(titleScreenBuffer); - if (xyz == null) { - return null; - } - return BitmapFactory.decodeByteArray(xyz, 0, xyz.length); - } - - return bmp; - } - - /** Return the game title screen, in a dumb way following last Enterbrain conventions */ - public static Bitmap extractTitleScreenImage(Context context, Uri titleScreenFolderURI) { - try { - // Retrieve the Title folder, containing titles screens - DocumentFile titleFolder = Helper.getFileFromURI(context, titleScreenFolderURI); - - if (titleFolder != null && titleFolder.isDirectory()) { - - // Display the first image found in the Title folder - for (String fileID : Helper.listChildrenDocumentID(context, titleScreenFolderURI)) { - String fileName = Helper.getFileNameFromDocumentID(fileID).toLowerCase().trim(); - - if (!fileName.startsWith(".")) { - if (fileName.endsWith("png") || fileName.endsWith("bmp")) { - Uri imageUri = Helper.getURIFromDocumentID(titleScreenFolderURI, fileID); - return MediaStore.Images.Media.getBitmap(context.getContentResolver(), imageUri); - } - else if (fileName.endsWith("xyz")) { - Uri imageUri = Helper.getURIFromDocumentID(titleScreenFolderURI, fileID); - Bitmap b = MediaStore.Images.Media.getBitmap(context.getContentResolver(), imageUri); - if (b == null && GameBrowserActivity.libraryLoaded) { - // Check for XYZ - try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(imageUri, "r")) { - byte[] xyz = decodeXYZfd(fd.detachFd()); - if (xyz == null) { - return null; - } - return BitmapFactory.decodeByteArray(xyz, 0, xyz.length); - } catch (IOException e) { - return null; - } - } - return b; - } - } - } - } - } catch (Exception e) { - Log.e("EasyRPG", e.getMessage()); - } - - return null; - } - - private static native byte[] decodeXYZfd(int fd); - - private static native byte[] decodeXYZbuffer(byte[] buffer); + private static native Game[] findGames(String path, String mainFolderName); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java index fc37f6144..828be979e 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java @@ -84,6 +84,14 @@ public class EasyRpgPlayerActivity extends SDLActivity implements NavigationView private boolean uiVisible = true; SurfaceView surface; + @Override + protected String[] getLibraries() { + return new String[] { + "SDL2", + "easyrpg_android" + }; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -145,9 +153,6 @@ public void onDrawerStateChanged(int newState) { mLayout.addView(surface); updateScreenPosition(); - // Set speed multiplier - setFastForwardMultiplier(SettingsManager.getFastForwardMultiplier()); - showInputLayout(); } @@ -176,10 +181,14 @@ public boolean onNavigationItemSelected(MenuItem item) { } else if (item.getItemId() == R.id.toggle_ui) { uiVisible = !uiVisible; showInputLayout(); + } else if (item.getItemId() == R.id.toggle_fps) { + toggleFps(); } else if (item.getItemId() == R.id.edit_layout) { editLayout(); } else if (item.getItemId() == R.id.report_bug) { reportBug(); + } else if (item.getItemId() == R.id.reset_game) { + showResetGameDialog(); } else if (item.getItemId() == R.id.end_game) { showEndGameDialog(); } @@ -307,6 +316,20 @@ public void openOrCloseMenu() { } } + private void showResetGameDialog() { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(R.string.app_name); + + // set dialog message + alertDialogBuilder.setMessage(R.string.do_want_reset).setCancelable(false) + .setPositiveButton(R.string.yes, (dialog, id) -> resetGame()).setNegativeButton(R.string.no, (dialog, id) -> dialog.cancel()); + + // create alert dialog + AlertDialog alertDialog = alertDialogBuilder.create(); + + alertDialog.show(); + } + private void showEndGameDialog() { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); alertDialogBuilder.setTitle(R.string.app_name); @@ -325,7 +348,9 @@ private void showEndGameDialog() { public static native void endGame(); - public static native void setFastForwardMultiplier(int m); + public static native void resetGame(); + + public static native void toggleFps(); protected String[] getArguments() { return getIntent().getStringArrayExtra(TAG_COMMAND_LINE); @@ -356,7 +381,7 @@ public String getRtpPath() { return ""; } - public SafFile getHandleForPath(String path) { + public static SafFile getHandleForPath(String path) { return SafFile.fromPath(getContext(), path); } @@ -412,8 +437,8 @@ public void updateScreenPosition() { * * @return asset manager */ - public AssetManager getAssetManager() { - return getAssets(); + public static AssetManager getAssetManager() { + return getContext().getAssets(); } /** diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java index 9276bfde5..1220dd87b 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java @@ -10,6 +10,7 @@ public class IniFile { public SectionView video; public SectionView audio; + public SectionView input; public SectionView engine; public IniFile(File iniFile) { @@ -28,7 +29,8 @@ public IniFile(File iniFile) { video = new SectionView("Video"); audio = new SectionView("Audio"); - engine = new SectionView("Engine"); + input = new SectionView("Input"); + engine = new SectionView("Player"); } public boolean save() { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java index 1fed83876..6a8426518 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java @@ -7,7 +7,6 @@ import android.provider.DocumentsContract; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RadioButton; @@ -15,7 +14,6 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.AppCompatSpinner; import androidx.documentfile.provider.DocumentFile; import org.easyrpg.player.Helper; @@ -90,14 +88,14 @@ private List scanAvailableSoundfonts(){ Uri soundFontsFolder = SettingsManager.getSoundFontsFolderURI(this); if (soundFontsFolder != null) { - for (String[] array : Helper.listChildrenDocumentIDAndType(this, soundFontsFolder)) { + for (String[] array : Helper.listChildrenDocuments(this, soundFontsFolder)) { String fileDocumentID = array[0]; String fileDocumentType = array[1]; + String name = array[2]; // Is it a soundfont file ? boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); - String name = Helper.getFileNameFromDocumentID(fileDocumentID); - if (!isDirectory && name.toLowerCase().endsWith(".sf2")) { + if (!isDirectory && (name.toLowerCase().endsWith(".sf2") || name.toLowerCase().endsWith(".soundfont"))) { DocumentFile soundFontFile = Helper.getFileFromDocumentID(this, soundFontsFolder, fileDocumentID); if (soundFontFile != null) { soundfontList.add(new SoundfontItemList(this, name, soundFontFile.getUri())); @@ -113,7 +111,7 @@ public SoundfontItemList getDefaultSoundfont(Context context) { } public static boolean isSelectedSoundfontFile(Context context, Uri soundfontUri) { - Uri selectedSoundFontUri = SettingsManager.getSoundFountFileURI(context); + Uri selectedSoundFontUri = SettingsManager.getSoundFontFileURI(); if (soundfontUri == null && selectedSoundFontUri == null) { return true; } @@ -147,7 +145,7 @@ public SoundfontItemList(Context context, String name, Uri uri) { } public void select() { - SettingsManager.setSoundFountFileURI(uri); + SettingsManager.setSoundFontFileURI(uri); // Uncheck other RadioButton for (SoundfontItemList s : soundfontList) { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java index 5735bb6e4..d06a03c0b 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java @@ -11,19 +11,24 @@ enum SettingsEnum { LAYOUT_SIZE("PREF_SIZE_EVERY_BUTTONS"), EASYRPG_FOLDER_URI("PREF_EASYRPG_FOLDER_URI"), ENABLE_RTP_SCANNING("PREF_ENABLE_RTP_SCANNING"), - SOUNDFONT_URI("PREF_SOUNDFONT_URI"), + SOUNDFONT_URI("Soundfont"), FAVORITE_GAMES("PREF_FAVORITE_GAMES_NEW"), CACHE_GAMES_HASH("PREF_CACHE_GAMES_HASH"), CACHE_GAMES("PREF_CACHE_GAMES"), FORCED_LANDSCAPE("PREF_FORCED_LANDSCAPE"), FAST_FORWARD_MODE("FAST_FORWARD_MODE"), - FAST_FORWARD_MULTIPLIER("FAST_FORWARD_MULTIPLIER"), INPUT_LAYOUT_HORIZONTAL("INPUT_LAYOUT_HORIZONTAL"), INPUT_LAYOUT_VERTICAL("INPUT_LAYOUT_VERTICAL"), MUSIC_VOLUME("MusicVolume"), SOUND_VOLUME("SoundVolume"), STRETCH("Stretch"), - GAME_RESOLUTION("GameResolution") + GAME_RESOLUTION("GameResolution"), + SPEED_MODIFIER_A("SpeedModifierA"), + FONT1_URI("Font1"), + FONT2_URI("Font2"), + FONT1_SIZE("Font1Size"), + FONT2_SIZE("Font2Size") + ; diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java new file mode 100644 index 000000000..2429ad646 --- /dev/null +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java @@ -0,0 +1,308 @@ +package org.easyrpg.player.settings; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.DocumentsContract; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.documentfile.provider.DocumentFile; + +import org.easyrpg.player.Helper; +import org.easyrpg.player.R; +import org.libsdl.app.SDL; + +import java.util.ArrayList; +import java.util.List; + +public class SettingsFontActivity extends AppCompatActivity { + private LinearLayout fonts1ListLayout; + private LinearLayout fonts2ListLayout; + private String[] extensions = new String[] {".fon", ".fnt", ".bdf", ".ttf", ".ttc", ".otf", ".woff2", ".woff"}; + + List font1List; + List font2List; + private final int SIZE_MIN = 6; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings_font); + + fonts1ListLayout = findViewById(R.id.settings_font1_list); + fonts2ListLayout = findViewById(R.id.settings_font2_list); + + SettingsManager.init(getApplicationContext()); + SDL.setContext(getApplicationContext()); + + // Setup UI components + // The Font Button + Button button = this.findViewById(R.id.button_open_font_folder); + // We can open the file picker in a specific folder only with API >= 26 + if (android.os.Build.VERSION.SDK_INT >= 26) { + button.setOnClickListener(v -> { + // Open the file explorer in the "fonts" folder + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getFontsFolderURI(this)); + startActivity(intent); + }); + } else { + ViewGroup layout = (ViewGroup) button.getParent(); + if(layout != null) { + layout.removeView(button); + } + } + + configureFont1Size(); + configureFont2Size(); + } + + @Override + protected void onResume() { + super.onResume(); + + updateFontsListView(); + } + + private void updateFontsListView() { + fonts1ListLayout.removeAllViews(); + fonts2ListLayout.removeAllViews(); + + boolean font1Selected = false; + boolean font2Selected = false; + + scanAvailableFonts(); + for (FontItemList i : font1List) { + fonts1ListLayout.addView(i.getRadioButton()); + if (i.isSelected()) { + font1Selected = true; + } + } + for (FontItemList i : font2List) { + fonts2ListLayout.addView(i.getRadioButton()); + if (i.isSelected()) { + font2Selected = true; + } + } + + // If no font is selected, select the default one + if (!font1Selected) { + font1List.get(0).setSelected(true); + } + if (!font2Selected) { + font2List.get(0).setSelected(true); + } + } + + private void updatePreview(boolean firstFont) { + ImageView imageView; + String font = ""; + int size; + if (firstFont) { + imageView = findViewById(R.id.settings_font1_preview); + Uri fontUri = SettingsManager.getFont1FileURI(); + if (fontUri != null) { + font = fontUri.toString(); + } + size = SettingsManager.getFont1Size(); + } else { + imageView = findViewById(R.id.settings_font2_preview); + Uri fontUri = SettingsManager.getFont2FileURI(); + if (fontUri != null) { + font = fontUri.toString(); + } + size = SettingsManager.getFont2Size(); + } + + byte[] image = DrawText(font, size, firstFont); + if (image != null) { + Bitmap bitmap = Helper.createBitmapFromRGBA(image,304, 16 * 6); + imageView.setImageBitmap(bitmap); + } + } + + private void scanAvailableFonts(){ + font1List = new ArrayList<>(); + font2List = new ArrayList<>(); + font1List.add(new FontItemList(this, this.getString(R.string.settings_font_default), null, true)); + font2List.add(new FontItemList(this, this.getString(R.string.settings_font_default), null, false)); + + Uri fontsFolder = SettingsManager.getFontsFolderURI(this); + if (fontsFolder != null) { + for (String[] array : Helper.listChildrenDocuments(this, fontsFolder)) { + String fileDocumentID = array[0]; + String fileDocumentType = array[1]; + String name = array[2]; + + // Is it a font file ? + boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); + String lname = name.toLowerCase(); + boolean fontOk = false; + for (String ext: extensions) { + if (lname.endsWith(ext)) { + fontOk = true; + break; + } + } + if (!isDirectory && fontOk) { + DocumentFile fontFile = Helper.getFileFromDocumentID(this, fontsFolder, fileDocumentID); + if (fontFile != null) { + font1List.add(new FontItemList(this, name, fontFile.getUri(), true)); + font2List.add(new FontItemList(this, name, fontFile.getUri(), false)); + } + } + } + } + } + + public static boolean isSelectedFontFile(Context context, Uri fontUri, boolean firstFont) { + Uri selectedFontUri = null; + if (firstFont) { + selectedFontUri = SettingsManager.getFont1FileURI(); + } else { + selectedFontUri = SettingsManager.getFont2FileURI(); + } + if (fontUri == null && selectedFontUri == null) { + return true; + } + else if (fontUri != null) { + return fontUri.equals(selectedFontUri); + } else { + return false; + } + } + + class FontItemList { + private final String name; + private final Uri uri; + private final RadioButton radioButton; + private final boolean firstFont; + + public FontItemList(Context context, String name, Uri uri, boolean firstFont) { + this.name = name; + this.uri = uri; + this.firstFont = firstFont; + + // The Radio Button + View layout = getLayoutInflater().inflate(R.layout.settings_soundfont_item_list, null); + this.radioButton = layout.findViewById(R.id.settings_soundfont_radio_button); + radioButton.setOnClickListener(v -> select()); + if (isSelectedFontFile(context, uri, firstFont)) { + setSelected(true); + } + + // The name + radioButton.setText(name); + radioButton.setOnClickListener(v -> select()); + } + + public void select() { + if (firstFont) { + SettingsManager.setFont1FileURI(uri); + for (FontItemList s : font1List) { + s.getRadioButton().setChecked(false); + } + radioButton.setChecked(true); + updatePreview(true); + } else { + SettingsManager.setFont2FileURI(uri); + for (FontItemList s : font2List) { + s.getRadioButton().setChecked(false); + } + radioButton.setChecked(true); + updatePreview(false); + } + } + + public String getName() { + return name; + } + + public Uri getUri() { + return uri; + } + + public boolean isSelected() { + return radioButton.isChecked(); + } + + public void setSelected(boolean selected) { + radioButton.setChecked(selected); + } + + public RadioButton getRadioButton() { + return radioButton; + } + } + + private void configureFont1Size() { + SeekBar fontSize1SeekBar = findViewById(R.id.settings_font1_size); + fontSize1SeekBar.setProgress(SettingsManager.getFont1Size() - SIZE_MIN); + + TextView t = findViewById(R.id.settings_font1_size_text_view); + + fontSize1SeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + SettingsManager.setFont1Size(seekBar.getProgress() + SIZE_MIN); + updatePreview(true); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + t.setText(String.valueOf(fontSize1SeekBar.getProgress() + SIZE_MIN)); + } + }); + + t.setText(String.valueOf(fontSize1SeekBar.getProgress() + SIZE_MIN)); + updatePreview(true); + } + + private void configureFont2Size() { + SeekBar fontSize2SeekBar = findViewById(R.id.settings_font2_size); + fontSize2SeekBar.setProgress(SettingsManager.getFont2Size() - SIZE_MIN); + + TextView t = findViewById(R.id.settings_font2_size_text_view); + + fontSize2SeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + SettingsManager.setFont2Size(seekBar.getProgress() + SIZE_MIN); + updatePreview(false); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + t.setText(String.valueOf(fontSize2SeekBar.getProgress() + SIZE_MIN)); + } + }); + + t.setText(String.valueOf(fontSize2SeekBar.getProgress() + SIZE_MIN)); + updatePreview(false); + } + + private static native byte[] DrawText(String font, int size, boolean firstFont); +} + diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java index 5c7e54cdd..37e4aad8f 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java @@ -8,12 +8,14 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; +import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import org.easyrpg.player.R; import org.easyrpg.player.game_browser.GameBrowserActivity; import org.easyrpg.player.game_browser.GameBrowserHelper; +import org.libsdl.app.SDL; public class SettingsGamesFolderActivity extends AppCompatActivity { private GameBrowserHelper.SafError safError = GameBrowserHelper.SafError.OK; @@ -22,6 +24,7 @@ public class SettingsGamesFolderActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_settings_easyrpg_folders); + SDL.setContext(getApplicationContext()); safError = GameBrowserHelper.SafError.OK; @@ -78,6 +81,8 @@ public void onCreate(Bundle savedInstanceState) { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(GameBrowserHelper.VIDEO_URL)); startActivity(browserIntent); }); + + DetectRtp(); } @Override @@ -87,6 +92,8 @@ public void onResume() { if (safError != GameBrowserHelper.SafError.OK && safError != GameBrowserHelper.SafError.ABORTED) { GameBrowserHelper.showErrorMessage(this, safError); safError = GameBrowserHelper.SafError.OK; + } else { + DetectRtp(); } } @@ -110,4 +117,52 @@ public void onActivityResult(int requestCode, int resultCode, Intent resultData) intent = new Intent(this, GameBrowserActivity.class); startActivity(intent); } + + private void DetectRtp() { + Uri rtpUri = SettingsManager.getRTPFolderURI(this); + if (rtpUri == null) { + return; + } + + RtpHitInfo hitInfo = new RtpHitInfo(); + DetectRtp(rtpUri + "/2000", hitInfo, 2000); + + TextView res = findViewById(R.id.settings_rtp_2000_result); + String rtpText; + if (hitInfo.version == 2000) { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_found); + rtpText = rtpText.replace("$YEAR", "2000") + .replace("$NAME", hitInfo.name) + .replace("$FOUND", Integer.toString(hitInfo.hits)) + .replace("$MAX", Integer.toString(hitInfo.max)); + } else { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_not_found); + rtpText = rtpText.replace("$YEAR", "2000"); + } + res.setText(rtpText); + + DetectRtp(rtpUri + "/2003", hitInfo, 2003); + + res = findViewById(R.id.settings_rtp_2003_result); + if (hitInfo.version == 2003) { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_found); + rtpText = rtpText.replace("$YEAR", "2003") + .replace("$NAME", hitInfo.name) + .replace("$FOUND", Integer.toString(hitInfo.hits)) + .replace("$MAX", Integer.toString(hitInfo.max)); + } else { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_not_found); + rtpText = rtpText.replace("$YEAR", "2003"); + } + res.setText(rtpText); + } + + public static class RtpHitInfo { + String name; + int version = 0; + int hits = 0; + int max = 0; + }; + + private native void DetectRtp(String path, RtpHitInfo hitInfo, int version); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java index 006977f20..924c3b4d6 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java @@ -76,13 +76,13 @@ public void onNothingSelected(AdapterView adapterView) { }); fastForwardMultiplierSeekBar = findViewById(R.id.settings_fast_forward_multiplier); - fastForwardMultiplierSeekBar.setProgress(SettingsManager.getFastForwardMultiplier() - 2); + fastForwardMultiplierSeekBar.setProgress(SettingsManager.getSpeedModifierA() - 2); fastForwardMultiplierSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { - // The seekbar has values 0-8, we want 2-10 - SettingsManager.setFastForwardMultiplier(seekBar.getProgress() + 2); + // The seekbar has values 0-98, we want 2-100 + SettingsManager.setSpeedModifierA(seekBar.getProgress() + 2); } @Override diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java index ae94fcbfb..b13979b2f 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java @@ -24,6 +24,8 @@ public void onCreate(Bundle savedInstanceState) { audioButton.setOnClickListener(this); Button inputsButton = findViewById(R.id.settings_main_input); inputsButton.setOnClickListener(this); + Button fontButton = findViewById(R.id.settings_main_font); + fontButton.setOnClickListener(this); Button folderButton = findViewById(R.id.settings_main_easyrpg_folders); folderButton.setOnClickListener(this); } @@ -41,6 +43,8 @@ public void onClick(View v) { intent = new Intent(this, SettingsGamesFolderActivity.class); } else if (id == R.id.settings_main_input) { intent = new Intent(this, SettingsInputActivity.class); + } else if (id == R.id.settings_main_font) { + intent = new Intent(this, SettingsFontActivity.class); } if (intent != null) { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java index fdcf39857..0c414d7ba 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java @@ -37,15 +37,18 @@ public class SettingsManager { private static int imageSize, gameResolution; private static int layoutTransparency, layoutSize, fastForwardMode, fastForwardMultiplier; private static int musicVolume, soundVolume; + private static int speedModifierA; private static InputLayout inputLayoutHorizontal, inputLayoutVertical; // Note: don't store DocumentFile as they can be nullify with a change of context - private static Uri easyRPGFolderURI, soundFountFileURI; + private static Uri easyRPGFolderURI, soundFontFileURI, font1FileURI, font2FileURI; + private static int font1Size, font2Size; private static Set favoriteGamesList = new HashSet<>(); private static int gamesCacheHash; private static Set gamesCache = new HashSet<>(); public static String RTP_FOLDER_NAME = "rtp", RTP_2000_FOLDER_NAME = "2000", RTP_2003_FOLDER_NAME = "2003", SOUND_FONTS_FOLDER_NAME = "soundfonts", - GAMES_FOLDER_NAME = "games", SAVES_FOLDER_NAME = "saves"; + GAMES_FOLDER_NAME = "games", SAVES_FOLDER_NAME = "saves", + FONTS_FOLDER_NAME = "fonts"; public static int FAST_FORWARD_MODE_HOLD = 0, FAST_FORWARD_MODE_TAP = 1; private static List imageSizeOption = Arrays.asList("nearest", "integer", "bilinear"); @@ -76,11 +79,15 @@ private static void loadSettings(Context context) { forcedLandscape = sharedPref.getBoolean(FORCED_LANDSCAPE.toString(), false); stretch = configIni.video.getBoolean(STRETCH.toString(), false); fastForwardMode = sharedPref.getInt(FAST_FORWARD_MODE.toString(), FAST_FORWARD_MODE_TAP); - fastForwardMultiplier = sharedPref.getInt(FAST_FORWARD_MULTIPLIER.toString(), 3); musicVolume = configIni.audio.getInteger(MUSIC_VOLUME.toString(), 100); soundVolume = configIni.audio.getInteger(SOUND_VOLUME.toString(), 100); + font1Size = configIni.engine.getInteger(FONT1_SIZE.toString(), 12); + font2Size = configIni.engine.getInteger(FONT2_SIZE.toString(), 12); + + speedModifierA = configIni.input.getInteger(SPEED_MODIFIER_A.toString(), 3); + favoriteGamesList = new HashSet<>(sharedPref.getStringSet(FAVORITE_GAMES.toString(), new HashSet<>())); gamesCacheHash = sharedPref.getInt(CACHE_GAMES_HASH.toString(), 0); @@ -103,13 +110,12 @@ public static Set getFavoriteGamesList() { public static void addFavoriteGame(Game game) { // Update user's preferences - favoriteGamesList.add(game.getTitle()); - + favoriteGamesList.add(game.getKey()); setFavoriteGamesList(favoriteGamesList); } public static void removeAFavoriteGame(Game game) { - favoriteGamesList.remove(game.getTitle()); + favoriteGamesList.remove(game.getKey()); setFavoriteGamesList(favoriteGamesList); } @@ -197,16 +203,6 @@ public static void setFastForwardMode(int i) { editor.commit(); } - public static int getFastForwardMultiplier() { - return fastForwardMultiplier; - } - - public static void setFastForwardMultiplier(int i) { - fastForwardMultiplier = i; - editor.putInt(SettingsEnum.FAST_FORWARD_MULTIPLIER.toString(), i); - editor.commit(); - } - public static boolean isVibrateWhenSlidingDirectionEnabled() { return vibrateWhenSlidingDirectionEnabled; } @@ -324,6 +320,15 @@ public static Uri getRTPFolderURI(Context context) { } } + public static Uri getFontsFolderURI(Context context) { + DocumentFile easyRPGFolder = Helper.getFileFromURI(context, easyRPGFolderURI); + if (easyRPGFolder != null) { + return Helper.findFileUri(context, easyRPGFolder.getUri(), FONTS_FOLDER_NAME); + } else { + return null; + } + } + public static Uri getSoundFontsFolderURI(Context context) { DocumentFile easyRPGFolder = Helper.getFileFromURI(context, easyRPGFolderURI); if (easyRPGFolder != null) { @@ -333,27 +338,93 @@ public static Uri getSoundFontsFolderURI(Context context) { } } - public static Uri getSoundFountFileURI(Context context) { - if (soundFountFileURI == null) { - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - String soundfontURI = sharedPref.getString(SettingsEnum.SOUNDFONT_URI.toString(), ""); + public static int getFont1Size() { + return font1Size; + } + + public static void setFont1Size(int i) { + font1Size = i; + configIni.engine.set(FONT1_SIZE.toString(), i); + configIni.save(); + } + + public static int getFont2Size() { + return font2Size; + } + + public static void setFont2Size(int i) { + font2Size = i; + configIni.engine.set(FONT2_SIZE.toString(), i); + configIni.save(); + } + + public static Uri getSoundFontFileURI() { + if (soundFontFileURI == null) { + String soundfontURI = configIni.audio.getString(SOUNDFONT_URI.toString(), ""); if (soundfontURI.isEmpty()) { - soundFountFileURI = null; + soundFontFileURI = null; } else { - soundFountFileURI = Uri.parse(soundfontURI); + soundFontFileURI = Uri.parse(soundfontURI); } } - return soundFountFileURI; + return soundFontFileURI; } - public static void setSoundFountFileURI(Uri soundFountFileURI) { + public static void setSoundFontFileURI(Uri soundFontFileURI) { String st = ""; - SettingsManager.soundFountFileURI = soundFountFileURI; - if (soundFountFileURI != null) { - st = soundFountFileURI.toString(); + SettingsManager.soundFontFileURI = soundFontFileURI; + if (soundFontFileURI != null) { + configIni.audio.set(SOUNDFONT_URI.toString(), soundFontFileURI.toString()); + } else { + configIni.audio.set(SOUNDFONT_URI.toString(), ""); } - editor.putString(SettingsEnum.SOUNDFONT_URI.toString(), st); - editor.commit(); + configIni.save(); + } + + public static Uri getFont1FileURI() { + if (font1FileURI == null) { + String fontURI = configIni.engine.getString(FONT1_URI.toString(), ""); + if (fontURI.isEmpty()) { + font1FileURI = null; + } else { + font1FileURI = Uri.parse(fontURI); + } + } + return font1FileURI; + } + + public static void setFont1FileURI(Uri fontFileURI) { + String st = ""; + SettingsManager.font1FileURI = fontFileURI; + if (fontFileURI != null) { + configIni.engine.set(FONT1_URI.toString(), fontFileURI.toString()); + } else { + configIni.engine.set(FONT1_URI.toString(), ""); + } + configIni.save(); + } + + public static Uri getFont2FileURI() { + if (font2FileURI == null) { + String fontURI = configIni.engine.getString(FONT2_URI.toString(), ""); + if (fontURI.isEmpty()) { + font2FileURI = null; + } else { + font2FileURI = Uri.parse(fontURI); + } + } + return font2FileURI; + } + + public static void setFont2FileURI(Uri fontFileURI) { + String st = ""; + SettingsManager.font2FileURI = fontFileURI; + if (fontFileURI != null) { + configIni.engine.set(FONT2_URI.toString(), fontFileURI.toString()); + } else { + configIni.engine.set(FONT2_URI.toString(), ""); + } + configIni.save(); } public static InputLayout getInputLayoutHorizontal(Activity activity) { @@ -391,11 +462,30 @@ public static void setInputLayoutVertical(Activity activity, InputLayout inputLa } public static Encoding getGameEncoding(Game game) { - return Encoding.regionCodeToEnum(pref.getString(game.getTitle() + "_Encoding", "")); + return Encoding.regionCodeToEnum(pref.getString(game.getKey() + "_Encoding", "")); } public static void setGameEncoding(Game game, Encoding encoding) { - editor.putString(game.getTitle() + "_Encoding", encoding.getRegionCode()); + editor.putString(game.getKey() + "_Encoding", encoding.getRegionCode()); editor.commit(); } + + public static String getCustomGameTitle(Game game) { + return pref.getString(game.getKey() + "_Title", ""); + } + + public static void setCustomGameTitle(Game game, String customTitle) { + editor.putString(game.getKey() + "_Title", customTitle); + editor.commit(); + } + + public static int getSpeedModifierA() { + return speedModifierA; + } + + public static void setSpeedModifierA(int speedModifierA) { + SettingsManager.speedModifierA = speedModifierA; + configIni.input.set(SPEED_MODIFIER_A.toString(), speedModifierA); + configIni.save(); + } } diff --git a/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 5310d6016..21a1c1d18 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -277,6 +277,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz + 0x0b05, // ASUS 0x0e6f, // PDP 0x0f0d, // Hori 0x10f5, // Turtle Beach @@ -285,6 +286,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa 0x24c6, // PowerA 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x3537, // GameSir }; if (usbInterface.getId() == 0 && @@ -358,6 +360,12 @@ private void connectHIDDeviceUSB(UsbDevice usbDevice) { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); + if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT"); + return; + } + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); @@ -583,7 +591,13 @@ public boolean openDevice(int deviceID) { } else { flags = 0; } - mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); + if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) { + Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION); + intent.setPackage(mContext.getPackageName()); + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags)); + } else { + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); + } } catch (Exception e) { Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); HIDDeviceOpenResult(deviceID, false); diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDL.java b/builds/android/app/src/main/java/org/libsdl/app/SDL.java index 44c21c1c7..139be9d15 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDL.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDL.java @@ -38,6 +38,10 @@ public static Context getContext() { } public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + loadLibrary(libraryName, mContext); + } + + public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { if (libraryName == null) { throw new NullPointerException("No library name provided."); @@ -53,10 +57,10 @@ public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, // To use ReLinker, just add it as a dependency. For more information, see // https://github.com/KeepSafe/ReLinker for ReLinker's repository. // - Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); - Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); - Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); - Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + Class relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = context.getClassLoader().loadClass("android.content.Context"); + Class stringClass = context.getClassLoader().loadClass("java.lang.String"); // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // they've changed during updates. @@ -66,7 +70,7 @@ public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, // Actually load the library! Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); - loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); + loadMethod.invoke(relinkInstance, context, libraryName, null, null); } catch (final Throwable e) { // Fall back diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java index e1f5e8434..77053ba71 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -62,8 +62,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 28; - private static final int SDL_MICRO_VERSION = 5; + private static final int SDL_MINOR_VERSION = 30; + private static final int SDL_MICRO_VERSION = 6; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -276,14 +276,14 @@ protected String[] getLibraries() { // "SDL2_mixer", // "SDL2_net", // "SDL2_ttf", - "easyrpg_android" + "main" }; } // Load the .so public void loadLibraries() { for (String lib : getLibraries()) { - SDL.loadLibrary(lib); + SDL.loadLibrary(lib, this); } } @@ -1004,8 +1004,8 @@ public void setOrientationBis(int w, int h, boolean resizable, String hint) /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { - /* All orientations are allowed */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + /* All orientations are allowed, respecting user orientation lock setting */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); @@ -1014,8 +1014,8 @@ public void setOrientationBis(int w, int h, boolean resizable, String hint) /* At least one orientation is allowed */ if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { - /* hint allows both landscape and portrait, promote to full sensor */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + /* hint allows both landscape and portrait, promote to full user */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java index d6913f157..9d8b20b7b 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -546,13 +546,15 @@ public void pollHapticDevices() { if (haptic == null) { InputDevice device = InputDevice.getDevice(deviceIds[i]); Vibrator vib = device.getVibrator(); - if (vib.hasVibrator()) { - haptic = new SDLHaptic(); - haptic.device_id = deviceIds[i]; - haptic.name = device.getName(); - haptic.vib = vib; - mHaptics.add(haptic); - SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + if (vib != null) { + if (vib.hasVibrator()) { + haptic = new SDLHaptic(); + haptic.device_id = deviceIds[i]; + haptic.name = device.getName(); + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } } } } diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java b/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java index d894c9791..0857e4b6f 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -354,11 +354,11 @@ public void onSensorChanged(SensorEvent event) { SDLActivity.onNativeOrientationChanged(newOrientation); } - /* EasyRPG modification: prevent movement to be input SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, y / SensorManager.GRAVITY_EARTH, event.values[2] / SensorManager.GRAVITY_EARTH); - */ + + } } diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png deleted file mode 100644 index 184d154f92246b5b599eb71bd6360fec92f915b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 970 zcmV;*12z1KP)YX$Gblm@opJmyqo{C4Ds^&Y_Rb1%czho~}sD1%b1`7r;yxB<6}hl)!~9GSL|U z+lzpkf%hu~+%W6!VcO}i#&>O2V zF#+3;0?z|W$9nr$;XB}ENnh5~AE`DG0oyx($AJauDPX6Nnm8;Z7+^M zNcyFeT%!!wehJu?&LZ-A2Z4|8Z66&KdE2+dk^{b#^jhZxa#SA6X%jX7f~0Dl{||%h z<&p;0yzLu-oxoNgbe@&;LkWrE4A|ZdMDvDx0PL^Ue2H`0(fkL1P5Iocq_<0KG$C-P z;&7Lwk4hX4=#ht`#Pah~l6IHaDRlK!eoqLjc2 z;7uUjL)JGfpB_NlHvpgI&#^b2mK5)`q?AD1H9=r`T7N*&Keap!QOZ8(4S^q%KN*>wXFDcP42FYzn z$LNQ^zNUSUlMy{hmxn}m<){*~y&gCMECep*A}cD%;u6X% zyqLeR#992u5b$gAY<_ac_e*k#1&NU3k`9-aV^adR0aqmbHZ1bFoYqRZzhB38fZq#j z$nWBadrng9fg)){V3Y^j*CG$B4ZSLXtoi$Zr(%bsojARtkD{N7rBwoX=*3xZ4SY%f zjs0;qurXgz^C1EL9Z9pr$n$~L2-vT_fO{%V9s&N8RQo)r zk;VU1S|utrO_a#JnU>&4i>U#zk?G?>osr4^Y?t+z3CN5||{ib)TIQ ss2i!S^rji;DzbI$PBTz9QeEj?1*@qvKo{-f!2kdN07*qoM6N<$g41lo(f|Me diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png deleted file mode 100644 index 23e0c73bf3ff06eed0160e43d4d838ea6cc891da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 803 zcmV+;1Kj+HP)1scCQ76XMR`94 zVuHMvmcGNWJNxc$-@WfWcjv6(IqO?{{nuLGTL1Nlii;}lKP>>w&R{D8tqc^5K&yn3 z0W`A#z>t){ewOsTV9@_LUU~-1Y%?%F#mP=dC;B>oHegnYlXoR;?6UyOY#7iEOiXd| z39v@ehq6GFT0&+v1vmo?O>yueuwK%`rU5Xs$-p$=o}||`M?&;h0DcF$lA}O}q)&Bc z#{e^c2a+D;0hcQSX6BJS2+RiF0GEL4z#w1?Fc&iU)lc9ia2)ssECiMTqk!AMZb_c0 zD9H<86|g;F*&TA}7xl%zNxfh9fzfRT}QPD?tOr5g)iJm3>_0Wh!&qwf4ZNw*s5n3;E}y%`|9 zGj#%OlAf0WAVkqg+S-r_Z<|T+vhfabzN?R#*~-w#8g;ENVGy>*lD?=6)Eo*-b2+f1 zK_;F7>uP0_2Exon0A~TuYQ1>m4*?fro%h4J0EA3T2TlQ=k$SlyXa1r!6XtX2Qz^ zl^C0YN;sX~Stga}Z8FyZ>@SVXg)*ovovxe2tAXUOath(Jw||}i1oZAg6S9z8DK7)p zfWcvsB*O*wn8aY_h~TzsQlw@wV&6sE_&Jj=U!;acA%)e7J#A=EPB3`fua#C hdcKu`q7f{5{uhG7B|zh8^(z1X002ovPDHLkV1oHoaXkP4 diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png deleted file mode 100644 index 85f7b680ec8dd97466e0268be1a3e3c87ce5b8e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 568 zcmV-80>}M{P)_-O?G+rj2QbD&UxRzigW7N<7{iX|*upgB+|U~zhp1FE_P zyo<H-A16O~M@Zb-D9S`2+-nuuZ6tf68 zm3;}E1DgSms1pOqJMd4yiM0faN~lZrfR2FefW)8^6O?}dF8^{ZR+iZg^hk_iPDGR+ zmw+!ig+f(gPWZTf%@-?QrGy4Z3_0P#y3?ks`C{d(6Yv|`9gqDz(K+qFS5tl}1OgJF z6ThGRX;V%4X%h&WR7b$n>{1$HwN2bRk@_1pETN=kmkPcrfyhzm{*&D7Ea0mVh&3q> zD5=?5!B-)W(xiM$wAtl=FD76a4=iJcg-rG+;fJ|1OOuK?aXQms&flc^<8)G!n&8B2 z$LWC!jT?!Ro78|4v%S#xjyu0s%Aj$r18+GuDKCP%*{zDmi)G`6vDuqA_lLFf8iCr1 zTGwk1SQV=s-yEo|sCB*OfK{>D@y&tSidxre4p;Y^a*8c*01$+d&1AO1+ za@vH@w!o>t5x`~zrT+r&0nY$GWNUW@P6G~wV)FP0cnf$I__gWG5`evd3jq)BA|CoM z@Ilyg5O5CQWpoiQ;T~Xp-KHi0djSguI5eK+Eeb->_>%YPZ1gg21-`6WPzPWeUQ-~T*wx2ow29;>Tayco)dwSvOit`UTSH)%k-#W z>|2atc6+4+;3B|VjPe+;ro+K5-4Zx2@q)Ma0Ptr=Jx2m(C42PV-6a5qj}I!!brm_i zqa6b;H+?Tnc3a#Gqm5D(Le~OYhZTm6fXou(5be|8n1X)?t|bDGA_8K)&*S z2%(*TtGkBK{J>Y@J|Ew^G8vx7sWQsKAimV{606s4P@PEmd+&_vTWGGFhSVs(@DE10B<_6SpAxYU~8G6IW zaGDG`1UNrC=%LUsP59hIr%%ram9+L|Kt?)Bk%=zOX#8iuEmifX3BXpsKM!?Q57tWk~hV|i#>H&I>c$n1YF&04^ZfxCdehA^~h>lVPp!2a2vj{|SkomB$h zKDl{HYyI|qNUdEMY7<_mf+_RhY|-n$lT91j05F~E1*Zt9ybV0s15eV1vw_1i{W8yc zOEkU>0QFiM7FD#EwuMg&0mAbwnzo~z+T-w;muc*H@J;nem`81sfrS}fHs-`Dtjrc?{0D}V^%l4LR$mHV)*)}`!Z zwz10Q4_2dZ>q=P$0ieMSynNWcjZ{prHtw5@Kf)sW3>*GDRm!uB`Bvk^^uR#@#5W-b zIeRK!ggWt4_Fb^%`F*nQ7Dp-aglR)D_``x>0I0&=SrBOha?1V5{jQTBbk}|7y zuQDnC8aXFaTW|isz+ihdUS*>Mpne&w06y*A@}O|k07hl^M!nMjMt;SG1ExJNZV&ti XRGrN+geHsV00000NkvXXu0mjfd5$c* diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png deleted file mode 100644 index e09bac46e14b18d2e15a23d46feb31f435f77d03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1353 zcmV-P1-AN$P)$z)2gp{tN+F z-yR(Dzy{-ih!_D(2gU*W0^29$e*iuJUI7+K`lJ=8h}a9528;&w0d`3IKLYOqF9HiC zt*B$1$`Fc(ZGjtr(|}DXgsuUW0GCMmGUttm;lNG6iNHn`a{dM$0Om^isTFTa01EJ^(yw~gw zdtkJr_Zl%Y1P~Eh1MdL4mkxajEC7}PD`9ONF^}KH*}%vW|DWmaVWr;@bS%mMZ+@tfx$NxzgqGy`xOa9)Y|7T{7zg;M=KN5sa!g}}AIrcKQ0vzGz4O8Tb} zPed5(b-+cX@AD;{UkM;0b_SdQa;x1aX>Of3iiioolMO)p1spHw*_v{$_an}D4Ss~A zZ}Ri&cpxG!PT8X1bL@?h#5 zO{ugHxl)n~YD)mCfGrY-!)BNyXNjIuMA*QN{i1-bmb7IR0H><05|cB90l32UoDu+s zpEI0km83$qwLdfpp^pLgEB}WO+6BnNcZH;WwW$$+$AQTifGLs|_uTw_$>3z*vFz?; zlEzns&}`t2jA@Ca98k}&L`2z`^CVqa1z^YIwxW<7#B;L(lhP*ba3c6gbG?NY5%eg&3Yj>Wv;+E`pJ^q>vx?ZVp5tb^1G%> zT2u=_@`9T;H)jR8tvyuIXWc{)5qkg*le~?89XPyEJ!&-%xR}`sdF5LU93jaAOUEf9 zJn*~*?3W35`*DaQkFD!a)kHQn_5S$?_%bj-((j!_5D}XJ?o^K|$vsn2-VgQrTxTXO zx3w1V1aMM29cnAEsq=KoJfXz5K+?Ib&ej3ohx`I?WJ%D2!0Cf+Ga|y?y&sralD{;~ z4=vmHN&x9WyEeShV8px!IA@^EUg~x30?sNa??*gV((0-(tpy-~*c*5?+v60n4Pth? zo=BFzvw`v-+S`)8uTtLMYXe9i4o;7{ys2>En^pHsq?q@RRBSB@T|J9f`P%bGTL3Z; z&jZ_)EaZnhwJJNMZ1YHi2kRZs7)hVkv3$YPHh=`e3onE4+sNr9D<5l=KJv-D9011S9nT57$g;KWz7qnY9n#t@K zfCR$ZgZVCO4KDO*<|;|^25a^Fd`AIf@4^dQuMrFRUeC;tRd0%{!%O zlAQlLPF(|#K=}V)Yb3dc>I!ugKvy&_2^|8^3oEy}W5@&R+XMds%W9lJJabCs00000 LNkvXXu0mjfi%WTs diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 2e1df8b4dec074925f19f342d6f37db2095f9236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 852 zcmV-a1FQUrP)= zMd?4_K5!ZMm{>a)SOiP~Yw`FC+ySlt-}7wR2#f;O0y17iWb_>H&}^CntN`SUA|fZ8 z0$yfq$|Eom*j&PBWJ_KY35melqL+nH&Nu?RN?VXcpdYXu7+S<#DQo|j-+FrLc@G={ zekAo|5m*Au@iZEG`z_$23xPqvPQce|p$RCO`+(1J#c2_kYpO+P03r0Ly4|o5SO-iD zVXXokkARal0!kkpT1^xkZc3wWNlT$UK;I^!2Yv#(+L?gL=x!T&LLADq8Vk4mccbGeWlX$2{h%Se@WmJEv;aJ%q z^z!~e;H}GDNNaRHYyrkJgv3qYQW}=Len9QsHlTOSBy{>70DcuEp!G9>Wi=8|i+arT z$*C}GEu}Us1!mVo;=1XJGtIjYP%F5mh8yZL;3yDvb)_SqjeUTPfX;|kh+4?Q$(mp0 z8;I}G{6>@AoS5bw<0$SiW1N^O)55(m=)PD zX)<@b2>9s0vMEmcUlGtZ;X8JTbnAX2&u0#>vOt!JK%`643thc6C-sp}2E0?wCk^=! zkS+~)*LzeaH7N4D(;(Nk3T1g&C3ba*Cg=rl!lhK^h1WL%k?vl=a$u@sE4NYQCw-br1;|fjFR9<`Ni<)OD-8 z3S0|-U|uO5%*{=RN{>piQd=ef@RfBx6G?sH$~Jfe%6x_JC_3;5m{ z?9M=!$$*)S0NQ|Az&K!F2>uCh8#oViNV=OQ%FKQSmI5<@vB03P{wZ(^I0Ljx`Y+Gs zWF8?i8vtwumIJ*qKtBRUf%TH^H?B3aVZc^k5zr$8=YL>7utC!Ew6&=M%xn^H9B}d? z_5xTY>4ZC(*?iy-@M94Wz&Bxmr0Yq3i2}@QVmKC_fimX1xGXpx%#pn$4$r_$NjF+8 zXeq$V`UBU1k!2!p@Oc+L_pXTZFECls%VwM;0Xu=!6-i^e?~=48Q-GNb0lWfK-L=>P zJewmVJ!}O2Dg$P=E>w%y0mQ)b>b5OQz)4_U47M6DPD%2L_Ssrxz>m-H8pvZn-jn1n z)Kmelfxa==YQT6Ssb7|WSHKsGY6zq(K8qs&Vq_wT^()xihz!l(!h=G)Wx(7^< z^rn!2aFkX8`^vCJ^6?gRjwJ7sTUwr;csKBSq=1UTY>~7f%_)xnZ{)o%o?Dc?;`)m| z0g>bnaH3@j2uH~;cYg!^j`$Yl*O_0MYkuKppwYq11_KuWf4J*3pEJ#n3!mpMFSsR4Ve|R9Qd}(FZsHEXf(nIbfkA@4qWq6ahhmkIZ)ft1=?;Y_&;xTPgCG z8E6c|%)A}_GoiwHJGffXhZw<^E~8oGX7($v2k05r8n98)rYzs0zehVl&BSK55I7wE zjaeb7qiAq>*4HK=JQveF3zGc*vs$cH0kuTj1lld&TkLWgM|TFkcLqKI?5pZAgvNvy P00000NkvXXu0mjf+Sjc7 diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png deleted file mode 100755 index dffd0a4fe30c98ae106624f9bb4ed4aae99d5c18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1074 zcmV-21kL-2P)CD}b)+?qQ6*+lZ7>>aLXXG5}Dgh<7Z7Mn^|CM^SW= zh&DBH4iTR?j&o{qa&m6X5fDNQA>s)la+(l`i0E_>1T$-n0O$NY#@I2FV@jzDVHn;r z>DkIVmO|NVcDLg=`%Q+UD7uu(<^DA3*~+a5jE#*YN~Kb|QmGWDr>Bec;GX9tiRcgj zd|+YtdS74PcM}s6)q36G;o(#=nM@4~3>21=FNbYKfO9^^7(0lFiiqw2z*R)d`@UcC zJWmkOJ^Hh4w?BO@aldwP0~6H%%T zu!M+nMD&g}YGbTUM*+Y+0N4Tm150I^;KIVfnW?F%e@wVW!$Jrh-A_$GU6B6~1i{ZH z+@=IL=WjE{z5swXOh8?bKUGSd4a4wZGwfyr9M1Vs#@J{VoU?)?qMK66Zvh}`c(*hX zxURd4G4@%ri>Zkhx>P6;(Njd!tEY3^n6CABh!`oQE=eh`v`jz<@f84UvlD84gkRWjf^7V?Q*$%er9H7-b7$%XsEZpzki&F^rf}j z(EI0lu~__Rc6Roe3D0(8G1qm+5i#8W@V6icem5Db$%PO)HTCbMK|}>9<#_wa{78TecwNAi}l(Nh;ItTdK1U`7>)f!)=nug zKR998SpP6zPVVASf6+`;%yak0I=%i zv{Gs^48w0$R8H$CWHOm;iA3V_)q2oWtJQP)eEvaOwNR{|l$v?7M?DeEA>v9O^+ZJX z5pi=@AN88V(zXgB-cd@`+A38cgaClOEw)wk3H3{TTZM?W6A|lZTSXJljjeLIJgggA zOOMA|ZnoOkLd5TNPpLk)yVlsUsSMY2Yte>4?3=r>Z?DJx1BZSu$NCeK%|7XdPRj*W sH=Jk(bDcmt(d)$A^%dwu6x+dg4v@VqPk6iB<^TWy07*qoM6N<$f^vTOssI20 diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png deleted file mode 100644 index d2b6e3e698c2fde173ffb40cc5f3aec7ee328018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 813 zcmV+|1JeA7P)Rc-+=*8)FfbxFuOyTV z6bTcOiO0aeV`;HJoj%`p&i>AKy6oINI(63TzaMM=*V-cSp~T~72*BT7!Ae)a%&r6T z3P9bJw74qZl>sobF~IxCz&a&;h?LD0s}jI$;C^IKb0j^8lpQnxtATToL9LN=K2mnj z0PF?!M+UV|(t$|XK?86aSRWZwzoZS3vh4vdvn9Y8;2O{i3`nw?RWln6>;ZNG{v9(u z0*8Tvl71h2GxIaK2-pI+-*uAQZ_|-`h|R16cmzz(Sa=4U1p0ybz;R%FOwXvk zs_xiF6%%m_=*nh5H{1xo3gBWJ!9NcN{|fLb*qI0{1G<4JZP;EZ=~7m|5rE|ZK$#5F z=V?*W=SD%A*+}3hu(1RP&r}ZJaxMV>X=Z+zIIkJkf}=i3n_}e6>{N>&{JwDhG*Ea3 zY65090k{AxhzQEJ&5UL~Dq%2>gwOShoO(1&zO z%CHJ)7xe)|x^0yJAg{6It;DWSVYO2DWck?}z-jdzz*~*gHnsu?Z}FW4t~O~cdf&)v z-7>Ak9!bu#evZ1<;!|k0wldT1B8LpG*1`DkP61PSG>8J9RwIIzPh7b zJflYMsk(bE^P@Beu4=(eGmA7oyR_x~a$D?{a5p37g|Cs|p&E~Q4@@WZu3cW0RzKfl zOG4W1`7gJ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index f7d4fecf5e5e5e9a043874a1920224a8950132a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+Y)==*kP61P*9>_N7zj8A`fI*b z>L~H>cYL6fF)d0isGhlE*V_p}Ry(;{*|zUiT=i|jwCoGV1-}%Q6qOWB$y;b(Hlyb0 zkz1V~-`|+t#L6wE6R{zoN{7FnBl?{B)+aZPWHYyl#hDrwxI<~ONcH0-rE4w(k#dIPzR31sQy|!iI zt*l8ClTRJE8W78!z3Oj)kEw4R^V!{nISsus_DiKY{+<^MZ7^gHVLc`jz$#JTAewx} z@age)_HOmfKcfGi>%UbHds9E@f5*=H3ZtWQ${$uHNkl9LM4B;APNAOe8{k>Ka6ZdoY+NjnQBvqLY`~Fj)kHsiE3P+(RRg*h+08!O+I( zho6SjasKC2x=Ei$>e7EgnjA%{)Oq895;;7zU`O%rIKd~#=a8g6u?vdBGOxHivmmj@ z2SsC(rUrR3c=XakiVOw5T<}RzSf;7Wqym`bmKJiu;}uh4Kp`07j&E|~7*l3Iq1dB^ zTu9>s4JH+eU4D=YIZR%m*dahJ6fqfvV4Gj$LKWkwQ`9lN>J)WMpE|`oCaq3U#4ITW zE0|Ncu!T8O42qaFxsbxgOeq30m^#C9;|Oz2LJkaagV|RUlDuFpNhkz^Twxv=Q8Y3% zT{Eo!X1Kxl%qbr0d^sn}1pV|eL6!nd4OZ127J0_yiFvh;aSrhbdgFjm{S`?zDNvzC hjS6RMFs3g3C-2Jk?*l<_)DHjv002ovPDHLkV1oL9pSu76 diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png deleted file mode 100644 index 97ded33b5558a0efbd7812296a52325b4170076e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 460 zcmV;-0W=4>kIV21T<&3Kg>clSSXlU}7 zNsTbZb7CDT8exhj%*e(#KY#)6xy}u?uzckjH(4PAzEPEh1-w0q?BPAtVVvU+$MuUd zim^-?Z*&k19JY)eT?W{4>I$)hC8bSG`$%Zf$FisplYAzY(o}#3ePW-OP>dHiLTyE8 z5z6Vv#(lhunj$1*cz0EVJ9s@A*ucB3BFy1+WndHUmWuEIZ$P35H8Q+=vQXg_LqaV@ zm?z|TsTk9IA=am%09Qzftuv(%OIZ4}NVJa{^Q2f76=H@QTgC>PWY~7OsDpS*8Lt)N zEWbIfpG@m8p5YBC;_%wCP~|(2vdmRx>0#;8WR`dQ1-2NIjXDoGtr048h%M+pCV0dp z9SxU>omUy0Vn}G0s>)%9(6-8A4Tr6%Lb0000A&Mw*{LoajC}9rmr4{gKb*Yr%t0Rc$4w2XeQa|K z6K8Ht?mJV|?`tXaHh$@jdb7L#EgyMos-OBH-LF0`z{l{YNHB!h_$TB|tY~*pzpE;5 wb8FwuO8yVUdIo#$gbB-qJ-W2CrX_-LqT}Vi366%6K*uq7y85}Sb4q9e0MQvwbpQYW diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index 757ee1a0775ccf77f075c3c4c2aebad039a29f75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 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 diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 4014ef3bfc1c0561923e1ea54b080f6e31c752de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 779 zcmV+m1N8ifP)QnDEqE_)*} zWk|xXz@uk6^z;7S%Xw$~-exh+YUcNz^PJE5KIc5ob3`9A^)Vj$fr=uMtB{!u24(>> zfw90K;4|bI2h0T~0mFeGz&qeM@J!P8EU-)Bz^G(%xoC29cTtd0Yk$k3~-1~z$0L)d88AtFQ=Y1;!Le{0q1O>D{tBpk&OM+>k}H-9v;w+#ez@7?${Oa$M zbSMjGA#gLK{3^IQfMt?iWC3~oISZ_)fX3B14eXHAl?voPn%R2bWGJYO%;t>&naqZiE)LH(cITt^@8T&rRVO zO0H^V^MP}rM=twP@i2Ms-7M)&(T<{=o7uq7v>gYg7CB9PZwGb(*OKjO>5$b4n5O7=uV>)CAfL!;67L(od4GVlBTMMMmNN406& zvaAP@08G<7Cxp0;h>K%ci)eGsUjjf_fgxX}Y2GFxecU@D+GLEq;hb+o0thdw)oP~G z>0AbYD_YP)Y*kh=DRQA_Fs}6!JGyQf6GbS1Dy?AVy}&fDr>E|CA_6 z3i%1ifTdU8(Z1*Iea_kUa{5?xCg*&6t#^IvUGG}^h|Elx;o<)P6cJA3EU=;ChA#k{ zB}Lr|4C{s>ViIs2m|GF>39wSqhi(Cy2V4SXRs?(jtdsP(TY%O8=YVMy0p9?dBwhJq zfFfckumZR$=~2URL`(+u0lR^*6#>5j$ALac?+VY(2UY_&B;6@{wvx=mQo43$`kh!_JLPW*7${-(KH=Gg&B-x`4i-B2R( zMPPb|NKD?>z^8P60?^Zf?9Fap0qctdZw6%I^>@e<6Lui!(eo5(mh~rb&>hvz%&lFL z8Vxg;pb;?xI09@0T9wRTwg9)9$3q$`%YZXLD^M=kd0@Y!m!&21mk}`zI0S43CYGe= zGVK9QRn4jdY8!B@qI@5L)4)MVUs`rq0_7GpppzvxwAw*kvY(X2a*B|R)l=-&X8)H;unWku@j+CCz( z6GrbqS&fdohHDbVeAbVGO6RknB~WE6tS7s__X_Kw*~tp~Ak96yb&~a0*oK@(L@%(s zWXYeB_T)~M{G*cI7koCB{F{+ iBK1fDwtF@^KtBOQB_}jIrkh0o0000=Dm!pL5PtJwWQDwD%^7mKX72vfc*YT)xac;A{}-v*=ps zr9JO5s)Jrvt$1~wJ<6?Hv?}9?*Yy)i*G<&jSt@L~vF4xM{qD^_8RvC6y^4F&G81Sm NgQu&X%Q~loCIAp8KMVi> diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 4f90c63104aba086b4dc5c6a69a7720fc5c262b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8pr?ytNCo5D%Nuzc3`CeOMhm#+ z9ND4bJws08z%M74Pdn@9dIoR5bn&cNTv<|$*NV0vQPt$;Fy_uU)|bpLG8>s#KP{}^ lTOV=e+)_958Lge(>`jQFBkv- diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index 16d5090559a1a03a06320219fc0bc3922f76d4c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8o~MgrNCo5DD;xP*4LDjKe$MEz z36r1w*6P*y1TDpK&Gwa?e}zJ>%#8fCG>Vz`i{&x~VI$GU{8~?{lQ-xDnkVlR=W~1D z8vbEQ^?k*lnK#=#8Z{l3ocQAWckx&IH@{xL|F7^``_ucSbL~@3IjBEly0yRJ!ObZJ Rwm_R1JYD@<);T3K0RZK*L6iUh diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 97e42b5251a984f151a3fc469c583c360763aa17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iqn<8~Ar^vn58C=20?J;<7d+)D z!|^{xU``;1V%Y-~zhmA4){=|W3l^Ae^mp#jTxxh`k9c8Y&l00n|Cv_|_|7cq>|2|> zG$hGy&ZUnnds{bdo7Op%Nx$xhX7rv{ZmkRX*#pyC_U6eHJ`TU)xv@j}**}-LJdalx z1PhjOZFyV+!t)F}KkqyqYqH^3?iERmr&WQ;3qCxx^7H(-OkVWYy^TF;Lj5OneM?oA zg%#QC=t>h>85EkiR`kE&hK-Z8-{gFG#8k`7Qt>7y-mx$k=phDAS3j3^P6>O6 zS70QgNAU8v+(?*#6#o9*V@~mgC;x#Rf^DRK!rxsIH1Y+{1W$v50*~CV#54`Q2vW|N zVwqbWh7Wvdgwql&RGT?0V4Y7)9ZgCcW4f$baDbN(6Vt-m zvtW!jvUC`+K-kE98fAa!h|k0wldT1B8K8oTrOpNCo5DsoO|wOqc5SxZqnQf-Uu#W~MF)-rgy`njxgN@xNA%xE?2 diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index 159d9c2787a3a3e3946c636198cd1eb84181eb8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 364 zcmV-y0h9iTP)(SA8EEWNd@XsA`1=9f1^7b^bxnRzA1Z zVG5)i_pndFov5ybeUJ}~f+V!mTMaoPmrQ`Gs`J7?+@USeR%a(}j0|(9{U?%`!8XQ? zzrW>}QM<&W$8UxY|3%J874k_nkKfTdWn%cquTvyalLKby3-k@)_AuR&OJC9e0000< KMNUMnLSTYh!I?_{ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png deleted file mode 100644 index 2d3f96c4bcd705c1b447881a20590fdc39237080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1297 zcmV+s1@8KZP)N0GQb(z`nrVz^=d+z{bE1 zIf1V=cW4{{GusO|2{;GX8}h7TTi9cKZAb)R!DlR z!ranR8Uet}b_Om34l1q5{|7Duo|N=k!WxBBYX`v04hNP1+hlfkN`C;n26*!GIq*I3 zR%V=^{7OlF8#FGp0uWCAj*Rhc_$R;*Y*Sl0WuwYaSmG0)6nRkR2OY)L=qL`WAG9GJoiw3TibVJeH zqVLHuAOPT<->ZQ)$~}EB<(lO`e6L2fRB&mQAu$DR&>x~zt_Tmvb&FH4|oq?PR0QcXn!$qRAzmz{=D4& zuI#>(#x|Z;8tBA-YmT6cvI7J0FMp1*I-pcs?KYL0VCz| z++k8CSkkg7ub!L5=sc@c=UH>TUI(5c? zE>At)djOy>C()H20|UB3zAyU@4Cu>Abfw3@fUc15%f5dDy&b4fIWm%z00000NkvXX Hu0mjfGYwy! diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png deleted file mode 100644 index f1d378050e63a838132d51741edc9f27349826bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1109 zcmV-b1giUqP)`_6-Ed_SP%_L zs;MAKAVM0|pawlODrizBS{alp7VQfkx9^;@_dfTY``vra9v=2yYwiDD?{!4Wms);* zIsiHiXgLC%3A9`RIw#nfKxYCiN1#y?FtY`~SrwT7De14K-lZ`B% zC7o&lgeC#F6nGW5u;OAr1M4JxJ81wc2VMr|S6u9G;3-M_CJlf)fhQv`wN28yk$Y=x zYtjTB1MZHL_H9Yik$Y=x8wddI;HsRKnfZloij?+aNe)=yis2c*?3I0rtGv?+&l764{; z1+WX~*dmzz)O;jr_jj( zU?uQE4F~5T@;%^F;HMz$k`OWO06f&?EO(U8N%9C3y>bA!J^*k*G?)K2x|)6fye7%x z{Qp+aF3%~ZGR*52;CV?0Q{>74U>%gz;3`pR%y%*xrK+;RS6gINU#k!Ea{(w zbNP9InRzDW#f@iWRm;_Ws_5nW4+l+fZHX}lPlmjB$||3eHDIQ&X6D@;f2l_S*Nmd| zrlcKJDD}}^2dpkR<1g*?#qO#)q>OJZ0GzOy`NiD>FDhW<-u`Jx2ND7B!f7*bcEP!m z9*I&_zMocY00IcEhDABSkDYzdm{8i=*q-e(J&Js&jGeK zk_eeu-Bj4yShE%^;pHEh>#498LoZe&K4n_rS>gM@XTT3(<5J)%;PyzhpN+mJMjM(a zh6Ys*|It?t7bQHgCe}C0HOdi$~yAv0@lODt%CH$}Gs7pI~=;Qga% ziUttw2OcQ6&}6s80{~Tn5g(YWsW}+&p1-%x=H6f=fZ!F`Lt!-LQ=vhP#@_KVThfu9 zhMAc+NNxzzA)hvlb~H9au=Wl|V;!2AP40L+1_U!}{SOkY|3#+tKS}v-a*qScDokcF zf0GL#v%Nn`#yHAR?8z>MMj`Alcy<5`1J!Y&*=fKyz1A>f?KEH*sE!lOP6Nj2wT2;U b>owqC2y}u_A)^$@00000NkvXXu0mjfX~Fv9 diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png deleted file mode 100644 index 972993e41a9b98b4b2157da4b402e3cf48be83f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 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 diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png deleted file mode 100644 index 64f0ef6daa2de661afefcf109790cd7ed5a2e3d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2362 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 LNkvXXu0mjfA+pa7 diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png deleted file mode 100644 index 6bc437298ab7bfbfbf128acdf5849e304b3c6903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DO`a}}Ar*{or)}gqq9DL3|6^j{ z=hBm&C)~N@b{5Sp4w$_m*}geiNBekToKL{TWpQP5xqRwZuPjq53pI^Z3A|FbUaRcY z{O^J0fmhZ3y?R?;$@JP$iP2W!Cd>8DEZ3V|*s2`1aV~kzx#D@}&nn~61{Xzi+b(jKntiPo%%5fP`-@uPgu|~7wTA~r iR9eY#70Eo`TEl2=&Qt5V_=GOd^$eb_elF{r5}E*WQ-P zW<5if=@(u#g$TuZ3UoBTGF#jD?1`;@(Z9`&`85GvnoIe@7d$z|e0ziO#nUg%9VSg= zGUxg1Y?U}e{SxbE*Imi`Jp6TTrdeefg;ghZng{)0l4L$8eqzO*#;E)SnbI0%_f;DE zn5(!|F61+Ds!RYP2M`S;o^YE~?@rzo>%iN6b$5f8*lCH$#Z^q&qCB0Jmi4DrWXxO? ktS05f#O|%RbjmM=BQ4vwv}Z8P1bTwO)78&qol`;+00w_q(*OVf diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index d3d33362cae322616fd61331410a8559f6be46fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawmV3H5hEy=VyOC+hyRVY|xP^>^`>dV7#XU>kc3SuGiLB_6FYnN!RLbTWgd LtDnm{r-UW||7=r? diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 1989184b14751617cf6e19e833810f91563f7522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 509 zcmV(sT0)b>h9b-n(poNi8 zG-zQY6b*JU4l5e0V&oJJDj3%k4Q4T3VPt|orM3o^7Z9?FUlo?v_>EjL`pcT=Gl;SRnlaz&CFzDJ}KfN37%TVq`A$WX`kh6{M= z!6=v6#lpy;7r2s24Mgk2IZK^^iBh`hXFsLp$Hi^-XdC|o$nN|w}JYolv9aiaA3)AHJ#jL^+Vnw!iASI;~WiKXmD0RMEHO!?-j&6;kHOKLX;?@ zWGE7DPJUcs54$WqYC|tM?Dm*a5EB$J%My_f5ptOQ;JnO8utv}pJu;w=5<;?v>&>8 zhF?Y&EaUf-F4>V}ouDGUG9XTwpfwUQW0DePIU@2QLIJZP6AEI68g@DQ)P^_(>}pKO zkE?_$vBVfLVvMs)nQ&JW#5%6Ll?i8P;6j~onehS_p38;=e>tq*49Si(?RM$JF6|Z- zhLe1y=@Yj&%{<$LDKkeuxA{!dCfy3hWwyzZQVXYeKoyfe+#{|QF7lc(Gdc+Jm}KP& zE12AuD=c7gL#~j*pF diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index d593d30aa5146ffa749710e3a62f866be3c9e18a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawIz3$+Ln;{G-n3*qVjyrdK+;vf zO2%B~%<;L~&)hvW-Gt%US9xROYW)q9PE6s~pWMMB7j;8pYF_fgpcMx#C+fL+J?)<3 zb=9#mc&C*{@hX{jhwuI85>oM)py1fV!iivn{08w>d@?oHH8J03V&{KV?nB*Lzd6g9 iHeTZRGjHb1zl?|eo|iLc{InA2C{t diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index c1e9affbafaef32b8efbafe2c8c9d430536ba8ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 626 zcmV-&0*(ENP)FZPkEWDolnB21APuB3+~+1|dz<(hkJJ#UM2_#8f0$B?hS$5mj@HdgurmiK495EfdRQVaQB6afivTMZb z3uf%$FOh$$yk9s=%riCPmc7st1BBGnj4xEIg@hMmXsRr#(SCa&;UFP(z0y2{?h|rR zjotPTYO)&5klsQ(goNE_dez9Rk5CQGIyLguN9a77F8w*nCiD}4^vMR>{;3;2%YIec zW&glK=pG@L^!Dl@G(^ZlHFm;YXqpVo+F*0tW-YYH4nj_<8K3QsA7jMmL+qsW5%wxE z2Z9-cmWTW%r-|w0jmp2ZyaqjDJFScd`QvsM#eB8~ZL9_>e6P|Qm<`gSzRiYe)K8o8 zZO3fnNsyeU$FpJ8Pa{em@@wgJFwR1xbHT6axt~`^R5yLv!rjzzWP}@*ooY=pN9Gq3 zWqMkeXShs{FH>wP&-2s=hH*s;z2*p=;EyoJb#_u3wvb395{duCZ*dvEM$7kCH~;_u M07*qoM6N<$f=QhsL;wH) diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png deleted file mode 100644 index 0b322ffb95e6f5a5e04c0e06dc86b03903d31497..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909 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 diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png deleted file mode 100644 index 0b295f3de2b3271e974d286fbe11f745e051773b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1300 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 QjQ{`u07*qoM6N<$f=&Hb{{R30 diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 8bc0088daa179c2aaa193da964c5a8ef13db254f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1823 zcmV+)2jKXLP)IRs^PH9lZ`tB_Q)dPzk66WJVzK z@O1@L0x~0zdHA{lDgl`h$UJ;q0hNHv2xK0Pk|4BuUo=pabQJYIbaEA&%6Wn{Os)c68IQ+5BRnvds=Zt%nhs$tP3myL1T!F z7lCJiA0j8e0I&hDjsx9S-9NzRzze{0z%RZEhP6+*1|euzXDGHj{(1@(3y&W)qvgIl1^dQpbl+S54g$y-7vr`?jhK=Z)-_y zleWxTfVX26#}c6a+q)+}R+nWi?*rEl*_O23>$1 z5j1jmC%qG{06rT~q4&*!fVQ4d%Im)|_TtNd51qOdfP)gg zdwi{Y$2}C@B!m#4K6>)k^0hK?``>^|ozBC6ITP#mRriWp30~V#2m#vw`cs1Z=!}@J z1Y>{)-5;|#z65Bg9}eiGQDsI1bV9qNZ)=G}{6@$x(M@>}1e}t=#aleGMgw(= zcV;XBr@M=E4J zwje1{=$|4Zs$XdML-r_euk|_fSpm|iXu95CWuNH(=wgA=IM10$tz_CXF-> z8A^Z>@{Xxn(SP`Qy?A5Z0sVcK1&&IXf!O%86x!em-~vk==*;X7T>(8^8@lYsLa}Mx zEPd%}>rwPWki-(8K_}TybT{yL5b~~53IdcZa|p0Xj3FKD`-Tg;^2!MtoM#FGq*LVy zj&$`PI*6sW$x75d-_^PB?wsl+JUFllbLgc!?nxZ-_2{O{`^eeGOL$P{(!dcJ^L3-1 zMnh+9H@akh$k~&nX=B8xGCp-mB zWz$B^WziPNsj(QQ?=?VIj9z{oi-B!}GY*$H!F3=?AV4;hRNBCTNq?JnT@enQd|HvV zS3u9`E~Yu@$yatuYv2o5vl6ZktQ#%yJXVEG?MA%7moxdDwcld^p zSe0Abl|X=ORmn*?@aDG~9@$P;i8ouy$wZFR*u4}2Way$U-bnWfZHh_`tIp?}Z4P49 zT@nGZSw&a15|*+~z_Xn~2UYY{*^5O;X#@xqU2p20pfd9*MP&mn15}p27%7nefumEA z?h{g#Mb_^T2>jN52OyNuQVGy}6m6l3?D{Q&82QANIo1zF6elGUAh2YgoOoLXA}6Ps zE>e=z`VX{WXt&Y{=ozoP1SO37*sC9YP>uh_Jgqi>wd(YlkX2_}?bdb);}+M&!@&JH zTnoOF?-Bu;o666U!?rd)Zp7sSISNpCJAedY^Ba>4*E%xPDAR01+1ka_sJ0xAKS5y(7zT>+JV%m`#2{$Eoy=wZF{Z*Twr N002ovPDHLkV1jkdSQ-ET diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 72bd1b81a40e00dec7ad7541e712362f1e0856e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1955 zcmV;U2VD4xP)1Ueb{52)ki=G;fufzfCGSy3fnM@-vAd$dbUgFLkOz@2Lk&=r{}5*!2Q6b zz$KFYm#bh@`Dy?{2+IOL1&#ogt7PPx?lf?!q-Sz;h7eW&*sz}QTAov7r@z) z{$E396#yZGZGj8okgj3ZY#pxwCj&p1^zNtzLI|G%j*8`%>BE>fWga5w#;*E10SF;1 z0h|$k`JHqncH|CVpT$i}ak{x8&hv>Xi;H<`IvY4%(tD*^ik%ih@Iv}q;QOVG7uNHd zu%o2=X4VfOc)htE@YFMx@&{mVN#0OoOA&zR{Of@4Ww%(T!Yjb`lJ2y+5W*I~pMcNT zQJ<~l=J=!Vnw2Hl0OGXsJ7D*$mg*ON71$>3vTgxBSHFoYjaSE!kTo5Z0PtdXQkH4` z6@EUdUa7w(vNWF}$^J=}EC3;dErHtr?=+fx+ymaBeOuDsQk|0wAcU|a@Ic|iOsYNe zp`0HgH@9m zzD@VQ0g^5oS3DJfw|U<3HA(ltt&)5aJW|F22q7#FJO_MwoU-=+k9go6U=>L(jq*zh zfL(xVn{$giu$!cjzDoh%!?xp!ns1fPlyrR85#Yn7otiI;JaC<)k%yHjBfzJQK51-{ z?}7UzeZA`lczS>%-u$jkW{qbgeW@z|zohm~s7byDUXtV>gvCnA6yy^}M-DW}_rUX# zR_+SGAsG(eXp--Ne@R-qD*(spY@FYAs{!04Y4ffC+BUTOw{*FreY*nq8Ss;G8y0lU zVVGizvZI9fyJpv*#)f!^b)IC-mj^s>7aoX#=RLE3&33sA+()P zKJ%+2?KQ4iDu7=DhmKR${{ImV>?z5iDkCKoz_(&F#5jHJ{~vn553g&Cn|@p{6@bhA z6ZlH=aFPd3ljO+kv62KJt^*D|KWnVUR^WADElG=l#=1{I5r9Mg92qh4aH;v9vm_mx z>WAbJ5Fs4t82wb`3$WaOf%PQ$Z<6E!aCF>*fbUv0`M3uTlk}?`$7BJBqrl-Qe@$J$ zbBvktvVL>8sU$~3Buh4c2;p4dN6D?s|AKb&uOh`ol z;wbRT=D*LlW193|z-f~u7gl8)ZA#4cTUNKk1q=EEwv#%uI#;+pPM!+I# zLI{3R{xeW3K}MC1R#I}Jq|++t`an%}0C76n1GsXYU1z;mlGEN)m0kcu2!{aY^#!@A z7sf05J+MdaInQ{tJ&k}F)6vl}qezM^lUk-Y4@63bdK&>VAWTwfgA||4EaS_G-s?MG zQ7_UvfH)mZ<29$7fSo0I+u57U34jRUbSF3JttWLA`^kRS(gU^}Q|1gngm6mCjX^MxqnIwol^i2f^S@UG|YM12@dks**DzZPAFTY$RJJ#Avns;?@pa_Zc^kz z;D=rEzm;@k5CCx`IBxzT;4>wLSFYpCCP#KX*(|YXFaQz4j)2pjEnQ-Kr8?D*7joLEV4X=;EzH$04nF`!e6{N zcrMR%N&rm-fH)fVi5JFwM`4`!uU#|YaUY} zQw1PG_(uFz!0bFczL~z4B+vU(BU1+;LRbg50q|do_xOImDvnYfir7>Fh!8vi{M8I^ zSM7X%`)lfCY5_zDzB(&O{-nuN%2WfGN{`Pnx&auNVgt|s4D5k{n{Oka0T|c=12^AB pKm#za2L^7wjerJVU=Ix3{QFWT+i<>Ys6GGy002ovPDHLkV1flZlTiQw diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png deleted file mode 100755 index 6c9dd340a6b044028e82c699ee7146eb613a5448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2292 zcmaJ@c|6p67yjDD5H*7=S+dS+Pp&K@`_D46)I^k>aAUZUWo(fK=N+sC`A_fz00c2M5caTp|F7V% zL!5pTtq%aa2?Loq;XPM9g6~UNBdeB2##)b}g?NN4WM#wUMHiYT9vk$+Ik`6xoI+Ri%7<|>nH#Q1Fw^IjBXGXHPcRp{rJ)3I`*!Tg$B$6*o3|`=8=tMedd&rg z5BrkyHJ!S|HG7R>V`HP*qWYMX)-DMiKVGg?Szh-lm1)7&8O}`tN@b3@^-(32S_ACt>~Lj6D`SW-_4EyBJED4M>}`EL zUj6?%Q}GnCP=5)Bw@%512Xs(I+Ke`s6M64>8JtLKBP_&FM_!>ZnbP?Vf&D4!W7=f` zEp?QI=B|`?n9T8^rHGpW7C9jj**voFb^SLe;;~>b za)VE0&IR|eMx(F>aGR~$9}!+xNsKI1{{ARrVdSBU2+%h+_Vl`PWjR+T+}C%yp|G&f zOK-{}tz>NVW_VcG##5AbdwaVzIVUF;n4%7XH(YrSr<7(}%9fi($)mZ!{^;trrxB99 zl3GOrJH%bIhm15@{`=gr;iSr~Zy=*Wj(X}Tjpf=|1LX3-0{~+)t?e}Z#60pl=0I&XFhF%HvNxx*W z=)+sIA=zmG69&NBQ~CqHr;d!TKK_R zmRA~)RoXa%`zxJzb0CzONO1`W1-QA@trOa~n{EpNx&W_+^e`a|cD+iVQH;$QM%Yf? zFAC~7g}?l}a_+6{d|$ncdVMynQub8!yz$e(%Ry2cKH?9Qi;R^Vsy`?pZ zfeZ_kuXK(&_$~Bwce@X>*d}mL-CgE%hoXm`2xa&F$$M<`gX&B=x$|(zsk6>Ny0j!|`~{7g9qNw;HrdpkHAEA?9U)7NpcsJ1ZLLp`}b zuB2PShj*=HPLxD$U_+`a?C{)3`g6mn41k53Uj6Ok(X*PoS1$4t)(Bv(hAsalg9*lKP(imOw+|sAJJreLu-+awlmCm{>yO z;KfAPUF`Cd{sUG|C;7IqKr36gKl?HsmZdc9qcS&Cvsr1)0iN;8E zOlC*z-ubtrU=?#>d#2)D7q?sMw%uE_Vr*(wNFb|@lkDf zxCkmpqFp`~Dizr=1&l9xc|E}d!~y!}lRXla-4=Fm-Sf{I$IPyA?LJ=qPYT`-T|LpT ztH!p=uEcy6rUt~k>^U{Bw*FTAqU7##UF3vMxNxk}K!_#i>UA$ORjFfc(H@`W)i-F| zoJ>-}WH!_mIB^)7Q(f<1gIF~|N)*<F;_Pxj#L#j+XN^C_uqOGoT5YeBpx3~AM zsp>Pmqod=<^3HcjeodFp7L2FgVwS>!OcS70%zMYEbwee(LgGsaBa);gqeI*u00}yDB_2B`Z}iY;NV=c2x?F z&3p8OgK8$@l8TO7%zgo`@*m@*thhDD_|#;Ni=nx)c@c?zMN3-NITKvYGRZY=H(=rV sz!uiSaToBMYybcN diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png deleted file mode 100644 index 8fe9e5f95b9ae347bfddf3254bbe80a42d3fe819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2022 zcmVU6@DJI#1gv;#8NvKsA!Z6BDNMOiM{rv2#L0-CM0$e62y{_T5Dh11VKuOa6ubV ztqc3sin?eiQpEbm<3At2-}m?X-g#%{{oeP-oU1SSX3m^B&n#!onK>^}saFlK*Vb#} zr~vdl2r2*-fSxtbbMtiuQ~-L`K+nzB8BhWEb`40{1;AwhR?5`T+W?Ly`9LP0PO1-f z21t4Vzy^hS{UXT?3-Kz-W{?0#S_Z)TK(TX*04z)L|3$S|fZ0$1*bKnm3-oU$i_J;? zqcG2Gc0&bVZvb~@_ii?WeMtVXFwbmuLj~X@0Ow}+ZZ?CHNuF1jXEwW`0&oq0BeHup zo58gtk1WhHo83?Wcoe|4*}a?1;4zZh73P`EZm0nK8^EgBy_?P8Et0Dh=9$fIZ~$~* zf{v6lNK&pa;1rS%N0$G(n4~p>WwsB1F8~}$@^5YVjF-;_K+>WBe8Svh2J_DW*!gu8 zfO|=PHc3!PKLKzdfNgR+6j}cX06rJk+M1I>-*yM^O8|Bb{$NJoX8~;1)VC&iN&s*I zfJ-Jc^cH|W0Jw(at24_cM01gkcr%B$D|9exu4ZyER{%1_CbpZIxV7Vq)W3GE? zj!;BvnPH)r5_o(cdB(0xlY6UDM`lcWblkr^=Z-8Atl{r_J9 z97yt&6vmR)1#okR@r6lZy-TvuNf|o>ei~f2%cUqHv5sBEhmrg^vHn-pBrO5pIsp6U z@M&51J-0{l&rS8B00};1i^2gf_Grze(s2MT0$}T- zBzePim@jF~ZxSTz2@ao@-k5ET{f6Y_O)`xCp9DZyk{rji_iv51jZP@v+LzL{ZAqnl zS#Q`oK+*yK)|3oD0AUpXUauX(%O%_StH+v}sf`5__02-(1cz7;7>_Bpku3{=0D@1+ zX9Mt!<{W;vkUX%B@Tp}b*);t{YJD450DRN>)R@!mq;_%?HjA$$@A~Uybjntb71KFM zGSJ>IQ|V=rwhFVUBmh38=A08YB<_$>u1@BDyQW#9EV70Z14= zB>|{QP|pC=O}J+Osu|h`03lpi4`K<*ca^L?KvH!@^yP|>WVfzgvsY&v;HOV^qqnA{ z8BpEP3+Z_}?&uJZ@$HC%TSFaRVLk_BZUo@l5YhKTqi295JKFv2V;Gzn(s7S5keT(F{U24>ZCo@WZ_N zL}_jix_zP)Kq!5&sd-GzpAxMvmPH-JX7;~6Q=M}(Oy`+um<9Er_QE(!=b`qD4Y{T& z_y&aOrYZ=tpeE~B06;&9cd~r zsU~i1Qc{=yhg@_|chts@Qggd?B+t}0zQ%skb`)an!dh!HP79Gq%PbX$?k$K zbun$*JE|{|>L*O1G}TcdN%bQ>9qBfTZAYf);JzJTWejx*vZuAQDLOb4bjSeIPZ>t7 z6&)6m^gMu4AK-j}i2f%UlbGDgU{H|6X> zDtx?O6@Z?Rt^ia3de%VC&DR-F0q9u+JvU!xK+gbt3DZx)f9>t2o&W#<07*qoM6N<$ Ef*;7W!2kdN diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png deleted file mode 100644 index 51b4401ca053ca8cad6e9903646709a2f44444df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw{&>1LhEy=VysgJ$~x+g}6OvsxLimbaeN-!xQItoH=3nd`|H@dF!kYp_dChV`b9AudQLRn7yg2 zv~UiON9*MGOYAy6D=_*g@;c3(5`S(Hi^X;wk8Ms*3SMP^!WTf z-E_DAd$kLMWY`Z`);MuKF9=d$?_mzLoFj7Xp~|_3ODg!(3;AT&wLoS^O+0tsvcZJ& zD(m_$dNm(!9@n+CTyDa$w$*a^-$!>qK3P{4end8+qbAwoFEAV!JYD@<);T3K0RXh> Be)#|Z diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 1eb3365e090b4bdc3c2837b4815d93b0e68706ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=eq%^N1iT@Ar*{oZ|%)xN)&LtD8BJZ z!V@9E4z3t^`%Mn(SaZ$zIun`g+@9~_@-w>VGD*dAl8SELTxY$R!Q9^_Km2v>zS5-y zTfR15?Jo0JYIR9(<-9GHS0{%7saIaB*TIQK^XGGFm&I*3o_F(0OX#}DuPvcvv5Cvq zZKw{9Xl&eDnUIjbijxN)ICGsrLPFxkov(tQ{|IhKo|pGua6|6Ayi>Uq*Yd+E7y7Pi w-n{QqQgE5&Q@aNzEz)L(f?Vo(>Cit$pWi%PUFdcK`qY diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index c32339a09e342a3f9e359cde16acae5feb9169a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^IUvl!3?yqky#4_xp#Yx{S0IfB9%xH00t&H~1o;Is zG;r*w2!3qS0~EOI>Eakt5%>1mM!qHk3D?BjM|zy^RQ5D_${#o=%e3$RYKxben_`OH z-`Q7x64c7|Z=ZkW?bS1LbZ)*^@t*ePX4>jGy}4!6HwUep*68r;Knb7E^8^1LvAnrs z`1ixZ1E&@zue<+dQ~krGO}XMJx6)_NZwggxW)?QGFi>n>EDoliJY-6*Dwx9lTx7E)*dDI|s eQ_9IcyP4)+j~PrC)LwKD6a=2GelF{r5}E)T*?(gI diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 1692d8a242162dc878d42fb1b112e918cc25c3ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 734 zcmV<40wMj0P)}R?1pi{*}C8ro5mjO-_Ru*)DiiH)#7^4tLdZA<=>SdMkpq|I%AXn6pz$;0s3PKrYm?8_gA;c@ZhG~!mn)t?F z$qgNN4O1ga+~BX|f)yk&k~GK?-DDvb#PDjBCA!H<_NZhGqgR%Ql7sAV3M0iTSs_YJ zvc~|%OAhJatMnt8@AV$4Pk2pqH zrN<;jNTtUlMo6Vc93!mKV-TZWrAHq|M5RX$##I#o*D*R}iwMSB`9>KZFt*DURd{Wb zPi)1Qpj`HNj&VyqaS!9ZTyPR2MXh{b9chdMazO=2j0bW?8T}X|ER-8A;dMxkIDywW zxnddLF~(Rg2W(;jBhDhZqaCl0)X5U-7{TkX{9pku@%qFDS)h^6cs-&_{!mK-uW^p3 zK(sNA*C&?CKX#F3riZLn9;~6CnI>pbad4E`Qru>b~1{qINMZw)bIiqPg$vdg9V)BJ2t*?ntA${sOADm3Ngxg zD)c{4!Ev53O*Yd!;W*{`zgWT^F4D^zJ~P22UwF-ZF0h-W3IqayKw!@L0j`rmsrdc? QM*si-07*qoM6N<$f&f-ZIRF3v diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png deleted file mode 100644 index eabb0a2ba41bbe0e109263ee9b0d6ada40a6e792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmV-R1GM~!P)RPWZAHfP!)RVB2S(!`lt|Um(uYf2_cGfFjG~?QJ_SKM>Hu7Qz_%` zC^KY0j^p?%F-hrI#t?oEkx>e=^x$WZC9-20$8bAJMiyi_f!j|s$&C!#uq!e{4&*3d z*U4D<;$4cQ>yQUJNq3uf6oz@6$E?Ckd0-Y5%+4`a;aG`TuRL)UvsTrDjhJ=G6Wy3? zP%Zcjv%EY}z-*IhK|5x7d7^;XCe?zCn03h$-I#4qEm(P<#_c#+Sul>1xE*7f>{!k} z_~{|56pZ6*{0y;7>1g5}{!WmS0WF-w-(4mv4bw>oQKFMsszM7}DHGx$QauHt_cw57G zmNrf?VwFPCz(EGt%|eYrmJb-fVG|Z VF?y~eTbBR;002ovPDHLkV1i=jh>-vQ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index 700d0bcbd794a386392d846b5802f7b9e0a5c9b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=eq%^-<~dxAr*{o@0ibQHWXlc_)@Q{ zo7bu0$bp-Cc%B}45cEF4y8Kpc+P+I4SvT0Hfk4@-sb0zFl0KZx42i2;acG+Sx5jUW zryZKRtmGTx^_h3l{%+>qRlh7|<%OHi&LkyFhOJpO*H&A5KM1t!)ZDm!+A| zyZKpq{;iD?HM=+ew|Tzi=*`(;J$I)5*YTNQka(ztQ&`f=wS_Hf%5C{YU zfj}Uz$`FrGLCGQiRe_IbE=95b>k_?Gl%kj*tkF+-Nr)-6LI>s%M}5i77~rkcV!}3k z{%c9Bn7b}g>9f>_yMuRFi&ZPo>aUg%N0x5pW7Phk(^oAanRb44svVK{ zQ%g7rIbP#$huR_Mebf?8N-f_x)b@D5KP})2wn43LT0%Oy*i+ROxymmsAuTNw z)SPj@w1o6D@RypD_eo1AhWpeza+4~5w1nb#lw&1p(jP6MSO(Oz2Yt~hUM$0BO4<)( z{Lm7Lr;2qYugecD;WRw0@$9vd#Dq`C631Oi=7qD@N){7_iRY_G=FexLm0nDEJ@wsKM=Ip>Rg~BP zcjM-AHY2I8*bozbioHZeBdOC?#Do`c^OW+WpRppg8uK^1MtRn+wIU{*!_6=HGouZ$ zDcp=iPQPhKY=;~!UX7d{wIe1p;o^zN*;z|sFX5sga(2m**dQ)$h@AbylGr3JDkEoi zEs4$GBB%6W(uxJK@UIvyj~8*#X#2XqH^mS1H+ICf$)NO}El+&)5)~qYO7=k^1@Q>cjIr19+GHOe!;%*yHz{QO(gTJ zlGtXyx&DljH*bF&;(9ieybk*VEHg^l92x5)M{lcX&DMv++SMVkaqDA!E$k{u>s)7h z5c3|k)TAeEkIuHTt>z5d9(K;NuWDW5O1tCfIo?Oq`jcDjE)?9x_YSRH?zg%EQo$SS zIkXOV%<9rkC##5~4(YPEW|iYv77%Cc^EiO5#khLj<98&cHfTY|^eW>bb?BI0KAqrd zbPTTx7kD1SxU{CJhEfw-;v71L#eSoY9F&&Wk36SK*%jO5Bbq2FR``H6$_WGl_6Y<6 dfk40j{{l8Uxz9{kZdm{T002ovPDHLkV1n>#$ddp7 diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png deleted file mode 100644 index ea913dbc4aae4749129fbc0bbf4cdcc32dc83a66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2827 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 MK5^EJdfb!rKTmrNm;e9( diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png deleted file mode 100644 index 7b203e489b333c73730c58ca57a1b316705b22a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4949 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 wY*d2D49EroGyz|B(lPvh0J6HF0{{R3 diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 52dca5aa1c52015c2ffdcc4e1a437d5ac69d65b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3653 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 V_^s#2S8e`FIojb4SJ?)p{2x6yPAvcc diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png deleted file mode 100755 index 708dd7b55bab291382f744933dc9db7eb06b354e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3812 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 AQ2+n{ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png deleted file mode 100644 index a966d5edb0e77620b2b7bfb196b1f193701c76a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4454 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 FngA8*uUr5C diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 11bc67e56dede6790062cf84365ff36f0ac29ee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFwXUKaSW+oe0#?>PuNlB*u(Eq z948}-HI@kMU=j*a`FycJc-M!}fXN-*7nGdc9mSlk-PGY-^f}_j?KAH_SNngcUy`Xl zNyQV2{%t+;Ip#_BE*|B#scONK%U^Dn?Ruf|_M5Vxt)^_pi@Ln+^6fA3>ZbTPe_2%` zw#fSCls=~~J8Fa$@jq=AsMVX_{G$4&giCIl=R39gpEUK0^x3}|A6|0(dF`}@Q`w(6 zF05P2@b;Fo!_%#q3{$_WRe#BLa{x(IbFJ7@?C^9gt3mUB$`-VdlYEzp@$`7(t>7Zo^rO5HqCgK}IsMtT=73xb)}y zYx|CCmOpG>T6ErM$}{t$YJYPoewo}{ynQpH)#*6drE^)&r;6R&JUNm<_IZP!`uXBn z%+(W4bK0+a+;c%`{bhf?<>w>CB-bup^T{y2DAv|zdg-~%Uc#`jRPn6;&Fo)sV~2|U SQ6FG@F?hQAxvXmumcZ^uR^Q*r2k7!7A~Fl$p6WH&2yJqQg2>!yItjQ z_^#)tYdbyX%{PAErpIA=CBWG_K=K7+Nsik6qdjMzZe5VM@UFJv)2S0ad3i*tF4-c~ z9_%689rv^OnWpKA)e3e@-W^6$LS^1=2uYO;p@)O;svyJnM zrzCv7IGKNAjf`8fsrudnL7+Wl{}3DBjyrR%21n`p7`K3v8U P3knTSS3j3^P6`{$AjijdZyt1MC$Azf957h>3-t3^RKU9_d^!eEr!Ikee|V#8uo zB-PXcagY>r5oi=j5EdwHm3A>)o13%M6gJwvNrd#g?>T4ZywCH_z3_ZKlIMAz=Xsvz zd7dNO!jtqc#7V}u%mNEsW{i^z(ajUwq>|97TB%YIXrP0O6iIIJ7P-vPO|?qE^}Nd> zMUYE8MJ~gvR|Gu77)z2n+|Ddvhp16R+{kg3AXiB9En#nymIbOfLP2tc=Lma8*65}n zxj_R{1RWwROJrGs+#pNPFg3D7mLX^Ya$9h>JOA+J(I|=HRB{CF6E-;K~lxkTa zLy=^U`v_{26*3e}_IMrBBvrCN7bTEAPGjnm1-dASY_XQ04T_8|N+ers!!)MI=%i$_ z#coXd6%n08AX^;7)T&76Bof);IHt`?7S3X7QnE0OsZq(oB}^4c7XHLkp=9A4rbZ)udzg;N1$r<|QLPwI z&n%`FHnD(d zlnS{+2SFp$$Prd^hM?!=5_OCdbeOappo}jG`i)AtMGIjAvPO!J2-_z2I7-+6X<4A0 zeS{rR99YLi!iK0-MAY*+VH2!XJb08`e&!xUz(#%{Y>_RB4|~XEo?cd|1k}>Y-{i7O zaU#Ws|}!6-dEfx=QHw|VlU04lm{uA*~>Kf43bv-DCc7enc!1)u!Y-L zLnYTy&mBBWI|mr2kbRVCd61$P*Iv=`;xYcfm1(wX`Ee^>XL$g*c*07zI8RR?8GRYkCOf$*} p4w7XnH^}om&+|Ob^E}Uye*phd*siKI(OCch002ovPDHLkV1hNcx5WSe diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png deleted file mode 100644 index 507c5edd44bfb5efe41327d5cef511a108d35f13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1074 zcmV-21kL-2P)+t)}?IPi4`?n-BcG{I5ur- z(S>O`4876<#~6}k=`>Up4hWM~CUltm-)|Ri1Ni>m=Xw7B>2vXZK2lazR#sM4OxVB` zGWm@sMM^ z-;l({UGfbz*r=0l*o=*rbn~#1)BM5?7AY+r;d^YHV6D>PUbfT6IjR&Lx6#YE6o*+) zuBKoO-MHxHK}|s}>p4WgxIShmD&AzWe%_@-6DZ+pqS#BZCQ!mV^fOt5qM?`(rW@k` zYsq0Fe-hOnJV_2~IlvgxjZ&g0_?+}E6PL@R*DQCeAjPd$B{wAK#0SSo$OW75#b()K zHUs$LSLVnT+wsWmw*M;;$z=L|Dth}oRO&PU|Q78@DG)EG-;f#n348sTZV zVF~9kvs0GXg_$1ilRM_pLS`uz%MxXz$ZS8iD;g5KO+dIKazGp50^T5@IM7A7EpotC z!W~mwxJ0;WIiQwsy^0H?ge#H*l7zdexR6PK98gHOKye|H0y&_NaDn2&2;t_-0ZGDL zRb044xEeWN72z%_E_4yDUJlqsxMPX~IW&?Y+)+88op1pSBoqyIaEQ!Ol*$riq{ys= zxpK!+dNI=^OYFwX1(wJSoA?`3V^qijl?0d?Wut77&!^Zq%PbjU4n5fUgnXH!m=5f< zk}m_~a~ON=BxQz5hH%r$Y-PY44&!E!3Yp*}?#{7XA*iGWcbzi9S)vHoMVW4ZGIkRX z#g8(<%S4r;jjhy@q>w_AtYRDOq=;&>OpwD#d~}?I%ut7qs$`Bm_@-GlSja#4WRw!w z;XQoPAY06$A7At_L-u$UUpyrjB^DU#RVFRX2UE@R5j!x|C?D|xrk<0JScR#T@)5nxBvhE diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index 769f4b95d3c896c26ed0bfba2f021ef62b55474b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=ECYN(T!A!sU{6_qEl{UWNswPK zgSdpehi6)P#kA?Wc0d2{`_JDdADKTu+2@`vjv*C{Z*Ol;Jix$n#6c^)^WWe0Qtjt& z(iwdUxD`4;U|)dwd-qrJr<3RJu2@~RH|0gt?TW1rr>rdQ2%jaFqJ6yL;sm26zlh@7D>I!$e>V38|Ffy@l2q-u- z5MZp(Iw8N{6kOecDSLIc2VRl$O}jb!^jWW~W$TI#M;}%FkQppxo4j%Dmj3W;vGWzX qP2R6LdAol7$=i31K37%(f+`l)U$5VAPKas*1*NB}pUXO@geCyG#(&-b diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index c44990e20d97894c0175f09f134874522678a6af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1236 zcmV;_1S|WAP)^n&N=6LXk!{qB?zNz;~vh$DS=L|ar)_?gu(-zT)(lJCR83s5RUR3 z%_OAp3f(Jc34yRptSzlgMKRvez!=CXlCsK*C?FqZE4{J5c`mf=gr0Q+Xhv z%z~KnR31o}peNoug604czIcV&IP(aa8g8PGa&V&U)Ww)bu+89pz7I_FIrZ`75%OiU z^L0?7&zKQg9wAIMOZX)q(RCA6TLjaS#d1!V*Bs`uSn>!{vyd_Ks$WtYM;>9SS{X90`&1lxgsHoNVdJ_dxH5)3LM5)@ zPvgqFDHT7DP?_7zm+Oa$pGTF6n`M$cJeGy zPg#~ipkXe;R;Rwde>a6d!iVJ9q?~e;x)cHlcVN3(J!gIzf%a01rAqxw|A{mL3HM=p zN!j;lDuKQu!_ukjJ3>_&frRU@T%hi2NhQ!GY`-e|+EWR1fDFr<%D#tF2_#&OWv#NS zH=RJsuq;(}^`{ePBbFPKT|3hW^a++0W!GRjf%afIPuVq;PM|WDnaZxQbOMcIDXF*3 zf_NbR6Z1c@0WAI^&Bs_;?Em)p|0v$5?An3N-)f{2XwH=0tUjMgpx?=`Y)k!i$3|@X zQomt#EtclgZ@GShtxMTAlFCi{rP#LUw@6Y5^dluK)%>CCT$e_mPHgkkb8b!{&@I`xOeWB(d( zFBy3y4>-|BLf+s6BKMs?8W# z(HrXnb+v?QWUuis)NFqwbE|RnXZ@kxRptkbXQ%#Hc!xPK=Rts!2Mb(pPVo4O8c2CG zMJuD`oX)ub=?*t(W7xdz;{fT7$63s=oDrraNOizc#>$*oW{0^1Ak7h7v*^zqUAh1u z%^`1X{DkDJRRAfD2|R;7BzL_Gklvul8oJpZkmqemklLuyYP$FV30NO79U!G)w~Z`k z5DCi@=R9CdiCfvi2okv7;&gy?25uih2wyK!gUUy?uR!HP{|_-AmH%wS|Cx=#e|Y3L zJ=CD^pRU=#92EZJM+fPqib?s;XN~bBGjWr~8RZ$y#Z4OMXI3x^H%Xu|Ugs97`3DbF yW-E8nz$DyCuA+o<&N=6tbIv*EoO8~(;`$p0wElkC;D`qR0000 + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml new file mode 100644 index 000000000..0660d17a3 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml new file mode 100644 index 000000000..cd319196e --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml new file mode 100644 index 000000000..c40df29b4 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml new file mode 100644 index 000000000..d4e7acf83 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_article_black.xml b/builds/android/app/src/main/res/drawable/ic_article_black.xml new file mode 100644 index 000000000..65aa6380f --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_article_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml b/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml new file mode 100644 index 000000000..147578267 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml b/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml new file mode 100644 index 000000000..726cd8946 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_home_dark.xml b/builds/android/app/src/main/res/drawable/ic_home_dark.xml new file mode 100644 index 000000000..020b4a395 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_home_dark.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_info.xml b/builds/android/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 000000000..fbd717f75 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_refresh_white.xml b/builds/android/app/src/main/res/drawable/ic_refresh_white.xml new file mode 100644 index 000000000..1c312cf67 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_refresh_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_settings_black.xml b/builds/android/app/src/main/res/drawable/ic_settings_black.xml new file mode 100644 index 000000000..ab81e9457 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_settings_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_settings_white.xml b/builds/android/app/src/main/res/drawable/ic_settings_white.xml new file mode 100644 index 000000000..207639d9d --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_settings_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_video_label_black.xml b/builds/android/app/src/main/res/drawable/ic_video_label_black.xml new file mode 100644 index 000000000..5532709f0 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_video_label_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml b/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml new file mode 100644 index 000000000..64f91a05b --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/layout/activity_settings_audio.xml b/builds/android/app/src/main/res/layout/activity_settings_audio.xml index 41920d092..e2274a949 100644 --- a/builds/android/app/src/main/res/layout/activity_settings_audio.xml +++ b/builds/android/app/src/main/res/layout/activity_settings_audio.xml @@ -89,11 +89,6 @@ android:layout_height="wrap_content" android:text="@string/settings_soundfont_explanation" android:textSize="18sp" /> - -