Compare commits

..
58 Commits
Author SHA1 Message Date
kichikuou 48458ac034 Statically link SDL3 on Windows and drop DLL collection 2026-09-23 08:04:59 +09:00
kichikuou ffdd362f35 Limit SDL2 SysWM header to Windows users 2026-09-23 07:24:01 +09:00
kichikuou 78519bb5ef CI: Use emscripten 6.0.10 2026-09-23 07:14:32 +09:00
kichikuou 5989fe72e2 Fetch SDL3_mixer when the package is unavailable 2026-09-23 07:14:15 +09:00
kichikuou 66d0636197 Port Windows menu integration to SDL3 2026-09-23 07:05:14 +09:00
kichikuou 6c8590f8d4 SDL: Fix screenshot and input compatibility 2026-09-23 06:46:02 +09:00
kichikuou 3cbe4c73d8 CI: Add SDL2 and SDL3 build matrix
Document the supported SDL configurations and build dependencies for each
platform. Keep Android on SDL3 only and identify every packaged artifact by
its SDL version.
2026-09-22 21:39:05 +09:00
kichikuou 80da6cd782 Android: Use SDL3_mixer AAR
Use the official SDL3_mixer Android archive through Prefab instead of
building SDL3_mixer from source. Verify both release archives and their
extracted AAR files with fixed SHA-256 hashes.
2026-09-22 21:27:40 +09:00
kichikuou 2f73abd486 Android: Migrate to SDL3
Use the official SDL 3.4.16 AAR through Prefab and build SDL_mixer
3.2.4 from fixed source. Remove the bundled SDL2 Java sources and make
Android an SDL3-only target.
2026-09-22 21:10:36 +09:00
kichikuou 5844226b2f Emscripten: Fix SDL3 compilation
Include the standard I/O declarations used by system.c directly and handle
SDL3's pointer-array locale API in the built-in NLS fallback.
2026-09-22 20:53:24 +09:00
kichikuou 87596c58c5 SDL: Keep mixer conversions simple
Remove the helper layer and unit tests added solely for simple gain and
time expressions. Keep these conversions directly in their SDL3_mixer
backends instead of shaping production code around testability.
2026-09-22 20:39:28 +09:00
kichikuou 212a7bf138 SDL: Test mixer time and volume conversions
Share SDL3_mixer gain and 10-millisecond time conversions across the
PCM, BGM, CD, and MIDI backends. Add SDL3 unit coverage for volume
clamping, combined gain, time units, and tick counter wraparound.
2026-09-22 20:34:26 +09:00
kichikuou 652724a9ab SDL: Keep MIDI decoder selection automatic
Do not force the Timidity decoder based on TIMIDITY_CFG. Leave decoder
selection to SDL3_mixer unless SDL_SOUNDFONTS requires FluidSynth for
SDL2_mixer-compatible SoundFont handling.
2026-09-22 16:05:19 +09:00
kichikuou 658debdcf3 SDL: Add SDL3 MIDI backend
Play MIDI through the shared SDL3_mixer music track while preserving
the existing loop, pause, volume, position, and fade behavior. Support
FluidSynth SoundFonts and Timidity selection through their established
environment variables.
2026-09-22 16:00:21 +09:00
kichikuou ed4fbc1f19 SDL: Add SDL3 CD audio backend
Load playlist tracks through SDL3_mixer and play them on the shared
owner-aware music track. Preserve the CD-specific loop convention,
volume valancer, and playback position reporting.
2026-09-22 15:51:54 +09:00
kichikuou c1910382f8 SDL: Add SDL3 BGM backend
Add an owner-aware music track shared by the SDL3 BGM, CD, and MIDI
backends. Use it to preserve exclusive music playback while moving BGM
loading, looping, fades, and volume control to SDL3_mixer.
2026-09-22 15:47:24 +09:00
kichikuou 495d6146f3 SDL: Add SDL3 PCM backend
Create shared SDL3_mixer device ownership and map the 129 PCM slots to
MIX_Track objects. Decode left/right WAV data through the public decoder
API before combining channels.
2026-09-22 15:41:00 +09:00
kichikuou 6f01734215 SDL: Add event and input compatibility
Map SDL2 and SDL3 event names, keyboard fields, touch queries, joystick
device identifiers, message boxes, and text input APIs through the common
interface.  Keep the deferred touch deadline on the millisecond clock so
SDL3's nanosecond event timestamps are not mixed with SDL_GetTicks().
2026-09-22 15:33:51 +09:00
kichikuou b55574c162 SDL: Add cursor compatibility
Map system cursor shapes to their SDL2 and SDL3 names and hide the
cursor destruction API difference.  Also include the allocation
declarations required by the SDL3 debug build.
2026-09-22 15:27:28 +09:00
kichikuou 9f92186905 SDL: Add display mode compatibility
Hide the SDL2 display index and SDL3 display ID and return-value
differences behind the compatibility layer.
2026-09-22 15:24:07 +09:00
kichikuou d80d82912a Build: Fix debug compilation errors
Include the standard allocation declarations where needed and use the
parsed command argument in TPC and TPS trace messages.
2026-09-22 15:23:45 +09:00
kichikuou eeb4953213 SDL: Add IO stream compatibility
Unify SDL2 RWops and SDL3 IOStream operations for files, memory,
font resources, and WAV decoding. Keep stream close ownership explicit
and cover memory stream reads in both SDL configurations.
2026-09-22 15:16:24 +09:00
kichikuou 5abc9e8177 SDL: Preserve blit destination updates
Emulate SDL2's clipped destination rectangle output when using SDL3.
Cover source clipping, destination clipping, and empty blits in the
SDL compatibility tests.
2026-09-22 15:10:05 +09:00
kichikuou 5d7ec418b1 SDL: Add renderer compatibility layer
Convert SDL2 integer render rectangles to SDL3 floating-point rectangles
inside the compatibility layer. Preserve logical presentation, integer
scaling, HiDPI sizing, texture filtering, and vertex color semantics.
2026-09-22 15:03:02 +09:00
kichikuou e488123345 SDL: Add thread primitive compatibility
Wrap mutexes, condition variables, and atomic integers with common SDL2
and SDL3 types and semantics.  Normalize timed condition waits to report
whether the condition was signaled.
2026-09-22 14:47:34 +09:00
kichikuou 5eea90c9a8 SDL: Add surface compatibility layer
Route surface formats, palettes, creation, conversion, and destruction
through SDL2/SDL3 compatibility helpers.  Preserve indexed-surface palette
ownership and adapt renamed fill, stretch, blit, and rectangle APIs.
2026-09-22 14:42:55 +09:00
kichikuou 677ecb5a0f Build: Add SDL version selection
Add the compile-time SDL major version option and propagate it through
interface targets. Route SDL includes through C99 compatibility headers
while keeping SDL2 as the desktop and Emscripten default.
2026-09-22 14:21:09 +09:00
kichikuou 1918350b19 Update censor lists for Rance 1-4 2026-09-21 14:26:11 +09:00
kichikuou 9504ee7b65 Add censor lists for Rance 1-4
Contributed by @RottenBlock and Vivvers (x.com/VivversX).
2026-09-21 13:17:23 +09:00
kichikuou f033a507d3 ZZ2: Return platform name from SDL
Fixes #82.
2026-09-20 12:23:02 +09:00
kichikuou fbeb9a780e Version 2.20.0 2026-09-11 14:40:11 +09:00
kichikuou 8c69f53073 SACT: Use bold font weight 2026-09-11 14:09:04 +09:00
kichikuou cc96c85381 SACT: Honor message skip state
The following SACT functions now return immediately while message
skipping is active:

- DrawEffect
- DrawEffectAlphaMap
- MessageOutput
- MessageOutputEx
- QuakeSprite
- TimerWait
- Wait
- SoundWaitKey
- WaitKeyMessage
2026-09-11 11:27:44 +09:00
kichikuou a2618319d9 SACT: MusicPlay() uses BGI music loop count 2026-09-11 08:58:26 +09:00
kichikuou 30fd5768da Avoid updating internal mouse position for System 3.8/3.9 games
Do not update the internal mouse position when cursor warping is
unavailable / disabled. System 3.8 and later can disable mouse automatic
cursor movement from the menubar, so no games for these versions are
designed with the assumption that this feature is available.
2026-09-11 07:57:00 +09:00
kichikuou 15a769d83c SACT: Increase MAX_TIMER to 128
Rakuen Yuki uses timer 101--103.
2026-09-11 07:22:27 +09:00
kichikuou 253de6adc8 Remove enable_zb option
Always enable ZB font weight handling and remove the enable_zb CLI and
profile configuration.
2026-09-06 16:12:19 +09:00
kichikuou a0daa3261c Font: Reduce synthetic bold strength at small sizes
Use half-pixel outline emboldening for font sizes up to 22 pixels to
better match GDI's synthetic bold rendering of MS Gothic and MS Mincho.
Keep the existing full-pixel strength for larger sizes.
2026-09-06 15:00:52 +09:00
kichikuou 98e9d8bcfb Debugger: Add cache statistics command
Add `info cache` command that reports cache usage and cumulative hit
rates for glyph, CG, and archive data.
2026-09-06 14:02:03 +09:00
kichikuou b7854aaec1 Generalize the glyph cache
Replace the old cache manager with a keyed LRU cache and use it for
glyph, CG, and archive data.
2026-09-06 14:02:03 +09:00
kichikuou 905eb34590 Font: Add glyph cache
Cache rendered glyph bitmaps by font properties with a 4 MiB LRU limit.
2026-09-06 14:02:03 +09:00
kichikuou 8f22566adb Add unit test for font.c 2026-09-06 14:02:00 +09:00
kichikuou 48ded43e2a Fix menu text being erased when removing selection highlights
Restore the saved area for opaque menus as well as translucent ones.
Painting over highlights with the background color could erase glyph
pixels in the selected row and the next row.

Also match the original engine's menu item text position.
2026-09-06 13:48:07 +09:00
kichikuou 571bec63a2 Windows: Use msgothic.ttc / msmincho.ttc by default
Fall back to the bundled fonts when these fonts are unavailable.
2026-09-06 13:46:46 +09:00
kichikuou 345c017c22 Render text with FreeType instead of SDL_ttf
SDL_ttf's synthetic bold is too thick. Implement a custom bolding
algorithm that approximates GDI's synthetic bold.
2026-09-06 13:13:09 +09:00
kichikuou 205004086b Move drawing text to the screen out of font.c
This makes font.c unit-testable.
2026-09-06 13:10:20 +09:00
kichikuou 6820bcfcb8 Derive default patchEC value from scenario signature
The EC bug exists only in System 3.8 and later. System 3.5 / 3.6 games
(e.g. Atlach=Nacha) need the patched behavior.

This deviates from System39.exe, which has the buggy behavior for older
scenarios too.
2026-09-06 13:03:51 +09:00
kichikuou 5ce1494b59 Match the original engine's patchEC behavior
The patchEC flag is default off, and EC 0 goes through the same
width/height adjustment as EC with a registered rectangle.
2026-09-05 12:23:09 +09:00
kichikuou dd45e566f6 Emscripten: Remove xsystem35.shell.quit() call in sys_exit()
sys_exit() is unreachable in emscripten build.
2026-08-31 16:37:15 +09:00
kichikuou 1ccc7630c1 QNT: Fix alpha decoding of 1-pixel-wide images 2026-08-31 07:02:44 +09:00
kichikuou 6456f59c37 emscripten: Add JSPI build option 2026-08-21 08:54:34 +09:00
reiicheru f748e8f678 android: Hide android status bar when app is resumed (#81) 2026-08-10 09:44:56 +09:00
kichikuou a9e23508b7 Reimplement the string input dialog with a microui textbox
The dialog edited and drew its field by hand because microui's textbox
cannot display IME composition text.

Teach microui about compositions instead, and split mu_textbox_raw()
into input and drawing halves so the MJ inline editor can share the
input path while keeping the game's own appearance.
2026-08-08 13:03:31 +09:00
kichikuou c3bd978f02 microui: Add keyboard navigation
ctx->focus is a transient "pressed with the mouse" state, so it cannot
serve as a keyboard focus. Add a persistent ctx->kb_focus alongside it.
Controls enter the focus cycle from mu_update_control(), which every
widget already calls, so this needs no per-widget code.

Tab and Down move the focus forward, Shift+Tab and Up backward, Enter or
Space activates the focused control, and Left/Right adjust a focused
slider. The focused control is outlined with the new MU_COLOR_FOCUSRING;
window chrome is excluded via MU_OPT_NOKBNAV.
2026-08-08 10:17:02 +09:00
kichikuou 50a6b17c5e Version 2.19.1 2026-08-02 12:41:18 +09:00
kichikuou 0d49619366 Fix unit map storage for 16-bit variables
Store unit map attributes as vmvar_t and derive VR/VW transfer sizes
from their element type. This prevents buffer overruns after vmvar_t
became uint16_t and fixes upward movement in Rance IV.

Add scenario coverage for both transfer directions and verify that
adjacent variables remain intact.
2026-08-02 12:34:40 +09:00
kichikuou da3cc5d900 ags: Consolidate display info accessors 2026-07-24 21:04:25 +09:00
kichikuou 1785e93fcc Refactor gfx API 2026-07-24 21:04:22 +09:00
150 changed files with 5611 additions and 7145 deletions
+1
View File
@@ -24,5 +24,6 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: xsystem35-android-sdl3
path: android/app/build/outputs/apk/release/app-release.apk
archive: false
+15 -9
View File
@@ -2,12 +2,17 @@ name: Emscripten Build
on: [push, pull_request]
env:
EM_VERSION: latest
EM_VERSION: 6.0.10
EM_CACHE_FOLDER: 'emsdk-cache'
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sdl-version: [2, 3]
name: Emscripten SDL${{ matrix.sdl-version }}
steps:
- uses: actions/checkout@v6
@@ -17,7 +22,7 @@ jobs:
uses: actions/cache@v5
with:
path: ${{env.EM_CACHE_FOLDER}}
key: ${{env.EM_VERSION}}-${{ runner.os }}
key: ${{env.EM_VERSION}}-${{ runner.os }}-sdl${{ matrix.sdl-version }}
- name: Setup Emscripten toolchain
uses: emscripten-core/setup-emsdk@v16
@@ -30,15 +35,16 @@ jobs:
- name: Build
run: |
mkdir -p out/wasm
cd out/wasm
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ../../
make -j4
mkdir xsystem35
mv src/xsystem35.* xsystem35/
emcmake cmake -S . -B out/wasm \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }}
cmake --build out/wasm -j4
mkdir out/wasm/xsystem35
mv out/wasm/src/xsystem35.* out/wasm/xsystem35/
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: xsystem35-wasm
name: xsystem35-wasm-sdl${{ matrix.sdl-version }}
path: out/wasm/xsystem35
+15 -10
View File
@@ -3,33 +3,38 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-26.04
strategy:
fail-fast: false
matrix:
build-type: ["Debug", "Release"]
sdl-version: [2, 3]
name: Linux ${{ matrix.build-type }}
name: Linux SDL${{ matrix.sdl-version }} ${{ matrix.build-type }}
steps:
- uses: actions/checkout@v6
- name: Install Deps
run: |
sudo apt update
sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
sudo apt install libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
if [ "${{ matrix.sdl-version }}" = 2 ]; then
sudo apt install libsdl2-dev libsdl2-mixer-dev
else
sudo apt install libsdl3-dev
fi
- name: Build
run: |
mkdir -p out/${{ matrix.build-type }}
cd out/${{ matrix.build-type }}
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} ../../
make -j4
cmake -S . -B out/build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }}
cmake --build out/build -j4
- name: Test
run: ctest --output-on-failure
working-directory: out/${{ matrix.build-type }}
run: ctest --test-dir out/build --output-on-failure
- name: Verify xsystem35.pot is up to date
if: matrix.build-type == 'Debug'
if: matrix.build-type == 'Debug' && matrix.sdl-version == 2
run: |
xgettext --default-domain=xsystem35 --directory=. \
--keyword=_ --keyword=N_ \
+22 -10
View File
@@ -9,11 +9,23 @@ jobs:
matrix:
include:
- sys: mingw32
package: "xsystem35-32bit"
package: "xsystem35-sdl2-32bit"
sdl-version: 2
sdl-deps: "SDL2:p SDL2_mixer:p"
cmake_args: "-DENABLE_DEBUGGER=no"
deps: ""
- sys: ucrt64
package: "xsystem35-64bit"
package: "xsystem35-sdl2-64bit"
sdl-version: 2
sdl-deps: "SDL2:p SDL2_mixer:p"
cmake_args: ""
deps: >-
cjson:p
portmidi:p
- sys: ucrt64
package: "xsystem35-sdl3-64bit"
sdl-version: 3
sdl-deps: "sdl3:p sdl3-mixer:p"
cmake_args: ""
deps: >-
cjson:p
@@ -21,7 +33,7 @@ jobs:
defaults:
run:
shell: msys2 {0}
name: MSYS2 ${{ matrix.sys }}
name: MSYS2 SDL${{ matrix.sdl-version }} ${{ matrix.sys }}
steps:
- name: Set up MSYS2
@@ -29,9 +41,8 @@ jobs:
with:
msystem: ${{ matrix.sys }}
pacboy: >-
SDL2:p
SDL2_ttf:p
SDL2_mixer:p
${{ matrix.sdl-deps }}
freetype:p
libwebp:p
${{ matrix.deps }}
@@ -50,10 +61,11 @@ jobs:
- name: Build
run: |
mkdir -p out/release
cd out/release
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ${{ matrix.cmake_args }} ../../
ninja
cmake -S . -B out/release -GNinja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }} \
${{ matrix.cmake_args }}
cmake --build out/release
- name: Test
run: ctest --output-on-failure
+1
View File
@@ -1,6 +1,7 @@
out
android/.gradle
android/.idea
android/app/aars
android/app/.cxx
android/app/.externalNativeBuild
android/app/build
+17
View File
@@ -1,5 +1,22 @@
# Changelog
## 2.20.0 - 2026-09-11
- Improved font rendering to more closely match the original engine. On
Windows, MS Gothic and MS Mincho are now used by default when available.
- The experimental `enable_zb` option has been removed. (#44)
- SACT: Message skipping now also skips text animation, effects, waits, and
related input waits.
- SACT: BGM now honors the loop count specified by the game.
- SACT: Fixed timer handling in 楽園行.
- The popup menu and dialogs can now be operated with the keyboard.
- Fixed mouse coordinates changing unexpectedly when automatic mouse movement
is disabled in System 3.8/3.9 games.
- Android: The status bar now stays hidden after returning to the game. (#81)
- Debugger: Added an `info cache` command for viewing cache usage and hit rates.
## 2.19.1 - 2026-08-02
- Fixed an issue that prevented moving upward during battles in Rance 4.
## 2.19.0 - 2026-07-19
- Replaced the GTK-based popup menu and dialogs with a pure-SDL implementation.
- On Android, the popup menu opens with a three-finger tap.
+74 -19
View File
@@ -5,7 +5,24 @@ set(CMAKE_C_STANDARD 99)
enable_testing()
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(XSYSTEM35_VERSION "2.19.0")
set(XSYSTEM35_VERSION "2.20.0")
if (ANDROID)
set(XSYSTEM35_DEFAULT_SDL_VERSION 3)
else()
set(XSYSTEM35_DEFAULT_SDL_VERSION 2)
endif()
set(XSYSTEM35_SDL_VERSION "${XSYSTEM35_DEFAULT_SDL_VERSION}" CACHE STRING
"SDL major version (2 or 3)")
set_property(CACHE XSYSTEM35_SDL_VERSION PROPERTY STRINGS 2 3)
if (NOT XSYSTEM35_SDL_VERSION STREQUAL "2" AND
NOT XSYSTEM35_SDL_VERSION STREQUAL "3")
message(FATAL_ERROR "XSYSTEM35_SDL_VERSION must be 2 or 3")
endif()
add_library(xsystem35_sdl INTERFACE)
target_compile_definitions(xsystem35_sdl INTERFACE
XSYSTEM35_SDL_VERSION=${XSYSTEM35_SDL_VERSION})
add_library(xsystem35_sdl_mixer INTERFACE)
include(CheckSymbolExists)
include(FetchContent)
@@ -55,6 +72,8 @@ check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
if (EMSCRIPTEN)
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
function(add_emscripten_library name option)
add_library(${name} INTERFACE)
set_target_properties(${name} PROPERTIES
@@ -62,16 +81,23 @@ if (EMSCRIPTEN)
INTERFACE_LINK_OPTIONS ${option})
endfunction()
add_emscripten_library(zlib -sUSE_ZLIB=1)
add_emscripten_library(sdl2 -sUSE_SDL=2)
add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2)
target_compile_options(xsystem35_sdl INTERFACE
-sUSE_SDL=${XSYSTEM35_SDL_VERSION})
target_link_options(xsystem35_sdl INTERFACE
-sUSE_SDL=${XSYSTEM35_SDL_VERSION})
add_emscripten_library(freetype2 -sUSE_FREETYPE=1)
set(DEFAULT_FONT_PATH /fonts/)
fetch_webp()
set(HAVE_WEBP 1)
elseif (ANDROID)
add_library(sdl2 ALIAS SDL2)
add_library(sdl2_ttf ALIAS SDL2_ttf)
add_library(sdl2_mixer ALIAS SDL2_mixer)
if (NOT XSYSTEM35_SDL_VERSION STREQUAL "3")
message(FATAL_ERROR "Android supports SDL3 only")
endif()
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3)
target_link_libraries(xsystem35_sdl_mixer INTERFACE
SDL3_mixer::SDL3_mixer)
add_library(freetype2 ALIAS freetype)
find_library(ndk_log log)
find_library(ndk_zlib z)
add_library(zlib INTERFACE)
@@ -87,20 +113,48 @@ else() # non-emscripten, non-android
add_library(zlib ALIAS ZLIB::ZLIB)
include(FindPkgConfig)
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
if (WIN32)
add_static_library(sdl2 SDL2)
add_static_library(sdl2_ttf SDL2TTF)
# Workaround for linking error
set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES
$<LINK_GROUP:RESCAN,${SDL2TTF_STATIC_LIBRARIES} -lstdc++>)
add_static_library(sdl2_mixer SDL2MIXER)
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
if (XSYSTEM35_SDL_VERSION STREQUAL "2")
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
if (WIN32)
add_static_library(sdl2 SDL2)
add_static_library(sdl2_mixer SDL2MIXER)
target_link_libraries(xsystem35_sdl INTERFACE sdl2)
target_link_libraries(xsystem35_sdl_mixer INTERFACE sdl2_mixer)
else()
target_link_libraries(xsystem35_sdl INTERFACE PkgConfig::SDL2)
target_link_libraries(xsystem35_sdl_mixer INTERFACE
PkgConfig::SDL2MIXER)
endif()
else()
add_library(sdl2 ALIAS PkgConfig::SDL2)
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
find_package(SDL3 3.4 REQUIRED CONFIG)
find_package(SDL3_mixer 3.2 QUIET CONFIG)
if (NOT SDL3_mixer_FOUND)
message(STATUS "SDL3_mixer not found; building SDL3_mixer 3.2.4 from source")
FetchContent_Declare(
SDL3_mixer
URL https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-3.2.4.tar.gz
URL_HASH SHA256=f2ea848ccdf2f394cd4973ee0f6c482e04511044695cccfd46bab6dcd7f780aa
)
FetchContent_MakeAvailable(SDL3_mixer)
endif()
if (WIN32)
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3-static)
target_link_libraries(xsystem35_sdl_mixer INTERFACE
SDL3_mixer::SDL3_mixer-static)
else()
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3)
target_link_libraries(xsystem35_sdl_mixer INTERFACE
SDL3_mixer::SDL3_mixer)
endif()
endif()
if (WIN32)
add_static_library(freetype2 FREETYPE)
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
target_link_libraries(freetype2 INTERFACE -lstdc++)
else()
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
endif()
@@ -211,6 +265,7 @@ endfunction()
message("xsystem35 summary:")
message("------------------")
print_summary(SDL ${XSYSTEM35_SDL_VERSION})
print_summary(audio ${SUMMARY_AUDIO})
print_summary(cdrom ${SUMMARY_CDROM})
print_summary(midi ${SUMMARY_MIDI})
+66 -22
View File
@@ -1,4 +1,4 @@
# xsytem35-sdl2
# xsystem35 for SDL2/SDL3
This is a multi-platform port of `xsystem35`, a free implementation of
AliceSoft's System 3.x game engine.
@@ -102,53 +102,97 @@ $ xsystem35
See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
## Building
SDL2 is used by default on desktop platforms and Emscripten. Pass
`-DXSYSTEM35_SDL_VERSION=3` to CMake to select SDL3. Use a separate build
directory for each SDL version.
The supported configurations are:
| Platform | SDL2 | SDL3 |
| --- | --- | --- |
| Linux and macOS | Yes | Yes |
| Windows 64-bit | Yes | Yes |
| Windows 32-bit | Yes | No |
| Emscripten | Yes | Yes |
| Android | No | Yes |
SDL2 builds require SDL 2.18 or later and SDL2_mixer. SDL3 builds require
SDL 3.4 or later and SDL3_mixer 3.2 or later. The remaining common
dependencies are FreeType, zlib, and optionally libwebp, PortMidi, and cJSON.
### Linux (Debian / Ubuntu)
```bash
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
$ make && make install
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev \
libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ cmake -S . -B out/sdl2 -DCMAKE_BUILD_TYPE=Debug \
-DXSYSTEM35_SDL_VERSION=2
$ cmake --build out/sdl2
$ sudo cmake --install out/sdl2
```
For SDL3 on a distribution that provides SDL 3.4 or later (for example,
Ubuntu 26.04), install `libsdl3-dev` instead of the SDL2 packages and
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`. If SDL3_mixer 3.2 or
later is installed, CMake uses it; otherwise, CMake downloads and builds
SDL3_mixer 3.2.4 during configuration.
### MacOS
[Homebrew](https://brew.sh/) is required.
```bash
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
$ make && make install
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi \
cjson asciidoctor
$ cmake -S . -B out/sdl2 -DCMAKE_BUILD_TYPE=Debug \
-DXSYSTEM35_SDL_VERSION=2
$ cmake --build out/sdl2
$ sudo cmake --install out/sdl2
```
For SDL3, install `sdl3 sdl3_mixer` instead of `sdl2 sdl2_mixer` and
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`.
### Windows
[MSYS2](https://www.msys2.org) is required.
```bash
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
$ mkdir -p out/debug
$ cd out/debug
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
$ make
$ pacman -S mingw-w64-ucrt-x86_64-gcc \
mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 \
mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-freetype \
mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi \
mingw-w64-ucrt-x86_64-cjson
$ cmake -S . -B out/sdl2 -G"MSYS Makefiles" \
-DCMAKE_BUILD_TYPE=Debug -DXSYSTEM35_SDL_VERSION=2
$ cmake --build out/sdl2
```
For a 64-bit SDL3 build, install `mingw-w64-ucrt-x86_64-sdl3` and
`mingw-w64-ucrt-x86_64-sdl3-mixer` instead of the SDL2 packages, then
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`. The MSYS2 mingw32
repository does not provide SDL3, so 32-bit Windows builds remain on SDL2.
### Emscripten
```bash
$ mkdir -p out/wasm
$ cd out/wasm
$ emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../../
$ make
$ emcmake cmake -S . -B out/wasm-sdl2 -DCMAKE_BUILD_TYPE=MinSizeRel \
-DXSYSTEM35_SDL_VERSION=2
$ cmake --build out/wasm-sdl2
```
For SDL3, use a different build directory and
`-DXSYSTEM35_SDL_VERSION=3`. Both versions use Emscripten's bundled SDL port;
SDL_mixer is not required.
To use the generated binary, check out
[Kichikuou on Web](https://github.com/kichikuou/web) and copy `out/xsystem35.*`
into its `docs` directory.
[Kichikuou on Web](https://github.com/kichikuou/web) and copy
`out/wasm-sdl2/src/xsystem35.*` into its `docs` directory.
### Android
See [android/README.md](android/).
Android uses SDL3 exclusively. The Gradle build downloads fixed official SDL3
3.4.16 and SDL3_mixer 3.2.4 AARs and exposes them to CMake through Prefab.
+5
View File
@@ -73,6 +73,11 @@ Note: This ZIP format is also compatible with
## Building from Source
Android builds use SDL3 exclusively. Gradle downloads the fixed official SDL3
3.4.16 and SDL3_mixer 3.2.4 AARs, verifies their checksums, and supplies their
native libraries to CMake through Prefab. No separate SDL installation is
required.
### Using Android Studio
Open this directory as an Android Studio project.
+73 -2
View File
@@ -8,19 +8,89 @@ else {
}
apply plugin: 'kotlin-android'
def aarsDir = file('aars')
def assetsLicensesDir = file('src/main/assets/licenses')
def sha256 = { File file ->
def digest = java.security.MessageDigest.getInstance('SHA-256')
file.withInputStream { input ->
byte[] buffer = new byte[8192]
for (int n = input.read(buffer); n >= 0; n = input.read(buffer)) {
digest.update(buffer, 0, n)
}
}
digest.digest().encodeHex().toString()
}
def installAar = { String url, String archiveSha256, String aarName,
String aarSha256, String licenseName ->
def aarFile = new File(aarsDir, aarName)
def licenseFile = new File(assetsLicensesDir, licenseName)
if (!aarFile.exists() || sha256(aarFile) != aarSha256 || !licenseFile.exists()) {
logger.lifecycle("Downloading ${aarName} ...")
def archive = File.createTempFile('sdl-android', '.zip')
try {
new URL(url).withInputStream { input ->
archive.withOutputStream { it << input }
}
if (sha256(archive) != archiveSha256) {
throw new GradleException("Checksum mismatch for ${url}")
}
aarsDir.mkdirs()
assetsLicensesDir.mkdirs()
copy {
from zipTree(archive)
include aarName
into aarsDir
}
copy {
from zipTree(archive)
include 'LICENSE.txt'
into assetsLicensesDir
rename 'LICENSE.txt', licenseName
}
} finally {
archive.delete()
}
}
if (!aarFile.exists() || sha256(aarFile) != aarSha256) {
throw new GradleException("Checksum mismatch for ${aarFile}")
}
aarFile
}
def sdlVersion = '3.4.16'
def sdlAarFile = installAar(
"https://github.com/libsdl-org/SDL/releases/download/release-${sdlVersion}/SDL3-devel-${sdlVersion}-android.zip",
'e1da8d9298471ea4ab8c6d48a804c0ac5415f35f30fcf7eba61aa7357e165765',
"SDL3-${sdlVersion}.aar",
'03710fc7b49cc070551446841a843840fabf2aaaaa3043cd5739292a55e4e61c',
'SDL')
def sdlMixerVersion = '3.2.4'
def sdlMixerAarFile = installAar(
"https://github.com/libsdl-org/SDL_mixer/releases/download/release-${sdlMixerVersion}/SDL3_mixer-devel-${sdlMixerVersion}-android.zip",
'b7f9f1ee5162a0492e71941f2cbdc50ec256ef15c0e995ca72c7e519eebd810d',
"SDL3_mixer-${sdlMixerVersion}.aar",
'd13dc45d4a4d6a2db4132400e77629c466ed533641fc9e211dfe85fab025b792',
'SDL_mixer')
android {
if (buildAsApplication) {
namespace "io.github.kichikuou.xsystem35"
}
buildFeatures {
prefab true
}
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
versionCode 36
versionName "2.19.0" + gitRevision()
versionName "2.20.0" + gitRevision()
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
arguments "-DANDROID_STL=c++_static", "-DXSYSTEM35_SDL_VERSION=3"
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
@@ -83,6 +153,7 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation files(sdlAarFile, sdlMixerAarFile)
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
testImplementation 'junit:junit:4.13.2'
-1
View File
@@ -1 +0,0 @@
include $(call all-subdir-makefiles)
-10
View File
@@ -1,10 +0,0 @@
# Uncomment this if you're using STL in your project
# You can find more information here:
# https://developer.android.com/ndk/guides/cpp-support
# APP_STL := c++_shared
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# Min runtime API level
APP_PLATFORM=android-16
+16 -32
View File
@@ -3,37 +3,25 @@ cmake_minimum_required(VERSION 3.14)
project(GAME)
set(PROJECT_ROOT_DIR ../../..)
# Compilation of SDL and companion libraries
# SDL3 and SDL3_mixer are provided by their official Android AARs through Prefab.
find_package(SDL3 3.4 REQUIRED CONFIG)
find_package(SDL3_mixer 3.2.4 REQUIRED CONFIG)
include(FetchContent)
FetchContent_Declare(
SDL
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
)
FetchContent_Declare(
SDL_ttf
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz
URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d
)
FetchContent_Declare(
SDL_mixer
URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.8.0/SDL2_mixer-2.8.0.tar.gz
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
freetype
URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz
https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz
URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678
)
# Only FreeType's own rasterizer is needed, none of its optional dependencies.
set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE)
set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE)
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE)
set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE)
set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE)
set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE)
set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
set(SDL2MIXER_MOD OFF CACHE BOOL "Support loading MOD music" FORCE)
set(SDL2MIXER_MIDI OFF CACHE BOOL "Enable MIDI music" FORCE)
set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE)
set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE)
set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE)
FetchContent_MakeAvailable(SDL SDL_ttf SDL_mixer)
FetchContent_MakeAvailable(freetype)
# The main CMakeLists.txt of xsystem35
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
@@ -48,8 +36,4 @@ file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/MTLc3m.txt ${ASSETS_DIR}/licenses/MT
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mincho)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui)
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz)
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
file(COPY_FILE ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
-18
View File
@@ -1,18 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := main
SDL_PATH := ../SDL
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
# Add your application source files here...
LOCAL_SRC_FILES := YourSourceHere.c
LOCAL_SHARED_LIBRARIES := SDL2
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
include $(BUILD_SHARED_LIBRARY)
-13
View File
@@ -1,13 +0,0 @@
cmake_minimum_required(VERSION 3.6)
project(MY_APP)
find_library(SDL2 SDL2)
add_library(main SHARED)
target_sources(main PRIVATE YourSourceHere.c)
target_link_libraries(main SDL2)
+1
View File
@@ -66,6 +66,7 @@
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
-->
<meta-data android:name="SDL_ENV.SDL_IOS_ORIENTATIONS" android:value="LandscapeLeft LandscapeRight"/>
<activity android:name=".LauncherActivity"
android:exported="true">
@@ -45,6 +45,8 @@ class GameActivity : SDLActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT)!!)
// Workaround for https://github.com/libsdl-org/SDL/issues/8995
SDLActivity.setWindowStyle(true)
}
override fun onStop() {
@@ -70,7 +72,7 @@ class GameActivity : SDLActivity() {
}
override fun getLibraries(): Array<String> {
return arrayOf("SDL2", "xsystem35")
return arrayOf("SDL3", "xsystem35")
}
override fun getArguments(): Array<String> {
@@ -12,11 +12,9 @@ class LicensesMenuActivity : Activity() {
Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"),
Entry("SDL", "SDL", "https://www.libsdl.org/"),
Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"),
Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"),
Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"),
Entry("microui", "microui", "https://github.com/rxi/microui"),
Entry("FreeType", "freetype", "https://freetype.org/"),
Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"),
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
)
@@ -1,22 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.UsbDevice;
interface HIDDevice
{
public int getId();
public int getVendorId();
public int getProductId();
public String getSerialNumber();
public int getVersion();
public String getManufacturerName();
public String getProductName();
public UsbDevice getDevice();
public boolean open();
public int sendFeatureReport(byte[] report);
public int sendOutputReport(byte[] report);
public boolean getFeatureReport(byte[] report);
public void setFrozen(boolean frozen);
public void close();
public void shutdown();
}
@@ -1,650 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattService;
import android.hardware.usb.UsbDevice;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.os.*;
//import com.android.internal.util.HexDump;
import java.lang.Runnable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.UUID;
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
private static final String TAG = "hidapi";
private HIDDeviceManager mManager;
private BluetoothDevice mDevice;
private int mDeviceId;
private BluetoothGatt mGatt;
private boolean mIsRegistered = false;
private boolean mIsConnected = false;
private boolean mIsChromebook = false;
private boolean mIsReconnecting = false;
private boolean mFrozen = false;
private LinkedList<GattOperation> mOperations;
GattOperation mCurrentOperation = null;
private Handler mHandler;
private static final int TRANSPORT_AUTO = 0;
private static final int TRANSPORT_BREDR = 1;
private static final int TRANSPORT_LE = 2;
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
static class GattOperation {
private enum Operation {
CHR_READ,
CHR_WRITE,
ENABLE_NOTIFICATION
}
Operation mOp;
UUID mUuid;
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
}
public void run() {
// This is executed in main thread
BluetoothGattCharacteristic chr;
switch (mOp) {
case CHR_READ:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
if (!mGatt.readCharacteristic(chr)) {
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case CHR_WRITE:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
chr.setValue(mValue);
if (!mGatt.writeCharacteristic(chr)) {
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case ENABLE_NOTIFICATION:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
if (chr != null) {
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
int properties = chr.getProperties();
byte[] value;
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
} else {
Log.e(TAG, "Unable to start notifications on input characteristic");
mResult = false;
return;
}
mGatt.setCharacteristicNotification(chr, true);
cccd.setValue(value);
if (!mGatt.writeDescriptor(cccd)) {
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
mResult = false;
return;
}
mResult = true;
}
}
}
}
public boolean finish() {
return mResult;
}
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
return null;
return valveService.getCharacteristic(uuid);
}
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.CHR_READ, uuid);
}
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
}
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
}
}
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
mManager = manager;
mDevice = device;
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
mIsRegistered = false;
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
mOperations = new LinkedList<GattOperation>();
mHandler = new Handler(Looper.getMainLooper());
mGatt = connectGatt();
// final HIDDeviceBLESteamController finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.checkConnectionForChromebookIssue();
// }
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
public String getIdentifier() {
return String.format("SteamController.%s", mDevice.getAddress());
}
public BluetoothGatt getGatt() {
return mGatt;
}
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
private BluetoothGatt connectGatt(boolean managed) {
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
try {
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
} catch (Exception e) {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
} else {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
}
private BluetoothGatt connectGatt() {
return connectGatt(false);
}
protected int getConnectionState() {
Context context = mManager.getContext();
if (context == null) {
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
return BluetoothProfile.STATE_DISCONNECTED;
}
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
if (btManager == null) {
// This device doesn't support Bluetooth. We should never be here, because how did
// we instantiate a device to start with?
return BluetoothProfile.STATE_DISCONNECTED;
}
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
}
public void reconnect() {
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
mGatt.disconnect();
mGatt = connectGatt();
}
}
protected void checkConnectionForChromebookIssue() {
if (!mIsChromebook) {
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
// over and over.
return;
}
int connectionState = getConnectionState();
switch (connectionState) {
case BluetoothProfile.STATE_CONNECTED:
if (!mIsConnected) {
// We are in the Bad Chromebook Place. We can force a disconnect
// to try to recover.
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
else if (!isRegistered()) {
if (mGatt.getServices().size() > 0) {
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
probeService(this);
}
else {
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
}
else {
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
return;
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
case BluetoothProfile.STATE_CONNECTING:
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
break;
}
final HIDDeviceBLESteamController finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.checkConnectionForChromebookIssue();
}
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
private boolean isRegistered() {
return mIsRegistered;
}
private void setRegistered() {
mIsRegistered = true;
}
private boolean probeService(HIDDeviceBLESteamController controller) {
if (isRegistered()) {
return true;
}
if (!mIsConnected) {
return false;
}
Log.v(TAG, "probeService controller=" + controller);
for (BluetoothGattService service : mGatt.getServices()) {
if (service.getUuid().equals(steamControllerService)) {
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(inputCharacteristic)) {
Log.v(TAG, "Found input characteristic");
// Start notifications
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
enableNotification(chr.getUuid());
}
}
}
return true;
}
}
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
mIsConnected = false;
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
private void finishCurrentGattOperation() {
GattOperation op = null;
synchronized (mOperations) {
if (mCurrentOperation != null) {
op = mCurrentOperation;
mCurrentOperation = null;
}
}
if (op != null) {
boolean result = op.finish(); // TODO: Maybe in main thread as well?
// Our operation failed, let's add it back to the beginning of our queue.
if (!result) {
mOperations.addFirst(op);
}
}
executeNextGattOperation();
}
private void executeNextGattOperation() {
synchronized (mOperations) {
if (mCurrentOperation != null)
return;
if (mOperations.isEmpty())
return;
mCurrentOperation = mOperations.removeFirst();
}
// Run in main thread
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
mCurrentOperation.run();
// now wait for the GATT callback and when it comes, finish this operation
}
}
});
}
private void queueGattOperation(GattOperation op) {
synchronized (mOperations) {
mOperations.add(op);
}
executeNextGattOperation();
}
private void enableNotification(UUID chrUuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
queueGattOperation(op);
}
public void writeCharacteristic(UUID uuid, byte[] value) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
queueGattOperation(op);
}
public void readCharacteristic(UUID uuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
queueGattOperation(op);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////////// BluetoothGattCallback overridden methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
mIsReconnecting = false;
if (newState == 2) {
mIsConnected = true;
// Run directly, without GattOperation
if (!isRegistered()) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGatt.discoverServices();
}
});
}
}
else if (newState == 0) {
mIsConnected = false;
}
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onServicesDiscovered status=" + status);
if (status == 0) {
if (gatt.getServices().size() == 0) {
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
mIsReconnecting = true;
mIsConnected = false;
gatt.disconnect();
mGatt = connectGatt(false);
}
else {
probeService(this);
}
}
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
}
finishCurrentGattOperation();
}
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic)) {
// Only register controller with the native side once it has been fully configured
if (!isRegistered()) {
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
setRegistered();
}
}
finishCurrentGattOperation();
}
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// Enable this for verbose logging of controller input reports
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
}
}
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//Log.v(TAG, "onDescriptorRead status=" + status);
}
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
if (chr.getUuid().equals(inputCharacteristic)) {
boolean hasWrittenInputDescriptor = true;
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
if (reportChr != null) {
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
gatt.writeCharacteristic(reportChr);
}
}
finishCurrentGattOperation();
}
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
}
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
//Log.v(TAG, "onReadRemoteRssi status=" + status);
}
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
//Log.v(TAG, "onMtuChanged status=" + status);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////// Public API
//////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
// Valve Corporation
final int VALVE_USB_VID = 0x28DE;
return VALVE_USB_VID;
}
@Override
public int getProductId() {
// We don't have an easy way to query from the Bluetooth device, but we know what it is
final int D0G_BLE2_PID = 0x1106;
return D0G_BLE2_PID;
}
@Override
public String getSerialNumber() {
// This will be read later via feature report by Steam
return "12345";
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
return "Valve Corporation";
}
@Override
public String getProductName() {
return "Steam Controller";
}
@Override
public UsbDevice getDevice() {
return null;
}
@Override
public boolean open() {
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
// We need to skip the first byte, as that doesn't go over the air
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
}
@Override
public int sendOutputReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
}
@Override
public boolean getFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return false;
}
//Log.v(TAG, "getFeatureReport");
readCharacteristic(reportCharacteristic);
return true;
}
@Override
public void close() {
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
@Override
public void shutdown() {
close();
BluetoothGatt g = mGatt;
if (g != null) {
g.disconnect();
g.close();
mGatt = null;
}
mManager = null;
mIsRegistered = false;
mIsConnected = false;
mOperations.clear();
}
}
@@ -1,698 +0,0 @@
package org.libsdl.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.os.Build;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.*;
import android.os.Handler;
import android.os.Looper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class HIDDeviceManager {
private static final String TAG = "hidapi";
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
private static HIDDeviceManager sManager;
private static int sManagerRefCount = 0;
public static HIDDeviceManager acquire(Context context) {
if (sManagerRefCount == 0) {
sManager = new HIDDeviceManager(context);
}
++sManagerRefCount;
return sManager;
}
public static void release(HIDDeviceManager manager) {
if (manager == sManager) {
--sManagerRefCount;
if (sManagerRefCount == 0) {
sManager.close();
sManager = null;
}
}
}
private Context mContext;
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
private int mNextDeviceId = 0;
private SharedPreferences mSharedPreferences = null;
private boolean mIsChromebook = false;
private UsbManager mUsbManager;
private Handler mHandler;
private BluetoothManager mBluetoothManager;
private List<BluetoothDevice> mLastBluetoothDevices;
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceAttached(usbDevice);
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceDetached(usbDevice);
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
}
}
};
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Bluetooth device was connected. If it was a Steam Controller, handle it
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device connected: " + device);
if (isSteamController(device)) {
connectBluetoothDevice(device);
}
}
// Bluetooth device was disconnected, remove from controller manager (if any)
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device disconnected: " + device);
disconnectBluetoothDevice(device);
}
}
};
private HIDDeviceManager(final Context context) {
mContext = context;
HIDDeviceRegisterCallback();
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
// if (shouldClear) {
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
// spedit.clear();
// spedit.commit();
// }
// else
{
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
}
}
public Context getContext() {
return mContext;
}
public int getDeviceIDForIdentifier(String identifier) {
SharedPreferences.Editor spedit = mSharedPreferences.edit();
int result = mSharedPreferences.getInt(identifier, 0);
if (result == 0) {
result = mNextDeviceId++;
spedit.putInt("next_device_id", mNextDeviceId);
}
spedit.putInt(identifier, result);
spedit.commit();
return result;
}
private void initializeUSB() {
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
if (mUsbManager == null) {
return;
}
/*
// Logging
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
Log.i(TAG,"Path: " + device.getDeviceName());
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
Log.i(TAG,"Product: " + device.getProductName());
Log.i(TAG,"ID: " + device.getDeviceId());
Log.i(TAG,"Class: " + device.getDeviceClass());
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
Log.i(TAG,"Vendor ID " + device.getVendorId());
Log.i(TAG,"Product ID: " + device.getProductId());
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
Log.i(TAG,"---------------------------------------");
// Get interface details
for (int index = 0; index < device.getInterfaceCount(); index++) {
UsbInterface mUsbInterface = device.getInterface(index);
Log.i(TAG," ***** *****");
Log.i(TAG," Interface index: " + index);
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
// Get endpoint details
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
{
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
Log.i(TAG," ++++ ++++ ++++");
Log.i(TAG," Endpoint index: " + epi);
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
Log.i(TAG," Direction: " + mEndpoint.getDirection());
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
Log.i(TAG," Interval: " + mEndpoint.getInterval());
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
Log.i(TAG," Type: " + mEndpoint.getType());
}
}
}
Log.i(TAG," No more devices connected.");
*/
// Register for USB broadcasts and permission completions
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
mContext.registerReceiver(mUsbBroadcast, filter);
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
handleUsbDeviceAttached(usbDevice);
}
}
UsbManager getUSBManager() {
return mUsbManager;
}
private void shutdownUSB() {
try {
mContext.unregisterReceiver(mUsbBroadcast);
} catch (Exception e) {
// We may not have registered, that's okay
}
}
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
return true;
}
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
return true;
}
return false;
}
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
final int XB360_IFACE_SUBCLASS = 93;
final int XB360_IFACE_PROTOCOL = 1; // Wired
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
final int[] SUPPORTED_VENDORS = {
0x0079, // GPD Win 2
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
0x056e, // Elecom
0x06a3, // Saitek
0x0738, // Mad Catz
0x07ff, // Mad Catz
0x0e6f, // PDP
0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
0x1532, // Razer Sabertooth
0x15e4, // Numark
0x162e, // Joytech
0x1689, // Razer Onza
0x1949, // Lab126, Inc.
0x1bad, // Harmonix
0x20d6, // PowerA
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x9886, // ASTRO Gaming
};
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
int vendor_id = usbDevice.getVendorId();
for (int supportedVid : SUPPORTED_VENDORS) {
if (vendor_id == supportedVid) {
return true;
}
}
}
return false;
}
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
final int XB1_IFACE_SUBCLASS = 71;
final int XB1_IFACE_PROTOCOL = 208;
final int[] SUPPORTED_VENDORS = {
0x03f0, // HP
0x044f, // Thrustmaster
0x045e, // Microsoft
0x0738, // Mad Catz
0x0b05, // ASUS
0x0e6f, // PDP
0x0f0d, // Hori
0x10f5, // Turtle Beach
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
0x3537, // GameSir
};
if (usbInterface.getId() == 0 &&
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
int vendor_id = usbDevice.getVendorId();
for (int supportedVid : SUPPORTED_VENDORS) {
if (vendor_id == supportedVid) {
return true;
}
}
}
return false;
}
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
connectHIDDeviceUSB(usbDevice);
}
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
List<Integer> devices = new ArrayList<Integer>();
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
devices.add(device.getId());
}
}
for (int id : devices) {
HIDDevice device = mDevicesById.get(id);
mDevicesById.remove(id);
device.shutdown();
HIDDeviceDisconnected(id);
}
}
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
boolean opened = false;
if (permission_granted) {
opened = device.open();
}
HIDDeviceOpenResult(device.getId(), opened);
}
}
}
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
synchronized (this) {
int interface_mask = 0;
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
// Check to see if we've already added this interface
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
int interface_id = usbInterface.getId();
if ((interface_mask & (1 << interface_id)) != 0) {
continue;
}
interface_mask |= (1 << interface_id);
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
int id = device.getId();
mDevicesById.put(id, device);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
}
}
}
}
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");
return;
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
return;
}
// Find bonded bluetooth controllers and create SteamControllers for them
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
// This device doesn't support Bluetooth.
return;
}
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
if (btAdapter == null) {
// This device has Bluetooth support in the codebase, but has no available adapters.
return;
}
// Get our bonded devices.
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
Log.d(TAG, "Bluetooth device available: " + device);
if (isSteamController(device)) {
connectBluetoothDevice(device);
}
}
// NOTE: These don't work on Chromebooks, to my undying dismay.
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mContext.registerReceiver(mBluetoothBroadcast, filter);
if (mIsChromebook) {
mHandler = new Handler(Looper.getMainLooper());
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
// final HIDDeviceManager finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.chromebookConnectionHandler();
// }
// }, 5000);
}
}
private void shutdownBluetooth() {
try {
mContext.unregisterReceiver(mBluetoothBroadcast);
} catch (Exception e) {
// We may not have registered, that's okay
}
}
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
// This function provides a sort of dummy version of that, watching for changes in the
// connected devices and attempting to add controllers as things change.
public void chromebookConnectionHandler() {
if (!mIsChromebook) {
return;
}
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
for (BluetoothDevice bluetoothDevice : currentConnected) {
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
connected.add(bluetoothDevice);
}
}
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
if (!currentConnected.contains(bluetoothDevice)) {
disconnected.add(bluetoothDevice);
}
}
mLastBluetoothDevices = currentConnected;
for (BluetoothDevice bluetoothDevice : disconnected) {
disconnectBluetoothDevice(bluetoothDevice);
}
for (BluetoothDevice bluetoothDevice : connected) {
connectBluetoothDevice(bluetoothDevice);
}
final HIDDeviceManager finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.chromebookConnectionHandler();
}
}, 10000);
}
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
synchronized (this) {
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
device.reconnect();
return false;
}
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
int id = device.getId();
mBluetoothDevices.put(bluetoothDevice, device);
mDevicesById.put(id, device);
// The Steam Controller will mark itself connected once initialization is complete
}
return true;
}
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
synchronized (this) {
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
if (device == null)
return;
int id = device.getId();
mBluetoothDevices.remove(bluetoothDevice);
mDevicesById.remove(id);
device.shutdown();
HIDDeviceDisconnected(id);
}
}
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
if (bluetoothDevice == null) {
return false;
}
// If the device has no local name, we really don't want to try an equality check against it.
if (bluetoothDevice.getName() == null) {
return false;
}
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
}
private void close() {
shutdownUSB();
shutdownBluetooth();
synchronized (this) {
for (HIDDevice device : mDevicesById.values()) {
device.shutdown();
}
mDevicesById.clear();
mBluetoothDevices.clear();
HIDDeviceReleaseCallback();
}
}
public void setFrozen(boolean frozen) {
synchronized (this) {
for (HIDDevice device : mDevicesById.values()) {
device.setFrozen(frozen);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
private HIDDevice getDevice(int id) {
synchronized (this) {
HIDDevice result = mDevicesById.get(id);
if (result == null) {
Log.v(TAG, "No device for id: " + id);
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
}
return result;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// JNI interface functions
//////////////////////////////////////////////////////////////////////////////////////////////////////
public boolean initialize(boolean usb, boolean bluetooth) {
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
if (usb) {
initializeUSB();
}
if (bluetooth) {
initializeBluetooth();
}
return true;
}
public boolean openDevice(int deviceID) {
Log.v(TAG, "openDevice deviceID=" + deviceID);
HIDDevice device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
// Look to see if this is a USB device and we have permission to access it
UsbDevice usbDevice = device.getDevice();
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
HIDDeviceOpenPending(deviceID);
try {
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
int flags;
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
flags = FLAG_MUTABLE;
} else {
flags = 0;
}
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);
}
return false;
}
try {
return device.open();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public int sendOutputReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendOutputReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public int sendFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public boolean getFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
return device.getFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public void closeDevice(int deviceID) {
try {
Log.v(TAG, "closeDevice deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return;
}
device.close();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////// Native methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
native void HIDDeviceOpenPending(int deviceID);
native void HIDDeviceOpenResult(int deviceID, boolean opened);
native void HIDDeviceDisconnected(int deviceID);
native void HIDDeviceInputReport(int deviceID, byte[] report);
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
}
@@ -1,309 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
class HIDDeviceUSB implements HIDDevice {
private static final String TAG = "hidapi";
protected HIDDeviceManager mManager;
protected UsbDevice mDevice;
protected int mInterfaceIndex;
protected int mInterface;
protected int mDeviceId;
protected UsbDeviceConnection mConnection;
protected UsbEndpoint mInputEndpoint;
protected UsbEndpoint mOutputEndpoint;
protected InputThread mInputThread;
protected boolean mRunning;
protected boolean mFrozen;
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
mManager = manager;
mDevice = usbDevice;
mInterfaceIndex = interface_index;
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
mRunning = false;
}
public String getIdentifier() {
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
}
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
return mDevice.getVendorId();
}
@Override
public int getProductId() {
return mDevice.getProductId();
}
@Override
public String getSerialNumber() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
try {
result = mDevice.getSerialNumber();
}
catch (SecurityException exception) {
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
}
}
if (result == null) {
result = "";
}
return result;
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getManufacturerName();
}
if (result == null) {
result = String.format("%x", getVendorId());
}
return result;
}
@Override
public String getProductName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getProductName();
}
if (result == null) {
result = String.format("%x", getProductId());
}
return result;
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
public String getDeviceName() {
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
}
@Override
public boolean open() {
mConnection = mManager.getUSBManager().openDevice(mDevice);
if (mConnection == null) {
Log.w(TAG, "Unable to open USB device " + getDeviceName());
return false;
}
// Force claim our interface
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
if (!mConnection.claimInterface(iface, true)) {
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
close();
return false;
}
// Find the endpoints
for (int j = 0; j < iface.getEndpointCount(); j++) {
UsbEndpoint endpt = iface.getEndpoint(j);
switch (endpt.getDirection()) {
case UsbConstants.USB_DIR_IN:
if (mInputEndpoint == null) {
mInputEndpoint = endpt;
}
break;
case UsbConstants.USB_DIR_OUT:
if (mOutputEndpoint == null) {
mOutputEndpoint = endpt;
}
break;
}
}
// Make sure the required endpoints were present
if (mInputEndpoint == null || mOutputEndpoint == null) {
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
close();
return false;
}
// Start listening for input
mRunning = true;
mInputThread = new InputThread();
mInputThread.start();
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
0x09/*HID set_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
return -1;
}
if (skipped_report_id) {
++length;
}
return length;
}
@Override
public int sendOutputReport(byte[] report) {
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (r != report.length) {
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
}
return r;
}
@Override
public boolean getFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
will remain in byte 0. */
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
return false;
}
if (skipped_report_id) {
++res;
++length;
}
byte[] data;
if (res == length) {
data = report;
} else {
data = Arrays.copyOfRange(report, 0, res);
}
mManager.HIDDeviceFeatureReport(mDeviceId, data);
return true;
}
@Override
public void close() {
mRunning = false;
if (mInputThread != null) {
while (mInputThread.isAlive()) {
mInputThread.interrupt();
try {
mInputThread.join();
} catch (InterruptedException e) {
// Keep trying until we're done
}
}
mInputThread = null;
}
if (mConnection != null) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
mConnection.close();
mConnection = null;
}
}
@Override
public void shutdown() {
close();
mManager = null;
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
protected class InputThread extends Thread {
@Override
public void run() {
int packetSize = mInputEndpoint.getMaxPacketSize();
byte[] packet = new byte[packetSize];
while (mRunning) {
int r;
try
{
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
}
catch (Exception e)
{
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
break;
}
if (r < 0) {
// Could be a timeout or an I/O error
}
if (r > 0) {
byte[] data;
if (r == packetSize) {
data = packet;
} else {
data = Arrays.copyOfRange(packet, 0, r);
}
if (!mFrozen) {
mManager.HIDDeviceInputReport(mDeviceId, data);
}
}
}
}
}
}
@@ -1,90 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import java.lang.Class;
import java.lang.reflect.Method;
/**
SDL library initialization
*/
public class SDL {
// This function should be called first and sets up the native code
// so it can call into the Java classes
public static void setupJNI() {
SDLActivity.nativeSetupJNI();
SDLAudioManager.nativeSetupJNI();
SDLControllerManager.nativeSetupJNI();
}
// This function should be called each time the activity is started
public static void initialize() {
setContext(null);
SDLActivity.initialize();
SDLAudioManager.initialize();
SDLControllerManager.initialize();
}
// This function stores the current activity (SDL or not)
public static void setContext(Context context) {
SDLAudioManager.setContext(context);
mContext = context;
}
public static Context getContext() {
return mContext;
}
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.");
}
try {
// Let's see if we have ReLinker available in the project. This is necessary for
// some projects that have huge numbers of local libraries bundled, and thus may
// trip a bug in Android's native library loader which ReLinker works around. (If
// loadLibrary works properly, ReLinker will simply use the normal Android method
// internally.)
//
// To use ReLinker, just add it as a dependency. For more information, see
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
//
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.
Method forceMethod = relinkClass.getDeclaredMethod("force");
Object relinkInstance = forceMethod.invoke(null);
Class<?> relinkInstanceClass = relinkInstance.getClass();
// Actually load the library!
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
}
catch (final Throwable e) {
// Fall back
try {
System.loadLibrary(libraryName);
}
catch (final UnsatisfiedLinkError ule) {
throw ule;
}
catch (final SecurityException se) {
throw se;
}
}
}
protected static Context mContext;
}
File diff suppressed because it is too large Load Diff
@@ -1,514 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
public class SDLAudioManager {
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
protected static Context mContext;
private static final int[] NO_DEVICES = {};
private static AudioDeviceCallback mAudioDeviceCallback;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
{
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
};
}
}
public static void setContext(Context context) {
mContext = context;
if (context != null) {
registerAudioDeviceCallback();
}
}
public static void release(Context context) {
unregisterAudioDeviceCallback(context);
}
// Audio
protected static String getAudioFormatString(int audioFormat) {
switch (audioFormat) {
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
}
}
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
int channelConfig;
int sampleSize;
int frameSize;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
/* On older devices let's use known good settings */
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
sampleRate = 48000;
}
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
}
switch (audioFormat)
{
case AudioFormat.ENCODING_PCM_8BIT:
sampleSize = 1;
break;
case AudioFormat.ENCODING_PCM_16BIT:
sampleSize = 2;
break;
case AudioFormat.ENCODING_PCM_FLOAT:
sampleSize = 4;
break;
default:
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
sampleSize = 2;
break;
}
if (isCapture) {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_IN_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
}
} else {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
case 3:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 4:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
break;
case 5:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
desiredChannels = 6;
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
}
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
}
/*
Log.v(TAG, "Speaker configuration (and order of channels):");
if ((channelConfig & 0x00000004) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
}
if ((channelConfig & 0x00000008) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
}
if ((channelConfig & 0x00000010) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
}
if ((channelConfig & 0x00000020) != 0) {
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
}
if ((channelConfig & 0x00000040) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
}
if ((channelConfig & 0x00000080) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
}
if ((channelConfig & 0x00000100) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
}
if ((channelConfig & 0x00000200) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
}
if ((channelConfig & 0x00000400) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
}
if ((channelConfig & 0x00000800) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
}
if ((channelConfig & 0x00001000) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
}
*/
}
frameSize = (sampleSize * desiredChannels);
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
int minBufferSize;
if (isCapture) {
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
} else {
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
}
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
int[] results = new int[4];
if (isCapture) {
if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
}
mAudioRecord.startRecording();
}
results[0] = mAudioRecord.getSampleRate();
results[1] = mAudioRecord.getAudioFormat();
results[2] = mAudioRecord.getChannelCount();
} else {
if (mAudioTrack == null) {
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
/* Try again, with safer values */
Log.e(TAG, "Failed during initialization of Audio Track");
mAudioTrack.release();
mAudioTrack = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
}
mAudioTrack.play();
}
results[0] = mAudioTrack.getSampleRate();
results[1] = mAudioTrack.getAudioFormat();
results[2] = mAudioTrack.getChannelCount();
}
results[3] = desiredFrames;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
return results;
}
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static void registerAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
}
}
private static void unregisterAudioDeviceCallback(Context context) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioOutputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioInputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteFloatBuffer(float[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(float)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteShortBuffer(short[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(short)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteByteBuffer(byte[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(byte)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return 0;
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
/** This method is called by SDL using JNI. */
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
try {
/* Set thread name */
if (iscapture) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
}
/* Set thread priority */
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
} catch (Exception e) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
}
public static native int nativeSetupJNI();
public static native void removeAudioDevice(boolean isCapture, int deviceId);
public static native void addAudioDevice(boolean isCapture, int deviceId);
}
@@ -1,856 +0,0 @@
package org.libsdl.app;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.content.Context;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
public class SDLControllerManager
{
public static native int nativeSetupJNI();
public static native int nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
boolean is_accelerometer, int button_mask,
int naxes, int axis_mask, int nhats, int nballs);
public static native int nativeRemoveJoystick(int device_id);
public static native int nativeAddHaptic(int device_id, String name);
public static native int nativeRemoveHaptic(int device_id);
public static native int onNativePadDown(int device_id, int keycode);
public static native int onNativePadUp(int device_id, int keycode);
public static native void onNativeJoy(int device_id, int axis,
float value);
public static native void onNativeHat(int device_id, int hat_id,
int x, int y);
protected static SDLJoystickHandler mJoystickHandler;
protected static SDLHapticHandler mHapticHandler;
private static final String TAG = "SDLControllerManager";
public static void initialize() {
if (mJoystickHandler == null) {
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
mJoystickHandler = new SDLJoystickHandler_API19();
} else {
mJoystickHandler = new SDLJoystickHandler_API16();
}
}
if (mHapticHandler == null) {
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
mHapticHandler = new SDLHapticHandler_API26();
} else {
mHapticHandler = new SDLHapticHandler();
}
}
}
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
public static boolean handleJoystickMotionEvent(MotionEvent event) {
return mJoystickHandler.handleMotionEvent(event);
}
/**
* This method is called by SDL using JNI.
*/
public static void pollInputDevices() {
mJoystickHandler.pollInputDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void pollHapticDevices() {
mHapticHandler.pollHapticDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticRun(int device_id, float intensity, int length) {
mHapticHandler.run(device_id, intensity, length);
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticStop(int device_id)
{
mHapticHandler.stop(device_id);
}
// Check if a given device is considered a possible SDL joystick
public static boolean isDeviceSDLJoystick(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
// We cannot use InputDevice.isVirtual before API 16, so let's accept
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
if ((device == null) || (deviceId < 0)) {
return false;
}
int sources = device.getSources();
/* This is called for every button press, so let's not spam the logs */
/*
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
}
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
}
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
}
*/
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
);
}
}
class SDLJoystickHandler {
/**
* Handles given MotionEvent.
* @param event the event to be handled.
* @return if given event was processed.
*/
public boolean handleMotionEvent(MotionEvent event) {
return false;
}
/**
* Handles adding and removing of input devices.
*/
public void pollInputDevices() {
}
}
/* Actual joystick functionality available for API >= 12 devices */
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
static class SDLJoystick {
public int device_id;
public String name;
public String desc;
public ArrayList<InputDevice.MotionRange> axes;
public ArrayList<InputDevice.MotionRange> hats;
}
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
int arg0Axis = arg0.getAxis();
int arg1Axis = arg1.getAxis();
if (arg0Axis == MotionEvent.AXIS_GAS) {
arg0Axis = MotionEvent.AXIS_BRAKE;
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
arg0Axis = MotionEvent.AXIS_GAS;
}
if (arg1Axis == MotionEvent.AXIS_GAS) {
arg1Axis = MotionEvent.AXIS_BRAKE;
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
arg1Axis = MotionEvent.AXIS_GAS;
}
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
// This is because the usual pairing are:
// - AXIS_X + AXIS_Y (left stick).
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
// This sorts the axes in the above order, which tends to be correct
// for Xbox-ish game pads that have the right stick on RX/RY and the
// triggers on Z/RZ.
//
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
//
// References:
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
if (arg0Axis == MotionEvent.AXIS_Z) {
arg0Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
--arg0Axis;
}
if (arg1Axis == MotionEvent.AXIS_Z) {
arg1Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
--arg1Axis;
}
return arg0Axis - arg1Axis;
}
}
private final ArrayList<SDLJoystick> mJoysticks;
public SDLJoystickHandler_API16() {
mJoysticks = new ArrayList<SDLJoystick>();
}
@Override
public void pollInputDevices() {
int[] deviceIds = InputDevice.getDeviceIds();
for (int device_id : deviceIds) {
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
SDLJoystick joystick = getJoystick(device_id);
if (joystick == null) {
InputDevice joystickDevice = InputDevice.getDevice(device_id);
joystick = new SDLJoystick();
joystick.device_id = device_id;
joystick.name = joystickDevice.getName();
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
joystick.hats = new ArrayList<InputDevice.MotionRange>();
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
} else {
joystick.axes.add(range);
}
}
}
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
getVendorId(joystickDevice), getProductId(joystickDevice), false,
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = null;
for (SDLJoystick joystick : mJoysticks) {
int device_id = joystick.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
}
}
if (removedDevices != null) {
for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveJoystick(device_id);
for (int i = 0; i < mJoysticks.size(); i++) {
if (mJoysticks.get(i).device_id == device_id) {
mJoysticks.remove(i);
break;
}
}
}
}
}
protected SDLJoystick getJoystick(int device_id) {
for (SDLJoystick joystick : mJoysticks) {
if (joystick.device_id == device_id) {
return joystick;
}
}
return null;
}
@Override
public boolean handleMotionEvent(MotionEvent event) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
SDLJoystick joystick = getJoystick(event.getDeviceId());
if (joystick != null) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
}
for (int i = 0; i < joystick.hats.size() / 2; i++) {
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
}
}
}
return true;
}
public String getJoystickDescriptor(InputDevice joystickDevice) {
String desc = joystickDevice.getDescriptor();
if (desc != null && !desc.isEmpty()) {
return desc;
}
return joystickDevice.getName();
}
public int getProductId(InputDevice joystickDevice) {
return 0;
}
public int getVendorId(InputDevice joystickDevice) {
return 0;
}
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
return -1;
}
public int getButtonMask(InputDevice joystickDevice) {
return -1;
}
}
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
@Override
public int getProductId(InputDevice joystickDevice) {
return joystickDevice.getProductId();
}
@Override
public int getVendorId(InputDevice joystickDevice) {
return joystickDevice.getVendorId();
}
@Override
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
// For compatibility, keep computing the axis mask like before,
// only really distinguishing 2, 4 and 6 axes.
int axis_mask = 0;
if (ranges.size() >= 2) {
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
axis_mask |= 0x0003;
}
if (ranges.size() >= 4) {
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
axis_mask |= 0x000c;
}
if (ranges.size() >= 6) {
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
axis_mask |= 0x0030;
}
// Also add an indicator bit for whether the sorting order has changed.
// This serves to disable outdated gamecontrollerdb.txt mappings.
boolean have_z = false;
boolean have_past_z_before_rz = false;
for (InputDevice.MotionRange range : ranges) {
int axis = range.getAxis();
if (axis == MotionEvent.AXIS_Z) {
have_z = true;
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
have_past_z_before_rz = true;
}
}
if (have_z && have_past_z_before_rz) {
// If both these exist, the compare() function changed sorting order.
// Set a bit to indicate this fact.
axis_mask |= 0x8000;
}
return axis_mask;
}
@Override
public int getButtonMask(InputDevice joystickDevice) {
int button_mask = 0;
int[] keys = new int[] {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BACK,
KeyEvent.KEYCODE_MENU,
KeyEvent.KEYCODE_BUTTON_MODE,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_THUMBL,
KeyEvent.KEYCODE_BUTTON_THUMBR,
KeyEvent.KEYCODE_BUTTON_L1,
KeyEvent.KEYCODE_BUTTON_R1,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER,
// These don't map into any SDL controller buttons directly
KeyEvent.KEYCODE_BUTTON_L2,
KeyEvent.KEYCODE_BUTTON_R2,
KeyEvent.KEYCODE_BUTTON_C,
KeyEvent.KEYCODE_BUTTON_Z,
KeyEvent.KEYCODE_BUTTON_1,
KeyEvent.KEYCODE_BUTTON_2,
KeyEvent.KEYCODE_BUTTON_3,
KeyEvent.KEYCODE_BUTTON_4,
KeyEvent.KEYCODE_BUTTON_5,
KeyEvent.KEYCODE_BUTTON_6,
KeyEvent.KEYCODE_BUTTON_7,
KeyEvent.KEYCODE_BUTTON_8,
KeyEvent.KEYCODE_BUTTON_9,
KeyEvent.KEYCODE_BUTTON_10,
KeyEvent.KEYCODE_BUTTON_11,
KeyEvent.KEYCODE_BUTTON_12,
KeyEvent.KEYCODE_BUTTON_13,
KeyEvent.KEYCODE_BUTTON_14,
KeyEvent.KEYCODE_BUTTON_15,
KeyEvent.KEYCODE_BUTTON_16,
};
int[] masks = new int[] {
(1 << 0), // A -> A
(1 << 1), // B -> B
(1 << 2), // X -> X
(1 << 3), // Y -> Y
(1 << 4), // BACK -> BACK
(1 << 6), // MENU -> START
(1 << 5), // MODE -> GUIDE
(1 << 6), // START -> START
(1 << 7), // THUMBL -> LEFTSTICK
(1 << 8), // THUMBR -> RIGHTSTICK
(1 << 9), // L1 -> LEFTSHOULDER
(1 << 10), // R1 -> RIGHTSHOULDER
(1 << 11), // DPAD_UP -> DPAD_UP
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
(1 << 4), // SELECT -> BACK
(1 << 0), // DPAD_CENTER -> A
(1 << 15), // L2 -> ??
(1 << 16), // R2 -> ??
(1 << 17), // C -> ??
(1 << 18), // Z -> ??
(1 << 20), // 1 -> ??
(1 << 21), // 2 -> ??
(1 << 22), // 3 -> ??
(1 << 23), // 4 -> ??
(1 << 24), // 5 -> ??
(1 << 25), // 6 -> ??
(1 << 26), // 7 -> ??
(1 << 27), // 8 -> ??
(1 << 28), // 9 -> ??
(1 << 29), // 10 -> ??
(1 << 30), // 11 -> ??
(1 << 31), // 12 -> ??
// We're out of room...
0xFFFFFFFF, // 13 -> ??
0xFFFFFFFF, // 14 -> ??
0xFFFFFFFF, // 15 -> ??
0xFFFFFFFF, // 16 -> ??
};
boolean[] has_keys = joystickDevice.hasKeys(keys);
for (int i = 0; i < keys.length; ++i) {
if (has_keys[i]) {
button_mask |= masks[i];
}
}
return button_mask;
}
}
class SDLHapticHandler_API26 extends SDLHapticHandler {
@Override
public void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
if (intensity == 0.0f) {
stop(device_id);
return;
}
int vibeValue = Math.round(intensity * 255);
if (vibeValue > 255) {
vibeValue = 255;
}
if (vibeValue < 1) {
stop(device_id);
return;
}
try {
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
}
catch (Exception e) {
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
// something went horribly wrong with the Android 8.0 APIs.
haptic.vib.vibrate(length);
}
}
}
}
class SDLHapticHandler {
static class SDLHaptic {
public int device_id;
public String name;
public Vibrator vib;
}
private final ArrayList<SDLHaptic> mHaptics;
public SDLHapticHandler() {
mHaptics = new ArrayList<SDLHaptic>();
}
public void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.vibrate(length);
}
}
public void stop(int device_id) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.cancel();
}
}
public void pollHapticDevices() {
final int deviceId_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for (int i = deviceIds.length - 1; i > -1; i--) {
SDLHaptic haptic = getHaptic(deviceIds[i]);
if (haptic == null) {
InputDevice device = InputDevice.getDevice(deviceIds[i]);
Vibrator vib = device.getVibrator();
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);
}
}
}
}
/* Check VIBRATOR_SERVICE */
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vib != null) {
hasVibratorService = vib.hasVibrator();
if (hasVibratorService) {
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
if (haptic == null) {
haptic = new SDLHaptic();
haptic.device_id = deviceId_VIBRATOR_SERVICE;
haptic.name = "VIBRATOR_SERVICE";
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = null;
for (SDLHaptic haptic : mHaptics) {
int device_id = haptic.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
}
} // else: don't remove the vibrator if it is still present
}
if (removedDevices != null) {
for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveHaptic(device_id);
for (int i = 0; i < mHaptics.size(); i++) {
if (mHaptics.get(i).device_id == device_id) {
mHaptics.remove(i);
break;
}
}
}
}
}
protected SDLHaptic getHaptic(int device_id) {
for (SDLHaptic haptic : mHaptics) {
if (haptic.device_id == device_id) {
return haptic;
}
}
return null;
}
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
// Generic Motion (mouse hover, joystick...) events go here
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
public boolean supportsRelativeMouse() {
return false;
}
public boolean inRelativeMode() {
return false;
}
public boolean setRelativeMouseEnabled(boolean enabled) {
return false;
}
public void reclaimRelativeMouseModeIfNeeded()
{
}
public float getEventX(MotionEvent event) {
return event.getX(0);
}
public float getEventY(MotionEvent event) {
return event.getY(0);
}
}
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
// Handle relative mouse mode
if (mRelativeModeEnabled) {
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_HOVER_MOVE) {
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
}
}
}
// Event was not managed, call SDLGenericMotionListener_API12 method
return super.onGenericMotion(v, event);
}
@Override
public boolean supportsRelativeMouse() {
return true;
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
mRelativeModeEnabled = enabled;
return true;
}
@Override
public float getEventX(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
} else {
return event.getX(0);
}
}
@Override
public float getEventY(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
} else {
return event.getY(0);
}
}
}
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
// DeX desktop mouse cursor is a separate non-standard input type.
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
case InputDevice.SOURCE_MOUSE_RELATIVE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
@Override
public boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
} else {
SDLActivity.getContentView().releasePointerCapture();
}
mRelativeModeEnabled = enabled;
return true;
} else {
return false;
}
}
@Override
public void reclaimRelativeMouseModeIfNeeded()
{
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
SDLActivity.getContentView().requestPointerCapture();
}
}
@Override
public float getEventX(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getX(0);
}
@Override
public float getEventY(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getY(0);
}
}
@@ -1,405 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady;
// Startup
public SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
public void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
try
{
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
}
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip in MultiWindow.
if (skip) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
Log.v("SDL", "Don't skip in Multi-Window");
skip = false;
}
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int i = -1;
float x,y,p;
/*
* Prevent id to be -1, since it's used in SDL internal for synthetic events
* Appears when using Android emulator, eg:
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
*/
if (touchDevId < 0) {
touchDevId -= 1;
}
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
int mouseButton = 1;
try {
Object object = event.getClass().getMethod("getButtonState").invoke(event);
if (object != null) {
mouseButton = (Integer) object;
}
} catch(Exception ignored) {
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event);
y = motionListener.getEventY(event);
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
/* fallthrough */
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
return true;
}
// Sensor events
public void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
} else {
mSensorManager.unregisterListener(this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newOrientation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
break;
}
if (newOrientation != SDLActivity.mCurrentOrientation) {
SDLActivity.mCurrentOrientation = newOrientation;
SDLActivity.onNativeOrientationChanged(newOrientation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Captured pointer events for API 26.
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(0);
y = event.getY(0);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
return false;
}
}
+3 -2
View File
@@ -1,7 +1,8 @@
#cmakedefine PACKAGE "@PACKAGE@"
#define VERSION "@XSYSTEM35_VERSION@"
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
#ifndef XSYSTEM35_SDL_VERSION
#define XSYSTEM35_SDL_VERSION @XSYSTEM35_SDL_VERSION@
#endif
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
+2 -2
View File
@@ -12,8 +12,8 @@ or in your `.xsys35rc` file. For example:
xsystem35 -censor misc/censor/kichikuou.txt
```
The list files contain image numbers (integers), one per line. Lines starting
with `#` are treated as comments.
The list files contain image numbers (integers), one per line. A `#` starts a
comment that continues to the end of the line.
## Contributing
+30
View File
@@ -0,0 +1,30 @@
# Censor list for ランス -光をもとめて- (Rance: The Quest for Hikari)
# Images marked with a * might be allowed in an 18+ stream.
5
6
8
22
26
27
29
32
39
40
41
51
56
57
75
76
77
78 # *
80
82
89
91
96 # *
97 # *
98
99
101
208
+59
View File
@@ -0,0 +1,59 @@
# Censor list for ランスII (Rance II)
# Images marked with a * might be allowed in an 18+ stream.
26
39 # *
40
41
77
78
86
98
103
104
121
122
123
124
130
131
132
133
134
140
141
142
148
156
157
158
159
161
166
171
172
185
187
191
195
197
200
201
234
236
245
246
247
272
274
275
276
278
280
281
282
284
289
292
299
300
302
+121
View File
@@ -0,0 +1,121 @@
# Censor list for Rance 3
# Images marked with a * might be allowed in an 18+ stream.
1 # *
2 # *
3
6
8
9
32 # *
53 # *
54
70
71
72
73
75
77 # *
78
79
112 # *
113
114
115
116
117
118 # *
150
151
152
153
154
155
166
167
180
181
183
184
185
186
187
188
189
190
195
196
208
209
210
211
212
213
215
216
217
218
219
231
232
233
234
235
236
238
239
260
261
263
264
265
266
268
269
330
331
332
334
335
336
337
338
339
420
421
422
423
424
425
426
427
428
429
440
441
442
443
444
445
447
473
474
475
476
477
479
519 # *
521 # *
523
524
525
526
530
533 # *
534
535
536
537
570
571
572
581
+168
View File
@@ -0,0 +1,168 @@
# Censor list for Rance IV
# Images marked with a * might be allowed in an 18+ stream.
1
2
10
20
26
28
29
30 # *
31
32
33
34
35
36
37
38
39 # *
40
41
42
43
44
45
46
48
49
75
76
77
78
79
122
126
140
142
148
149
162 # *
163
164
165 # *
166
167
168
169 # *
170
171
172
176
177
178
185
186
188
189
205
207
217 # *
220
221
222
223
228
241
242
251
252
253
254
260
261
262 # *
264 # *
265
266
267
268
269
280
281
283
284
285
286 # *
287
288
292
294
299
370
371
372
373
376
378
382
384
385 # *
386
387
388
389
414 # *
416
428 # *
531 # *
532
533
534 # *
535
550
551
552
553
554
555
562 # *
565
615
625
626
711
723
1055
1056
1057
1058
1059
1060
1061
1062
1545 # *
1546 # *
1547 # *
1548 # *
1549 # *
1653 # *
1654 # *
1655 # *
1656 # *
1657 # *
1658 # *
1659 # *
1660 # *
1661 # *
1662 # *
1663 # *
1664 # *
1665 # *
1728
1729
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
+3 -3
View File
@@ -84,7 +84,7 @@ endif()
target_compile_options(modules PRIVATE -Wno-pointer-sign)
target_link_libraries(modules PRIVATE sdl2 zlib)
if (TARGET sdl2_mixer)
target_link_libraries(modules PRIVATE sdl2_mixer)
target_link_libraries(modules PRIVATE xsystem35_sdl zlib)
if (TARGET xsystem35_sdl_mixer)
target_link_libraries(modules PRIVATE xsystem35_sdl_mixer)
endif()
+9 -8
View File
@@ -3,7 +3,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -76,12 +76,13 @@ void gr_copy(surface_t *dst, int dx, int dy, surface_t *src, int sx, int sy, int
SDL_Rect r;
if (SDL_IntersectRect(&src_rect, &dst_rect, &r)) {
SDL_Surface *view = gfx_createSurfaceView(src->sdl_surface, sx, sy, sw, sh);
SDL_Surface *tmp = SDL_ConvertSurface(view, dst->sdl_surface->format, 0);
SDL_Surface *tmp = sdl_convert_surface(
view, sdl_surface_format(dst->sdl_surface));
src_rect.x = 0;
src_rect.y = 0;
SDL_LowerBlit(tmp, &src_rect, dst->sdl_surface, &dst_rect);
SDL_FreeSurface(tmp);
SDL_FreeSurface(view);
sdl_destroy_surface(tmp);
sdl_destroy_surface(view);
return;
}
}
@@ -99,7 +100,7 @@ void gr_copy_bright(surface_t *dst, int dx, int dy, surface_t *src, int sx, int
void gr_fill(surface_t *dst, int dx, int dy, int dw, int dh, int r, int g, int b) {
SDL_Rect rect = {dx, dy, dw, dh};
uint32_t color = SDL_MapRGB(dst->sdl_surface->format, r, g, b);
uint32_t color = sdl_map_rgb(dst->sdl_surface, r, g, b);
SDL_FillRect(dst->sdl_surface, &rect, color);
}
@@ -227,12 +228,12 @@ void gr_copy_stretch_blend_alpha_map(surface_t *dst, int dx, int dy, int dw, int
// Fill the rectangle with the specified color (rgb) and blend rate (lv)
void gr_fill_alpha_color(surface_t *dst, int dx, int dy, int dw, int dh, int r, int g, int b, int lv) {
SDL_Surface *src = SDL_CreateRGBSurfaceWithFormat(0, dw, dh, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(src, NULL, SDL_MapRGBA(src->format, r, g, b, lv));
SDL_Surface *src = sdl_create_surface(dw, dh, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(src, NULL, sdl_map_rgba(src, r, g, b, lv));
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);
SDL_Rect dstrect = {dx, dy, dw, dh};
SDL_BlitSurface(src, NULL, dst->sdl_surface, &dstrect);
SDL_FreeSurface(src);
sdl_destroy_surface(src);
}
#include "graph2.c"
+6 -4
View File
@@ -3,7 +3,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "surface.h"
@@ -15,7 +15,8 @@ static surface_t *create(int width, int height, bool has_pixel, bool has_alpha)
s->height = height;
if (has_pixel) {
s->sdl_surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGB888);
s->sdl_surface = sdl_create_surface(
width, height, 32, SDL_PIXELFORMAT_RGB888);
}
if (has_alpha) {
@@ -47,7 +48,7 @@ surface_t *sf_create_pixel(int width, int height) {
*/
void sf_free(surface_t *s) {
if (s == NULL) return;
if (s->sdl_surface) SDL_FreeSurface(s->sdl_surface);
if (s->sdl_surface) sdl_destroy_surface(s->sdl_surface);
if (s->alpha) free(s->alpha);
free(s);
}
@@ -67,7 +68,8 @@ surface_t *sf_dup(surface_t *in) {
memcpy(sf, in, sizeof(surface_t));
if (in->sdl_surface) {
sf->sdl_surface = SDL_ConvertSurface(in->sdl_surface, in->sdl_surface->format, 0);
sf->sdl_surface = sdl_convert_surface(
in->sdl_surface, sdl_surface_format(in->sdl_surface));
}
if (in->alpha) {
+2 -2
View File
@@ -1,5 +1,5 @@
#include <stdio.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -194,5 +194,5 @@ void nt_gr_screencg(int no, int x, int y) {
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_ADD);
SDL_BlitSurface(sf, NULL, main_surface, &(SDL_Rect){x, y, sf->w, sf->h});
ags_updateArea(x, y, sf->w, sf->h);
SDL_FreeSurface(sf);
sdl_destroy_surface(sf);
}
+3 -3
View File
@@ -3,7 +3,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -142,7 +142,7 @@ void ntmsg_set_frame(int type) {
nt_sp_set_show(night.sp[SPNO_MSGFRAME_BG], true);
nt_sp_set_show(night.sp[SPNO_MSGFRAME_FG], true);
SDL_FillRect(sf, &msgframe_0, SDL_MapRGBA(sf->format, 16, 32, 64, 192));
SDL_FillRect(sf, &msgframe_0, sdl_map_rgba(sf, 16, 32, 64, 192));
ntmsg_clear(SPNO_MSGFRAME_FG);
nt_sp_update_all(true);
break;
@@ -151,7 +151,7 @@ void ntmsg_set_frame(int type) {
nt_sp_set_show(night.sp[SPNO_MSGFRAME_BG], false);
nt_sp_set_show(night.sp[SPNO_MSGFRAME_FG], true);
SDL_FillRect(sf, &msgframe_1, SDL_MapRGBA(sf->format, 32, 32, 32, 128));
SDL_FillRect(sf, &msgframe_1, sdl_map_rgba(sf, 32, 32, 32, 128));
ntmsg_clear(SPNO_MSGFRAME_FG);
nt_sp_update_all(true);
+4 -3
View File
@@ -1,7 +1,7 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "sprite.h"
@@ -62,7 +62,8 @@ sprite_t *nt_sp_msg_new(int no, int x, int y, int width, int height) {
sp->height = height;
sp->u.msg.dspcur.x = 0;
sp->u.msg.dspcur.y = 0;
sp->u.msg.canvas = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888);
sp->u.msg.canvas = sdl_create_surface(
width, height, 32, SDL_PIXELFORMAT_ARGB8888);
sp->update = ntmsg_update;
return sp;
@@ -76,7 +77,7 @@ void nt_sp_free(sprite_t *sp) {
if (sp->cg3) scg_deref(sp->cg3);
if (sp->type == SPRITE_MSG) {
SDL_FreeSurface(sp->u.msg.canvas);
sdl_destroy_surface(sp->u.msg.canvas);
}
free(sp);
+1 -1
View File
@@ -3,7 +3,7 @@
#define __SPRITE_H__
#include "portab.h"
#include <SDL_rect.h>
#include "sdl_compat.h"
#include "sactcg.h"
#define DEFAULT_UPDATE nt_sp_draw
+2 -2
View File
@@ -24,7 +24,7 @@
#include "config.h"
#include <stdio.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -55,7 +55,7 @@ void nt_sp_draw(sprite_t *sp, SDL_Rect *r) {
return;
}
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format) || sp->blendrate < 255) {
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf)) || sp->blendrate < 255) {
SDL_SetSurfaceBlendMode(cg->sf, SDL_BLENDMODE_BLEND);
SDL_SetSurfaceAlphaMod(cg->sf, sp->blendrate);
} else {
+2 -2
View File
@@ -25,7 +25,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -204,5 +204,5 @@ void nt_sp_clear_updatelist(void) {
// デフォルトの壁紙update
void nt_sp_draw_wall(sprite_t *sp, SDL_Rect *area) {
SDL_FillRect(main_surface, area, SDL_MapRGB(main_surface->format, 0, 0, 0));
SDL_FillRect(main_surface, area, sdl_map_rgb(main_surface, 0, 0, 0));
}
+3 -2
View File
@@ -60,10 +60,11 @@ static void ndd_run(int demonum) {
cgdata *cg = jpeg_extract(alk->entries[i].data, alk->entries[i].size);
if (cg) {
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width,
cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_BlitSurface(sf, NULL, main_surface, NULL);
ags_updateFull();
SDL_FreeSurface(sf);
sdl_destroy_surface(sf);
cgdata_free(cg);
} else {
WARNING("Cannot decode CG %d in %s", i, alk_path);
+22 -12
View File
@@ -25,7 +25,7 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -33,6 +33,7 @@
#include "portab.h"
#include "system.h"
#include "ald_manager.h"
#include "audio_meta.h"
#include "input.h"
#include "msgskip.h"
#include "xsystem35.h"
@@ -146,7 +147,8 @@ static void Init() {
}
// create depth map
sact.dmap = SDL_CreateRGBSurface(0, main_surface->w, main_surface->h, 16, 0, 0, 0, 0);
sact.dmap = sdl_create_surface_with_masks(
main_surface->w, main_surface->h, 16, 0, 0, 0, 0);
// その他 System35 のデフォルト動作の変更
ags_setAntialiasedStringMode(true);
@@ -286,8 +288,11 @@ static void DrawEffect() {
if (sact.version >= 110) {
wEffectkey = getCaliValue();
}
sp_eupdate(wType, wEffectTime, wEffectkey);
if (sact.waitskiplv > 1 || msgskip_isSkipping())
sp_update_all(true);
else
sp_eupdate(wType, wEffectTime, wEffectkey);
TRACE("SACT.DrawEffect %d,%d,%d:", wType, wEffectTime, wEffectkey);
}
@@ -304,7 +309,10 @@ static void DrawEffectAlphaMap() {
int wEffectTime = getCaliValue();
int wEffectKey = getCaliValue();
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
if (sact.waitskiplv > 1 || msgskip_isSkipping())
sp_update_all(true);
else
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
TRACE("SACT.DrawEffectAlphaMap %d,%d,%d:", nIndexAlphaMap, wEffectTime, wEffectKey);
}
@@ -622,9 +630,10 @@ static void QuakeSprite() {
if (sact.version >= 110) {
nfKeyEnable = getCaliValue();
}
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
if (!msgskip_isSkipping())
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
TRACE("SACT.QuakeSprite %d,%d,%d,%d:", wType, wAmplitudeX, wAmplitudeY, wCount);
}
@@ -1615,7 +1624,7 @@ static void TimerWait() {
int msec = (wCount - stimer_get(wTimerID)) * 10;
if (msec > 0)
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE);
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
TRACE("SACT.TimerWait %d,%d:", wTimerID, wCount);
}
@@ -1628,7 +1637,7 @@ static void TimerWait() {
static void Wait() {
int wCount = getCaliValue();
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE);
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
TRACE("SACT.Wait %d:", wCount);
}
@@ -1845,8 +1854,9 @@ static void MusicPlay() {
int wNum = getCaliValue();
int wFadeTime = getCaliValue();
int wVolume = getCaliValue();
bgi_t *bgi = bgi_find(wNum);
musbgm_play(wNum, wFadeTime, wVolume, 0);
musbgm_play(wNum, wFadeTime, wVolume, bgi ? bgi->loopno : 0);
TRACE("SACT.MusicPlay %d,%d,%d:", wNum, wFadeTime, wVolume);
}
@@ -2145,7 +2155,7 @@ static void SACT_reset(void) {
sp_reset();
scg_freeall();
SDL_FreeSurface(sact.dmap);
sdl_destroy_surface(sact.dmap);
memset(&sact, 0, sizeof(sact));
}
+1 -1
View File
@@ -25,7 +25,7 @@
#define __SACT_H__
#include "config.h"
#include <SDL_rect.h>
#include "sdl_compat.h"
#include "portab.h"
#include "list.h"
#include "ags.h"
+4 -3
View File
@@ -25,7 +25,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -90,13 +90,14 @@ void sp_eupdate_amap(int index, int time, int cancel) {
sp_update_all(true);
return;
}
SDL_Surface *mask_sf = SDL_CreateRGBSurfaceFrom(mask->pic, mask->width, mask->height, 8, mask->width, 0, 0, 0, 0);
SDL_Surface *mask_sf = sdl_create_surface_from(mask->pic, mask->width,
mask->height, 8, mask->width, SDL_PIXELFORMAT_INDEX8);
sp_update_all(false); // old = gfx_texture, new = main_surface
struct effect *eff = effect_sactamask_init(mask_sf);
ags_runEffect(time * 10, cancel, (ags_EffectStepFunc)effect_step, eff);
effect_finish(eff);
ags_updateFull();
SDL_FreeSurface(mask_sf);
sdl_destroy_surface(mask_sf);
cgdata_free(mask);
}
+11 -6
View File
@@ -26,7 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -73,8 +73,9 @@ static void draw_log() {
load_mincho_font();
#endif
SDL_Surface *hline = SDL_CreateRGBSurfaceWithFormat(0, main_surface->w, 3, 32, SDL_PIXELFORMAT_RGB888);
SDL_FillRect(hline, NULL, SDL_MapRGB(hline->format, 255, 255, 255));
SDL_Surface *hline = sdl_create_surface(
main_surface->w, 3, 32, SDL_PIXELFORMAT_RGB888);
SDL_FillRect(hline, NULL, sdl_map_rgb(hline, 255, 255, 255));
SDL_SetSurfaceBlendMode(hline, SDL_BLENDMODE_BLEND);
SDL_SetSurfaceAlphaMod(hline, 128);
@@ -97,7 +98,11 @@ static void draw_log() {
if (0 == strcmp(str, "\n")) {
SDL_BlitSurface(hline, NULL, main_surface, &(SDL_Rect){0, y + FONTSIZE/2, main_surface->w, 3});
} else {
FontSpec font_spec = { .type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC, .size = FONTSIZE };
FontSpec font_spec = {
.type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC,
.weight = cur < 6 ? FONT_WEIGHT_NORMAL : FONT_WEIGHT_BOLD,
.size = FONTSIZE
};
gfx_drawString(0, y, str, 255, font_spec);
}
y += FONTSIZE;
@@ -118,7 +123,7 @@ bool sblog_start(void) {
for (int i = 0; i < LOGMSG_LINES; i++)
sact.log = list_append(sact.log, logmsg[i]);
back = SDL_ConvertSurface(main_surface, main_surface->format, 0);
back = sdl_convert_surface(main_surface, sdl_surface_format(main_surface));
curline = 6;
draw_log();
return true;
@@ -131,7 +136,7 @@ void sblog_end(void) {
SDL_BlitSurface(back, NULL, main_surface, NULL);
ags_updateFull();
SDL_FreeSurface(back);
sdl_destroy_surface(back);
// Remove instruction text
for (i = 0; i < LOGMSG_LINES; i++) {
+2 -1
View File
@@ -30,6 +30,7 @@
#include "system.h"
#include "nact.h"
#include "input.h"
#include "msgskip.h"
#include "sactsound.h"
#include "music.h"
#include "sact.h"
@@ -120,7 +121,7 @@ void ssnd_waitkey(int no, vmvar_t *res) {
return;
}
if (sact.waitskiplv > 1) {
if (sact.waitskiplv > 1 || msgskip_isSkipping()) {
*res = SYS35KEY_RET;
return;
}
+1 -1
View File
@@ -28,7 +28,7 @@
#include "sacttimer.h"
#include "system.h"
#define MAX_TIMER 10
#define MAX_TIMER 128
#define TICKS_PER_CENTISECOND 10
uint32_t ticks_base[MAX_TIMER];
+6 -4
View File
@@ -26,7 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -65,7 +65,8 @@ static int compare_spriteno_smallfirst(const void *a, const void *b) {
// デフォルトの壁紙update
static void sp_draw_wall(sprite_t *sp) {
SDL_FillRect(main_surface, &sact.updaterect, SDL_MapRGB(main_surface->format, 0, 0, 0));
SDL_FillRect(main_surface, &sact.updaterect,
sdl_map_rgb(main_surface, 0, 0, 0));
}
/**
@@ -207,7 +208,8 @@ void sp_new_msg(int no, int x, int y, int width, int height) {
sp->u.msg.buf = NULL;
// canvas for drawing text
sp->u.msg.canvas = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888);
sp->u.msg.canvas = sdl_create_surface(
width, height, 32, SDL_PIXELFORMAT_ARGB8888);
// スプライト再描画用コールバック
sp->update = smsg_update;
@@ -279,7 +281,7 @@ void sp_free(int no) {
if (sp->type == SPRITE_MSG) {
slist_free(sp->u.msg.buf);
SDL_FreeSurface(sp->u.msg.canvas);
sdl_destroy_surface(sp->u.msg.canvas);
}
sact.updatelist = slist_remove(sact.updatelist, sp);
+4 -4
View File
@@ -26,7 +26,7 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -38,7 +38,7 @@
static void fill_dmap_mask(SDL_Surface *src, int sx, int sy, int dx ,int dy, int w, int h, uint16_t val) {
uint8_t *sp, *dp;
int x, y;
assert(src->format->Amask == 0xff000000);
assert(sdl_surface_alpha_mask(src) == 0xff000000);
dp = PIXEL_AT(sact.dmap, dx, dy);
sp = ALPHA_AT(src, sx, sy);
@@ -75,7 +75,7 @@ void sp_draw(sprite_t *sp) {
return;
}
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format) || sp->blendrate < 255) {
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf)) || sp->blendrate < 255) {
SDL_SetSurfaceBlendMode(cg->sf, SDL_BLENDMODE_BLEND);
SDL_SetSurfaceAlphaMod(cg->sf, sp->blendrate);
} else {
@@ -116,7 +116,7 @@ void sp_draw_dmap(void* data, void* userdata) {
return;
}
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format)) {
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf))) {
fill_dmap_mask(cg->sf, sx, sy, dx, dy, w, h, sp->no);
} else {
SDL_FillRect(sact.dmap, &(SDL_Rect){dx, dy, w, h}, sp->no);
-5
View File
@@ -39,11 +39,6 @@
@param cancel: キー抜け(0:なし, 1:あり)
*/
void sp_eupdate(int type, int time, int cancel) {
if (sact.waitskiplv > 1) {
sp_update_all(true);
return;
}
sp_update_all(false);
enum effect_type effect = from_sact_effect(type);
+17 -8
View File
@@ -30,9 +30,11 @@
#include "portab.h"
#include "system.h"
#include "ags.h"
#include "gfx.h"
#include "nact.h"
#include "variable.h"
#include "input.h"
#include "msgskip.h"
#include "sact.h"
#include "sprite.h"
#include "drawtext.h"
@@ -240,7 +242,8 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
char *msg;
sprite_t *sp;
int len = 0; // 処理した文字数?
bool needupdate = false;
bool has_batched_output = false;
bool flush_batched_output = wSpeed > 0;
SDL_Rect uparea = {0,0,0,0};
// wRSize == 0 -> ルビ無し(SACT.MessageOutputからの呼出)
@@ -250,7 +253,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
if (!is_messagesprite(wNum)) return;
// MessageSkip中は文字送り速度を最大に
if (sact.waitskiplv > 1) wSpeed = 0;
if (sact.waitskiplv > 1 || msgskip_isSkipping()) wSpeed = 0;
// shortcut
sp = sact.sp[wNum];
@@ -302,7 +305,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
mbuf,
wColorR, wColorG, wColorB);
needupdate = true;
has_batched_output = true;
append_to_log(mbuf);
@@ -313,7 +316,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
cw,
wSize + wRSize + wRLineSpace);
sp_update_clipped();
needupdate = false;
has_batched_output = false;
// keywait
delta = sys_get_ticks() - wcnt;
@@ -333,11 +336,17 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
// バッファリング中の文字のクリア
sact.msgbuf[0] = '\0';
// Waitなしの出力は最後にupdate
if (needupdate) {
// Register the update area after rendering without waits.
if (has_batched_output) {
uparea.w = sp->width;
uparea.h = min(sp->height, uparea.y - sp->u.msg.dspcur.y + wLineSpace + wLineSpace + wRSize);
uparea.h = min(sp->height - uparea.y,
sp->u.msg.dspcur.y - uparea.y + wSize + wRSize + wRLineSpace);
sp_updateme_part(sp, uparea.x, uparea.y, uparea.w, uparea.h);
// Present now if waiting was skipped.
if (flush_batched_output) {
sp_update_clipped();
gfx_updateScreen();
}
}
// ????
@@ -408,7 +417,7 @@ int smsg_keywait(int wNum1, int wNum2, int msglen) {
struct markinfo minfo[6];
int i = 0, j, maxstep;
if (sact.waitskiplv > 0) {
if (sact.waitskiplv > 0 || msgskip_isSkipping()) {
sys_getInputInfo();
return 0;
}
+7 -6
View File
@@ -26,7 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "nact.h"
@@ -140,14 +140,14 @@ static void cb_select_release(agsevent_t *e) {
}
static void draw_box(SDL_Surface *dst, int x, int y, int w, int h) {
SDL_FillRect(dst, &(SDL_Rect){x, y, w, h}, SDL_MapRGB(dst->format, 0, 0, 0));
SDL_FillRect(dst, &(SDL_Rect){x, y, w, h}, sdl_map_rgb(dst, 0, 0, 0));
SDL_Rect border_rects[4] = {
{x, y, w, 1},
{x, y + h - 1, w, 1},
{x, y, 1, h},
{x + w - 1, y, 1, h}
};
SDL_FillRects(dst, border_rects, 4, SDL_MapRGB(dst->format, 255, 255, 255));
SDL_FillRects(dst, border_rects, 4, sdl_map_rgb(dst, 255, 255, 255));
}
// 選択ウィンドを更新するときの callback
@@ -180,7 +180,8 @@ static void setup_selwindow() {
int i;
// 選択肢文字用 canvas
sact.sel.charcanvas = SDL_CreateRGBSurfaceWithFormat(0, sp->cg1->sf->w, sp->cg1->sf->h, 32, SDL_PIXELFORMAT_ARGB8888);
sact.sel.charcanvas = sdl_create_surface(
sp->cg1->sf->w, sp->cg1->sf->h, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_SetSurfaceBlendMode(sact.sel.charcanvas, SDL_BLENDMODE_BLEND);
dt_setfont(sact.sel.font_type, sact.sel.font_size);
@@ -228,7 +229,7 @@ static void remove_selwindow() {
// 作業用 surface の削除
if (sact.sel.charcanvas)
SDL_FreeSurface(sact.sel.charcanvas);
sdl_destroy_surface(sact.sel.charcanvas);
sact.sel.charcanvas = NULL;
}
@@ -266,7 +267,7 @@ void ssel_init() {
void ssel_reset(void) {
ssel_clear();
if (sact.sel.charcanvas) {
SDL_FreeSurface(sact.sel.charcanvas);
sdl_destroy_surface(sact.sel.charcanvas);
sact.sel.charcanvas = NULL;
}
}
+2 -2
View File
@@ -88,11 +88,11 @@ static vmvar_t *add_p5[SLOT]; /* どこまでアニメーションのコマが
static void copy_sprite(int sx, int sy, int width, int height, int dx, int dy, int r, int g, int b) {
SDL_Surface *sf = nact->ags.dib->sdl_surface;
SDL_SetColorKey(sf, SDL_TRUE, SDL_MapRGB(sf->format, r, g, b));
sdl_set_surface_color_key(sf, true, sdl_map_rgb(sf, r, g, b));
SDL_Rect src_rect = { sx, sy, width, height };
SDL_Rect dst_rect = { dx, dy, width, height };
SDL_BlitSurface(sf, &src_rect, sf, &dst_rect);
SDL_SetColorKey(sf, SDL_FALSE, 0);
sdl_set_surface_color_key(sf, false, 0);
}
static void Init() {
+9 -4
View File
@@ -25,8 +25,9 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "LittleEndian.h"
@@ -44,7 +45,7 @@ static struct {
} memwav;
static void free_memory_wav(void) {
SDL_FreeWAV(memwav.buf);
sdl_free_wav(memwav.buf);
memwav.buf = NULL;
}
@@ -132,11 +133,15 @@ static void wavLoadMemory() {
WARNING("cannot open WAVE %d", no - 1);
return;
}
if (!SDL_LoadWAV_RW(SDL_RWFromConstMem(dfile->data, dfile->size), 1, &memwav.spec, &memwav.buf, &memwav.len)) {
sdl_iostream_t *stream = sdl_io_from_const_memory(
dfile->data, dfile->size);
if (!stream || !sdl_load_wav_io(
stream, true, &memwav.spec, &memwav.buf, &memwav.len)) {
WARNING("cannot load WAVE %d", no - 1);
return;
}
if (memwav.spec.channels != 2 || memwav.spec.format != AUDIO_S16LSB) {
if (memwav.spec.channels != 2 ||
memwav.spec.format != sdl_audio_s16le_format()) {
WARNING("unexpected audio format");
free_memory_wav();
return;
+59 -53
View File
@@ -2,7 +2,7 @@
#include <stdio.h>
#include <math.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -31,7 +31,7 @@ static void render_quad(SDL_Texture *tex, SDL_Vertex verts[4]) {
static void fade_overlay(Uint8 r, Uint8 g, Uint8 b, Uint8 alpha) {
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(gfx_renderer, r, g, b, alpha);
SDL_RenderFillRect(gfx_renderer, NULL);
sdl_render_fill_rect(gfx_renderer, NULL);
}
#define NR_SCENES 13
@@ -121,7 +121,7 @@ static void Init() {
continue;
}
dd.images[i] = SDL_CreateTextureFromSurface(gfx_renderer, sf);
SDL_FreeSurface(sf);
sdl_destroy_surface(sf);
}
*var = 1;
TRACE("dDemo.Init %d,%d,%d,%p:", p1, p2, p3, var);
@@ -144,7 +144,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
if (!scroll_bg) return;
int bg_w, bg_h;
SDL_QueryTexture(scroll_bg, NULL, NULL, &bg_w, &bg_h);
sdl_get_texture_size(scroll_bg, &bg_w, &bg_h);
int xs[4], ys[4];
const float angle = angle_deg * (float)M_PI / 180.0f;
@@ -183,7 +183,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
for (int i = 0; i < 4; i++) {
verts[i].position.x = (i == 1 || i == 2) ? w : 0;
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].color = sdl_vertex_color(255, 255, 255, 255);
verts[i].tex_coord.x = uv[i][0];
verts[i].tex_coord.y = uv[i][1];
}
@@ -194,7 +194,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
y_toggle = (y_toggle + 1) & 1;
for (int i = 0; i < 4; i++) {
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 128};
verts[i].color = sdl_vertex_color(255, 255, 255, 128);
}
// Swap mapping: TL<->BR, TR<->BL for 180° rotation effect
verts[0].tex_coord.x = uv[2][0]; verts[0].tex_coord.y = uv[2][1];
@@ -212,7 +212,7 @@ static void render_scene0(int elapsed) {
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
sdl_get_texture_size(sprite, &spr_w, &spr_h);
const int slide_end = 4700;
const int duration = scene_duration[0]; // 5400
@@ -234,12 +234,12 @@ static void render_scene0(int elapsed) {
int dest_w = a0 - ((a0 - spr_w) * elapsed) / slide_end;
if (dest_w <= 0) continue;
SDL_Rect dst = {view_w - 2 * dest_w, dest_y, dest_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
}
} else {
SDL_SetTextureAlphaMod(sprite, 0xd0);
SDL_Rect dst = {view_w - 2 * spr_w, dest_y, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
}
// 3. Horizontal accent line (grows rightward over 4700ms)
@@ -247,12 +247,12 @@ static void render_scene0(int elapsed) {
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xc0, 0xc0, 0xc0, 0xff);
SDL_Rect hline = {0, (spr_h + view_h) / 2 - 4, line_w, 2};
SDL_RenderFillRect(gfx_renderer, &hline);
sdl_render_fill_rect(gfx_renderer, &hline);
// 4. Vertical accent line (right edge of sprite, grows downward)
int line_h = elapsed < slide_end ? 479 * elapsed / slide_end + 1 : 480;
SDL_Rect vline = {view_w - spr_w, 0, 2, line_h};
SDL_RenderFillRect(gfx_renderer, &vline);
sdl_render_fill_rect(gfx_renderer, &vline);
}
static void render_scene1(int elapsed) {
@@ -261,7 +261,7 @@ static void render_scene1(int elapsed) {
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
sdl_get_texture_size(sprite, &spr_w, &spr_h);
const int duration = scene_duration[1]; // 4752
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
@@ -275,7 +275,7 @@ static void render_scene1(int elapsed) {
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(sprite, (Uint8)alpha);
SDL_Rect dst = {(view_w - spr_w) / 2, (view_h - spr_h) / 2, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
}
// Fade to black: graph_FillAlphaColor(0,0,0), starts at 4000ms.
@@ -397,12 +397,14 @@ static void render_scene_2_4_6_3d(int elapsed, int phase_dur, SDL_Texture *tex1,
// Object border quad: solid color RGB(244,221,181) = BGR(0xb5,0xdd,0xf4),
// drawn first as a backdrop. The bg quad drawn on top leaves only the thin
// border ring visible.
for (int i = 0; i < 4; i++) obj_verts[i].color = (SDL_Color){0xf4, 0xdd, 0xb5, 0xff};
for (int i = 0; i < 4; i++)
obj_verts[i].color = sdl_vertex_color(0xf4, 0xdd, 0xb5, 0xff);
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
render_quad(NULL, obj_verts);
// Background quad: textured, opaque, drawn on top (covers center of border quad)
for (int i = 0; i < 4; i++) bg_verts[i].color = (SDL_Color){255, 255, 255, 255};
for (int i = 0; i < 4; i++)
bg_verts[i].color = sdl_vertex_color(255, 255, 255, 255);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
render_quad(tex, bg_verts);
}
@@ -425,7 +427,7 @@ static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int t
if (bg) {
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_SetTextureColorMod(bg, 0xff, 0xff, 0xff);
SDL_RenderCopy(gfx_renderer, bg, NULL, NULL);
sdl_render_texture(gfx_renderer, bg, NULL, NULL);
}
// 2. 3D rotating quad animation (8-phase timing)
@@ -439,23 +441,23 @@ static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int t
// 3. Scrolling sprite: x moves from -sprite_w (off-screen left) to view_w (off-screen right)
int sprite_w = 0, sprite_h = 0;
if (sprite)
SDL_QueryTexture(sprite, NULL, NULL, &sprite_w, &sprite_h);
sdl_get_texture_size(sprite, &sprite_w, &sprite_h);
int x = sprite_w ? ((sprite_w + view_w) * elapsed) / duration - sprite_w : 0;
// Shadow drawn first (color-modded to black), sprite on top (full color)
if (sprite_shadow) {
int sw, sh;
SDL_QueryTexture(sprite_shadow, NULL, NULL, &sw, &sh);
sdl_get_texture_size(sprite_shadow, &sw, &sh);
SDL_SetTextureBlendMode(sprite_shadow, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite_shadow, 0, 0, 0);
SDL_Rect dst = {x, 0, sw, sh};
SDL_RenderCopy(gfx_renderer, sprite_shadow, NULL, &dst);
sdl_render_texture(gfx_renderer, sprite_shadow, NULL, &dst);
}
if (sprite) {
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite, 0xff, 0xff, 0xff);
SDL_Rect dst = {x, 0, sprite_w, sprite_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
}
}
@@ -490,7 +492,7 @@ static void render_scene_3_5_7_9(int elapsed, int image_idx) {
if (alpha > 0) {
SDL_SetTextureBlendMode(main_img, alpha < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(main_img, (Uint8)alpha);
SDL_RenderCopy(gfx_renderer, main_img, NULL, NULL);
sdl_render_texture(gfx_renderer, main_img, NULL, NULL);
}
}
@@ -500,10 +502,14 @@ static void render_scene_3_5_7_9(int elapsed, int image_idx) {
float t3 = (float)elapsed / (4000.0f * 3.0f); // 0 → 1/3
float w = (float)view_w, h = (float)view_h;
SDL_Vertex verts[4] = {
/* TL */ {{0.f, 0.f}, {255, 255, 255, 128}, {t3, 0.f }},
/* TR */ {{w, 0.f}, {255, 255, 255, 128}, {1.0f, t3 }},
/* BR */ {{w, h }, {255, 255, 255, 128}, {1.0f - t3, 1.0f }},
/* BL */ {{0.f, h }, {255, 255, 255, 128}, {0.f, 1.0f-t3}},
{{0.f, 0.f}, sdl_vertex_color(255, 255, 255, 128),
{t3, 0.f}},
{{w, 0.f}, sdl_vertex_color(255, 255, 255, 128),
{1.0f, t3}},
{{w, h}, sdl_vertex_color(255, 255, 255, 128),
{1.0f - t3, 1.0f}},
{{0.f, h}, sdl_vertex_color(255, 255, 255, 128),
{0.f, 1.0f - t3}},
};
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
render_quad(overlay, verts);
@@ -527,7 +533,7 @@ static void render_scene8(int elapsed) {
if (imgs[0]) {
SDL_SetTextureBlendMode(imgs[0], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[0], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[0], NULL, NULL);
sdl_render_texture(gfx_renderer, imgs[0], NULL, NULL);
}
} else if (elapsed < 6 * seg) {
// Segments 2–6: crossfade image[idx] → image[idx+1]
@@ -536,19 +542,19 @@ static void render_scene8(int elapsed) {
if (imgs[idx]) {
SDL_SetTextureBlendMode(imgs[idx], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[idx], 255);
SDL_RenderCopy(gfx_renderer, imgs[idx], NULL, NULL);
sdl_render_texture(gfx_renderer, imgs[idx], NULL, NULL);
}
if (imgs[idx + 1]) {
SDL_SetTextureBlendMode(imgs[idx + 1], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[idx + 1], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[idx + 1], NULL, NULL);
sdl_render_texture(gfx_renderer, imgs[idx + 1], NULL, NULL);
}
} else {
// Segment 7: hold image[5] at full opacity
if (imgs[5]) {
SDL_SetTextureBlendMode(imgs[5], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[5], 255);
SDL_RenderCopy(gfx_renderer, imgs[5], NULL, NULL);
sdl_render_texture(gfx_renderer, imgs[5], NULL, NULL);
}
}
}
@@ -588,7 +594,7 @@ static void render_scene10(int elapsed) {
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(tex, 255);
SDL_SetTextureColorMod(tex, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < 9610) {
// Phase 2: zoom in
@@ -607,7 +613,7 @@ static void render_scene10(int elapsed) {
SDL_SetTextureBlendMode(surface3, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(surface3, 255);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, &src_rect, NULL);
sdl_render_texture(gfx_renderer, surface3, &src_rect, NULL);
}
} else {
// Phase 3: fade out to black
@@ -618,7 +624,7 @@ static void render_scene10(int elapsed) {
combined < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(surface3, (Uint8)combined);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, NULL, NULL);
sdl_render_texture(gfx_renderer, surface3, NULL, NULL);
}
}
}
@@ -644,12 +650,12 @@ static void render_scene11(int elapsed) {
// srcY = (bg_h - view_h) * (duration - elapsed) / duration
if (bg) {
int bg_w, bg_h;
SDL_QueryTexture(bg, NULL, NULL, &bg_w, &bg_h);
sdl_get_texture_size(bg, &bg_w, &bg_h);
int scroll = (int)((long long)(bg_h - view_h) * elapsed / duration);
int src_y = SDL_max(0, (bg_h - view_h) - scroll);
SDL_Rect src = {0, src_y, SDL_min(view_w, bg_w), view_h};
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg, &src, NULL);
sdl_render_texture(gfx_renderer, bg, &src, NULL);
}
if (elapsed < unit) {
@@ -659,12 +665,12 @@ static void render_scene11(int elapsed) {
SDL_Texture *tex = slide[2];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
sdl_get_texture_size(tex, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 3) {
// Segment 2: overlay[0] (ALK 69) pingpong fade
@@ -675,19 +681,19 @@ static void render_scene11(int elapsed) {
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 4) {
// Segment 3: slide[1] (ALK 62) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[1];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
sdl_get_texture_size(tex, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 3) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 5) {
// Segment 4: overlay[1] (ALK 70) pingpong fade
@@ -698,19 +704,19 @@ static void render_scene11(int elapsed) {
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 6) {
// Segment 5: slide[0] (ALK 61) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[0];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
sdl_get_texture_size(tex, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 5) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 7) {
// Segment 6: overlay[2] (ALK 71) pingpong fade
@@ -721,7 +727,7 @@ static void render_scene11(int elapsed) {
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
}
} else {
// Segment 7: white fade out
@@ -799,7 +805,7 @@ static void render_scene12_3d(int elapsed) {
// NDC → screen
verts[i].position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
verts[i].position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].color = sdl_vertex_color(255, 255, 255, 255);
verts[i].tex_coord.x = muv[i][0];
verts[i].tex_coord.y = muv[i][1];
}
@@ -817,7 +823,7 @@ static void render_scene12(int elapsed) {
// 0–2076ms: bg_a with white fade-in overlay
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
}
int white_alpha = 0xff - (elapsed * 0xff) / 2076;
fade_overlay(0xff, 0xff, 0xff, (Uint8)white_alpha);
@@ -825,32 +831,32 @@ static void render_scene12(int elapsed) {
// 2076–2226ms: bg_a static
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
}
} else if (elapsed < 3826) {
// 2226–3826ms: crossfade bg_a → bg_b
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
}
if (bg_b) {
int alpha = ((elapsed - 2226) * 0xff) / 1600;
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(bg_b, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 3976) {
// 3826–3976ms: bg_b static
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 7803) {
// 3976–7803ms: TODO wave distortion on bg_b; for now just show bg_b,
// then fade to white
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
}
int white_alpha = ((elapsed - 3976) * 0xff) / 3827;
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(white_alpha, 0xff));
@@ -858,19 +864,19 @@ static void render_scene12(int elapsed) {
// 7803–9397ms: white bg + 3D rotating quad + overlay fade-in
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderFillRect(gfx_renderer, NULL);
sdl_render_fill_rect(gfx_renderer, NULL);
render_scene12_3d(elapsed);
if (overlay) {
int alpha = ((elapsed - 7803) * 0xff) / 1594;
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(overlay, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
sdl_render_texture(gfx_renderer, overlay, NULL, NULL);
}
} else {
// 9397–10338ms: overlay static
if (overlay) {
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
sdl_render_texture(gfx_renderer, overlay, NULL, NULL);
}
}
}
+9 -6
View File
@@ -31,8 +31,11 @@
#include "ags.h"
#include "font.h"
static FontType ftype;
static int fsize; // フォントの大きさ
static FontSpec dt_spec = {
.type = FONT_GOTHIC,
.weight = FONT_WEIGHT_BOLD,
.size = 12
};
/**
* 次に描く文字のフォントの種類と大きさを設定
@@ -48,8 +51,8 @@ void dt_setfont(FontType type, int size) {
}
#endif
ftype = type;
fsize = size;
dt_spec.type = type;
dt_spec.size = size;
}
/**
@@ -66,12 +69,12 @@ void dt_setfont(FontType type, int size) {
* @return: 実際に描画した幅
*/
int dt_drawtext_col(SDL_Surface *sf, int x, int y, char *buf, int r, int g, int b) {
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, (FontSpec){ .type = ftype, .size = fsize });
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, dt_spec);
if (glyph == NULL) return 0;
SDL_Rect rect = {x, y, glyph->w, glyph->h};
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
SDL_BlitSurface(glyph, NULL, sf, &rect);
SDL_FreeSurface(glyph);
sdl_destroy_surface(glyph);
return rect.w;
}
+1 -2
View File
@@ -13,7 +13,7 @@ static void gre_BlendUseAMap(surface_t *dst, int dx, int dy, surface_t *src, int
uint8_t *dp = GETOFFSET_PIXEL(dst, dx, dy);
uint8_t *ap = GETOFFSET_ALPHA(src, sx, sy);
switch (dst->sdl_surface->format->BitsPerPixel) {
switch (sdl_surface_bits_per_pixel(dst->sdl_surface)) {
case 16:
for (int y = 0; y < height; y++) {
uint16_t *yls = (uint16_t *)(sp + y * src->sdl_surface->pitch);
@@ -55,4 +55,3 @@ void gr_blend_alpha_map(surface_t *dst, int dx, int dy, surface_t *src, int sx,
gre_BlendUseAMap(dst, dx, dy, src, sx, sy, sw, sh);
}
+31 -21
View File
@@ -27,7 +27,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "nact.h"
@@ -103,7 +103,7 @@ void scg_deref(cginfo_t *cg) {
return;
if (cg->sf)
SDL_FreeSurface(cg->sf);
sdl_destroy_surface(cg->sf);
free(cg);
}
@@ -111,8 +111,9 @@ void scg_deref(cginfo_t *cg) {
void scg_create(int wNumCG, int wWidth, int wHeight, int wR, int wG, int wB, int wBlendRate) {
SPCG_ASSERT_NO(wNumCG);
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, wR, wG, wB, wBlendRate));
SDL_Surface *sf = sdl_create_surface(
wWidth, wHeight, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, wR, wG, wB, wBlendRate));
scg_new(CG_SET, wNumCG, sf);
}
@@ -125,7 +126,7 @@ void scg_create_reverse(int wNumCG, int wNumSrcCG, int wReverseX, int wReverseY)
if (!src)
return;
SDL_Surface *sf = SDL_ConvertSurface(src->sf, src->sf->format, 0);
SDL_Surface *sf = sdl_convert_surface(src->sf, sdl_surface_format(src->sf));
if (wReverseX)
gfx_FlipSurfaceHorizontal(sf);
if (wReverseY)
@@ -142,15 +143,17 @@ void scg_create_stretch(int wNumCG, int wWidth, int wHeight, int wNumSrcCG) {
if (!src)
return;
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, src->sf->format->BitsPerPixel, src->sf->format->format);
SDL_Surface *sf = sdl_create_surface(wWidth, wHeight,
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
SDL_BlitScaled(src->sf, NULL, sf, NULL);
scg_new(CG_STRETCH, wNumCG, sf);
}
static SDL_Surface *blend(SDL_Surface *base, int x, int y, SDL_Surface *blend, int mode) {
assert(blend->format->format == SDL_PIXELFORMAT_ARGB8888 || blend->format->format == SDL_PIXELFORMAT_XRGB8888);
bool src_has_alpha = SDL_ISPIXELFORMAT_ALPHA(blend->format->format);
SDL_Surface *dst = SDL_ConvertSurfaceFormat(base, SDL_PIXELFORMAT_ARGB8888, 0);
assert(sdl_surface_format(blend) == SDL_PIXELFORMAT_ARGB8888 ||
sdl_surface_format(blend) == SDL_PIXELFORMAT_XRGB8888);
bool src_has_alpha = SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(blend));
SDL_Surface *dst = sdl_convert_surface(base, SDL_PIXELFORMAT_ARGB8888);
SDL_Rect rect = {x, y, blend->w, blend->h};
SDL_IntersectRect(&(SDL_Rect){0, 0, dst->w, dst->h}, &rect, &rect);
@@ -220,12 +223,14 @@ void scg_create_text(int wNumCG, int wSize, int wR, int wG, int wB, int wText) {
// 勝手に出ていいのかな?
if (svar_length(wText) == 0) return;
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, (FontSpec){ .size = wSize });
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, spec);
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_Surface *sf = sdl_create_surface(
glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
SDL_BlitSurface(glyph, NULL, sf, NULL);
SDL_FreeSurface(glyph);
sdl_destroy_surface(glyph);
scg_new(CG_SET, wNumCG, sf);
}
@@ -244,12 +249,14 @@ void scg_create_textnum(int wNumCG, int wSize, int wR, int wG, int wB, int wFigs
}
sprintf(s, ss, wValue);
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, (FontSpec){ .size = wSize });
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, spec);
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_Surface *sf = sdl_create_surface(
glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
SDL_BlitSurface(glyph, NULL, sf, NULL);
SDL_FreeSurface(glyph);
sdl_destroy_surface(glyph);
scg_new(CG_SET, wNumCG, sf);
}
@@ -263,7 +270,8 @@ void scg_copy(int wNumDstCG, int wNumSrcCG) {
if (!src)
return;
scg_new(CG_SET, wNumDstCG, SDL_ConvertSurface(src->sf, src->sf->format, 0));
scg_new(CG_SET, wNumDstCG,
sdl_convert_surface(src->sf, sdl_surface_format(src->sf)));
}
// CGの一部を切りぬいたCGを作成
@@ -275,7 +283,8 @@ void scg_cut(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int wHeig
if (!src)
return;
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, src->sf->format->BitsPerPixel, src->sf->format->format);
SDL_Surface *sf = sdl_create_surface(wWidth, wHeight,
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
SDL_BlitSurface(src->sf, &(SDL_Rect){wX, wY, wWidth, wHeight}, sf, NULL);
scg_new(CG_SET, wNumDstCG, sf);
@@ -290,8 +299,9 @@ void scg_partcopy(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int
if (!src)
return;
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, src->sf->w, src->sf->h, src->sf->format->BitsPerPixel, src->sf->format->format);
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, 0, 0, 0, 255));
SDL_Surface *sf = sdl_create_surface(src->sf->w, src->sf->h,
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, 0, 0, 0, 255));
SDL_BlitSurface(src->sf, &(SDL_Rect){wX, wY, wWidth, wHeight}, sf, &(SDL_Rect){wX, wY, wWidth, wHeight});
scg_new(CG_SET, wNumDstCG, sf);
@@ -351,7 +361,7 @@ int scg_querybpp(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return 0;
if (!cg_store || !cg_store[wNumCG]) return 0;
if (cg_store[wNumCG]->sf == NULL) return 0;
return cg_store[wNumCG]->sf->format->BitsPerPixel;
return sdl_surface_bits_per_pixel(cg_store[wNumCG]->sf);
}
// CGの alphamap が存在するかを取得
@@ -359,5 +369,5 @@ bool scg_existalphamap(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return false;
if (!cg_store || !cg_store[wNumCG]) return false;
if (cg_store[wNumCG]->sf == NULL) return false;
return SDL_ISPIXELFORMAT_ALPHA(cg_store[wNumCG]->sf->format->format);
return SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg_store[wNumCG]->sf));
}
+1
View File
@@ -1,6 +1,7 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
#include "portab.h"
+3 -2
View File
@@ -84,10 +84,11 @@ static void Run() {
cgdata *cg = jpeg_extract(alk->entries[i].data, alk->entries[i].size);
if (cg) {
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width,
cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_BlitSurface(sf, NULL, main_surface, NULL);
ags_updateFull();
SDL_FreeSurface(sf);
sdl_destroy_surface(sf);
cgdata_free(cg);
} else {
WARNING("Cannot decode CG %d", i);
+50 -16
View File
@@ -10,6 +10,7 @@ add_library(src_lib STATIC
cali.c
dri.c
filecheck.c
font.c
gameresource.c
hankaku.c
mmap.c
@@ -72,7 +73,6 @@ target_sources(xsystem35 PRIVATE
ecopy.c
effect.c
event.c
font.c
hacks.c
gfx_draw.c
gfx_image.c
@@ -105,12 +105,20 @@ target_sources(xsystem35 PRIVATE
if (EMSCRIPTEN)
target_sources(xsystem35 PRIVATE pcm.emscripten.c bgm.emscripten.c)
elseif (XSYSTEM35_SDL_VERSION STREQUAL "3")
target_sources(xsystem35 PRIVATE
pcm.sdl3mixer.c
sdl3_mixer_backend.c
bgm.sdl3mixer.c)
target_compile_definitions(xsystem35 PRIVATE HAVE_SDL3_MIXER_BACKEND)
else()
target_sources(xsystem35 PRIVATE pcm.sdlmixer.c bgm.sdlmixer.c)
endif()
if (EMSCRIPTEN)
target_sources(xsystem35 PRIVATE cdrom.emscripten.c)
elseif (XSYSTEM35_SDL_VERSION STREQUAL "3")
target_sources(xsystem35 PRIVATE cdrom.sdl3mixer.c)
else()
target_sources(xsystem35 PRIVATE cdrom.mp3.c)
endif()
@@ -120,7 +128,11 @@ if (EMSCRIPTEN)
elseif (ANDROID)
target_sources(xsystem35 PRIVATE midi.android.c)
else()
target_sources(xsystem35 PRIVATE midi.sdlmixer.c)
if (XSYSTEM35_SDL_VERSION STREQUAL "3")
target_sources(xsystem35 PRIVATE midi.sdl3mixer.c)
else()
target_sources(xsystem35 PRIVATE midi.sdlmixer.c)
endif()
if (ENABLE_MIDI_PORTMIDI)
target_sources(xsystem35 PRIVATE midi.portmidi.c midifile.c)
endif()
@@ -137,18 +149,19 @@ if (ENABLE_DEBUGGER)
endif()
if (WIN32)
target_sources(src_lib PRIVATE win/resources.c)
target_sources(xsystem35 PRIVATE
win/dialog.c win/resources.rc win/resources.c win/menubar.c win/console.c)
win/dialog.c win/resources.rc win/menubar.c win/console.c)
endif()
if (NOT HAVE_LIBINTL)
target_sources(xsystem35 PRIVATE nls.c)
endif()
target_link_libraries(src_lib PRIVATE sdl2 zlib)
target_link_libraries(xsystem35 PRIVATE m zlib sdl2 sdl2_ttf)
if (TARGET sdl2_mixer)
target_link_libraries(xsystem35 PRIVATE sdl2_mixer)
target_link_libraries(src_lib PRIVATE xsystem35_sdl freetype2 zlib)
target_link_libraries(xsystem35 PRIVATE m zlib xsystem35_sdl)
if (TARGET xsystem35_sdl_mixer)
target_link_libraries(xsystem35 PRIVATE xsystem35_sdl_mixer)
endif()
if (EMSCRIPTEN)
@@ -157,18 +170,28 @@ if (EMSCRIPTEN)
# Without optimizations, Asyncify generates very large code.
list(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG "-O1")
if (JSPI)
target_link_options(xsystem35 PRIVATE
-sJSPI
-sJSPI_IMPORTS=wait_vsync
)
else()
target_link_options(xsystem35 PRIVATE
-sASYNCIFY=1
-sASYNCIFY_IGNORE_INDIRECT=1
-sASYNCIFY_REMOVE=SDL_Delay
-sASYNCIFY_IMPORTS=wait_vsync
# Functions that call an asynchronous function via a function pointer must
# be added to ASYNCIFY_ADD, even if it had a direct call to another
# asynchronous function, so that the indirect call itself is instrumented.
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
)
endif()
target_link_options(xsystem35 PRIVATE
-sENVIRONMENT=web
-sMODULARIZE=1
-sEXPORT_ES6=1
-sASYNCIFY=1
-sASYNCIFY_IGNORE_INDIRECT=1
-sASYNCIFY_REMOVE=SDL_Delay
-sASYNCIFY_IMPORTS=wait_vsync
# Functions that call an asynchronous function via a function pointer must
# be added to ASYNCIFY_ADD, even if it had a direct call to another
# asynchronous function, so that the indirect call itself is instrumented.
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
-sALLOW_MEMORY_GROWTH=1
-sNO_EXIT_RUNTIME=1
-sEXPORTED_FUNCTIONS=_main,_malloc
@@ -185,19 +208,30 @@ else() # non-emscripten, non-android
endif()
if (ENABLE_MIDI_PORTMIDI)
target_link_libraries(xsystem35 PRIVATE ${PORTMIDI})
if (WIN32)
# Other static dependencies can place winmm before PortMidi on the link
# line. Rescan the pair so PortMidi's WinMM imports are resolved.
target_link_libraries(xsystem35 PRIVATE
"-Wl,--start-group,-lportmidi,-lwinmm,--end-group")
endif()
endif()
install(TARGETS xsystem35 RUNTIME DESTINATION bin)
add_executable(src_tests
src_tests.c
cache_test.c
font_test.c
gameresource_test.c
hankaku_test.c
qnt_test.c
sdl_compat_test.c
variable_test.c
)
target_compile_options(src_tests PRIVATE -Wno-pointer-sign -Wall)
target_link_libraries(src_tests PRIVATE src_lib sdl2)
target_compile_definitions(src_tests PRIVATE
TEST_FONT_DIR="${PROJECT_SOURCE_DIR}/fonts")
target_link_libraries(src_tests PRIVATE src_lib xsystem35_sdl)
add_test(NAME src_tests COMMAND src_tests)
file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
endif()
+1 -1
View File
@@ -27,7 +27,7 @@
#define __LITTLEENDIAN__
#include <string.h>
#include <SDL_endian.h>
#include "sdl_compat.h"
static inline int LittleEndian_getDW(const uint8_t *b, int index) {
uint32_t t;
+38 -26
View File
@@ -125,7 +125,7 @@ bool ags_check_param_xy(int *x, int *y) {
return true;
}
void ags_init(const char *render_driver, bool enable_zb) {
void ags_init(const char *render_driver) {
nact->ags.mouse_warp_enabled = true;
nact->ags.world_width = SYS35_DEFAULT_WIDTH;
nact->ags.world_height = SYS35_DEFAULT_HEIGHT;
@@ -138,8 +138,7 @@ void ags_init(const char *render_driver, bool enable_zb) {
nact->ags.font_type = FONT_GOTHIC;
nact->ags.text_decoration_type = 0;
nact->ags.text_decoration_color = 0;
nact->ags.enable_zb = enable_zb;
nact->ags.font_weight = enable_zb ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
nact->ags.font_weight = FONT_WEIGHT_BOLD;
gfx_Initialize(render_driver);
event_init();
@@ -193,7 +192,7 @@ void ags_setViewArea(int x, int y, int width, int height) {
nact->ags.view_area.y = y;
nact->ags.view_area.w = width;
nact->ags.view_area.h = height;
gfx_setWindowSize(width, height);
gfx_setViewSize(width, height);
}
void ags_setWindowTitle(const char *title_utf8) {
@@ -202,20 +201,33 @@ void ags_setWindowTitle(const char *title_utf8) {
gfx_setWindowTitle(buf);
}
void ags_getDIBInfo(DispInfo *info) {
info->width = nact->ags.world_width;
info->height = nact->ags.world_height;
info->depth = nact->ags.world_depth;
}
void ags_getViewAreaInfo(DispInfo *info) {
gfx_getWindowInfo(NULL, NULL, &info->depth);
info->width = nact->ags.view_area.w;
info->height = nact->ags.view_area.h;
}
void ags_getWindowInfo(DispInfo *info) {
gfx_getWindowInfo(&info->width, &info->height, &info->depth);
void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth) {
SDL_DisplayMode dm;
switch (display) {
case AGS_DISPLAY_CURRENT:
sdl_get_current_display_mode(&dm);
*width = dm.w;
*height = dm.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
case AGS_DISPLAY_DIB:
*width = nact->ags.world_width;
*height = nact->ags.world_height;
*depth = nact->ags.world_depth;
break;
case AGS_DISPLAY_VIEW_AREA:
sdl_get_current_display_mode(&dm);
*width = nact->ags.view_area.w;
*height = nact->ags.view_area.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
case AGS_DISPLAY_DESKTOP:
sdl_get_desktop_display_mode(&dm);
*width = dm.w;
*height = dm.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
}
}
void ags_setExposeSwitch(bool expose) {
@@ -321,10 +333,10 @@ void ags_getPixel(int x, int y, PixelColor *cell) {
if (!ags_check_param_xy(&x, &y)) return;
uint32_t pixel = gfx_getPixel(x, y);
if (main_surface->format->BitsPerPixel == 8) {
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
cell->index = pixel;
} else {
SDL_GetRGB(pixel, main_surface->format, &cell->r, &cell->g, &cell->b);
sdl_get_rgb(pixel, main_surface, &cell->r, &cell->g, &cell->b);
}
}
@@ -466,7 +478,7 @@ int ags_drawString(int x, int y, const char *src, int col, int size, SDL_Rect *r
SDL_Surface *ags_drawStringToSurface(const char *str, int r, int g, int b, FontSpec font) {
char *utf8 = toUTF8(str);
SDL_Color color = {r, g, b, 255};
SDL_Surface *sf = font_render_text(font, utf8, color);
SDL_Surface *sf = font_render_text(font, utf8, color, true);
free(utf8);
return sf;
}
@@ -716,10 +728,10 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
}
#ifdef __EMSCRIPTEN__
if (!for_selection) {
if (!sl_is_s380 && !for_selection) {
// We can't move the actual cursor in the browser, but can change the
// internal mouse coordinates. This can help with keyboard/gamepad
// navigation.
// navigation in Toushin Toshi 2.
event_set_mouse_internal_location(x, y);
EM_ASM({ xsystem35.shell.showMouseMoveEffect($0, $1); }, x, y);
sys_sleep(cursor_move_time);
@@ -733,11 +745,11 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
for (int i = 1; i < 8; i++) {
int xi = ((dx*i*i*i) >> 9) - ((3*dx*i*i)>> 6) + ((3*dx*i) >> 3) + p.x;
int yi = ((dy*i*i*i) >> 9) - ((3*dy*i*i)>> 6) + ((3*dy*i) >> 3) + p.y;
event_set_mouse_location(xi, yi);
gfx_warpMouse(xi, yi);
sys_sleep(cursor_move_time / 7);
}
event_set_mouse_location(x, y);
} else if (!for_selection) {
gfx_warpMouse(x, y);
} else if (!sl_is_s380 && !for_selection) {
event_set_mouse_internal_location(x, y);
sys_sleep(cursor_move_time);
}
+10 -12
View File
@@ -26,7 +26,7 @@
#include "config.h"
#include <SDL_surface.h>
#include "sdl_compat.h"
#include "portab.h"
#include "cg.h"
#include "font.h"
@@ -58,7 +58,7 @@ typedef struct agsurface surface_t;
#define GETOFFSET_PIXEL(suf, x, y) PIXEL_AT((suf)->sdl_surface, x, y)
#define GETOFFSET_ALPHA(suf, x, y) ((suf)->alpha + (y) * (suf)->width + (x))
// for SDL_surface
#define PIXEL_AT(suf, x, y) ((suf)->pixels + (y) * (suf)->pitch + (x) * (suf)->format->BytesPerPixel)
#define PIXEL_AT(suf, x, y) ((suf)->pixels + (y) * (suf)->pitch + (x) * sdl_surface_bytes_per_pixel(suf))
#define ALPHA_AT(suf, x, y) (PIXEL_AT(suf, x, y) + (SDL_BYTEORDER == SDL_LIL_ENDIAN ? 3 : 0))
typedef struct {
@@ -102,11 +102,12 @@ typedef struct {
uint32_t index;
} PixelColor;
typedef struct {
int width;
int height;
int depth;
} DispInfo;
enum ags_display {
AGS_DISPLAY_CURRENT,
AGS_DISPLAY_DIB,
AGS_DISPLAY_VIEW_AREA,
AGS_DISPLAY_DESKTOP,
};
struct _ags {
SDL_Color pal[256]; /* system palette */
@@ -128,7 +129,6 @@ struct _ags {
int text_decoration_color;
bool mouse_warp_enabled;
bool enable_zb;
bool noantialias; /* antialias を使用しない */
bool noimagecursor; /* リソースファイルのカーソルを読みこまない */
};
@@ -137,7 +137,7 @@ typedef struct _ags ags_t;
extern SDL_Surface *main_surface;
/* 初期化関係 */
void ags_init(const char *render_driver, bool enable_zb);
void ags_init(const char *render_driver);
void ags_remove(void);
void ags_reset(void);
@@ -145,9 +145,7 @@ void ags_reset(void);
extern void ags_setWorldSize(int width, int height, int depth);
extern void ags_setViewArea(int x, int y, int width, int height);
extern void ags_setWindowTitle(const char *title_utf8);
extern void ags_getDIBInfo(DispInfo *info);
extern void ags_getWindowInfo(DispInfo *info);
extern void ags_getViewAreaInfo(DispInfo *info);
extern void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth);
extern bool ags_check_param(int *x, int *y, int *w, int *h);
extern bool ags_check_param_xy(int *x, int *y);
extern surface_t *ags_getDIB();
+37 -7
View File
@@ -33,18 +33,34 @@
/* drifiles object */
static drifiles *dri[DRIFILETYPEMAX];
/* cache handler for dri file */
static cacher *cacheid;
static Cache *dri_cache;
#ifndef ALD_CACHE_SIZE
#define ALD_CACHE_SIZE (10 << 20)
#endif
static uint32_t int_hash(const void *key) {
return (uint32_t)*(const int *)key;
}
static bool int_equal(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
/*
* free dridata
* dfile: dridata to be free
*/
static void ald_free(dridata *dfile) {
static void ald_free(void *data) {
dridata *dfile = data;
free(dfile->data_raw);
free(dfile);
}
static bool ald_is_pinned(const void *data) {
return ((const dridata *)data)->refcnt != 0;
}
bool ald_is_linked(DRIFILETYPE type, int no) {
if (type >= DRIFILETYPEMAX || !dri[type])
return false;
@@ -80,11 +96,12 @@ dridata *ald_getdata(DRIFILETYPE type, int no) {
if (dri[type]->mmapped) return dri_getdata(dri[type], no);
/* not mmapped */
if (NULL == (ddata = (dridata *)cache_foreach(cacheid, (type << 16) + no))) {
int key = (type << 16) + no;
if (NULL == (ddata = cache_get(dri_cache, &key))) {
ddata = dri_getdata(dri[type], no);
if (ddata != NULL) {
ddata->refcnt = 0;
cache_insert(cacheid, (type << 16) + no, (void *)ddata, ddata->size, &(ddata->refcnt));
ddata->cached = cache_insert(dri_cache, &key, ddata, ddata->size) == CACHE_INSERT_OK;
}
}
if (ddata != NULL)
@@ -105,6 +122,8 @@ void ald_freedata(dridata *data) {
free(data);
} else {
data->refcnt--;
if (!data->cached && data->refcnt == 0)
ald_free(data);
}
}
@@ -112,8 +131,15 @@ void ald_init(int type, const char **file, int cnt, bool use_mmap) {
if (type >= DRIFILETYPEMAX || cnt <= 0)
return;
dri[type] = dri_init(file, cnt, use_mmap);
if (!dri[type]->mmapped) {
cacheid = cache_new(ald_free);
if (!dri[type]->mmapped && !dri_cache) {
CacheOps ops = {
.key_size = sizeof(int),
.hash = int_hash,
.equal = int_equal,
.destroy = ald_free,
.is_pinned = ald_is_pinned,
};
dri_cache = cache_new((size_t)ALD_CACHE_SIZE, &ops);
}
}
@@ -122,3 +148,7 @@ int ald_get_maxno(DRIFILETYPE type) {
return 0;
return dri[type]->maxno;
}
CacheStats ald_get_cache_stats(void) {
return cache_get_stats(dri_cache);
}
+2 -1
View File
@@ -25,6 +25,7 @@
#define __ALD_MANAGER__
#include "portab.h"
#include "cache.h"
#include "dri.h"
#define DRIFILETYPEMAX 7
@@ -44,6 +45,6 @@ bool ald_exists(DRIFILETYPE type, int no);
dridata *ald_getdata(DRIFILETYPE type, int no);
void ald_freedata(dridata *data);
int ald_get_maxno(DRIFILETYPE type);
CacheStats ald_get_cache_stats(void);
#endif /* !__ALD_MANAGER__ */
+156
View File
@@ -0,0 +1,156 @@
/*
* bgm.sdl3mixer.c: BGM (*BA.ALD) playback using SDL3_mixer
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <stdint.h>
#include "ald_manager.h"
#include "audio_meta.h"
#include "bgm.h"
#include "music_private.h"
#include "nact.h"
#include "sdl3_mixer_backend.h"
#include "system.h"
static DRIFILETYPE dri_type;
static int base_no;
static int current_no;
static int current_vol = 100;
static uint32_t start_time;
static int clamp_volume(int volume)
{
if (volume < 0)
return 0;
if (volume > 100)
return 100;
return volume;
}
static float music_gain(int volume)
{
float gain = clamp_volume(volume) / 100.0f;
return gain * clamp_volume(prv.volval[BGM_VOLVAL_CH]) / 100.0f;
}
static void apply_music_volume(int volume)
{
current_vol = volume;
sdl3_mixer_set_music_gain(SDL3_MIXER_MUSIC_BGM,
music_gain(current_vol));
}
void musbgm_reapply_valance(void)
{
apply_music_volume(current_vol);
}
static void free_music(void)
{
current_no = 0;
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_BGM, 0);
}
static MIX_Audio *load_music(int no)
{
free_music();
int ald_no = no + base_no - 1;
dridata *dfile = ald_getdata(dri_type, ald_no);
if (!dfile) {
WARNING("Failed to open BGM %d", ald_no);
return NULL;
}
MIX_Audio *audio = sdl3_mixer_load_music_memory(
dfile->data, dfile->size);
ald_freedata(dfile);
if (!audio)
WARNING("Failed to load BGM %d: %s", ald_no, SDL_GetError());
return audio;
}
bool musbgm_init(DRIFILETYPE type, int base)
{
dri_type = type;
base_no = base;
if (type == DRIFILE_BGM)
return bgi_read(nact->files.bgi);
return true;
}
void musbgm_exit(void)
{
free_music();
}
void musbgm_reset(void)
{
free_music();
}
bool musbgm_play(int no, int time, int volume, int loop_count)
{
MIX_Audio *audio = load_music(no);
if (!audio)
return false;
int loops = loop_count == 0 ? -1 : loop_count - 1;
current_vol = volume;
if (!sdl3_mixer_play_music(SDL3_MIXER_MUSIC_BGM, audio,
loops, time * 10, music_gain(current_vol)))
return false;
current_no = no;
start_time = sys_get_ticks();
return true;
}
void musbgm_stop(int no, int time)
{
if (no == current_no)
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_BGM, time * 10);
}
void musbgm_fade(int no, int time, int volume)
{
(void)time;
if (no == current_no)
apply_music_volume(volume);
}
int musbgm_getpos(int no)
{
if (!musbgm_isplaying(no))
return 0;
return (sys_get_ticks() - start_time) / 10;
}
int musbgm_getlen(int no)
{
bgi_t *bgi = bgi_find(no);
return bgi ? bgi->len / 441 : 0;
}
bool musbgm_isplaying(int no)
{
return no == current_no &&
sdl3_mixer_music_active(SDL3_MIXER_MUSIC_BGM);
}
void musbgm_stopall(int time)
{
musbgm_stop(current_no, time);
}
void musbgm_wait(int no, int timeout)
{
while (timeout-- > 0 && musbgm_isplaying(no))
SDL_Delay(10);
}
+7 -5
View File
@@ -25,8 +25,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <SDL.h>
#include <SDL_mixer.h>
#include "sdl_mixer_compat.h"
#include "portab.h"
#include "system.h"
@@ -111,11 +110,14 @@ bool musbgm_play(int no, int time, int vol, int loop_count) {
if (!bgm_load(no))
return false;
// We don't use the loop information in the BGI file, but SDL_mixer
// understands loop info in the WAVE's "smpl" chunk.
// We don't use the loop positions in the BGI file; SDL_mixer instead
// understands loop positions in the WAVE's "smpl" chunk.
apply_music_volume(vol);
if (Mix_FadeInMusic(mix_music, loop_count == 0 ? -1 : loop_count, time * 10) != 0) {
// SDL_mixer counts repeats after the first play, while System 3.x counts
// the total number of plays. In both APIs, an infinite loop is special.
int loops = loop_count == 0 ? -1 : loop_count - 1;
if (Mix_FadeInMusic(mix_music, loops, time * 10) != 0) {
free_music();
return false;
}
+162 -115
View File
@@ -1,8 +1,5 @@
/*
* cache.c general cache manager
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,130 +16,180 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* $Id: cache.c,v 1.5 2003/07/21 23:06:47 chikama Exp $ */
#include "config.h"
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include "portab.h"
#include "cache.h"
/* maximum cache size (in MB) */
#ifndef CACHE_TOTALSIZE
#define CACHE_TOTALSIZE 20
#endif
#include <stdlib.h>
#include <string.h>
static int totalsize; /* total size in cache */
static int dummyfalse = 0; /* dummy in_use flag */
static int dummytrue = 1; /* dummy in_use flag */
#define CACHE_BUCKETS 1024
/*
* static methods
*/
static void remove_in_cache(cacher *id);
typedef struct CacheEntry {
struct CacheEntry *hash_next;
struct CacheEntry *lru_prev;
struct CacheEntry *lru_next;
uint32_t hash;
size_t cost;
void *data;
unsigned char key[];
} CacheEntry;
/*
* Remove data in cache
* id: cache handler
*/
static void remove_in_cache(cacher *id) {
cacheinfo *ip = id->top;
cacheinfo *ic = ip->next;
if (!ic)
return;
while(ic->next != NULL) {
if (!*ic->in_use) {
totalsize -= ic->size;
ip->next = ic->next;
id->free_(ic->data);
free(ic);
} else {
ip = ic;
}
ic = ip->next;
}
return;
struct Cache {
CacheOps ops;
CacheEntry *buckets[CACHE_BUCKETS];
CacheEntry *lru_head;
CacheEntry *lru_tail;
size_t capacity;
size_t size;
size_t count;
size_t hits;
size_t misses;
};
static bool is_pinned(const Cache *cache, const CacheEntry *entry) {
return cache->ops.is_pinned && cache->ops.is_pinned(entry->data);
}
/*
* Create new cache object
* delcallback: callback function for delete cache data object
* return: new cache handler
*/
cacher *cache_new(void *delcallback) {
cacher *c = calloc(1, sizeof(cacher));
c->top = calloc(1, sizeof(cacheinfo));
c->top->next = NULL;
c->top->in_use = &dummytrue;
c->free_ = delcallback;
return c;
static void lru_remove(Cache *cache, CacheEntry *entry) {
if (entry->lru_prev)
entry->lru_prev->lru_next = entry->lru_next;
else
cache->lru_head = entry->lru_next;
if (entry->lru_next)
entry->lru_next->lru_prev = entry->lru_prev;
else
cache->lru_tail = entry->lru_prev;
}
/*
* Insert data to cache
* id : cache handler
* key : data key
* data : data to be cached
* size : data size
* in_use: in_use mark pointer, if in_use is nonzero, dont remove from cache
*/
void cache_insert(cacher *id, int key, void *data, int size, int *in_use) {
cacheinfo *i = id->top;
if (CACHE_TOTALSIZE <= (totalsize >> 20)) {
remove_in_cache(id);
}
while(i->next != NULL) {
i = i->next;
}
i->key = key;
i->data = data;
i->size = size;
i->next = calloc(1, sizeof(cacheinfo));
i->next->next = NULL;
if (in_use) {
i->in_use = in_use;
} else {
i->in_use = &dummyfalse;
}
totalsize += size;
static void lru_prepend(Cache *cache, CacheEntry *entry) {
entry->lru_prev = NULL;
entry->lru_next = cache->lru_head;
if (cache->lru_head)
cache->lru_head->lru_prev = entry;
else
cache->lru_tail = entry;
cache->lru_head = entry;
}
/*
* Search data in cache
* id : cache handler
* key: data search key
* return: pointer to cached data
*/
void *cache_foreach(cacher *id, int key) {
cacheinfo *i = id->top;
while(i != NULL) {
if (i->key == key) {
return i->data;
}
i = i->next;
static void remove_entry(Cache *cache, CacheEntry *entry) {
CacheEntry **link = &cache->buckets[entry->hash % CACHE_BUCKETS];
while (*link != entry)
link = &(*link)->hash_next;
*link = entry->hash_next;
lru_remove(cache, entry);
cache->size -= entry->cost;
cache->count--;
cache->ops.destroy(entry->data);
free(entry);
}
static CacheEntry *find_entry(Cache *cache, const void *key, uint32_t hash) {
for (CacheEntry *entry = cache->buckets[hash % CACHE_BUCKETS]; entry;
entry = entry->hash_next) {
if (entry->hash == hash && cache->ops.equal(entry->key, key))
return entry;
}
return NULL;
}
void cache_clear(cacher *id) {
cacheinfo *ic = id->top->next;
cacheinfo *next;
while (ic != NULL) {
next = ic->next;
if (ic->data) {
totalsize -= ic->size;
id->free_(ic->data);
}
free(ic);
ic = next;
}
id->top->next = NULL;
Cache *cache_new(size_t capacity, const CacheOps *ops) {
if (!ops || !ops->key_size || !ops->hash || !ops->equal || !ops->destroy)
return NULL;
Cache *cache = calloc(1, sizeof(Cache));
if (!cache)
return NULL;
cache->ops = *ops;
cache->capacity = capacity;
return cache;
}
void cache_destroy(Cache *cache) {
if (!cache)
return;
while (cache->lru_tail)
remove_entry(cache, cache->lru_tail);
free(cache);
}
void *cache_get(Cache *cache, const void *key) {
if (!cache || !key)
return NULL;
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
if (!entry) {
cache->misses++;
return NULL;
}
cache->hits++;
lru_remove(cache, entry);
lru_prepend(cache, entry);
return entry->data;
}
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost) {
if (!cache || !key || !data)
return CACHE_INSERT_NOMEM;
uint32_t hash = cache->ops.hash(key);
if (find_entry(cache, key, hash))
return CACHE_INSERT_EXISTS;
if (cost > cache->capacity)
return CACHE_INSERT_FULL;
CacheEntry *entry = malloc(sizeof(CacheEntry) + cache->ops.key_size);
if (!entry)
return CACHE_INSERT_NOMEM;
for (CacheEntry *old = cache->lru_tail, *prev;
old && cache->size > cache->capacity - cost; old = prev) {
prev = old->lru_prev;
if (!is_pinned(cache, old))
remove_entry(cache, old);
}
if (cache->size > cache->capacity - cost) {
free(entry);
return CACHE_INSERT_FULL;
}
entry->hash = hash;
entry->cost = cost;
entry->data = data;
memcpy(entry->key, key, cache->ops.key_size);
unsigned bucket = hash % CACHE_BUCKETS;
entry->hash_next = cache->buckets[bucket];
cache->buckets[bucket] = entry;
lru_prepend(cache, entry);
cache->size += cost;
cache->count++;
return CACHE_INSERT_OK;
}
bool cache_remove(Cache *cache, const void *key) {
if (!cache || !key)
return false;
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
if (!entry || is_pinned(cache, entry))
return false;
remove_entry(cache, entry);
return true;
}
size_t cache_clear(Cache *cache) {
if (!cache)
return 0;
for (CacheEntry *entry = cache->lru_tail, *prev; entry; entry = prev) {
prev = entry->lru_prev;
if (!is_pinned(cache, entry))
remove_entry(cache, entry);
}
return cache->count;
}
CacheStats cache_get_stats(const Cache *cache) {
if (!cache)
return (CacheStats){0};
return (CacheStats){
.count = cache->count,
.size = cache->size,
.capacity = cache->capacity,
.hits = cache->hits,
.misses = cache->misses,
};
}
+46 -28
View File
@@ -1,8 +1,5 @@
/*
* cache.h general cache manager
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,33 +16,54 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* $Id: cache.h,v 1.2 2003/07/21 23:06:47 chikama Exp $ */
#ifndef XSYSTEM35_CACHE_H
#define XSYSTEM35_CACHE_H
#ifndef __CASHE__
#define __CASHE__
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "portab.h"
typedef struct Cache Cache;
/* cache controlr infomartion */
struct _cacheinfo {
int key; /* key of data */
int size; /* data size */
struct _cacheinfo *next; /* next data */
int *in_use; /* if *in_use is nonzero, dont remove from cache */
void *data; /* real data */
};
typedef struct _cacheinfo cacheinfo;
typedef struct {
size_t key_size;
uint32_t (*hash)(const void *key);
bool (*equal)(const void *a, const void *b);
void (*destroy)(void *data);
bool (*is_pinned)(const void *data);
} CacheOps;
/* cache handler */
struct _cacher {
void (*free_)(void *); /* free data callback */
struct _cacheinfo *top; /* pointer to data */
};
typedef struct _cacher cacher;
typedef enum {
CACHE_INSERT_OK,
CACHE_INSERT_EXISTS,
CACHE_INSERT_FULL,
CACHE_INSERT_NOMEM,
} CacheInsertResult;
extern cacher *cache_new(void *delcallback);
extern void cache_insert(cacher *id, int key, void *data, int size, int *in_use);
extern void *cache_foreach(cacher *id, int key);
extern void cache_clear(cacher *id);
typedef struct {
size_t count;
size_t size;
size_t capacity;
size_t hits;
size_t misses;
} CacheStats;
#endif /* !__CASHE__ */
Cache *cache_new(size_t capacity, const CacheOps *ops);
void cache_destroy(Cache *cache);
/* The returned pointer is owned by the cache and is invalidated by removal. */
void *cache_get(Cache *cache, const void *key);
/* Ownership of data is transferred only when CACHE_INSERT_OK is returned. */
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost);
/* Pinned entries cannot be removed. */
bool cache_remove(Cache *cache, const void *key);
/* Removes all unpinned entries and returns the number of entries left. */
size_t cache_clear(Cache *cache);
/* Returns zero-filled statistics when cache is NULL. */
CacheStats cache_get_stats(const Cache *cache);
#endif /* XSYSTEM35_CACHE_H */
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "cache.h"
#include "unittest.h"
#include <stdlib.h>
typedef struct {
int value;
bool pinned;
} TestValue;
static int destroyed;
static uint32_t int_hash(const void *key) {
return (uint32_t)*(const int *)key;
}
static bool int_equal(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
static void destroy_value(void *data) {
destroyed++;
free(data);
}
static bool value_is_pinned(const void *data) {
return ((const TestValue *)data)->pinned;
}
static TestValue *new_value(int value) {
TestValue *data = malloc(sizeof(TestValue));
ASSERT_TRUE(data);
data->value = value;
data->pinned = false;
return data;
}
void cache_test(void) {
CacheOps ops = {
.key_size = sizeof(int),
.hash = int_hash,
.equal = int_equal,
.destroy = destroy_value,
.is_pinned = value_is_pinned,
};
Cache *cache = cache_new(2, &ops);
ASSERT_TRUE(cache);
destroyed = 0;
CacheStats stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.count, 0);
ASSERT_EQUAL(stats.size, 0);
ASSERT_EQUAL(stats.capacity, 2);
ASSERT_EQUAL(stats.hits, 0);
ASSERT_EQUAL(stats.misses, 0);
// A lookup makes k1 most-recently used, so inserting k3 evicts k2.
int k1 = 1, k2 = 2, k3 = 3, k4 = 4, k5 = 5;
ASSERT_EQUAL(cache_insert(cache, &k1, new_value(1), 1), CACHE_INSERT_OK);
ASSERT_EQUAL(cache_insert(cache, &k2, new_value(2), 1), CACHE_INSERT_OK);
stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.count, 2);
ASSERT_EQUAL(stats.size, 2);
ASSERT_EQUAL(stats.capacity, 2);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
ASSERT_EQUAL(cache_insert(cache, &k3, new_value(3), 1), CACHE_INSERT_OK);
ASSERT_NULL(cache_get(cache, &k2));
stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.hits, 1);
ASSERT_EQUAL(stats.misses, 1);
// Pinned entries survive both capacity eviction and cache_clear().
((TestValue *)cache_get(cache, &k1))->pinned = true;
ASSERT_EQUAL(cache_insert(cache, &k4, new_value(4), 1), CACHE_INSERT_OK);
ASSERT_NULL(cache_get(cache, &k3));
((TestValue *)cache_get(cache, &k4))->pinned = true;
// Insertion fails when every remaining entry is pinned.
TestValue *rejected = new_value(5);
ASSERT_EQUAL(cache_insert(cache, &k5, rejected, 1), CACHE_INSERT_FULL);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k4))->value, 4);
ASSERT_EQUAL(cache_clear(cache), 2);
ASSERT_EQUAL(destroyed, 2);
free(rejected);
// Once unpinned, cache_clear() destroys the remaining entries.
((TestValue *)cache_get(cache, &k1))->pinned = false;
((TestValue *)cache_get(cache, &k4))->pinned = false;
ASSERT_EQUAL(cache_clear(cache), 0);
ASSERT_EQUAL(destroyed, 4);
cache_destroy(cache);
stats = cache_get_stats(NULL);
ASSERT_EQUAL(stats.count, 0);
ASSERT_EQUAL(stats.size, 0);
ASSERT_EQUAL(stats.capacity, 0);
ASSERT_EQUAL(stats.hits, 0);
ASSERT_EQUAL(stats.misses, 0);
}
+1 -2
View File
@@ -26,8 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL_timer.h>
#include <SDL_mixer.h>
#include "sdl_mixer_compat.h"
#include "portab.h"
#include "system.h"
+201
View File
@@ -0,0 +1,201 @@
/*
* cdrom.sdl3mixer.c CD audio replacement using SDL3_mixer
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "config.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ald_manager.h"
#include "cdrom.h"
#include "music_private.h"
#include "sdl3_mixer_backend.h"
#include "system.h"
#define PLAYLIST_MAX 256
static bool enabled;
static char *playlist[PLAYLIST_MAX];
static int track_no;
static uint32_t start_time;
static void cdrom_stop(void);
static int clamp_volume(int volume)
{
if (volume < 0)
return 0;
if (volume > 100)
return 100;
return volume;
}
static float music_gain(void)
{
return clamp_volume(prv.volval[BGM_VOLVAL_CH]) / 100.0f;
}
static void apply_volume(void)
{
sdl3_mixer_set_music_gain(SDL3_MIXER_MUSIC_CD, music_gain());
}
static void free_playlist(void)
{
for (int track = 0; track < PLAYLIST_MAX; track++) {
free(playlist[track]);
playlist[track] = NULL;
}
prv.cd_maxtrk = 0;
}
static bool cdrom_init(char *playlist_path)
{
if (!playlist_path || !playlist_path[0])
return false;
if (enabled)
cdrom_stop();
enabled = false;
free_playlist();
FILE *fp = fopen(playlist_path, "r");
char buf[256];
if (fp) {
/* The first line represents track 1, which is data on mixed CDs. */
fgets(buf, sizeof(buf), fp);
} else {
fp = fopen("_inmm.ini", "r");
}
if (!fp) {
if (ald_get_maxno(DRIFILE_MIDI) == 0)
NOTICE("cdrom: Cannot open playlist %s", playlist_path);
return false;
}
for (int track = 2; track < PLAYLIST_MAX; track++) {
if (!fgets(buf, sizeof(buf), fp))
break;
for (char *s = buf; *s; s++) {
if (*s == '\\')
*s = '/';
else if (*s == '\r' || *s == '\n') {
*s = '\0';
break;
}
}
if (*buf) {
playlist[track] = strdup(buf);
if (playlist[track])
prv.cd_maxtrk = track;
}
}
fclose(fp);
NOTICE("cdrom: Loaded playlist from %s", playlist_path);
track_no = 0;
enabled = true;
return true;
}
static void cdrom_exit(void)
{
if (enabled)
cdrom_stop();
enabled = false;
free_playlist();
}
static void cdrom_reset(void)
{
if (enabled)
cdrom_stop();
}
static bool cdrom_start(int track, int loop)
{
if (!enabled || track < 0 || track >= PLAYLIST_MAX || !playlist[track])
return false;
cdrom_stop();
const char *path = playlist[track];
#ifdef __ANDROID__
/* SDL's Android file IO requires an absolute path for installed tracks. */
char absolute_path[PATH_MAX];
if (!realpath(path, absolute_path))
return false;
path = absolute_path;
#endif
MIX_Audio *audio = sdl3_mixer_load_music_file(path);
if (!audio) {
WARNING("Cannot load %s: %s", path, SDL_GetError());
return false;
}
/* CD playback passes loop directly; this differs from BGM's loop - 1. */
int loops = loop == 0 ? -1 : loop;
if (!sdl3_mixer_play_music(SDL3_MIXER_MUSIC_CD, audio,
loops, 0, music_gain()))
return false;
track_no = track;
start_time = sys_get_ticks();
return true;
}
static void cdrom_stop(void)
{
if (!enabled)
return;
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_CD, 0);
track_no = 0;
}
static bool cdrom_get_playing_info(cd_time *info)
{
if (!enabled || !track_no ||
!sdl3_mixer_music_active(SDL3_MIXER_MUSIC_CD))
return false;
int ms = sys_get_ticks() - start_time;
info->t = track_no;
info->m = ms / (60 * 1000);
ms %= 60 * 1000;
info->s = ms / 1000;
ms %= 1000;
info->f = ms * CD_FPS / 1000;
return true;
}
static bool cdrom_is_available(void)
{
return enabled;
}
static void cdrom_reapply_volume(void)
{
if (enabled && track_no)
apply_volume();
}
cdromdevice_t cdrom_mp3 = {
.init = cdrom_init,
.exit = cdrom_exit,
.reset = cdrom_reset,
.start = cdrom_start,
.stop = cdrom_stop,
.getpos = cdrom_get_playing_info,
.is_available = cdrom_is_available,
.reapply_volume = cdrom_reapply_volume,
};
+86 -31
View File
@@ -25,7 +25,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -68,8 +68,11 @@ int cg_brightness;
static CG_WHERETODISP loc_policy, loc_policy0;
static SDL_Point loc_where, loc_where0;
/* extracted cg data cache control object */
static cacher *cacheid;
static Cache *cg_cache;
#ifndef CG_CACHE_SIZE
#define CG_CACHE_SIZE (10 << 20)
#endif
/* static methods */
static CG_TYPE check_cgformat(uint8_t *data);
@@ -78,6 +81,23 @@ static SDL_Point set_display_loc(cgdata *cg);
static void clear_display_loc();
static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend);
static cgdata *loader(int no);
static void release_cg(cgdata *cg);
static uint32_t int_hash(const void *key) {
return (uint32_t)*(const int *)key;
}
static bool int_equal(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
static void destroy_cg(void *data) {
cgdata_free(data);
}
static bool cg_is_pinned(const void *data) {
return ((const cgdata *)data)->refcnt != 0;
}
static int compare_integers(const void *a, const void *b) {
int int_a = *(const int *)a;
@@ -107,17 +127,19 @@ static void cg_mosaic(cgdata *cg) {
default:
SYSERROR("Unsupported cg depth %d", cg->depth);
}
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(
cg->pic, cg->width, cg->height, cg->depth, cg->width * (cg->depth / 8), format);
SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat(
0, (cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format);
if (sf->format->palette)
SDL_SetSurfacePalette(tmp, sf->format->palette);
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width, cg->height,
cg->depth, cg->width * (cg->depth / 8), format);
SDL_Surface *tmp = sdl_create_surface(
(cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE,
(cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format);
SDL_Palette *palette = sdl_get_surface_palette(sf);
if (palette)
sdl_set_surface_palette(tmp, palette);
// NOTE: SDL_BlitScaled() does not support 8-bit surfaces.
SDL_SoftStretch(sf, NULL, tmp, NULL);
SDL_SoftStretch(tmp, NULL, sf, NULL);
SDL_FreeSurface(tmp);
SDL_FreeSurface(sf);
sdl_destroy_surface(tmp);
sdl_destroy_surface(sf);
}
/*
@@ -244,7 +266,10 @@ static cgdata *loader(int no) {
cgdata *cg = NULL;
/* search in cache */
if (NULL != (cg = (cgdata *)cache_foreach(cacheid, no))) return cg;
if (NULL != (cg = cache_get(cg_cache, &no))) {
cg->refcnt++;
return cg;
}
/* read from file */
if (NULL == (dfile = ald_getdata(DRIFILE_CG, no))) return NULL;
@@ -286,8 +311,9 @@ static cgdata *loader(int no) {
cg_mosaic(cg);
}
/* insert to cache */
int size = cg->width * cg->height * (cg->depth / 8);
cache_insert(cacheid, no, cg, size, NULL);
size_t size = (size_t)cg->width * cg->height * (cg->depth / 8);
cg->refcnt = 1;
cg->cached = cache_insert(cg_cache, &no, cg, size) == CACHE_INSERT_OK;
}
/* ok to free */
@@ -296,11 +322,27 @@ static cgdata *loader(int no) {
return cg;
}
static void release_cg(cgdata *cg) {
if (!cg)
return;
cg->refcnt--;
if (!cg->cached && cg->refcnt == 0)
cgdata_free(cg);
}
/*
* Initilize cache
*/
void cg_init(void) {
cacheid = cache_new(cgdata_free);
CacheOps ops = {
.key_size = sizeof(int),
.hash = int_hash,
.equal = int_equal,
.destroy = destroy_cg,
.is_pinned = cg_is_pinned,
};
cache_destroy(cg_cache);
cg_cache = cache_new((size_t)CG_CACHE_SIZE, &ops);
cg_reset();
}
@@ -314,6 +356,10 @@ void cg_reset(void) {
memset(&loc_where0, 0, sizeof(loc_where0));
}
CacheStats cg_get_cache_stats(void) {
return cache_get_stats(cg_cache);
}
/*
* Set cg display location
* x : display location x
@@ -405,6 +451,7 @@ void cg_load(int no, int flg) {
}
/* clear display offset */
clear_display_loc();
release_cg(cg);
}
/*
@@ -413,7 +460,7 @@ void cg_load(int no, int flg) {
* shadowno: file no for alpha ( >= 0 )
*/
void cg_load_with_alpha(int cgno, int shadowno) {
cgdata *cg = NULL, *scg;
cgdata *cg = NULL, *scg = NULL;
SDL_Point p;
/* load pixel */
@@ -421,15 +468,15 @@ void cg_load_with_alpha(int cgno, int shadowno) {
if (NULL == (cg = loader(cgno))) return;
if (cg->type != ALCG_PMS16) {
WARNING("commandGX cg_no != 16bitPMS");
return;
goto cleanup;
}
}
/* load alpha pixel */
if (NULL == (scg = loader(shadowno))) return;
if (NULL == (scg = loader(shadowno))) goto cleanup;
if (scg->type != ALCG_PMS8) {
WARNING("commandGX shadow_no != 8bitPMS");
return;
goto cleanup;
}
/* set alpha pixel offset */
@@ -445,6 +492,10 @@ void cg_load_with_alpha(int cgno, int shadowno) {
/* clear display offset */
clear_display_loc();
cleanup:
release_cg(cg);
release_cg(scg);
}
static uint8_t* load_cg_from_file(char *fname_utf8, int *status, long *filesize) {
@@ -555,6 +606,7 @@ void cg_get_info(int no, SDL_Rect *info) {
info->y = p.y;
info->w = cg->width;
info->h = cg->height;
release_cg(cg);
}
}
@@ -594,34 +646,37 @@ SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mo
cg_mosaic(cg);
}
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, cg->width, cg->height, 32,
SDL_Surface *sf = sdl_create_surface(cg->width, cg->height, 32,
(cg->alpha || (type == ALCG_PMS8 && as_alpha)) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_XRGB8888);
if (type == ALCG_PMS8) {
if (as_alpha) {
// Treat PMS8 as an alpha map. Palette is ignored.
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, 255, 255, 255, 0));
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, 255, 255, 255, 0));
cg->alpha = cg->pic;
cg->pic = NULL;
} else {
// Treat PMS8 as a paletted image.
SDL_Surface *pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 8, cg->width, SDL_PIXELFORMAT_INDEX8);
SDL_Palette *palette = SDL_AllocPalette(256);
SDL_Surface *pic = sdl_create_surface_from(cg->pic, cg->width,
cg->height, 8, cg->width, SDL_PIXELFORMAT_INDEX8);
SDL_Palette *palette = sdl_create_palette(256);
SDL_SetPaletteColors(palette, cg->pal, 0, 256);
SDL_SetSurfacePalette(pic, palette);
SDL_FreePalette(palette);
sdl_set_surface_palette(pic, palette);
sdl_destroy_palette(palette);
SDL_BlitSurface(pic, NULL, sf, NULL);
SDL_FreeSurface(pic);
sdl_destroy_surface(pic);
}
} else {
SDL_Surface *pic;
if (type == ALCG_PMS16 || type == ALCG_BMP24) {
pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
pic = sdl_create_surface_from(cg->pic, cg->width, cg->height,
16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
} else {
pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
pic = sdl_create_surface_from(cg->pic, cg->width, cg->height,
24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
}
SDL_BlitSurface(pic, NULL, sf, NULL);
SDL_FreeSurface(pic);
sdl_destroy_surface(pic);
}
if (cg->alpha) {
// Copy alpha values from cg->alpha to sf->pixels.
@@ -650,8 +705,8 @@ SDL_Surface *cg_load_as_sdlsurface(int no) {
EMSCRIPTEN_KEEPALIVE
void load_censor_list(const char *path) {
if (cacheid)
cache_clear(cacheid);
if (cg_cache)
cache_clear(cg_cache);
if (censor_list) {
free(censor_list);
censor_list = NULL;
+5 -1
View File
@@ -24,7 +24,8 @@
#ifndef __CG__
#define __CG__
#include <SDL_rect.h>
#include "sdl_compat.h"
#include "cache.h"
#include "portab.h"
struct SDL_Surface;
@@ -61,6 +62,8 @@ typedef struct {
int vsp_bank; // palette bank for vsp
int pms_bank; // palette bank for pms
int refcnt; // references held by cache users
bool cached; // owned by the CG cache
} cgdata;
/*
@@ -86,6 +89,7 @@ extern void cgdata_free(cgdata *cg);
extern struct SDL_Surface *cg_load_as_sdlsurface(int no);
struct SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mosaic, bool as_alpha);
extern void load_censor_list(const char *path);
extern CacheStats cg_get_cache_stats(void);
extern int cg_vspPB;
extern int cg_fflg;
+3 -3
View File
@@ -562,7 +562,7 @@ void commands2F52() {
void commands2F53() {
int eFlag = getCaliValue();
nact->patch_ec = eFlag;
nact->patch_ec = (eFlag == 1);
TRACE("patchEC %d:", eFlag);
}
@@ -936,7 +936,7 @@ void commands2F6F() {
void commands2F70() {
int eFlag = getCaliValue();
nact->patch_emen = eFlag;
nact->patch_emen = (eFlag == 1);
TRACE("patchEMEN %d:", eFlag);
}
@@ -1152,7 +1152,7 @@ void commands2F85() {
void commands2F86() {
int eFlag = getCaliValue();
nact->patch_g0 = eFlag;
nact->patch_g0 = (eFlag == 1);
TRACE("patchG0 %d:", eFlag);
}
+5 -5
View File
@@ -55,13 +55,13 @@ void commandEC() {
int num = getCaliValue();
Ecomtbl *e;
SDL_Rect *r;
int adj = nact->patch_ec ? 0 : -1;
if (num == 0) {
r = &nact->ags.view_area;
ags_fillRectangle(r->x, r->y, r->w, r->h, 0);
ags_updateArea(r->x, r->y, r->w, r->h);
ags_fillRectangle(r->x, r->y, r->w + adj, r->h + adj, 0);
ags_updateArea(r->x, r->y, r->w + adj, r->h + adj);
} else {
int adj = (nact->patch_ec == 0) ? -1 : 0;
e = &tbl[num - 1];
ags_fillRectangle(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj, e->pal);
ags_updateArea(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj);
@@ -92,7 +92,7 @@ void commandEM() {
SDL_Point p = {x, y};
SDL_Rect r = tbl[num - 1].r;
if (nact->patch_emen == 0) {
if (!nact->patch_emen) {
r.w -= 1;
r.h -= 1;
}
@@ -112,7 +112,7 @@ void commandEN() {
for (int i = min; i <= max; i++) {
SDL_Rect r = tbl[i - 1].r;
if (nact->patch_emen == 0) {
if (!nact->patch_emen) {
r.w -= 1;
r.h -= 1;
}
+2 -2
View File
@@ -56,7 +56,7 @@ void commandTPC() {
case 1: sl_pushTextColor(type, nact->sel.MsgFontColor); break;
default: WARNING("TPC: unknown type %d", type); break;
}
TRACE("TPC %d", exp);
TRACE("TPC %d", type);
}
void commandTPS() {
@@ -67,7 +67,7 @@ void commandTPS() {
case 1: sl_pushTextSize(type, nact->sel.MsgFontSize); break;
default: WARNING("TPS: unknown type %d", type); break;
}
TRACE("TPS %d", exp);
TRACE("TPS %d", type);
}
void commandTOP() {
+6 -6
View File
@@ -90,7 +90,7 @@ typedef struct {
#define UNITMAP_ATTRIB_WALKRESULT (3)
/* UnitMAP 全体へのポインタ */
static int *UnitMap = NULL;
static vmvar_t *UnitMap = NULL;
/* VC command */
static int nPageNum;
static int x0Map;
@@ -160,7 +160,7 @@ void commandVC() { /* from Rance4 */
return;
}
UnitMap = (int *)calloc(cxMap * cyMap * nPageNum * UNITMAP_ATTRIB_DEPTH, sizeof(int));
UnitMap = calloc(cxMap * cyMap * nPageNum * UNITMAP_ATTRIB_DEPTH, sizeof(*UnitMap));
srcimg = (UnitMapSrcImg *)calloc(nPageNum, sizeof(UnitMapSrcImg));
if (NULL == UnitMap || NULL == srcimg) {
@@ -458,7 +458,7 @@ void commandVR() { /* from Rance4 */
int nPage = getCaliValue();
int nType = getCaliValue();
vmvar_t *var = getCaliVariable();
int *dst;
vmvar_t *dst;
TRACE("VR %d,%d,%p:",nPage, nType, var);
@@ -481,14 +481,14 @@ void commandVR() { /* from Rance4 */
return;
}
memcpy(dst, var, sizeof(int) * MAPSIZE_PER_ATTRIB);
memcpy(dst, var, MAPSIZE_PER_ATTRIB * sizeof(*dst));
}
void commandVW() { /* from Rance4 */
int nPage = getCaliValue();
int nType = getCaliValue();
vmvar_t *var = getCaliVariable();
int *src;
vmvar_t *src;
TRACE("VW %d,%d,%p:",nPage, nType, var);
@@ -511,7 +511,7 @@ void commandVW() { /* from Rance4 */
return;
}
memcpy(var, src, sizeof(int) * MAPSIZE_PER_ATTRIB);
memcpy(var, src, MAPSIZE_PER_ATTRIB * sizeof(*src));
}
void commandVE() { /* from T2 */
+1 -1
View File
@@ -146,7 +146,7 @@ void commandY() {
}
break;
case 1900:
nact->patch_ec = p2;
nact->patch_ec = (p2 == 1);
// We're sure this game was built for System3.9 v5.50, which by default
// routes CD-DA commands to DRIFILE_WAVE.
muscd_init_bgm(DRIFILE_WAVE, 999);
+16 -48
View File
@@ -22,6 +22,7 @@
/* $Id: cmdz.c,v 1.35 2003/01/12 10:48:50 chikama Exp $ */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
@@ -53,6 +54,14 @@ static uint32_t get_counter(int num) {
return sys_get_ticks() - counters[num].base;
}
static void write_display_info(vmvar_t *var, enum ags_display display) {
int width, height, depth;
ags_getDisplayInfo(display, &width, &height, &depth);
var[0] = width;
var[1] = height;
var[2] = depth;
}
void commandZC() {
/* システムの使用環境を変更する */
int m = getCaliValue();
@@ -106,8 +115,7 @@ void commandZB() {
/* メッセージ文字を太さを設定 */
int weight = getCaliValue();
if (nact->ags.enable_zb)
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
TRACE("ZB %d:", weight);
}
@@ -369,24 +377,8 @@ void commandZZ1() {
void commandZZ2() {
/* 機種文字列を文字列領域 num に返す(MAX12文字)*/
int num = getCaliValue();
#if defined(linux)
static uint8_t str[] = {0x82, 0x6B, 0x82, 0x89, 0x82, 0x8E, 0x82, 0x95, 0x82, 0x98, 0};
#elif defined(__FreeBSD__)
static uint8_t str[] = {0x82, 0x65, 0x82, 0x92, 0x82, 0x85, 0x82, 0x85, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
#elif defined(__OpenBSD__)
static uint8_t str[] = {0x82, 0x6E, 0x82, 0x90, 0x82, 0x85, 0x82, 0x8E, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
#elif defined(__NetBSD__)
static uint8_t str[] = {0x82, 0x6D, 0x82, 0x85, 0x82, 0x94, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
#elif defined(sun)
static uint8_t str[] = {0x82, 0x72, 0x82, 0x95, 0x82, 0x8E, 0x82, 0x6E, 0x82, 0x72, 0};
#elif defined(__osf__)
static uint8_t str[] = {0x82, 0x63, 0x82, 0x89, 0x82, 0x87, 0x82, 0x89, 0x82, 0x94, 0x82, 0x81, 0x82, 0x8C, 0x82, 0x74, 0x82, 0x6D, 0x82, 0x68, 0x82, 0x77, 0};
#elif defined(sgi)
static uint8_t str[] = {0x82, 0x68, 0x82, 0x71, 0x82, 0x68, 0x82, 0x77, 0};
#else
static uint8_t str[] = {0x82, 0x74, 0x82, 0x8e, 0x82, 0x8b, 0x82, 0x8e, 0x82, 0x8f, 0x82, 0x97, 0x82, 0x8e, 0};
#endif
svar_set(num, str);
svar_set(num, SDL_GetPlatform());
TRACE("ZZ2 %d:",num);
}
@@ -394,39 +386,21 @@ void commandZZ2() {
void commandZZ3() {
/* WINDOWSの全画面サイズや表示色数を変数列に返す */
vmvar_t *var = getCaliVariable();
DispInfo info;
ags_getWindowInfo(&info);
*var = info.width;
*(var + 1) = info.height;
*(var + 2) = info.depth;
write_display_info(var, AGS_DISPLAY_CURRENT);
TRACE("ZZ3 %p:",var);
}
void commandZZ4() {
/* DIB の全画面 サイズや色数を変数列に返す */
vmvar_t *var = getCaliVariable();
DispInfo info;
ags_getDIBInfo(&info);
*var = info.width;
*(var + 1) = info.height;
*(var + 2) = info.depth;
write_display_info(var, AGS_DISPLAY_DIB);
TRACE("ZZ4 %p:",var);
}
void commandZZ5() {
/* SYSTEM3.5用表示画面 の サイズや色数を変数列に返す */
vmvar_t *var = getCaliVariable();
DispInfo info;
ags_getViewAreaInfo(&info);
*var = info.width;
*(var + 1) = info.height;
*(var + 2) = info.depth;
write_display_info(var, AGS_DISPLAY_VIEW_AREA);
TRACE("ZZ5 %p:",var);
}
@@ -449,13 +423,7 @@ void commandZZ8() {
void commandZZ9() {
/* 起動時のスクリーンサイズを取得する */
vmvar_t *var = getCaliVariable();
DispInfo info;
ags_getWindowInfo(&info);
*var = info.width;
*(var + 1) = info.height;
*(var + 2) = info.depth;
write_display_info(var, AGS_DISPLAY_DESKTOP);
TRACE("ZZ9 %p:",var);
}
+12 -12
View File
@@ -23,7 +23,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -163,18 +163,18 @@ static SDL_Cursor *init_system_cursor(const char *image[]) {
void cursor_init(void) {
cursor[CURSOR_ARROW] = SDL_GetDefaultCursor();
cursor[CURSOR_CROSS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
cursor[CURSOR_IBEAM] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
cursor[CURSOR_CROSS] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_CROSSHAIR);
cursor[CURSOR_IBEAM] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_TEXT);
cursor[CURSOR_ICON] = SDL_GetDefaultCursor();
cursor[CURSOR_NO] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
cursor[CURSOR_SIZE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
cursor[CURSOR_SIZEALL] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
cursor[CURSOR_SIZENESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
cursor[CURSOR_SIZENS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
cursor[CURSOR_SIZENWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
cursor[CURSOR_SIZEWE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
cursor[CURSOR_NO] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NOT_ALLOWED);
cursor[CURSOR_SIZE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_MOVE);
cursor[CURSOR_SIZEALL] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_MOVE);
cursor[CURSOR_SIZENESW] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NESW_RESIZE);
cursor[CURSOR_SIZENS] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NS_RESIZE);
cursor[CURSOR_SIZENWSE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NWSE_RESIZE);
cursor[CURSOR_SIZEWE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_EW_RESIZE);
cursor[CURSOR_UPARROW] = init_system_cursor(cursor_uparrow);
cursor[CURSOR_WAIT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
cursor[CURSOR_WAIT] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_WAIT);
}
static bool cursor_new(uint8_t* data, int no, CursorImage *cursorImage, TCursorDirEntry *cursordirentry) {
@@ -215,7 +215,7 @@ static bool cursor_new(uint8_t* data, int no, CursorImage *cursorImage, TCursorD
}
if (cursor[no])
SDL_FreeCursor(cursor[no]);
sdl_destroy_cursor(cursor[no]);
cursor[no] = SDL_CreateCursor(buf3, buf4, 32, 32, cursordirentry->wxHotspot, cursordirentry->wyHotspot);
free(buf1);
+2
View File
@@ -18,7 +18,9 @@
*/
#include <assert.h>
#include <ctype.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "debugger.h"
+33
View File
@@ -18,11 +18,15 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system.h"
#include "debugger.h"
#include "debugger_private.h"
#include "debug_symbol.h"
#include "ald_manager.h"
#include "cg.h"
#include "font.h"
#include "nact.h"
#include "variable.h"
#ifdef HAVE_SIGACTION
@@ -207,6 +211,34 @@ static const char * const help_help = NULL;
static CommandResult cmd_help(void);
static const char desc_info[] = "Print information about the program.";
static const char help_info[] =
"Syntax: info cache\n"
"\n"
"Displays usage and lookup statistics for each cache.";
static void print_cache_stats(const char *name, CacheStats stats) {
size_t lookups = stats.hits + stats.misses;
double hit_rate = lookups ? 100.0 * stats.hits / lookups : 0.0;
printf("%-8s %8zu %12zu %12zu %8zu %8zu %8.1f%%\n",
name, stats.count, stats.size, stats.capacity,
stats.hits, stats.misses, hit_rate);
}
static CommandResult cmd_info(void) {
char *arg = strtok(NULL, whitespaces);
if (!arg || strcmp(arg, "cache") || strtok(NULL, whitespaces)) {
puts(help_info);
return CONTINUE_REPL;
}
puts("Cache Entries Size Capacity Hits Misses Hit rate");
print_cache_stats("Glyph", font_get_cache_stats());
print_cache_stats("CG", cg_get_cache_stats());
print_cache_stats("Archive", ald_get_cache_stats());
return CONTINUE_REPL;
}
static const char desc_list[] = "List specified function or line.";
static const char help_list[] =
"Syntax: list\n"
@@ -361,6 +393,7 @@ const Command dbg_cui_commands[] = {
{"continue", "c", desc_continue, help_continue, cmd_continue},
{"delete", "d", desc_delete, help_delete, cmd_delete},
{"help", "h", desc_help, help_help, cmd_help},
{"info", "i", desc_info, help_info, cmd_info},
{"list", "l", desc_list, help_list, cmd_list},
{"step", "s", desc_step, help_step, cmd_step},
{"finish", NULL, desc_finish, help_finish, cmd_finish},
+1 -1
View File
@@ -20,7 +20,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL_thread.h>
#include "sdl_compat.h"
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
+1
View File
@@ -46,6 +46,7 @@ struct _dridata {
char *data; /* real data */
char *name; /* not used */
int refcnt; /* reference count */
bool cached; /* owned by the archive cache */
drifiles *a; /* archive file obj */
};
typedef struct _dridata dridata;
+34 -33
View File
@@ -21,7 +21,8 @@
#include <assert.h>
#include <math.h>
#include <SDL.h>
#include <stdlib.h>
#include "sdl_compat.h"
#include "system.h"
#include "gfx.h"
@@ -139,7 +140,7 @@ static EffectTexture *create_effect_texture(surface_t *as, int x, int y, int w,
} else {
SDL_Surface *sf = gfx_createSurfaceView(as->sdl_surface, x, y, w, h);
t->tx = SDL_CreateTextureFromSurface(gfx_renderer, sf);
SDL_FreeSurface(sf);
sdl_destroy_surface(sf);
t->rect = (SDL_Rect){ 0, 0, w, h };
}
return t;
@@ -160,7 +161,7 @@ static void destroy_effect_texture(EffectTexture *t) {
free(t);
}
static int render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
static bool render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
SDL_Rect real_srcrect;
if (!srcrect) {
real_srcrect = t->rect;
@@ -169,7 +170,7 @@ static int render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, cons
real_srcrect.x += t->rect.x;
real_srcrect.y += t->rect.y;
}
return SDL_RenderCopy(gfx_renderer, t->tx, &real_srcrect, dstrect);
return sdl_render_texture(gfx_renderer, t->tx, &real_srcrect, dstrect);
}
struct effect {
@@ -197,7 +198,7 @@ static void eff_finish(struct effect *eff, bool present) {
if (present) {
SDL_RenderClear(gfx_renderer);
if (!eff->is_fullscreen)
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
render_effect_texture(eff->tx_new, NULL, &eff->dst_rect);
SDL_RenderPresent(gfx_renderer);
}
@@ -384,7 +385,7 @@ static void mosaic_step(struct effect *eff, float progress);
static void mosaic_free(struct effect *eff);
static struct effect *mosaic_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
if (!SDL_RenderTargetSupported(gfx_renderer))
if (!sdl_render_target_supported(gfx_renderer))
return fallback_effect_new(rect, old, new, type);
struct mosaic_effect *eff = calloc(1, sizeof(struct mosaic_effect));
@@ -409,7 +410,7 @@ static void mosaic(EffectTexture *src, SDL_Texture *tmp, SDL_Texture *dst, int w
.h = h / scale,
};
SDL_SetRenderTarget(gfx_renderer, tmp);
SDL_RenderCopyF(gfx_renderer, src->tx, &src->rect, &fr);
sdl_render_texture_float(gfx_renderer, src->tx, &src->rect, &fr);
SDL_Rect dstr = {
.x = w * (1 - scale) / 2,
@@ -419,8 +420,8 @@ static void mosaic(EffectTexture *src, SDL_Texture *tmp, SDL_Texture *dst, int w
};
SDL_SetRenderTarget(gfx_renderer, dst);
SDL_SetTextureScaleMode(tmp, SDL_ScaleModeNearest);
SDL_RenderCopy(gfx_renderer, tmp, NULL, &dstr);
sdl_set_texture_scale_mode_nearest(tmp);
sdl_render_texture(gfx_renderer, tmp, NULL, &dstr);
SDL_SetRenderTarget(gfx_renderer, NULL);
}
@@ -431,16 +432,16 @@ static void mosaic_step(struct effect *eff, float progress) {
const int max_scale = 80;
int scale = (1.f - progress) * (max_scale - 1) + 1;
mosaic(eff->tx_new, this->tmp_new, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
} else if (eff->type == EFFECT_CROSSFADE_MOSAIC) {
const int max_scale = 96;
float scale = (progress < 0.5f ? progress : 1.f - progress) * 2 * (max_scale - 1) + 1;
mosaic(eff->tx_old, this->tmp_old, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
mosaic(eff->tx_new, this->tmp_new, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
SDL_SetTextureBlendMode(this->tx_mosaic, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(this->tx_mosaic, progress * 255);
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
SDL_SetTextureBlendMode(this->tx_mosaic, SDL_BLENDMODE_NONE);
} else {
assert(!"Cannot happen");
@@ -514,7 +515,7 @@ static void brightness_step(struct effect *eff, float progress) {
render_effect_texture(texture, NULL, &eff->dst_rect);
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(gfx_renderer, color, color, color, alpha);
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderPresent(gfx_renderer);
@@ -531,9 +532,8 @@ static void dithering_fade_step(struct effect *eff, float progress);
static void dithering_fade_free(struct effect *eff);
static EffectTexture *create_dither_pattern_texture(int w, int h, int val) {
SDL_PixelFormat *fmt = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888);
Uint32 col = SDL_MapRGBA(fmt, val, val, val, SDL_ALPHA_OPAQUE);
SDL_FreeFormat(fmt);
Uint32 col = sdl_map_rgba_format(
SDL_PIXELFORMAT_ARGB8888, val, val, val, SDL_ALPHA_OPAQUE);
Uint32 *pixels = calloc(w * h, sizeof(Uint32));
if (!pixels)
@@ -610,10 +610,10 @@ static void dithering_fade_step(struct effect *eff, float progress) {
static void dithering_fade_free(struct effect *eff) {
if (eff->type == EFFECT_DITHERING_FADEOUT) {
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 255);
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
} else if (eff->type == EFFECT_DITHERING_WHITEOUT) {
SDL_SetRenderDrawColor(gfx_renderer, 255, 255, 255, 255);
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
}
eff_finish(eff, false);
free(eff);
@@ -1020,7 +1020,7 @@ static void zoom_blend_blur_step(struct effect *eff, float progress);
static void zoom_blend_blur_free(struct effect *eff);
static struct effect *zoom_blend_blur_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new) {
if (!SDL_RenderTargetSupported(gfx_renderer))
if (!sdl_render_target_supported(gfx_renderer))
return fallback_effect_new(rect, old, new, EFFECT_ZOOM_BLEND_BLUR);
struct zoom_blend_blur_effect *eff = calloc(1, sizeof(struct zoom_blend_blur_effect));
@@ -1054,7 +1054,7 @@ static void zoom_blend_blur_step(struct effect *eff, float progress) {
this->index = (this->index + 1) % ZOOM_BLEND_BLUR_STEPS;
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
EffectTexture texture_rect = { NULL, { 0, 0, eff->dst_rect.w, eff->dst_rect.h } };
for (int i = 0; i < ZOOM_BLEND_BLUR_STEPS; i++) {
texture_rect.tx = this->tx[i];
@@ -1092,7 +1092,7 @@ static void linear_blur_step(struct effect *eff, float progress);
static void linear_blur_free(struct effect *eff);
static struct effect *linear_blur_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
if (!SDL_RenderTargetSupported(gfx_renderer))
if (!sdl_render_target_supported(gfx_renderer))
return fallback_effect_new(rect, old, new, type);
struct linear_blur_effect *lbe = calloc(1, sizeof(struct linear_blur_effect));
@@ -1196,7 +1196,7 @@ static void polygon_mask_step(struct effect *eff, float progress);
static void polygon_mask_free(struct effect *eff);
static struct effect *polygon_mask_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
if (SDL_RenderTargetSupported(gfx_renderer)) {
if (sdl_render_target_supported(gfx_renderer)) {
struct polygon_mask_effect *pmf = calloc(1, sizeof(struct polygon_mask_effect));
if (!pmf)
NOMEMERR();
@@ -1229,7 +1229,7 @@ static void draw_pentagram(int center_x, int center_y, float radius, float rotat
for (int i = 0; i < 10; i++) {
v[i].position.x = radius * (p[i].x * cos_r - p[i].y * sin_r) + center_x;
v[i].position.y = radius * (p[i].x * sin_r + p[i].y * cos_r) + center_y;
v[i].color = (SDL_Color){0, 0, 0, 0};
v[i].color = sdl_vertex_color(0, 0, 0, 0);
}
const int indices[15] = {
0, 3, 7,
@@ -1257,7 +1257,7 @@ static void draw_hexagram(int center_x, int center_y, float radius, float rotate
for (int i = 0; i < 6; i++) {
v[i].position.x = radius * (p[i].x * cos_r - p[i].y * sin_r) + center_x;
v[i].position.y = radius * (p[i].x * sin_r + p[i].y * cos_r) + center_y;
v[i].color = (SDL_Color){0, 0, 0, 0};
v[i].color = sdl_vertex_color(0, 0, 0, 0);
}
SDL_RenderGeometry(gfx_renderer, NULL, v, 6, NULL, 0);
}
@@ -1428,7 +1428,7 @@ static void polygon_mask_step(struct effect *eff, float progress) {
SDL_SetRenderTarget(gfx_renderer, NULL);
render_effect_texture(eff->tx_new, NULL, &eff->dst_rect);
SDL_SetTextureBlendMode(this->tx_tmp, SDL_BLENDMODE_BLEND);
SDL_RenderCopy(gfx_renderer, this->tx_tmp, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tx_tmp, NULL, &eff->dst_rect);
SDL_RenderPresent(gfx_renderer);
}
@@ -1493,7 +1493,8 @@ static void rotate_step(struct effect *eff, float progress) {
r.w *= scale;
r.h *= scale;
render_effect_texture(bg_texture, NULL, &eff->dst_rect);
SDL_RenderCopyEx(gfx_renderer, fg_texture->tx, &fg_texture->rect, &r, angle, NULL, SDL_FLIP_NONE);
sdl_render_texture_rotated(gfx_renderer, fg_texture->tx,
&fg_texture->rect, &r, angle, NULL, SDL_FLIP_NONE);
SDL_RenderPresent(gfx_renderer);
}
@@ -1573,7 +1574,7 @@ static void zigzag_crossfade_step(struct effect *eff, float progress);
static void zigzag_crossfade_free(struct effect *eff);
static struct effect *zigzag_crossfade_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new) {
if (!SDL_RenderTargetSupported(gfx_renderer))
if (!sdl_render_target_supported(gfx_renderer))
return fallback_effect_new(rect, old, new, EFFECT_ZIGZAG_CROSSFADE);
struct zigzag_crossfade_effect *eff = calloc(1, sizeof(struct zigzag_crossfade_effect));
@@ -1608,7 +1609,7 @@ static void wave_warp_v(SDL_Texture *src, SDL_Texture *dst, int w, int h, float
for (int x = 0; x < w; x++) {
sr.x = dr.x = x;
dr.y = sinf(phase + x * (2 * M_PIf / 360 * length)) * amplitude;
SDL_RenderCopy(gfx_renderer, src, &sr, &dr);
sdl_render_texture(gfx_renderer, src, &sr, &dr);
}
SDL_SetRenderTarget(gfx_renderer, NULL);
}
@@ -1622,13 +1623,13 @@ static void zigzag_crossfade_step(struct effect *eff, float progress) {
wave_warp_h(eff->tx_old, this->tmp1, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
wave_warp_v(this->tmp1, this->tmp2, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
SDL_RenderCopy(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
wave_warp_h(eff->tx_new, this->tmp1, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
wave_warp_v(this->tmp1, this->tmp2, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
SDL_SetTextureBlendMode(this->tmp2, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(this->tmp2, progress * 255);
SDL_RenderCopy(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
sdl_render_texture(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
SDL_SetTextureBlendMode(this->tmp2, SDL_BLENDMODE_NONE);
SDL_RenderPresent(gfx_renderer);
@@ -1690,7 +1691,7 @@ static void raster_blend_free(struct effect *eff);
static struct effect *raster_blend_new(SDL_Rect *rect, int sx, int sy) {
SDL_Surface *sprite = gfx_dib_to_surface_with_alpha(sx, sy, rect->w, rect->h);
EffectTexture *texture = create_effect_texture_from_surface(sprite);
SDL_FreeSurface(sprite);
sdl_destroy_surface(sprite);
struct effect *eff = calloc(1, sizeof(struct effect));
if (!eff)
NOMEMERR();
@@ -1876,7 +1877,7 @@ struct effect *sprite_effect_init(SDL_Rect *rect, int dx, int dy, int sx, int sy
EffectTexture *tx_old = create_effect_texture(gfx_dibinfo, dx, dy, rect->w, rect->h);
SDL_Surface *sprite = gfx_dib_to_surface_colorkey(sx, sy, rect->w, rect->h, col);
EffectTexture *tx_new = create_effect_texture_from_surface(sprite);
SDL_FreeSurface(sprite);
sdl_destroy_surface(sprite);
switch (type) {
case EFFECT_PAN_IN_DOWN:
@@ -1906,7 +1907,7 @@ struct effect *effect_sactamask_init(SDL_Surface *mask) {
void effect_step(struct effect *eff, float progress) {
SDL_RenderClear(gfx_renderer);
if (!eff->is_fullscreen)
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
eff->step(eff, progress);
}
+1 -1
View File
@@ -20,7 +20,7 @@
#ifndef __EFFECT_H__
#define __EFFECT_H__
#include <SDL_surface.h>
#include "sdl_compat.h"
#include "ags.h"
// Effect types of the CE/CD command.
+146 -109
View File
@@ -24,7 +24,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <SDL.h>
#include "sdl_compat.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -35,7 +35,6 @@
#include "debugger.h"
#include "nact.h"
#include "gfx.h"
#include "gfx_private.h"
#include "scheduler.h"
#include "menu.h"
#include "modal.h"
@@ -44,6 +43,12 @@
#include "hacks.h"
#include "volume.h"
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 2
#include <SDL_syswm.h>
// Windows defines this name too; the key table below uses input.h's enum.
#undef KEY_EXECUTE
#endif
static void get_event(void);
static void keyEventProsess(SDL_KeyboardEvent *e, bool pressed);
@@ -52,17 +57,20 @@ static uint32_t custom_event_type = (uint32_t)-1;
enum CustomEventCode {
SIMULATE_RIGHT_BUTTON,
DEBUGGER_COMMAND,
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
WIN_MENU_COMMAND,
#endif
};
/* pointer の状態 */
static int mousex, mousey, mouseb;
static SDL_Point mouse_pos;
static int mouseb;
static int mouse_wheel_up, mouse_wheel_down;
bool RawKeyInfo[256];
/* SDL Joystick */
static int joyinfo=0;
static int keytable[SDL_NUM_SCANCODES] = {
static int keytable[SDL_COMPAT_SCANCODE_COUNT] = {
[SDL_SCANCODE_A] = KEY_A,
[SDL_SCANCODE_B] = KEY_B,
[SDL_SCANCODE_C] = KEY_C,
@@ -187,18 +195,18 @@ static int keytable[SDL_NUM_SCANCODES] = {
static int joy_device_index = -1;
static SDL_Joystick *js;
static bool joy_open(int index) {
static bool joy_open(sdl_joystick_device_t device) {
if (js)
return false;
js = SDL_JoystickOpen(index);
js = sdl_open_joystick(device);
if (!js)
return false;
const char *name = SDL_JoystickName(js);
int axes = SDL_JoystickNumAxes(js);
int buttons = SDL_JoystickNumButtons(js);
SDL_JoystickEventState(SDL_ENABLE);
const char *name = sdl_get_joystick_name(js);
int axes = sdl_get_num_joystick_axes(js);
int buttons = sdl_get_num_joystick_buttons(js);
sdl_set_joystick_events_enabled(true);
NOTICE("SDL joystick '%s' %d axes %d buttons", name, axes, buttons);
return true;
}
@@ -206,14 +214,22 @@ static bool joy_open(int index) {
static bool joy_init(void) {
SDL_Init(SDL_INIT_JOYSTICK);
if (joy_device_index >= 0)
return joy_open(joy_device_index);
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (joy_open(i))
return true;
int count = 0;
sdl_joystick_device_t *devices = sdl_get_joystick_devices(&count);
bool opened = false;
if (joy_device_index >= 0) {
if (joy_device_index < count)
opened = joy_open(devices[joy_device_index]);
} else {
for (int i = 0; i < count; i++) {
if (joy_open(devices[i])) {
opened = true;
break;
}
}
}
return false;
SDL_free(devices);
return opened;
}
void event_init(void) {
@@ -224,7 +240,7 @@ void event_init(void) {
void event_remove(void) {
if (js) {
SDL_JoystickClose(js);
sdl_close_joystick(js);
js = NULL;
}
}
@@ -264,8 +280,8 @@ void send_agsevent(enum agsevent_type type, int code) {
agsevent_t agse = {
.type = type,
.code = code,
.mousex = mousex,
.mousey = mousey
.mousex = mouse_pos.x,
.mousey = mouse_pos.y
};
nact->ags.eventcb(&agse); // Async in emscripten
@@ -277,37 +293,32 @@ void send_agsevent(enum agsevent_type type, int code) {
#endif
}
void event_set_mouse_location(int x, int y) {
// scale mouse x and y
float scalex, scaley;
SDL_RenderGetScale(gfx_renderer, &scalex, &scaley);
x *= scalex;
y *= scaley;
// calculate window borders
int logw, logh;
SDL_RenderGetLogicalSize(gfx_renderer, &logw, &logh);
float scalew, scaleh;
scalew = logw * scalex;
scaleh = logh * scaley;
int winw, winh;
SDL_GetWindowSize(gfx_window, &winw, &winh);
float border_left = (winw - scalew) / 2;
float border_top = (winh - scaleh) / 2;
// offset x and y by window borders
x += border_left;
y += border_top;
SDL_WarpMouseInWindow(gfx_window, x, y);
void event_set_mouse_internal_location(int x, int y) {
mouse_pos.x = x;
mouse_pos.y = y;
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
}
void event_set_mouse_internal_location(int x, int y) {
mousex = x;
mousey = y;
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
SDL_Point event_get_touch_position(const SDL_TouchFingerEvent *e) {
#if XSYSTEM35_SDL_VERSION == 2
int view_w, view_h;
gfx_getViewSize(&view_w, &view_h);
return (SDL_Point){e->x * view_w, e->y * view_h};
#else
return (SDL_Point){(int)e->x, (int)e->y};
#endif
}
static bool touch_is_on_view_edge(const SDL_TouchFingerEvent *e) {
#if XSYSTEM35_SDL_VERSION == 2
return e->x == 0.0f || e->x == 1.0f ||
e->y == 0.0f || e->y == 1.0f;
#else
int view_w, view_h;
gfx_getViewSize(&view_w, &view_h);
return e->x <= 0.0f || e->x >= view_w ||
e->y <= 0.0f || e->y >= view_h;
#endif
}
// Stores a deferred touch event (valid if .timestamp != 0) to add a delay
@@ -315,28 +326,33 @@ void event_set_mouse_internal_location(int x, int y) {
// touch event. This prevents the game from processing a button down event
// before reading the pointer position.
static SDL_TouchFingerEvent deferred_touch_event;
static uint64_t deferred_touch_deadline;
#define TOUCH_EVENT_DELAY 20
static void defer_touch_event(SDL_TouchFingerEvent *e) {
switch (deferred_touch_event.type) {
case SDL_FINGERDOWN:
case SDL_COMPAT_EVENT_FINGER_DOWN:
mouseb |= 1 << SDL_BUTTON_LEFT;
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = true;
break;
case SDL_FINGERUP:
case SDL_COMPAT_EVENT_FINGER_UP:
mouseb &= ~(1 << SDL_BUTTON_LEFT | 1 << SDL_BUTTON_RIGHT);
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = false;
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = false;
break;
default:
break;
}
if (e)
if (e) {
deferred_touch_event = *e;
else
deferred_touch_event.timestamp = 0;
deferred_touch_deadline = SDL_GetTicks() + TOUCH_EVENT_DELAY;
} else {
deferred_touch_deadline = 0;
}
}
static void fire_deferred_touch_event(void) {
if (deferred_touch_event.timestamp && deferred_touch_event.timestamp + TOUCH_EVENT_DELAY < SDL_GetTicks()) {
if (deferred_touch_deadline && deferred_touch_deadline < SDL_GetTicks()) {
defer_touch_event(NULL);
}
}
@@ -354,62 +370,66 @@ static void rance4v2_hack(void) {
}
void event_handle_event(SDL_Event *e) {
if (!gfx_convertEventCoordinates(e))
WARNING("Failed to convert event coordinates: %s", SDL_GetError());
if (modal_handle_event(e))
return;
switch (sdl_get_window_event(e)) {
case SDL_COMPAT_WINDOW_EVENT_EXPOSED:
gfx_requestRedraw();
return;
case SDL_COMPAT_WINDOW_EVENT_FOCUS_LOST:
volume_on_window_focus(false);
return;
case SDL_COMPAT_WINDOW_EVENT_FOCUS_GAINED:
volume_on_window_focus(true);
return;
case SDL_COMPAT_WINDOW_EVENT_NONE:
break;
}
switch (e->type) {
case SDL_QUIT:
case SDL_COMPAT_EVENT_QUIT:
menu_quitmenu_open();
break;
case SDL_WINDOWEVENT:
switch (e->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
gfx_dirty = true;
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
volume_on_window_focus(false);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
volume_on_window_focus(true);
break;
}
break;
#ifdef _WIN32
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 2
case SDL_SYSWMEVENT:
win_menu_onSysWMEvent(e->syswm.msg);
if (e->syswm.msg->msg.win.msg == WM_COMMAND)
win_menu_onCommand((unsigned int)e->syswm.msg->msg.win.wParam);
break;
#endif
case SDL_APP_DIDENTERFOREGROUND:
gfx_dirty = true;
case SDL_COMPAT_EVENT_DID_ENTER_FOREGROUND:
gfx_requestRedraw();
break;
case SDL_KEYDOWN:
case SDL_COMPAT_EVENT_KEY_DOWN:
keyEventProsess(&e->key, true);
break;
case SDL_KEYUP:
case SDL_COMPAT_EVENT_KEY_UP:
keyEventProsess(&e->key, false);
switch (e->key.keysym.sym) {
switch (sdl_keyboard_event_key(&e->key)) {
case SDLK_F1:
msgskip_activate(!msgskip_isActivated());
break;
case SDLK_F4:
gfx_setFullscreen(!gfx_fullscreen);
gfx_setFullscreen(!gfx_isFullscreen());
break;
}
#ifdef __ANDROID__
if (e->key.keysym.scancode == SDL_SCANCODE_AC_BACK) {
if (sdl_keyboard_event_scancode(&e->key) == SDL_SCANCODE_AC_BACK) {
menu_quitmenu_open();
}
#endif
break;
case SDL_MOUSEMOTION:
case SDL_COMPAT_EVENT_MOUSE_MOTION:
event_set_mouse_internal_location(e->motion.x, e->motion.y);
#ifdef _WIN32
win_menu_onMouseMotion(e->motion.x, e->motion.y);
#endif
break;
case SDL_MOUSEWHEEL:
case SDL_COMPAT_EVENT_MOUSE_WHEEL:
{
int y = e->wheel.y * (e->wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1);
if (y > 0)
@@ -420,13 +440,13 @@ void event_handle_event(SDL_Event *e) {
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_COMPAT_EVENT_MOUSE_BUTTON_DOWN:
mouseb |= (1 << e->button.button);
RawKeyInfo[mouse_to_rawkey(e->button.button)] = true;
send_agsevent(AGSEVENT_BUTTON_PRESS, mouse_to_agsevent(e->button.button));
break;
case SDL_MOUSEBUTTONUP:
case SDL_COMPAT_EVENT_MOUSE_BUTTON_UP:
mouseb &= (0xffffffff ^ (1 << e->button.button));
RawKeyInfo[mouse_to_rawkey(e->button.button)] = false;
send_agsevent(AGSEVENT_BUTTON_RELEASE, mouse_to_agsevent(e->button.button));
@@ -435,55 +455,55 @@ void event_handle_event(SDL_Event *e) {
}
break;
case SDL_FINGERDOWN:
if (SDL_GetNumTouchFingers(e->tfinger.touchId) >= 3) {
case SDL_COMPAT_EVENT_FINGER_DOWN: {
int finger_count =
sdl_get_num_touch_fingers(sdl_touch_event_id(&e->tfinger));
if (finger_count >= 3) {
menu_open();
} else if (SDL_GetNumTouchFingers(e->tfinger.touchId) >= 2) {
} else if (finger_count >= 2) {
mouseb &= ~(1 << SDL_BUTTON_LEFT);
mouseb |= 1 << SDL_BUTTON_RIGHT;
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = false;
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = true;
send_agsevent(AGSEVENT_BUTTON_PRESS, AGSEVENT_BUTTON_RIGHT);
} else {
// SDL_RendererEventWatch clamps touch locations outside of the
// viewport to 0.0-1.0. Treat such events as right-clicks.
// Treat touches on the edge or outside the viewport as
// right-clicks.
int button;
if (e->tfinger.x == 0.0f || e->tfinger.x == 1.0f || e->tfinger.y == 0.0f || e->tfinger.y == 1.0f) {
if (touch_is_on_view_edge(&e->tfinger)) {
button = SDL_BUTTON_RIGHT;
mouseb |= 1 << SDL_BUTTON_RIGHT;
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = true;
} else {
button = SDL_BUTTON_LEFT;
mousex = e->tfinger.x * view_w;
mousey = e->tfinger.y * view_h;
mouse_pos = event_get_touch_position(&e->tfinger);
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
defer_touch_event(&e->tfinger);
}
send_agsevent(AGSEVENT_BUTTON_PRESS, mouse_to_agsevent(button));
}
break;
}
case SDL_FINGERUP:
if (SDL_GetNumTouchFingers(e->tfinger.touchId) == 0) {
case SDL_COMPAT_EVENT_FINGER_UP:
if (sdl_get_num_touch_fingers(sdl_touch_event_id(&e->tfinger)) == 0) {
int ags_button = (mouseb & 1 << SDL_BUTTON_LEFT) ? AGSEVENT_BUTTON_LEFT : AGSEVENT_BUTTON_RIGHT;
mousex = e->tfinger.x * view_w;
mousey = e->tfinger.y * view_h;
mouse_pos = event_get_touch_position(&e->tfinger);
send_agsevent(AGSEVENT_BUTTON_RELEASE, ags_button);
defer_touch_event(&e->tfinger);
}
break;
case SDL_FINGERMOTION:
mousex = e->tfinger.x * view_w;
mousey = e->tfinger.y * view_h;
case SDL_COMPAT_EVENT_FINGER_MOTION:
mouse_pos = event_get_touch_position(&e->tfinger);
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
break;
case SDL_JOYDEVICEADDED:
case SDL_COMPAT_EVENT_JOYSTICK_ADDED:
joy_open(e->jdevice.which);
break;
case SDL_JOYAXISMOTION:
case SDL_COMPAT_EVENT_JOYSTICK_AXIS_MOTION:
if (abs(e->jaxis.value) < 0x4000) {
joyinfo &= e->jaxis.axis == 0 ? ~0xc : ~3;
} else {
@@ -493,10 +513,10 @@ void event_handle_event(SDL_Event *e) {
}
break;
case SDL_JOYBALLMOTION:
case SDL_COMPAT_EVENT_JOYSTICK_BALL_MOTION:
break;
case SDL_JOYHATMOTION:
case SDL_COMPAT_EVENT_JOYSTICK_HAT_MOTION:
joyinfo &= ~(SYS35KEY_UP | SYS35KEY_DOWN | SYS35KEY_LEFT | SYS35KEY_RIGHT);
switch (e->jhat.value) {
case SDL_HAT_UP: joyinfo |= SYS35KEY_UP; break;
@@ -510,8 +530,8 @@ void event_handle_event(SDL_Event *e) {
}
break;
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_COMPAT_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_COMPAT_EVENT_JOYSTICK_BUTTON_UP:
{
int mask = 0;
switch (e->jbutton.button) {
@@ -528,7 +548,7 @@ void event_handle_event(SDL_Event *e) {
#endif
}
if (mask) {
if (e->jbutton.state == SDL_PRESSED)
if (sdl_joystick_button_event_pressed(&e->jbutton))
joyinfo |= mask;
else
joyinfo &= ~mask;
@@ -552,6 +572,11 @@ void event_handle_event(SDL_Event *e) {
case DEBUGGER_COMMAND:
dbg_post_command(e->user.data1);
break;
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
case WIN_MENU_COMMAND:
win_menu_onCommand((unsigned int)(uintptr_t)e->user.data1);
break;
#endif
}
}
break;
@@ -576,7 +601,7 @@ static void get_event(void) {
/* キー情報の取得 */
static void keyEventProsess(SDL_KeyboardEvent *e, bool pressed) {
int code = keytable[e->keysym.scancode];
int code = keytable[sdl_keyboard_event_scancode(e)];
RawKeyInfo[code] = pressed;
send_agsevent(pressed ? AGSEVENT_KEY_PRESS : AGSEVENT_KEY_RELEASE, code);
}
@@ -608,8 +633,7 @@ int event_get_mouse(SDL_Point *p) {
get_event();
if (p) {
p->x = mousex;
p->y = mousey;
*p = mouse_pos;
}
int m1 = mouseb & (1 << 1) ? SYS35KEY_RET : 0;
@@ -646,6 +670,19 @@ void event_post_debugger_command(void *data) {
SDL_PushEvent(&event);
}
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
void event_post_win_menu_command(unsigned int command) {
SDL_Event event = {
.user = {
.type = custom_event_type,
.code = WIN_MENU_COMMAND,
.data1 = (void *)(uintptr_t)command
}
};
SDL_PushEvent(&event);
}
#endif
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
void simulate_right_button(int pressed) {
+5 -2
View File
@@ -21,14 +21,14 @@
#define __EVENT_H__
#include "config.h"
#include <SDL_events.h>
#include "sdl_compat.h"
#include "portab.h"
void event_init(void);
void event_remove(void);
void event_set_joy_device_index(int index);
void event_set_mouse_location(int x, int y);
void event_set_mouse_internal_location(int x, int y);
SDL_Point event_get_touch_position(const SDL_TouchFingerEvent *e);
int event_get_key(void);
int event_get_mouse(SDL_Point *p);
void event_get_wheel(vmvar_t *forward, vmvar_t *back);
@@ -36,6 +36,9 @@ void event_clear_wheel(void);
int event_get_joy(void);
void event_reset_input_state(void);
void event_post_debugger_command(void *data);
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
void event_post_win_menu_command(unsigned int command);
#endif
void event_handle_event(SDL_Event *e);
#endif // __EVENT_H__
+349 -187
View File
@@ -19,13 +19,18 @@
*/
#include "config.h"
#include <limits.h>
#include <math.h>
#include <SDL.h>
#include <SDL_ttf.h>
#include <stdlib.h>
#include <string.h>
#include "sdl_compat.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_OUTLINE_H
#include FT_TRUETYPE_TABLES_H
#ifdef _WIN32
#include "win/resources.h"
#include "win/resource_io.h"
#endif
#ifdef __EMSCRIPTEN__
@@ -34,219 +39,370 @@
#include "portab.h"
#include "system.h"
#include "cache.h"
#include "font.h"
#include "gfx_private.h"
#include "hacks.h"
#include "utfsjis.h"
// 26.6 fixed point helpers.
#define FT_FLOOR(x) ((x) >> 6)
#define FT_CEIL(x) (((x) + 63) >> 6)
#define GLYPH_CACHE_LIMIT (4 * 1024 * 1024)
typedef struct {
int size;
FontType type;
TTF_Font *id;
} FontTable;
FT_Face face;
int ascent; // distance from the top of a rendered surface to the baseline
int height; // height of a rendered surface
} Font;
#define FONTTABLEMAX 256
static FontTable fonttbl[FONTTABLEMAX];
static int fontcnt = 0;
static FT_Library ft_library;
typedef struct Glyph {
FT_Pos advance;
int bitmap_left;
int bitmap_top;
FT_Bitmap bitmap;
} Glyph;
typedef struct {
FontSpec spec;
int code;
bool antialias;
} GlyphKey;
static Cache *glyph_cache;
static struct {
bool antialiase_on;
const char *name[FONTTYPEMAX];
int face[FONTTYPEMAX];
int index[FONTTYPEMAX];
FT_Face face[FONTTYPEMAX]; // opened on first use, shared between all sizes
} this;
static void font_insert(int size, FontType type, TTF_Font *font) {
fonttbl[fontcnt].size = size;
fonttbl[fontcnt].type = type;
fonttbl[fontcnt].id = font;
if (fontcnt >= (FONTTABLEMAX -1)) {
WARNING("Font table is full.");
} else {
fontcnt++;
}
}
static FontTable *font_lookup(int size, FontType type) {
int i;
for (i = 0; i < fontcnt; i++) {
if (fonttbl[i].size == size && fonttbl[i].type == type) {
return &fonttbl[i];
}
}
return NULL;
}
// Resolve a (type, size) pair to a TTF_Font, opening and caching it on first
// use, and apply the requested weight. Returns NULL for an invalid type.
static FontTable *font_resolve(FontSpec font) {
FontType type = font.type;
int size = font.size;
if (type >= FONTTYPEMAX) {
WARNING("Invalid font type %d", type);
#if defined(_WIN32) || defined(__ANDROID__)
// Create a face from the whole content of `stream`. The memory holding the font
// file must outlive the face, and faces are never closed, so it is never freed.
static FT_Face face_from_io(sdl_iostream_t *stream, int index) {
int64_t size = sdl_get_io_size(stream);
if (size <= 0) {
sdl_close_io(stream);
return NULL;
}
FT_Byte *buf = malloc(size);
if (!buf) {
sdl_close_io(stream);
return NULL;
}
if (sdl_read_io(stream, buf, size) != (size_t)size) {
free(buf);
sdl_close_io(stream);
return NULL;
}
sdl_close_io(stream);
FontTable *tbl;
if (NULL == (tbl = font_lookup(size, type))) {
TTF_Font *fs = TTF_OpenFontIndex(this.name[type], size, this.face[type]);
#ifdef __ANDROID__
// If `this.name[type]` is a custom font file specified in .xys35rc,
// SDL_RWFromFile used by TTF_OpenFontIndex does not work because it
// does not resolve relative path with the current directory. On the
// other hand, we can't just use fopen because SDL_RWFromFile can open
// apk assets and the default fonts are stored as assets.
if (!fs) {
FILE *fp = fopen(this.name[type], "r");
if (fp)
fs = TTF_OpenFontIndexRW(SDL_RWFromFP(fp, true), true, size, this.face[type]);
}
FT_Face face;
if (FT_New_Memory_Face(ft_library, buf, size, index, &face)) {
free(buf);
return NULL;
}
return face;
}
#endif
static FT_Face open_face(const char *name, int index) {
FT_Face face = NULL;
#ifdef _WIN32
SDL_RWops *r = open_resource(this.name[type], "fonts");
if (r)
fs = TTF_OpenFontIndexRW(r, true, size, this.face[type]);
#endif
if (!fs)
SYSERROR("Cannot open font %s", this.name[type]);
const char *system_font = NULL;
if (!strcmp(name, DEFAULT_GOTHIC_TTF))
system_font = "C:/Windows/Fonts/msgothic.ttc";
else if (!strcmp(name, DEFAULT_MINCHO_TTF))
system_font = "C:/Windows/Fonts/msmincho.ttc";
if (system_font && FT_New_Face(ft_library, system_font, index, &face))
face = NULL;
font_insert(size, type, fs);
tbl = &fonttbl[fontcnt - 1];
if (!face) {
sdl_iostream_t *res = open_resource(name, "fonts");
if (res)
face = face_from_io(res, index);
}
TTF_SetFontStyle(tbl->id, font.weight == FONT_WEIGHT_BOLD ? TTF_STYLE_BOLD : TTF_STYLE_NORMAL);
return tbl;
#endif
if (!face && FT_New_Face(ft_library, name, index, &face))
face = NULL; // FT_New_Face() does not clear `face` on failure.
#ifdef __ANDROID__
// The default fonts are stored as apk assets, which can only be opened
// through SDL's IO API. (It is not used as the first choice because it
// does not resolve a relative path against the current directory, which a
// custom font specified in .xsys35rc may use.)
if (!face) {
sdl_iostream_t *stream = sdl_io_from_file(name, "rb");
if (stream)
face = face_from_io(stream, index);
}
#endif
if (face) {
// Prefer a Unicode charmap; if there is none, keep FreeType's choice.
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
}
return face;
}
SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color) {
FontTable *fontset = font_resolve(font);
if (!fontset)
// Since a face is shared between all sizes of a FontType, the size has to be
// applied on every call.
static bool font_select(FontSpec spec, Font *font) {
if (spec.type >= FONTTYPEMAX) {
WARNING("Invalid font type %d", spec.type);
return false;
}
if (!this.face[spec.type]) {
this.face[spec.type] = open_face(this.name[spec.type], this.index[spec.type]);
if (!this.face[spec.type])
SYSERROR("Cannot open font %s", this.name[spec.type]);
}
FT_Face face = this.face[spec.type];
// With a resolution of 0 (i.e. the default 72dpi), 1pt equals 1px.
if (FT_Set_Char_Size(face, 0, spec.size * 64, 0, 0) &&
FT_Set_Pixel_Sizes(face, 0, spec.size)) {
WARNING("Cannot set the size of font %s to %d", this.name[spec.type], spec.size);
return false;
}
font->face = face;
font->ascent = FT_CEIL(face->size->metrics.ascender);
font->height = font->ascent - FT_FLOOR(face->size->metrics.descender);
return true;
}
// Distance from the top of the character cell to the baseline, the same value
// GDI reports as TEXTMETRIC::tmAscent. The original engine positions text by
// the cell top, so this determines where a glyph lands on the screen.
static int cell_ascent(const Font *font) {
TT_OS2 *os2 = FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2);
if (!os2 || os2->version == 0xffff || !os2->usWinAscent)
return font->ascent;
int upem = font->face->units_per_EM;
return (os2->usWinAscent * font->face->size->metrics.y_ppem + upem / 2) / upem;
}
int font_cell_overhang(FontSpec spec) {
Font font;
if (!font_select(spec, &font))
return 0;
return font.ascent - cell_ascent(&font);
}
static bool load_glyph(FontSpec spec, FT_Face face, int code) {
if (FT_Load_Char(face, code, FT_LOAD_DEFAULT))
return false;
if (spec.weight == FONT_WEIGHT_BOLD) {
// Approximate GDI's synthetic bold without changing the advance.
// Expand outlines before rasterization so antialiased edges are
// generated only once.
FT_GlyphSlot slot = face->glyph;
if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
// At small sizes, use half-pixel emboldening to better match
// GDI's synthetic bold for the thin bitmap glyphs of MS Gothic
// and MS Mincho.
int xstrength = spec.size <= 22 ? 32 : 64;
if (FT_Outline_EmboldenXY(&slot->outline, xstrength, 0))
return false;
} else if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
if (FT_GlyphSlot_Own_Bitmap(slot) ||
FT_Bitmap_Embolden(ft_library, &slot->bitmap, 64, 0))
return false;
}
}
return true;
}
static uint32_t glyph_hash(const void *p) {
const GlyphKey *key = p;
uint32_t hash = (uint32_t)key->code;
hash = hash * 31 + key->spec.type;
hash = hash * 31 + key->spec.weight;
hash = hash * 31 + key->spec.size;
return hash * 31 + key->antialias;
}
static bool glyph_key_equal(const void *a, const void *b) {
const GlyphKey *ka = a;
const GlyphKey *kb = b;
return ka->spec.type == kb->spec.type &&
ka->spec.weight == kb->spec.weight &&
ka->spec.size == kb->spec.size &&
ka->code == kb->code && ka->antialias == kb->antialias;
}
static void glyph_destroy(void *data) {
Glyph *glyph = data;
FT_Bitmap_Done(ft_library, &glyph->bitmap);
free(glyph);
}
// Load and render a glyph on a cache miss. If allocation fails, return a
// temporary view of the face's glyph slot so text rendering can continue.
static const Glyph *get_glyph(FontSpec spec, FT_Face face, int code, bool antialias) {
GlyphKey key = {spec, code, antialias};
const Glyph *cached = cache_get(glyph_cache, &key);
if (cached)
return cached;
if (!load_glyph(spec, face, code))
return NULL;
FT_GlyphSlot slot = face->glyph;
static Glyph temporary;
temporary.advance = slot->advance.x;
FT_Bitmap_Init(&temporary.bitmap);
if (FT_Render_Glyph(slot, antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO))
return &temporary;
temporary.bitmap_left = slot->bitmap_left;
temporary.bitmap_top = slot->bitmap_top;
temporary.bitmap = slot->bitmap;
SDL_Surface *fs;
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, color);
if (!fs)
WARNING("Text rendering failed: %s", TTF_GetError());
size_t bitmap_size = slot->bitmap.rows * (size_t)abs(slot->bitmap.pitch);
size_t cache_size = sizeof(Glyph) + bitmap_size;
if (cache_size > GLYPH_CACHE_LIMIT)
return &temporary;
return fs;
Glyph *glyph = calloc(1, sizeof(Glyph));
if (!glyph)
return &temporary;
FT_Bitmap_Init(&glyph->bitmap);
if (FT_Bitmap_Copy(ft_library, &slot->bitmap, &glyph->bitmap)) {
FT_Bitmap_Done(ft_library, &glyph->bitmap);
free(glyph);
return &temporary;
}
glyph->advance = slot->advance.x;
glyph->bitmap_left = slot->bitmap_left;
glyph->bitmap_top = slot->bitmap_top;
if (cache_insert(glyph_cache, &key, glyph, cache_size) != CACHE_INSERT_OK) {
glyph_destroy(glyph);
return &temporary;
}
return glyph;
}
void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h) {
// Total advance of `str_utf8`, in pixels. A negative `len` means the whole
// string, otherwise only its first `len` bytes are measured.
static int text_width(FontSpec spec, FT_Face face, const char *str_utf8, int len,
bool antialias) {
const char *end = len < 0 ? NULL : str_utf8 + len;
int pen = 0; // 26.6
while (*str_utf8 && (!end || str_utf8 < end)) {
const Glyph *glyph = get_glyph(spec, face,
utf8_next_codepoint(&str_utf8), antialias);
if (glyph)
pen += glyph->advance;
}
return FT_CEIL(pen);
}
// Composite a rendered glyph into `dst`, which is either an ARGB8888 surface
// (the glyph is drawn in `color`, its coverage scaled by `color.a` becoming the
// alpha value) or an INDEX8 surface (covered pixels are set to the index 1).
static void blit_glyph(const FT_Bitmap *bmp, SDL_Surface *dst, int x, int y, SDL_Color color) {
int bpp = sdl_surface_bytes_per_pixel(dst);
uint32_t rgb = color.r << 16 | color.g << 8 | color.b;
for (unsigned int row = 0; row < bmp->rows; row++) {
int dy = y + row;
if (dy < 0 || dy >= dst->h)
continue;
const uint8_t *src = bmp->buffer + row * bmp->pitch;
uint8_t *dst_row = (uint8_t *)dst->pixels + dy * dst->pitch;
for (unsigned int col = 0; col < bmp->width; col++) {
int dx = x + col;
if (dx < 0 || dx >= dst->w)
continue;
uint8_t coverage;
switch (bmp->pixel_mode) {
case FT_PIXEL_MODE_MONO:
coverage = src[col >> 3] & (0x80 >> (col & 7)) ? 255 : 0;
break;
case FT_PIXEL_MODE_GRAY:
coverage = src[col];
break;
default:
continue;
}
if (!coverage)
continue;
uint8_t *dp = dst_row + dx * bpp;
if (bpp == 4)
*(uint32_t *)dp = (uint32_t)(coverage * color.a / 255) << 24 | rgb;
else
*dp = 1;
}
}
}
SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias) {
Font font;
if (!font_select(spec, &font))
return NULL;
int width = max(text_width(spec, font.face, str_utf8, -1, antialias), 1);
SDL_Surface *sf;
if (antialias) {
sf = sdl_create_surface(width, font.height, 32, SDL_PIXELFORMAT_ARGB8888);
if (sf)
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_BLEND);
} else {
sf = sdl_create_surface(width, font.height, 8, SDL_PIXELFORMAT_INDEX8);
if (sf) {
SDL_Color pal[2] = {{0, 0, 0, 0}, {color.r, color.g, color.b, 255}};
SDL_SetPaletteColors(sdl_get_surface_palette(sf), pal, 0, 2);
sdl_set_surface_color_key(sf, true, 0);
}
}
if (!sf) {
WARNING("Text rendering failed: %s", SDL_GetError());
return NULL;
}
int pen = 0; // 26.6
while (*str_utf8) {
const Glyph *glyph = get_glyph(spec, font.face,
utf8_next_codepoint(&str_utf8), antialias);
if (!glyph)
continue;
blit_glyph(&glyph->bitmap, sf, FT_FLOOR(pen) + glyph->bitmap_left,
font.ascent - glyph->bitmap_top, color);
pen += glyph->advance;
}
return sf;
}
void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
int *w, int *h) {
if (w) *w = 0;
if (h) *h = 0;
FontTable *fontset = font_resolve(font);
if (!fontset)
Font font;
if (!font_select(spec, &font))
return;
if (h)
*h = TTF_FontHeight(fontset->id);
if (!w)
return;
// A negative length means the whole string.
if (len < 0) {
TTF_SizeUTF8(fontset->id, str_utf8, w, NULL);
} else {
char buf[256];
if (len > (int)sizeof(buf) - 1)
len = sizeof(buf) - 1;
memcpy(buf, str_utf8, len);
buf[len] = '\0';
TTF_SizeUTF8(fontset->id, buf, w, NULL);
}
}
// SDL can't blit ARGB to an indexed bitmap properly, so we do it ourselves.
static void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col)
{
const SDL_Color* palette = gfx_palette->colors;
Uint8 cache[256*7];
memset(cache, 0, 256);
for (int y = 0; y < src->h && dsty + y < main_surface->h; y++) {
if (dsty + y < 0)
continue;
uint8_t *sp = (uint8_t*)src->pixels + y * src->pitch;
uint8_t *dp = (uint8_t*)main_surface->pixels + (dsty + y) * main_surface->pitch + dstx;
for (int x = 0; x < src->w && dstx + x < main_surface->w; x++) {
Uint8 r, g, b, alpha;
SDL_GetRGBA(*((Uint32*)sp), src->format, &r, &g, &b, &alpha);
alpha = alpha >> 5; // reduce bit depth
if (!alpha) {
// Transparent, do nothing
} else if (alpha == 7) {
*dp = col; // Fully opaque
} else if (cache[*dp] & 1 << alpha) {
*dp = cache[alpha << 8 | *dp]; // use cached value
} else {
// find nearest color in palette
cache[*dp] |= 1 << alpha;
int c = gfx_nearest_color(
(palette[col].r * alpha + palette[*dp].r * (7 - alpha)) / 7,
(palette[col].g * alpha + palette[*dp].g * (7 - alpha)) / 7,
(palette[col].b * alpha + palette[*dp].b * (7 - alpha)) / 7);
cache[alpha << 8 | *dp] = c;
*dp = c;
}
sp += src->format->BytesPerPixel;
dp++;
}
}
}
SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t cl) {
SDL_Surface *fs;
SDL_Rect r_src, r_dst = {};
int w, h;
if (!*str_utf8)
return r_dst;
FontTable *fontset = font_resolve(font);
if (!fontset)
return r_dst;
bool antialias = this.antialiase_on;
// The post-effect in Rance 3 opening does not work properly if colors other
// than the specified text color (32) are used.
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
// In Rance 3, text color 32 is only used in the opening.
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && cl == 32)
antialias = false;
if (antialias) {
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, gfx_palette->colors[cl]);
} else {
fs = TTF_RenderUTF8_Solid(fontset->id, str_utf8, gfx_palette->colors[cl]);
}
if (!fs) {
WARNING("Text rendering failed: %s", TTF_GetError());
return r_dst;
}
TTF_SizeUTF8(fontset->id, str_utf8, &w, &h);
// Center vertically to the box.
y -= (TTF_FontHeight(fontset->id) - fontset->size) / 2;
r_dst = (SDL_Rect){x, y, w, h};
if (main_surface->format->BitsPerPixel == 8 && antialias) {
gfx_drawAntiAlias_8bpp(x, y, fs, cl);
} else {
r_src = (SDL_Rect){0, 0, w, h};
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
}
SDL_FreeSurface(fs);
if (r_dst.y < 0) {
r_dst.h += r_dst.y;
r_dst.y = 0;
}
return r_dst;
*h = font.height;
if (w)
*w = text_width(spec, font.face, str_utf8, len, antialias);
}
void font_init(void) {
cache_clear(glyph_cache);
this.antialiase_on = false;
if (TTF_Init() == -1)
SYSERROR("Failed to intialize SDL_ttf: %s", TTF_GetError());
if (FT_Init_FreeType(&ft_library))
SYSERROR("Failed to initialize FreeType");
if (!glyph_cache) {
CacheOps ops = {
.key_size = sizeof(GlyphKey),
.hash = glyph_hash,
.equal = glyph_key_equal,
.destroy = glyph_destroy,
};
glyph_cache = cache_new(GLYPH_CACHE_LIMIT, &ops);
}
}
void font_set_name_and_index(FontType type, const char *name, int index) {
@@ -254,8 +410,10 @@ void font_set_name_and_index(FontType type, const char *name, int index) {
WARNING("Invalid font type %d", type);
return;
}
// Cached glyphs include the font type but not the font file name.
cache_clear(glyph_cache);
this.name[type] = name;
this.face[type] = index;
this.index[type] = index;
}
void font_set_antialias(bool enable) {
@@ -266,6 +424,10 @@ bool font_get_antialias(void) {
return this.antialiase_on;
}
CacheStats font_get_cache_stats(void) {
return cache_get_stats(glyph_cache);
}
#ifdef __EMSCRIPTEN__
EM_ASYNC_JS(bool, load_mincho_font, (void), {
return await xsystem35.load_mincho_font();
+9 -4
View File
@@ -24,7 +24,8 @@
#ifndef __FONT_H__
#define __FONT_H__
#include <SDL_surface.h>
#include "sdl_compat.h"
#include "cache.h"
#include "config.h"
#include "portab.h"
@@ -49,9 +50,13 @@ extern void font_init(void);
extern void font_set_name_and_index(FontType type, const char *name, int index);
extern void font_set_antialias(bool enable);
extern bool font_get_antialias(void);
extern struct SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color);
extern SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t col);
extern void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h);
extern struct SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias);
extern void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
int *w, int *h);
// Rows by which a rendered surface extends above the character cell. Subtract
// it from a Y coordinate that denotes the cell top before blitting.
extern int font_cell_overhang(FontSpec spec);
extern CacheStats font_get_cache_stats(void);
#ifdef __EMSCRIPTEN__
extern bool load_mincho_font(void);
+133
View File
@@ -0,0 +1,133 @@
/*
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "font.h"
#include "unittest.h"
#include "sdl_compat.h"
static const FontType font_types[] = {FONT_GOTHIC, FONT_MINCHO};
#define NTYPES ((int)(sizeof(font_types) / sizeof(font_types[0])))
static const int sizes[] = {8, 16, 24, 32};
#define NSIZES ((int)(sizeof(sizes) / sizeof(sizes[0])))
static const char *texts[] = {
"Hello, world!",
" ",
"iWiW",
"\xe9\xad\x94\xe7\x89\xa9\xe3\x81\xae\xe6\xa3\xae", // 魔物の森
"\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86" // あいう
"\xe3\x82\xa2\xe3\x82\xa4\xe3\x82\xa6", // アイウ
};
#define NTEXTS ((int)(sizeof(texts) / sizeof(texts[0])))
static const SDL_Color white = {255, 255, 255, 255};
static int measure_w(FontSpec f, const char *s, bool antialias) {
int w;
font_measure_text(f, s, -1, antialias, &w, NULL);
return w;
}
static int measure_h(FontSpec f) {
int h;
font_measure_text(f, "", -1, false, NULL, &h);
return h;
}
static void assert_same_pixels(SDL_Surface *a, SDL_Surface *b) {
ASSERT_EQUAL(a->w, b->w);
ASSERT_EQUAL(a->h, b->h);
ASSERT_EQUAL(sdl_surface_format(a), sdl_surface_format(b));
int row_bytes = a->w * sdl_surface_bytes_per_pixel(a);
for (int y = 0; y < a->h; y++) {
const uint8_t *ap = (uint8_t *)a->pixels + y * a->pitch;
const uint8_t *bp = (uint8_t *)b->pixels + y * b->pitch;
ASSERT_EQUAL(memcmp(ap, bp, row_bytes), 0);
}
}
static void glyph_cache_test(void) {
for (int ti = 0; ti < NTYPES; ti++) {
for (int weight = FONT_WEIGHT_NORMAL; weight <= FONT_WEIGHT_BOLD; weight++) {
FontSpec f = {font_types[ti], weight, 24};
for (int aa = 0; aa < 2; aa++) {
SDL_Surface *first = font_render_text(f, texts[3], white, aa);
SDL_Surface *cached = font_render_text(f, texts[3], white, aa);
ASSERT_TRUE(first);
ASSERT_TRUE(cached);
assert_same_pixels(first, cached);
sdl_destroy_surface(first);
sdl_destroy_surface(cached);
}
}
}
}
static void invariants_test(void) {
for (int ti = 0; ti < NTYPES; ti++) {
for (int si = 0; si < NSIZES; si++) {
FontSpec f = {font_types[ti], FONT_WEIGHT_NORMAL, sizes[si]};
for (int i = 0; i < NTEXTS; i++) {
const char *text = texts[i];
for (int aa = 0; aa < 2; aa++) {
SDL_Surface *s = font_render_text(f, text, white, aa);
ASSERT_TRUE(s);
// gfx_drawString() blits the surface using the measured
// width, so the two must agree.
ASSERT_EQUAL(s->w, measure_w(f, text, aa));
// microui lays out text rows by the measured height,
// so the surface must be exactly that tall.
ASSERT_EQUAL(s->h, measure_h(f));
// The antialiased surface goes to gfx_drawAntiAlias_8bpp(),
// which reads a 32-bit alpha channel. The other one is
// blitted onto the indexed screen surface as is.
ASSERT_EQUAL(sdl_surface_bits_per_pixel(s), aa ? 32 : 8);
sdl_destroy_surface(s);
}
}
// gfx_drawString() draws text by the top of the character cell,
// shifting the surface up by the overhang. The result must cover
// the whole cell.
ASSERT_TRUE(font_cell_overhang(f) >= 0);
ASSERT_TRUE(measure_h(f) - font_cell_overhang(f) >= f.size);
// microui positions the text cursor by measuring a prefix of the
// string, so that must equal measuring a truncated copy.
const char *text = texts[0];
for (int len = 0; text[len]; len++) {
char buf[64];
int w;
font_measure_text(f, text, len, false, &w, NULL);
memcpy(buf, text, len);
buf[len] = '\0';
ASSERT_EQUAL(w, measure_w(f, buf, false));
}
}
}
}
void font_test(void) {
font_init();
font_set_name_and_index(FONT_GOTHIC, TEST_FONT_DIR "/MTLc3m.ttf", 0);
font_set_name_and_index(FONT_MINCHO, TEST_FONT_DIR "/mincho.ttf", 0);
invariants_test();
glyph_cache_test();
}
+10 -3
View File
@@ -21,7 +21,7 @@
#define __GFX_H__
#include "config.h"
#include <SDL_surface.h>
#include "sdl_compat.h"
#include "portab.h"
#include "ags.h"
@@ -31,9 +31,13 @@ void gfx_Remove(void);
/* ウィンド関係 */
void gfx_setWorldSize(int width, int height, int depth);
void gfx_setWindowSize(int w, int h);
void gfx_setViewSize(int w, int h);
void gfx_getViewSize(int *width, int *height);
SDL_Window *gfx_getWindow(void);
bool gfx_convertEventCoordinates(SDL_Event *event);
void gfx_warpMouse(int x, int y);
SDL_Rect gfx_viewToWindowRect(SDL_Rect rect);
void gfx_setWindowTitle(char *name);
void gfx_getWindowInfo(int *width, int *height, int *depth);
void gfx_setFullscreen(bool on);
bool gfx_isFullscreen(void);
void gfx_raiseWindow(void);
@@ -44,10 +48,12 @@ SDL_Surface *gfx_createSurfaceView(SDL_Surface *sf, int x, int y, int w, int h);
/* 画面更新 */
void gfx_updateArea(SDL_Rect *src, SDL_Point *dst);
void gfx_updateAll(SDL_Rect *view_rect);
void gfx_requestRedraw(void);
void gfx_updateScreen(void);
/* パレット関係 */
void gfx_setPalette(SDL_Color *pal, int first, int count);
SDL_Color gfx_getPaletteColor(uint8_t color);
/* 描画関係 */
void gfx_drawRectangle(int x, int y, int w, int h, uint8_t c);
@@ -55,6 +61,7 @@ void gfx_fillRectangle(int x, int y, int w, int h, uint8_t c);
void gfx_fillRectangleRGB(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b);
void gfx_fillCircle(int left, int top, int diameter, uint8_t c);
void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c);
void gfx_drawAntiAlias_8bpp(int x, int y, SDL_Surface *src, uint8_t color);
SDL_Rect gfx_floodFill(int x, int y, int col);
SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpec font);
void gfx_copyArea(int sx,int sy, int w, int h, int dx, int dy);
+104 -22
View File
@@ -27,7 +27,7 @@
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <SDL.h>
#include "sdl_compat.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -38,11 +38,14 @@
#include "gfx_private.h"
#include "font.h"
#include "ags.h"
#include "hacks.h"
#include "image.h"
#include "nact.h"
#include "modal.h"
#include "debugger.h"
static bool gfx_dirty;
static void gfx_pal_check(void) {
if (nact->ags.pal_changed) {
nact->ags.pal_changed = false;
@@ -51,21 +54,30 @@ static void gfx_pal_check(void) {
}
static Uint32 palette_color(uint8_t c) {
if (main_surface->format->BitsPerPixel == 8)
if (sdl_surface_bits_per_pixel(main_surface) == 8)
return c;
return SDL_MapRGB(main_surface->format, gfx_palette->colors[c].r, gfx_palette->colors[c].g, gfx_palette->colors[c].b);
return sdl_map_rgb(main_surface, gfx_palette->colors[c].r,
gfx_palette->colors[c].g, gfx_palette->colors[c].b);
}
SDL_Color gfx_getPaletteColor(uint8_t color) {
return gfx_palette->colors[color];
}
void gfx_updateScreen(void) {
if (!gfx_dirty)
return;
SDL_RenderClear(gfx_renderer);
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
modal_render_overlay();
SDL_RenderPresent(gfx_renderer);
gfx_dirty = false;
}
void gfx_requestRedraw(void) {
gfx_dirty = true;
}
/* off-screen の指定領域を Main Window へ転送 */
void gfx_updateArea(SDL_Rect *rect, SDL_Point *dst) {
SDL_Rect sw = {0, 0, main_surface->w, main_surface->h};
@@ -122,11 +134,11 @@ void gfx_fillRectangle(int x, int y, int w, int h, uint8_t c) {
}
void gfx_fillRectangleRGB(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
if (main_surface->format->BitsPerPixel == 8)
if (sdl_surface_bits_per_pixel(main_surface) == 8)
return;
SDL_Rect rect = {x, y, w, h};
SDL_FillRect(main_surface, &rect, SDL_MapRGB(main_surface->format, r, g, b));
SDL_FillRect(main_surface, &rect, sdl_map_rgb(main_surface, r, g, b));
}
void gfx_fillCircle(int left, int top, int diameter, uint8_t c) {
@@ -172,13 +184,13 @@ static void gfx_dib_sprite_copy(SDL_Surface *dst, int sx, int sy, int w, int h,
gfx_pal_check();
Uint32 col = palette_color(sp);
SDL_SetColorKey(main_surface, SDL_TRUE, col);
sdl_set_surface_color_key(main_surface, true, col);
SDL_Rect r_src = {sx, sy, w, h};
SDL_Rect r_dst = {dx, dy, w, h};
SDL_BlitSurface(main_surface, &r_src, dst, &r_dst);
SDL_SetColorKey(main_surface, SDL_FALSE, 0);
sdl_set_surface_color_key(main_surface, false, 0);
}
/*
@@ -189,7 +201,7 @@ void gfx_copyAreaSP(int sx, int sy, int w, int h, int dx, int dy, uint8_t sp) {
}
SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int sp) {
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_Surface *s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(s, NULL, 0);
gfx_dib_sprite_copy(s, x, y, w, h, 0, 0, sp);
return s;
@@ -198,24 +210,24 @@ SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int sp) {
void gfx_drawImage8(cgdata *cg, int dx, int dy, int sprite_color) {
int w = cg->width;
int h = cg->height;
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormatFrom(
SDL_Surface *s = sdl_create_surface_from(
cg->pic, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
gfx_pal_check();
if (main_surface->format->BitsPerPixel > 8 && cg->pal) {
SDL_SetPaletteColors(s->format->palette, cg->pal, 0, 256);
if (sdl_surface_bits_per_pixel(main_surface) > 8 && cg->pal) {
SDL_SetPaletteColors(sdl_get_surface_palette(s), cg->pal, 0, 256);
} else {
SDL_SetSurfacePalette(s, gfx_palette);
sdl_set_surface_palette(s, gfx_palette);
}
if (sprite_color != -1)
SDL_SetColorKey(s, SDL_TRUE, sprite_color);
sdl_set_surface_color_key(s, true, sprite_color);
SDL_Rect r_dst = {dx, dy, w, h};
SDL_BlitSurface(s, NULL, main_surface, &r_dst);
SDL_FreeSurface(s);
sdl_destroy_surface(s);
}
/* 直線描画 */
@@ -224,7 +236,7 @@ void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c) {
SDL_Renderer *renderer = SDL_CreateSoftwareRenderer(main_surface);
SDL_SetRenderDrawColor(renderer, gfx_palette->colors[c].r, gfx_palette->colors[c].g, gfx_palette->colors[c].b, SDL_ALPHA_OPAQUE);
SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
sdl_render_line_int(renderer, x1, y1, x2, y2);
SDL_DestroyRenderer(renderer);
}
@@ -241,7 +253,7 @@ void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c) {
SDL_Rect gfx_floodFill(int x, int y, int c) {
Uint32 col = palette_color(c);
switch (main_surface->format->BytesPerPixel) {
switch (sdl_surface_bytes_per_pixel(main_surface)) {
case 1:
return gfx_floodFill_uint8_t(x, y, col);
case 2:
@@ -254,7 +266,7 @@ SDL_Rect gfx_floodFill(int x, int y, int c) {
}
}
int gfx_nearest_color(int r, int g, int b) {
static int nearest_color(int r, int g, int b) {
int i, col, mind = INT_MAX;
for (i = 0; i < 256; i++) {
int dr = r - gfx_palette->colors[i].r;
@@ -269,17 +281,87 @@ int gfx_nearest_color(int r, int g, int b) {
return col;
}
// SDL can't blit ARGB to an indexed bitmap properly, so we do it ourselves.
void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col) {
const SDL_Color *palette = gfx_palette->colors;
Uint8 cache[256 * 7];
memset(cache, 0, 256);
for (int y = 0; y < src->h && dsty + y < main_surface->h; y++) {
if (dsty + y < 0)
continue;
uint8_t *sp = (uint8_t *)src->pixels + y * src->pitch;
uint8_t *dp = (uint8_t *)main_surface->pixels
+ (dsty + y) * main_surface->pitch + dstx;
for (int x = 0; x < src->w && dstx + x < main_surface->w; x++) {
Uint8 r, g, b, alpha;
sdl_get_rgba(*(Uint32 *)sp, src, &r, &g, &b, &alpha);
alpha >>= 5; // reduce bit depth
if (!alpha) {
// Transparent, do nothing.
} else if (alpha == 7) {
*dp = col; // Fully opaque.
} else if (cache[*dp] & 1 << alpha) {
*dp = cache[alpha << 8 | *dp];
} else {
cache[*dp] |= 1 << alpha;
int c = nearest_color(
(palette[col].r * alpha + palette[*dp].r * (7 - alpha)) / 7,
(palette[col].g * alpha + palette[*dp].g * (7 - alpha)) / 7,
(palette[col].b * alpha + palette[*dp].b * (7 - alpha)) / 7);
cache[alpha << 8 | *dp] = c;
*dp = c;
}
sp += sdl_surface_bytes_per_pixel(src);
dp++;
}
}
}
SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpec font) {
SDL_Rect r_dst = {};
gfx_pal_check();
return font_draw_text(font, x, y, str_utf8, col);
if (!*str_utf8)
return r_dst;
bool antialias = font_get_antialias();
// The post-effect in Rance 3 opening does not work properly if colors other
// than the specified text color (32) are used.
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
// In Rance 3, text color 32 is only used in the opening.
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && col == 32)
antialias = false;
SDL_Surface *fs = font_render_text(font, str_utf8, gfx_getPaletteColor(col), antialias);
if (!fs)
return r_dst;
y -= font_cell_overhang(font);
r_dst = (SDL_Rect){x, y, fs->w, fs->h};
if (sdl_surface_bits_per_pixel(main_surface) == 8 && antialias) {
gfx_drawAntiAlias_8bpp(x, y, fs, col);
} else {
SDL_Rect r_src = {0, 0, fs->w, fs->h};
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
}
sdl_destroy_surface(fs);
if (r_dst.y < 0) {
r_dst.h += r_dst.y;
r_dst.y = 0;
}
return r_dst;
}
/*
* 指定範囲にパレット col を rate の割合で重ねる CK1
*/
void gfx_wrapColor(int sx, int sy, int w, int h, uint8_t c, int rate) {
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, main_surface->format->BitsPerPixel, main_surface->format->format);
assert(s->format->BitsPerPixel > 8);
SDL_Surface *s = sdl_create_surface(w, h,
sdl_surface_bits_per_pixel(main_surface), sdl_surface_format(main_surface));
assert(sdl_surface_bits_per_pixel(s) > 8);
SDL_Rect r_src = {0, 0, w, h};
SDL_FillRect(s, &r_src, palette_color(c));
@@ -288,5 +370,5 @@ void gfx_wrapColor(int sx, int sy, int w, int h, uint8_t c, int rate) {
SDL_SetSurfaceAlphaMod(s, rate);
SDL_Rect r_dst = {sx, sy, w, h};
SDL_BlitSurface(s, &r_src, main_surface, &r_dst);
SDL_FreeSurface(s);
sdl_destroy_surface(s);
}
+25 -21
View File
@@ -24,7 +24,7 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "system.h"
@@ -40,7 +40,7 @@ void gfx_FlipSurfaceHorizontal(SDL_Surface *s) {
SDL_FlipSurface(s, SDL_FLIP_HORIZONTAL);
#else
uint8_t *p = s->pixels;
int bpp = s->format->BytesPerPixel;
int bpp = sdl_surface_bytes_per_pixel(s);
uint8_t tmp[4];
for (int y = 0; y < s->h; y++) {
uint8_t *p1 = p;
@@ -88,7 +88,7 @@ void gfx_scaledCopyArea(int sx, int sy, int sw, int sh, int dx, int dy, int dw,
gfx_FlipSurfaceVertical(view);
if (mirror & 2)
gfx_FlipSurfaceHorizontal(view);
SDL_FreeSurface(view);
sdl_destroy_surface(view);
}
}
@@ -104,19 +104,19 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
if (cg->alpha && alpha_blend) {
uint16_t *p_src = (uint16_t *)cg->pic;
uint8_t *a_src = cg->alpha;
s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
for (int y = 0; y < h; y++) {
uint32_t *dst = (uint32_t *)(((uint8_t *)s->pixels) + y * s->pitch);
for (int x = 0; x < w; x++) {
*dst++ = rgb565_to_rgb888(*p_src++) | *a_src++ << 24;
}
}
} else if (sf->sdl_surface->format->BitsPerPixel > 16) {
} else if (sdl_surface_bits_per_pixel(sf->sdl_surface) > 16) {
// Convert to RGB888 by ourselves. SDL blit functions use a slightly
// different mapping, so the color expanded by SDL may not match the
// color key specified in the CX command.
uint16_t *src = (uint16_t *)cg->pic;
s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGB888);
s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_RGB888);
for (int y = 0; y < h; y++) {
uint32_t *dst = (uint32_t *)(((uint8_t *)s->pixels) + y * s->pitch);
for (int x = 0; x < w; x++) {
@@ -124,8 +124,8 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
}
}
} else {
s = SDL_CreateRGBSurfaceWithFormatFrom(
cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
s = sdl_create_surface_from(cg->pic, cg->width, cg->height,
16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
}
if (brightness != 255)
@@ -133,7 +133,7 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
SDL_Rect r_dst = {dx, dy, w, h};
SDL_BlitSurface(s, NULL, sf->sdl_surface, &r_dst);
SDL_FreeSurface(s);
sdl_destroy_surface(s);
}
void gfx_drawImage24(cgdata *cg, surface_t *sf, int x, int y, int brightness) {
@@ -141,14 +141,14 @@ void gfx_drawImage24(cgdata *cg, surface_t *sf, int x, int y, int brightness) {
gfx_drawImageAlphaMap(cg, sf, x, y);
}
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormatFrom(
cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_Surface *s = sdl_create_surface_from(cg->pic, cg->width, cg->height,
24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
if (brightness != 255)
SDL_SetSurfaceColorMod(s, brightness, brightness, brightness);
SDL_Rect r_dst = {x, y, cg->width, cg->height};
SDL_BlitSurface(s, NULL, sf->sdl_surface, &r_dst);
SDL_FreeSurface(s);
sdl_destroy_surface(s);
}
void gfx_drawImageAlphaMap(cgdata *cg, surface_t *sf, int x, int y) {
@@ -165,7 +165,7 @@ void gfx_drawImageAlphaMap(cgdata *cg, surface_t *sf, int x, int y) {
}
SDL_Surface *gfx_dib_to_surface_with_alpha(int x, int y, int w, int h) {
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_Surface *s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_Rect r_src = {x, y, w, h};
SDL_BlitSurface(main_surface, &r_src, s, NULL);
@@ -192,7 +192,7 @@ void gfx_copyAreaSP16_shadow(int sx, int sy, int w, int h, int dx, int dy, int l
SDL_Rect r_dst = {dx, dy, w, h};
SDL_BlitSurface(s, NULL, main_surface, &r_dst);
SDL_FreeSurface(s);
sdl_destroy_surface(s);
}
void gfx_copyAreaSP16_alphaBlend(int sx, int sy, int w, int h, int dx, int dy, int lv) {
@@ -230,7 +230,7 @@ void gfx_copy_to_alpha(int sx, int sy, int w, int h, int dx, int dy, ALPHA_DIB_C
uint32_t gfx_getPixel(int x, int y) {
uint8_t *p = PIXEL_AT(main_surface, x, y);
switch (main_surface->format->BytesPerPixel) {
switch (sdl_surface_bytes_per_pixel(main_surface)) {
case 1:
return *p;
case 2:
@@ -252,10 +252,14 @@ uint32_t gfx_getPixel(int x, int y) {
* dib から領域の切り出し
*/
void* gfx_saveRegion(int x, int y, int w, int h) {
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, main_surface->format->BitsPerPixel, main_surface->format->format);
if (main_surface->format->BitsPerPixel == 8)
memcpy(s->format->palette->colors, main_surface->format->palette->colors,
sizeof(SDL_Color) * main_surface->format->palette->ncolors);
SDL_Surface *s = sdl_create_surface(w, h,
sdl_surface_bits_per_pixel(main_surface), sdl_surface_format(main_surface));
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
SDL_Palette *dst_palette = sdl_get_surface_palette(s);
SDL_Palette *src_palette = sdl_get_surface_palette(main_surface);
SDL_SetPaletteColors(dst_palette, src_palette->colors, 0,
src_palette->ncolors);
}
SDL_Rect r_src = {x, y, w, h};
SDL_Rect r_dst = {0, 0, w, h};
SDL_BlitSurface(main_surface, &r_src, s, &r_dst);
@@ -266,7 +270,7 @@ void* gfx_saveRegion(int x, int y, int w, int h) {
* セーブした領域を破棄
*/
void gfx_delRegion(void *psrc) {
SDL_FreeSurface((SDL_Surface *)psrc);
sdl_destroy_surface((SDL_Surface *)psrc);
}
/*
@@ -287,5 +291,5 @@ void gfx_putRegion(void *psrc, int x, int y) {
void gfx_restoreRegion(void *psrc, int x, int y) {
SDL_Surface *src = (SDL_Surface *)psrc;
gfx_putRegion(src, x ,y);
SDL_FreeSurface(src);
sdl_destroy_surface(src);
}
+1 -5
View File
@@ -25,22 +25,18 @@
#include "config.h"
#include <SDL.h>
#include "sdl_compat.h"
#include "portab.h"
#include "ags.h"
extern SDL_Window *gfx_window;
extern SDL_Renderer *gfx_renderer;
extern SDL_Texture *gfx_texture;
extern SDL_Palette *gfx_palette;
extern surface_t *gfx_dibinfo;
extern int view_w;
extern int view_h;
extern bool gfx_dirty;
extern bool gfx_fullscreen;
int gfx_nearest_color(int r, int g, int b);
SDL_Surface *gfx_dib_to_surface_with_alpha(int x, int y, int w, int h);
SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int col);
+71 -41
View File
@@ -23,7 +23,8 @@
#include "config.h"
#include <stdio.h>
#include <SDL.h>
#include <stdlib.h>
#include "sdl_compat.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
@@ -36,7 +37,7 @@
#include "xsystem35.h"
#include "image.h"
SDL_Window *gfx_window;
static SDL_Window *gfx_window;
SDL_Renderer *gfx_renderer;
SDL_Texture *gfx_texture;
SDL_Surface *main_surface; // offscreen surface
@@ -44,8 +45,8 @@ SDL_Palette *gfx_palette;
surface_t *gfx_dibinfo;
int view_w;
int view_h;
bool gfx_dirty;
bool gfx_fullscreen;
static bool gfx_fullscreen;
static bool gfx_integer_scaling;
static void window_init(const char *render_driver);
static void makeDIB(int width, int height, int depth);
@@ -57,7 +58,7 @@ int gfx_Initialize(const char *render_driver) {
/* offscreen Pixmap */
makeDIB(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, SYS35_DEFAULT_DEPTH);
gfx_setWindowSize(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT);
gfx_setViewSize(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT);
#ifdef __EMSCRIPTEN__
// Prevent SDL from calling emscripten_exit_fullscreen on visibilitychange
@@ -69,9 +70,9 @@ int gfx_Initialize(const char *render_driver) {
void gfx_Remove(void) {
if (gfx_palette)
SDL_FreePalette(gfx_palette);
sdl_destroy_palette(gfx_palette);
if (main_surface)
SDL_FreeSurface(main_surface);
sdl_destroy_surface(main_surface);
if (gfx_renderer)
SDL_DestroyRenderer(gfx_renderer);
SDL_Quit();
@@ -92,7 +93,9 @@ static void window_init(const char *render_driver) {
if (render_driver)
SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver);
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if XSYSTEM35_SDL_VERSION == 2
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
#endif
SDL_Init(SDL_INIT_VIDEO);
@@ -109,24 +112,19 @@ static void window_init(const char *render_driver) {
#ifdef __ANDROID__
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
Uint32 flags = SDL_WINDOW_FULLSCREEN;
#else
Uint32 flags = SDL_WINDOW_RESIZABLE;
#endif
gfx_window = SDL_CreateWindow(
title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, flags);
gfx_renderer = SDL_CreateRenderer(gfx_window, -1, 0);
gfx_window = sdl_create_window(title, SYS35_DEFAULT_WIDTH,
SYS35_DEFAULT_HEIGHT, SDL_WINDOW_RESIZABLE);
gfx_renderer = sdl_create_renderer(gfx_window);
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
gfx_palette = SDL_AllocPalette(256);
gfx_palette = sdl_create_palette(256);
}
static void makeDIB(int width, int height, int depth) {
if (main_surface) {
SDL_FreeSurface(main_surface);
sdl_destroy_surface(main_surface);
}
uint32_t format = 0;
@@ -146,10 +144,10 @@ static void makeDIB(int width, int height, int depth) {
SYSERROR("invalid pixel depth %d", depth);
}
main_surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, format);
main_surface = sdl_create_surface(width, height, depth, format);
if (main_surface->format->BitsPerPixel == 8) {
SDL_SetSurfacePalette(main_surface, gfx_palette);
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
sdl_set_surface_palette(main_surface, gfx_palette);
}
if (gfx_dibinfo) {
@@ -162,7 +160,7 @@ static void makeDIB(int width, int height, int depth) {
gfx_dibinfo->alpha = NULL;
gfx_dibinfo->sdl_surface = main_surface;
image_setdepth(main_surface->format->BitsPerPixel);
image_setdepth(sdl_surface_bits_per_pixel(main_surface));
}
/* offscreen の設定 */
@@ -171,13 +169,38 @@ void gfx_setWorldSize(int width, int height, int depth) {
SDL_FillRect(main_surface, NULL, 0);
}
/* display の size と depth の取得 */
void gfx_getWindowInfo(int *width, int *height, int *depth) {
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm);
if (width) *width = dm.w;
if (height) *height = dm.h;
if (depth) *depth = SDL_BITSPERPIXEL(dm.format);
void gfx_getViewSize(int *width, int *height) {
if (width)
*width = view_w;
if (height)
*height = view_h;
}
SDL_Window *gfx_getWindow(void) {
return gfx_window;
}
bool gfx_convertEventCoordinates(SDL_Event *event) {
#if XSYSTEM35_SDL_VERSION == 2
return true;
#else
return SDL_ConvertEventToRenderCoordinates(gfx_renderer, event);
#endif
}
static SDL_Point view_to_window_point(int x, int y) {
return sdl_render_coordinates_to_window(gfx_renderer, x, y);
}
void gfx_warpMouse(int x, int y) {
SDL_Point point = view_to_window_point(x, y);
SDL_WarpMouseInWindow(gfx_window, point.x, point.y);
}
SDL_Rect gfx_viewToWindowRect(SDL_Rect rect) {
SDL_Point tl = view_to_window_point(rect.x, rect.y);
SDL_Point br = view_to_window_point(rect.x + rect.w, rect.y + rect.h);
return (SDL_Rect){tl.x, tl.y, br.x - tl.x, br.y - tl.y};
}
/* DIBの取得 */
@@ -187,11 +210,12 @@ surface_t *gfx_getDIB(void) {
SDL_Surface *gfx_createSurfaceView(SDL_Surface *sf, int x, int y, int w, int h) {
uint8_t *pixels = sf->pixels;
pixels += y * sf->pitch + x * sf->format->BytesPerPixel;
SDL_Surface *view = SDL_CreateRGBSurfaceWithFormatFrom(
pixels, w, h, sf->format->BitsPerPixel, sf->pitch, sf->format->format);
if (sf->format->palette)
SDL_SetSurfacePalette(view, sf->format->palette);
pixels += y * sf->pitch + x * sdl_surface_bytes_per_pixel(sf);
SDL_Surface *view = sdl_create_surface_from(pixels, w, h,
sdl_surface_bits_per_pixel(sf), sf->pitch, sdl_surface_format(sf));
SDL_Palette *palette = sdl_get_surface_palette(sf);
if (palette)
sdl_set_surface_palette(view, palette);
return view;
}
@@ -199,7 +223,7 @@ void gfx_setFullscreen(bool on) {
#ifndef __EMSCRIPTEN__
if (on == gfx_fullscreen)
return;
SDL_SetWindowFullscreen(gfx_window, on ? SDL_WINDOW_FULLSCREEN_DESKTOP: 0);
sdl_set_window_fullscreen(gfx_window, on);
gfx_fullscreen = on;
#endif
}
@@ -212,20 +236,24 @@ void gfx_raiseWindow(void) {
SDL_RaiseWindow(gfx_window);
}
void gfx_setWindowSize(int w, int h) {
void gfx_setViewSize(int w, int h) {
if (w == view_w && h == view_h) return;
view_w = w;
view_h = h;
#ifndef __ANDROID__
SDL_SetWindowSize(gfx_window, w, h);
sdl_set_window_content_size(gfx_window, w, h);
#endif
SDL_RenderSetLogicalSize(gfx_renderer, w, h);
sdl_set_render_logical_presentation(
gfx_renderer, w, h, gfx_integer_scaling);
if (gfx_texture)
SDL_DestroyTexture(gfx_texture);
gfx_texture = SDL_CreateTexture(
gfx_renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, w, h);
#ifdef __ANDROID__
SDL_SetTextureScaleMode(gfx_texture, SDL_SCALEMODE_LINEAR);
#endif
#ifdef __EMSCRIPTEN__
EM_ASM( xsystem35.shell.windowSizeChanged(); );
@@ -233,13 +261,15 @@ void gfx_setWindowSize(int w, int h) {
}
void gfx_setIntegerScaling(bool enable) {
SDL_RenderSetIntegerScale(gfx_renderer, enable);
gfx_integer_scaling = enable;
sdl_set_render_logical_presentation(
gfx_renderer, view_w, view_h, gfx_integer_scaling);
}
bool EMSCRIPTEN_KEEPALIVE save_screenshot(const char* path) {
SDL_Rect *r = &nact->ags.view_area;
SDL_Surface *view = gfx_createSurfaceView(main_surface, r->x, r->y, r->w, r->h);
bool ok = SDL_SaveBMP(view, path) == 0;
SDL_FreeSurface(view);
bool ok = sdl_save_bmp(view, path);
sdl_destroy_surface(view);
return ok;
}

Some files were not shown because too many files have changed in this diff Show More