Compare commits

...
28 Commits
Author SHA1 Message Date
kichikuou 82c8b76a58 Fix missing equipment gauges in Rance 5D
This fixes #83.

**CG_GRAPH checks the source CG's type before drawing, but
SACT.CG_QueryType only checked CGs already loaded into memory. The gauge
images had not been loaded yet, so they were reported as unused.

Check the ALD link table for unloaded CGs, then load them when querying
their dimensions.
2026-09-23 07:51:47 +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
67 changed files with 1981 additions and 703 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- 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 libsdl2-dev libfreetype-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
- name: Build
run: |
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
msystem: ${{ matrix.sys }}
pacboy: >-
SDL2:p
SDL2_ttf:p
freetype:p
SDL2_mixer:p
libwebp:p
${{ matrix.deps }}
+14
View File
@@ -1,5 +1,19 @@
# 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.
+10 -9
View File
@@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
enable_testing()
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(XSYSTEM35_VERSION "2.19.1")
set(XSYSTEM35_VERSION "2.20.0")
include(CheckSymbolExists)
include(FetchContent)
@@ -55,6 +55,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
@@ -63,14 +65,14 @@ if (EMSCRIPTEN)
endfunction()
add_emscripten_library(zlib -sUSE_ZLIB=1)
add_emscripten_library(sdl2 -sUSE_SDL=2)
add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2)
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(freetype2 ALIAS freetype)
add_library(sdl2_mixer ALIAS SDL2_mixer)
find_library(ndk_log log)
find_library(ndk_zlib z)
@@ -88,18 +90,17 @@ else() # non-emscripten, non-android
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(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
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(freetype2 FREETYPE)
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
target_link_libraries(freetype2 INTERFACE -lstdc++)
add_static_library(sdl2_mixer SDL2MIXER)
else()
add_library(sdl2 ALIAS PkgConfig::SDL2)
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
endif()
+3 -3
View File
@@ -105,7 +105,7 @@ See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
### 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
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
@@ -117,7 +117,7 @@ $ make && make install
[Homebrew](https://brew.sh/) is required.
```bash
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi cjson asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
@@ -129,7 +129,7 @@ $ make && make install
[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
$ 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-freetype 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 ../../
+1 -1
View File
@@ -17,7 +17,7 @@ android {
minSdkVersion 21
targetSdkVersion 34
versionCode 36
versionName "2.19.1" + gitRevision()
versionName "2.20.0" + gitRevision()
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
+12 -10
View File
@@ -11,9 +11,10 @@ FetchContent_Declare(
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
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
)
FetchContent_Declare(
SDL_mixer
@@ -21,9 +22,12 @@ FetchContent_Declare(
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
)
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)
# 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(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
@@ -33,7 +37,7 @@ 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(SDL freetype SDL_mixer)
# The main CMakeLists.txt of xsystem35
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
@@ -49,7 +53,5 @@ file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mi
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 ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
+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() {
@@ -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/"),
)
-2
View File
@@ -1,8 +1,6 @@
#cmakedefine PACKAGE "@PACKAGE@"
#define VERSION "@XSYSTEM35_VERSION@"
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
#cmakedefine ENABLE_MIDI_PORTMIDI @ENABLE_MIDI_PORTMIDI@
+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
+18 -9
View File
@@ -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"
@@ -286,8 +287,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 +308,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 +629,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 +1623,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 +1636,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 +1853,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);
}
+5 -1
View File
@@ -97,7 +97,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;
+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];
-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;
}
+8 -5
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,7 +69,7 @@ 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};
+21 -15
View File
@@ -35,6 +35,7 @@
#include "ags.h"
#include "variable.h"
#include "sactcg.h"
#include "ald_manager.h"
#include "cg.h"
#include "gfx.h"
@@ -220,7 +221,8 @@ 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_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
@@ -244,7 +246,8 @@ 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_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
@@ -326,24 +329,27 @@ void scg_free(int no) {
// CGの種類を取得
int scg_querytype(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return CG_NOTUSED;
if (!cg_store || !cg_store[wNumCG]) return CG_NOTUSED;
return cg_store[wNumCG]->type;
if (cg_store && cg_store[wNumCG])
return cg_store[wNumCG]->type;
// Linked CGs are valid even before they are loaded into cg_store.
return ald_is_linked(DRIFILE_CG, wNumCG - 1) ? CG_LINKED : CG_NOTUSED;
}
// CGの大きさを取得
bool scg_querysize(int wNumCG, vmvar_t *w, vmvar_t *h) {
if (wNumCG >= (CGMAX -1)) goto errexit;
if (!cg_store || !cg_store[wNumCG]) goto errexit;
if (cg_store[wNumCG]->sf == NULL) goto errexit;
*w = cg_store[wNumCG]->sf->w;
*h = cg_store[wNumCG]->sf->h;
return true;
errexit:
*w = *h = 0;
return false;
if (wNumCG >= (CGMAX - 1))
return false;
cginfo_t *cg = cg_store ? cg_store[wNumCG] : NULL;
if (!cg && ald_is_linked(DRIFILE_CG, wNumCG - 1))
cg = scg_get(wNumCG);
if (!cg || !cg->sf)
return false;
*w = cg->sf->w;
*h = cg->sf->h;
return true;
}
// CGのBPPを取得
+27 -12
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
@@ -137,16 +137,17 @@ 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)
target_link_libraries(src_lib PRIVATE sdl2 freetype2 zlib)
target_link_libraries(xsystem35 PRIVATE m zlib sdl2)
if (TARGET sdl2_mixer)
target_link_libraries(xsystem35 PRIVATE sdl2_mixer)
endif()
@@ -157,18 +158,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
@@ -191,12 +202,16 @@ else() # non-emscripten, non-android
add_executable(src_tests
src_tests.c
cache_test.c
font_test.c
gameresource_test.c
hankaku_test.c
qnt_test.c
variable_test.c
)
target_compile_options(src_tests PRIVATE -Wno-pointer-sign -Wall)
target_compile_definitions(src_tests PRIVATE
TEST_FONT_DIR="${PROJECT_SOURCE_DIR}/fonts")
target_link_libraries(src_tests PRIVATE src_lib sdl2)
add_test(NAME src_tests COMMAND src_tests)
file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+6 -7
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();
@@ -479,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;
}
@@ -729,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);
@@ -750,7 +749,7 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
sys_sleep(cursor_move_time / 7);
}
gfx_warpMouse(x, y);
} else if (!for_selection) {
} else if (!sl_is_s380 && !for_selection) {
event_set_mouse_internal_location(x, y);
sys_sleep(cursor_move_time);
}
+1 -2
View File
@@ -129,7 +129,6 @@ struct _ags {
int text_decoration_color;
bool mouse_warp_enabled;
bool enable_zb;
bool noantialias; /* antialias を使用しない */
bool noimagecursor; /* リソースファイルのカーソルを読みこまない */
};
@@ -138,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);
+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__ */
+6 -3
View File
@@ -111,11 +111,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);
}
+62 -12
View File
@@ -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;
@@ -244,7 +264,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 +309,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 +320,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 +354,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 +449,7 @@ void cg_load(int no, int flg) {
}
/* clear display offset */
clear_display_loc();
release_cg(cg);
}
/*
@@ -413,7 +458,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 +466,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 +490,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 +604,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);
}
}
@@ -650,8 +700,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;
+4
View File
@@ -25,6 +25,7 @@
#define __CG__
#include <SDL_rect.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;
}
+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);
+3 -20
View File
@@ -114,8 +114,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);
}
@@ -377,24 +376,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);
}
+32
View File
@@ -23,6 +23,9 @@
#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 +210,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 +392,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
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;
+343 -147
View File
@@ -19,10 +19,15 @@
*/
#include "config.h"
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <SDL_ttf.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"
@@ -34,181 +39,366 @@
#include "portab.h"
#include "system.h"
#include "cache.h"
#include "font.h"
#include "gfx.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 `rw`. 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_rwops(SDL_RWops *rw, int index) {
Sint64 size = SDL_RWsize(rw);
if (size <= 0) {
SDL_RWclose(rw);
return NULL;
}
FT_Byte *buf = malloc(size);
if (SDL_RWread(rw, buf, 1, size) != (size_t)size) {
free(buf);
SDL_RWclose(rw);
return NULL;
}
SDL_RWclose(rw);
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_RWops *res = open_resource(name, "fonts");
if (res)
face = face_from_rwops(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_RWFromFile. (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_RWops *rw = SDL_RWFromFile(name, "rb");
if (rw)
face = face_from_rwops(rw, 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 = dst->format->BytesPerPixel;
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_CreateRGBSurfaceWithFormat(0, width, font.height, 32, SDL_PIXELFORMAT_ARGB8888);
if (sf)
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_BLEND);
} else {
sf = SDL_CreateRGBSurfaceWithFormat(0, 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(sf->format->palette, pal, 0, 2);
SDL_SetColorKey(sf, SDL_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_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;
SDL_Color color = gfx_getPaletteColor(cl);
if (antialias)
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, color);
else
fs = TTF_RenderUTF8_Solid(fontset->id, str_utf8, color);
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) {
@@ -216,8 +406,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) {
@@ -228,6 +420,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();
+8 -3
View File
@@ -25,6 +25,7 @@
#define __FONT_H__
#include <SDL_surface.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.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(a->format->format, b->format->format);
int row_bytes = a->w * a->format->BytesPerPixel;
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_FreeSurface(first);
SDL_FreeSurface(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(s->format->BitsPerPixel, aa ? 32 : 8);
SDL_FreeSurface(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();
}
+34 -1
View File
@@ -38,6 +38,7 @@
#include "gfx_private.h"
#include "font.h"
#include "ags.h"
#include "hacks.h"
#include "image.h"
#include "nact.h"
#include "modal.h"
@@ -317,8 +318,40 @@ void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col) {
}
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 (main_surface->format->BitsPerPixel == 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_FreeSurface(fs);
if (r_dst.y < 0) {
r_dst.h += r_dst.y;
r_dst.y = 0;
}
return r_dst;
}
/*
+2 -5
View File
@@ -109,14 +109,11 @@ 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);
SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, SDL_WINDOW_RESIZABLE);
gfx_renderer = SDL_CreateRenderer(gfx_window, -1, 0);
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
gfx_palette = SDL_AllocPalette(256);
+47 -125
View File
@@ -148,118 +148,31 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
#include "utfsjis.h"
// The string-input dialog.
// The input field is not a microui textbox: to support IME (SDL_TEXTEDITING)
// composition we keep our own buffer (str_buf) and feed it from the handler,
// mirroring editor.c.
struct string_state {
modal base;
INPUTSTRING_PARAM *param;
char composing[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; // IME preedit text
bool done;
bool accepted;
FontSpec font; // inline edit (input_modal_string_inline) only
bool focus_init; // focus the text field on the first frame
FontSpec font; // inline edit (input_modal_string_inline) only
};
// Heap buffer holding the string dialog's edited text. It must outlive the
// modal loop because the caller reads p->newstring after input_modal_string()
// returns; it is freed and reallocated on the next call.
// Heap buffer holding the edited text. It must outlive the modal loop because
// the caller reads p->newstring after input_modal_string() returns; it is freed
// and reallocated on the next call.
static char *str_buf;
static int str_bufsz;
// Append `add` to str_buf, truncating so the total stays within
// st->param->max characters (counted as UTF-8 code points).
static void str_append(struct string_state *st, const char *add) {
int room = st->param->max;
for (const char *p = str_buf; *p; p = advance_char(p, UTF8))
room--;
const char *end = add;
while (room > 0 && *end) {
end = advance_char(end, UTF8);
room--;
}
strncat(str_buf, add, end - add);
}
// Delete the last UTF-8 character of str_buf.
static void str_backspace(void) {
if (!*str_buf)
return;
char *p = str_buf + strlen(str_buf) - 1;
while (p > str_buf && UTF8_TRAIL_BYTE(*p))
p--;
*p = '\0';
}
static bool menu_string_handler(const SDL_Event *e, modal *modal) {
struct string_state *st = (struct string_state *)modal;
switch (e->type) {
case SDL_KEYDOWN:
// While an IME composition is active, let it consume the key.
if (!*st->composing) {
switch (e->key.keysym.sym) {
case SDLK_RETURN:
st->accepted = true;
st->done = true;
break;
case SDLK_ESCAPE:
st->base.cancelled = true;
break;
case SDLK_BACKSPACE:
str_backspace();
break;
}
}
return true; // never forward keys to microui (there is no textbox)
case SDL_TEXTINPUT:
str_append(st, e->text.text);
st->composing[0] = '\0';
return true;
case SDL_TEXTEDITING:
strncpy(st->composing, e->edit.text, sizeof(st->composing) - 1);
st->composing[sizeof(st->composing) - 1] = '\0';
return true;
}
return modal_default_handler(e, modal);
}
// Tell SDL where the text input is, so the IME candidate window is positioned
// near the field. `box` is in logical (view) coordinates; convert to window
// pixels for SDL_SetTextInputRect.
static void set_text_input_rect(mu_Rect box) {
SDL_Rect r = gfx_viewToWindowRect(
(SDL_Rect){box.x, box.y, box.w, box.h});
SDL_SetTextInputRect(&r);
static void str_buf_init(const char *initial, int max_chars) {
free(str_buf);
str_bufsz = max_chars * MAX_UTF8_BYTES_PAR_CHAR + 1;
str_buf = malloc(str_bufsz);
strncpy(str_buf, initial ? initial : "", str_bufsz - 1);
str_buf[str_bufsz - 1] = '\0';
}
#ifndef _WIN32 // input_modal_string is provided by win/dialog.c on Windows.
// Draw the IME-aware text field (committed text, preedit with underline, caret).
static void draw_string_field(mu_Context *ctx, mu_Rect box, const struct string_state *st) {
mu_draw_rect(ctx, box, ctx->style->colors[MU_COLOR_BASE]);
mu_draw_box(ctx, box, ctx->style->colors[MU_COLOR_BORDER]);
mu_Font font = ctx->style->font;
mu_Color col = ctx->style->colors[MU_COLOR_TEXT];
int th = ctx->text_height(font);
int x = box.x + ctx->style->padding;
int y = box.y + (box.h - th) / 2;
mu_push_clip_rect(ctx, box);
if (*str_buf) {
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), col);
x += ctx->text_width(font, str_buf, -1);
}
if (*st->composing) {
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), col);
int cw = ctx->text_width(font, st->composing, -1);
mu_draw_rect(ctx, mu_rect(x, y + th, cw, 1), col); // preedit underline
x += cw;
}
mu_draw_rect(ctx, mu_rect(x, y, 1, th), col); // caret
mu_pop_clip_rect(ctx);
set_text_input_rect(box);
}
static bool inputstring_build(mu_Context *ctx, modal *modal) {
struct string_state *st = (struct string_state *)modal;
const char *title = (st->param->title && *st->param->title)
@@ -278,7 +191,17 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
snprintf(info, sizeof(info), _("Up to %d characters"), st->param->max);
mu_label(ctx, info);
draw_string_field(ctx, mu_layout_next(ctx), st);
mu_Id id = mu_get_id(ctx, "value", 5);
mu_Rect box = mu_layout_next(ctx);
if (st->focus_init) {
mu_set_kb_focus(ctx, id);
st->focus_init = false;
}
if (mu_textbox_raw(ctx, str_buf, str_bufsz, st->param->max, id, box, 0)
& MU_RES_SUBMIT) {
st->accepted = true;
st->done = true;
}
int content = w - ctx->style->padding * 2;
int half = (content - ctx->style->spacing) / 2;
@@ -303,16 +226,12 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
bool input_modal_string(INPUTSTRING_PARAM *p) {
struct string_state st = {
.base = { .build = inputstring_build, .handler = menu_string_handler },
.base = { .build = inputstring_build, .handler = modal_default_handler },
.param = p,
.focus_init = true,
};
free(str_buf);
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
strcpy(str_buf, p->oldstring ? p->oldstring : "");
SDL_StartTextInput();
str_buf_init(p->oldstring, p->max);
modal_run(&st.base);
SDL_StopTextInput();
p->newstring = st.accepted ? str_buf : p->oldstring;
return true;
@@ -320,8 +239,8 @@ bool input_modal_string(INPUTSTRING_PARAM *p) {
#endif // !_WIN32
// The inline text input (MJ command). It reuses the IME buffer handling
// and the event handler of input_modal_string.
// The inline text input (MJ command). It shares the textbox input handling but
// draws itself with the game's font and palette.
// Look up an AGS color index in the active palette and return it as a mu_Color.
static mu_Color palette_color(int index) {
@@ -342,6 +261,17 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
// all drawing below is done manually with the game's colors.
if (mu_begin_window_ex(ctx, "editstr", rect,
MU_OPT_NOFRAME | MU_OPT_NOTITLE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL)) {
mu_Id id = mu_get_id(ctx, "editstr", 7);
if (st->focus_init) {
mu_set_kb_focus(ctx, id);
st->focus_init = false;
}
if (mu_textbox_input(ctx, str_buf, str_bufsz, st->param->max, id, rect, 0)
& MU_RES_SUBMIT) {
st->accepted = true;
st->done = true;
}
mu_Color bg = palette_color(nact->msg.WinBackgroundColor);
mu_Color fg = palette_color(nact->msg.MsgFontColor);
mu_Font font = (mu_Font)&st->font;
@@ -354,14 +284,13 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), fg);
x += ctx->text_width(font, str_buf, -1);
}
if (*st->composing) {
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), fg);
int cw = ctx->text_width(font, st->composing, -1);
if (*ctx->preedit) {
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(x, y), fg);
int cw = ctx->text_width(font, ctx->preedit, -1);
mu_draw_rect(ctx, mu_rect(x, y + fh, cw, 2), fg); // preedit underline
x += cw;
}
mu_draw_rect(ctx, mu_rect(x, y, 2, fh), fg); // caret
set_text_input_rect(rect);
mu_end_window(ctx);
}
@@ -372,18 +301,14 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
bool input_modal_string_inline(INPUTSTRING_PARAM *p) {
struct string_state st = {
.base = { .build = inline_edit_build, .handler = menu_string_handler,
.base = { .build = inline_edit_build, .handler = modal_default_handler,
.no_dim = true },
.param = p,
.focus_init = true,
.font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, p->h },
};
free(str_buf);
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
strcpy(str_buf, p->oldstring ? p->oldstring : "");
SDL_StartTextInput();
str_buf_init(p->oldstring, p->max);
modal_run(&st.base);
SDL_StopTextInput();
p->newstring = st.accepted ? str_buf : NULL;
return true;
@@ -453,10 +378,10 @@ static bool inputnumber_build(mu_Context *ctx, modal *modal) {
mu_Id id = mu_get_id(ctx, "value", 5);
mu_Rect box = mu_layout_next(ctx);
if (st->focus_init) {
mu_set_focus(ctx, id);
mu_set_kb_focus(ctx, id);
st->focus_init = false;
}
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), id, box, 0);
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), 0, id, box, 0);
if (mu_button(ctx, "-"))
num_adjust(st, -1);
if (mu_button(ctx, "+"))
@@ -501,10 +426,7 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
.focus_init = true,
};
snprintf(st.buf, sizeof(st.buf), "%d", p->def);
SDL_StartTextInput();
modal_run(&st.base);
SDL_StopTextInput();
if (!st.accepted)
return false;
+171 -53
View File
@@ -68,7 +68,8 @@ static mu_Style default_style = {
{ 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */
{ 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */
{ 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */
{ 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */
{ 30, 30, 30, 255 }, /* MU_COLOR_SCROLLTHUMB */
{ 120, 170, 255, 255 } /* MU_COLOR_FOCUSRING */
}
};
@@ -144,6 +145,11 @@ void mu_begin(mu_Context *ctx) {
ctx->next_hover_root = NULL;
ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x;
ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y;
/* restart the keyboard-navigation scan */
ctx->nav_first = ctx->nav_last = 0;
ctx->nav_prev = ctx->nav_next = ctx->nav_cursor = 0;
ctx->kb_focus_seen = 0;
ctx->text_input = 0;
ctx->frame++;
}
@@ -171,6 +177,23 @@ void mu_end(mu_Context *ctx) {
if (!ctx->updated_focus) { ctx->focus = 0; }
ctx->updated_focus = 0;
/* move the keyboard focus. The new focus takes effect on the next frame,
** which is fine for a UI that is rebuilt every frame. */
if (!ctx->kb_focus_seen) { ctx->kb_focus = 0; }
if (ctx->key_pressed & (MU_KEY_TAB | MU_KEY_UP | MU_KEY_DOWN)) {
int back = (ctx->key_pressed & MU_KEY_UP) ||
((ctx->key_pressed & MU_KEY_TAB) && (ctx->key_down & MU_KEY_SHIFT));
if (!ctx->kb_focus) {
ctx->kb_focus = back ? ctx->nav_last : ctx->nav_first;
} else {
ctx->kb_focus = back ? (ctx->nav_prev ? ctx->nav_prev : ctx->nav_last)
: (ctx->nav_next ? ctx->nav_next : ctx->nav_first);
}
}
/* drop a composition left over from a textbox that lost the focus */
if (!ctx->text_input) { ctx->preedit[0] = '\0'; }
/* bring hover root to front if mouse was pressed */
if (ctx->mouse_pressed && ctx->next_hover_root &&
ctx->next_hover_root->zindex < ctx->last_zindex &&
@@ -216,6 +239,12 @@ void mu_set_focus(mu_Context *ctx, mu_Id id) {
}
void mu_set_kb_focus(mu_Context *ctx, mu_Id id) {
ctx->kb_focus = id;
ctx->kb_focus_seen = 1;
}
/* 32bit fnv-1a hash */
#define HASH_INITIAL 2166136261
@@ -402,6 +431,8 @@ void mu_input_scroll(mu_Context *ctx, int x, int y) {
void mu_input_keydown(mu_Context *ctx, int key) {
/* while an IME composition is active the keys belong to the IME */
if (*ctx->preedit) { return; }
ctx->key_pressed |= key;
ctx->key_down |= key;
}
@@ -413,10 +444,22 @@ void mu_input_keyup(mu_Context *ctx, int key) {
void mu_input_text(mu_Context *ctx, const char *text) {
/* SDL splits a long commit into several events, which all arrive within one
** frame and can overflow the buffer. Drop the excess instead of failing. */
int len = strlen(ctx->input_text);
int size = strlen(text) + 1;
expect(len + size <= (int) sizeof(ctx->input_text));
memcpy(ctx->input_text + len, text, size);
int n = mu_min((int) strlen(text), (int) sizeof(ctx->input_text) - 1 - len);
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
memcpy(ctx->input_text + len, text, n);
ctx->input_text[len + n] = '\0';
ctx->preedit[0] = '\0'; /* committing ends the composition */
}
void mu_input_preedit(mu_Context *ctx, const char *text) {
int n = mu_min((int) strlen(text), (int) sizeof(ctx->preedit) - 1);
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
memcpy(ctx->preedit, text, n);
ctx->preedit[n] = '\0';
}
@@ -652,6 +695,17 @@ void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect,
if (opt & MU_OPT_NOFRAME) { return; }
colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0;
ctx->draw_frame(ctx, rect, colorid);
if (ctx->kb_focus == id) {
mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_FOCUSRING]);
}
}
static int control_activated(mu_Context *ctx, mu_Id id) {
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { return 1; }
if (ctx->kb_focus == id &&
ctx->key_pressed & (MU_KEY_RETURN | MU_KEY_SPACE)) { return 1; }
return 0;
}
@@ -689,6 +743,22 @@ void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
if (opt & MU_OPT_NOINTERACT) { return; }
if (mouseover && !ctx->mouse_down) { ctx->hover = id; }
/* record this control in the keyboard focus cycle. Controls are visited in
** submission order, so tracking the previous one is enough to know both
** neighbours of kb_focus. */
if (~opt & MU_OPT_NOKBNAV && mu_check_clip(ctx, rect) != MU_CLIP_ALL) {
if (!ctx->nav_first) { ctx->nav_first = id; }
ctx->nav_last = id;
if (id == ctx->kb_focus) {
ctx->kb_focus_seen = 1;
ctx->nav_prev = ctx->nav_cursor;
} else if (ctx->kb_focus && !ctx->nav_next &&
ctx->nav_cursor == ctx->kb_focus) {
ctx->nav_next = id;
}
ctx->nav_cursor = id;
}
if (ctx->focus == id) {
if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); }
if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); }
@@ -697,6 +767,8 @@ void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
if (ctx->hover == id) {
if (ctx->mouse_pressed) {
mu_set_focus(ctx, id);
ctx->kb_focus = id; /* clicking moves the keyboard focus too */
ctx->kb_focus_seen = 1;
} else if (!mouseover) {
ctx->hover = 0;
}
@@ -742,7 +814,7 @@ int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) {
mu_Rect r = mu_layout_next(ctx);
mu_update_control(ctx, id, r, opt);
/* handle click */
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
if (control_activated(ctx, id)) {
res |= MU_RES_SUBMIT;
}
/* draw */
@@ -760,7 +832,7 @@ int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
mu_update_control(ctx, id, r, 0);
/* handle click */
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
if (control_activated(ctx, id)) {
res |= MU_RES_CHANGE;
*state = !*state;
}
@@ -775,54 +847,91 @@ int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
}
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r,
int opt)
/* number of characters in a utf-8 string */
static int utf8_strlen(const char *s) {
int n = 0;
for (; *s; s++) { if ((*s & 0xc0) != 0x80) { n++; } }
return n;
}
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars,
mu_Id id, mu_Rect r, int opt)
{
int res = 0;
mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
/* the keyboard focus decides which textbox is editable, so that Tab moves
** the caret out of it. A click reaches this through mu_update_control(). */
mu_update_control(ctx, id, r, opt);
if (ctx->kb_focus != id) { return 0; }
if (ctx->focus == id) {
/* handle text input */
int len = strlen(buf);
int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
if (n > 0) {
memcpy(buf + len, ctx->input_text, n);
len += n;
buf[len] = '\0';
res |= MU_RES_CHANGE;
}
/* handle backspace */
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
/* skip utf-8 continuation bytes */
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
buf[len] = '\0';
res |= MU_RES_CHANGE;
}
/* handle return */
if (ctx->key_pressed & MU_KEY_RETURN) {
mu_set_focus(ctx, 0);
res |= MU_RES_SUBMIT;
}
/* let the host turn the IME on and place its candidate window */
ctx->text_input = 1;
ctx->text_input_rect = r;
/* handle text input, honoring both the byte and the character limit */
int len = strlen(buf);
int room = bufsz - len - 1;
int chars = max_chars ? max_chars - utf8_strlen(buf) : bufsz;
int n = 0;
while (ctx->input_text[n] && chars > 0) {
int e = n + 1;
while ((ctx->input_text[e] & 0xc0) == 0x80) { e++; }
if (e > room) { break; }
n = e;
chars--;
}
if (n > 0) {
memcpy(buf + len, ctx->input_text, n);
len += n;
buf[len] = '\0';
res |= MU_RES_CHANGE;
}
/* handle backspace */
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
/* skip utf-8 continuation bytes */
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
buf[len] = '\0';
res |= MU_RES_CHANGE;
}
/* handle return */
if (ctx->key_pressed & MU_KEY_RETURN) { res |= MU_RES_SUBMIT; }
/* draw */
return res;
}
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r,
int opt)
{
mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
if (ctx->focus == id) {
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
mu_Font font = ctx->style->font;
int textw = ctx->text_width(font, buf, -1);
int texth = ctx->text_height(font);
int ofx = r.w - ctx->style->padding - textw - 1;
int textx = r.x + mu_min(ofx, ctx->style->padding);
int texty = r.y + (r.h - texth) / 2;
mu_push_clip_rect(ctx, r);
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
mu_pop_clip_rect(ctx);
} else {
if (ctx->kb_focus != id) {
mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
return;
}
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
mu_Font font = ctx->style->font;
int textw = ctx->text_width(font, buf, -1);
int prew = *ctx->preedit ? ctx->text_width(font, ctx->preedit, -1) : 0;
int texth = ctx->text_height(font);
int ofx = r.w - ctx->style->padding - textw - prew - 1;
int textx = r.x + mu_min(ofx, ctx->style->padding);
int texty = r.y + (r.h - texth) / 2;
mu_push_clip_rect(ctx, r);
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
if (prew) {
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(textx + textw, texty), color);
mu_draw_rect(ctx, mu_rect(textx + textw, texty + texth, prew, 1), color);
}
mu_draw_rect(ctx, mu_rect(textx + textw + prew, texty, 1, texth), color);
mu_pop_clip_rect(ctx);
}
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars,
mu_Id id, mu_Rect r, int opt)
{
int res = mu_textbox_input(ctx, buf, bufsz, max_chars, id, r, opt);
mu_draw_textbox(ctx, buf, id, r, opt);
return res;
}
@@ -836,8 +945,8 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
}
if (ctx->number_edit == id) {
int res = mu_textbox_raw(
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
if (res & MU_RES_SUBMIT || ctx->focus != id) {
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), 0, id, r, 0);
if (res & MU_RES_SUBMIT || ctx->kb_focus != id) {
*value = strtod(ctx->number_edit_buf, NULL);
ctx->number_edit = 0;
} else {
@@ -851,7 +960,7 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) {
mu_Id id = mu_get_id(ctx, &buf, sizeof(buf));
mu_Rect r = mu_layout_next(ctx);
return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
return mu_textbox_raw(ctx, buf, bufsz, 0, id, r, opt);
}
@@ -878,6 +987,12 @@ int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high,
v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
if (step) { v = ((long long)((v + step / 2) / step)) * step; }
}
/* left/right are not navigation keys, so they need no arbitration with the
** focus movement in mu_end() */
if (ctx->kb_focus == id && ctx->key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) {
mu_Real d = step ? step : (high - low) / 100;
v += (ctx->key_pressed & MU_KEY_RIGHT) ? d : -d;
}
/* clamp and store value, update res */
*value = v = mu_clamp(v, low, high);
if (last != v) { res |= MU_RES_CHANGE; }
@@ -916,6 +1031,9 @@ int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step,
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) {
*value += ctx->mouse_delta.x * step;
}
if (ctx->kb_focus == id && ctx->key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) {
*value += (ctx->key_pressed & MU_KEY_RIGHT) ? step : -step;
}
/* set flag if value changed */
if (*value != last) { res |= MU_RES_CHANGE; }
@@ -943,7 +1061,7 @@ static int header(mu_Context *ctx, const char *label, int istreenode, int opt) {
mu_update_control(ctx, id, r, 0);
/* handle click */
active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id);
active ^= control_activated(ctx, id);
/* update pool ref */
if (idx >= 0) {
@@ -1006,7 +1124,7 @@ void mu_end_treenode(mu_Context *ctx) {
base.w = ctx->style->scrollbar_size; \
\
/* handle input */ \
mu_update_control(ctx, id, base, 0); \
mu_update_control(ctx, id, base, MU_OPT_NOKBNAV); \
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \
cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \
} \
@@ -1111,7 +1229,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
/* do title text */
if (~opt & MU_OPT_NOTITLE) {
mu_Id id = mu_get_id(ctx, "!title", 6);
mu_update_control(ctx, id, tr, opt);
mu_update_control(ctx, id, tr, opt | MU_OPT_NOKBNAV);
mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt);
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
cnt->rect.x += ctx->mouse_delta.x;
@@ -1127,7 +1245,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
tr.w -= r.w;
mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]);
mu_update_control(ctx, id, r, opt);
mu_update_control(ctx, id, r, opt | MU_OPT_NOKBNAV);
if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) {
cnt->open = 0;
}
@@ -1141,7 +1259,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
int sz = ctx->style->title_height;
mu_Id id = mu_get_id(ctx, "!resize", 7);
mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
mu_update_control(ctx, id, r, opt);
mu_update_control(ctx, id, r, opt | MU_OPT_NOKBNAV);
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x);
cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y);
+34 -3
View File
@@ -58,6 +58,7 @@ enum {
MU_COLOR_BASEFOCUS,
MU_COLOR_SCROLLBASE,
MU_COLOR_SCROLLTHUMB,
MU_COLOR_FOCUSRING,
MU_COLOR_MAX
};
@@ -88,7 +89,8 @@ enum {
MU_OPT_AUTOSIZE = (1 << 9),
MU_OPT_POPUP = (1 << 10),
MU_OPT_CLOSED = (1 << 11),
MU_OPT_EXPANDED = (1 << 12)
MU_OPT_EXPANDED = (1 << 12),
MU_OPT_NOKBNAV = (1 << 13) /* exclude from the keyboard focus cycle */
};
enum {
@@ -102,7 +104,13 @@ enum {
MU_KEY_CTRL = (1 << 1),
MU_KEY_ALT = (1 << 2),
MU_KEY_BACKSPACE = (1 << 3),
MU_KEY_RETURN = (1 << 4)
MU_KEY_RETURN = (1 << 4),
MU_KEY_TAB = (1 << 5),
MU_KEY_UP = (1 << 6),
MU_KEY_DOWN = (1 << 7),
MU_KEY_LEFT = (1 << 8),
MU_KEY_RIGHT = (1 << 9),
MU_KEY_SPACE = (1 << 10)
};
@@ -179,6 +187,17 @@ struct mu_Context {
mu_Style *style;
mu_Id hover;
mu_Id focus;
/* Unlike `focus`, which only lives for as long as a mouse button is held,
** `kb_focus` persists across frames and is moved by Tab / arrow keys. The
** nav_* fields are rebuilt every frame as mu_update_control() walks the
** controls in submission order. */
mu_Id kb_focus;
mu_Id nav_first; /* first navigable control of this frame */
mu_Id nav_last; /* last navigable control of this frame */
mu_Id nav_prev; /* control registered just before kb_focus */
mu_Id nav_next; /* control registered just after kb_focus */
mu_Id nav_cursor; /* scan state: last registered control */
int kb_focus_seen; /* kb_focus was registered this frame */
mu_Id last_id;
mu_Rect last_rect;
int last_zindex;
@@ -210,6 +229,11 @@ struct mu_Context {
int key_down;
int key_pressed;
char input_text[32];
char preedit[32]; /* IME composition text, not yet committed */
/* Set by mu_textbox_input() while a textbox has the keyboard focus, so the
** host can enable the IME and place its candidate window. */
int text_input;
mu_Rect text_input_rect;
};
@@ -221,6 +245,7 @@ void mu_init(mu_Context *ctx);
void mu_begin(mu_Context *ctx);
void mu_end(mu_Context *ctx);
void mu_set_focus(mu_Context *ctx, mu_Id id);
void mu_set_kb_focus(mu_Context *ctx, mu_Id id);
mu_Id mu_get_id(mu_Context *ctx, const void *data, int size);
void mu_push_id(mu_Context *ctx, const void *data, int size);
void mu_pop_id(mu_Context *ctx);
@@ -243,6 +268,7 @@ void mu_input_scroll(mu_Context *ctx, int x, int y);
void mu_input_keydown(mu_Context *ctx, int key);
void mu_input_keyup(mu_Context *ctx, int key);
void mu_input_text(mu_Context *ctx, const char *text);
void mu_input_preedit(mu_Context *ctx, const char *text);
mu_Command* mu_push_command(mu_Context *ctx, int type, int size);
int mu_next_command(mu_Context *ctx, mu_Command **cmd);
@@ -278,7 +304,12 @@ void mu_text(mu_Context *ctx, const char *text);
void mu_label(mu_Context *ctx, const char *text);
int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt);
int mu_checkbox(mu_Context *ctx, const char *label, int *state);
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt);
/* Edit `buf` in place, without drawing. `max_chars` limits the length in UTF-8
** characters (0 for no limit beyond `bufsz`). Use this with mu_draw_textbox(),
** or with custom drawing when the default appearance does not fit. */
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r, int opt);
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt);
int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt);
int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt);
+45 -9
View File
@@ -61,13 +61,13 @@ static const FontSpec menu_font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, MODAL_FONT_
static int text_width_cb(mu_Font font, const char *text, int len) {
int w;
font_measure_text(*(const FontSpec *)font, text, len, &w, NULL);
font_measure_text(*(const FontSpec *)font, text, len, true, &w, NULL);
return w;
}
static int text_height_cb(mu_Font font) {
int h;
font_measure_text(*(const FontSpec *)font, "", -1, NULL, &h);
font_measure_text(*(const FontSpec *)font, "", -1, true, NULL, &h);
return h;
}
@@ -89,6 +89,7 @@ static void set_light_style(mu_Style *style) {
[MU_COLOR_BASEFOCUS] = { 235, 235, 242, 255 },
[MU_COLOR_SCROLLBASE] = { 200, 200, 200, 255 },
[MU_COLOR_SCROLLTHUMB] = { 170, 170, 170, 255 },
[MU_COLOR_FOCUSRING] = { 40, 90, 200, 255 },
};
memcpy(style->colors, light, sizeof(light));
}
@@ -119,6 +120,9 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
case SDL_TEXTINPUT:
mu_input_text(ctx, e->text.text);
break;
case SDL_TEXTEDITING:
mu_input_preedit(ctx, e->edit.text);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
int b = 0;
@@ -160,15 +164,23 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE)
// Esc cancels an IME composition first, not the modal.
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE &&
!*ctx->preedit)
modal->cancelled = true;
int c = 0;
switch (e->key.keysym.sym) {
case SDLK_LSHIFT: case SDLK_RSHIFT: c = MU_KEY_SHIFT; break;
case SDLK_LCTRL: case SDLK_RCTRL: c = MU_KEY_CTRL; break;
case SDLK_LALT: case SDLK_RALT: c = MU_KEY_ALT; break;
case SDLK_RETURN: c = MU_KEY_RETURN; break;
case SDLK_BACKSPACE: c = MU_KEY_BACKSPACE; break;
case SDLK_LSHIFT: case SDLK_RSHIFT: c = MU_KEY_SHIFT; break;
case SDLK_LCTRL: case SDLK_RCTRL: c = MU_KEY_CTRL; break;
case SDLK_LALT: case SDLK_RALT: c = MU_KEY_ALT; break;
case SDLK_RETURN: case SDLK_KP_ENTER: c = MU_KEY_RETURN; break;
case SDLK_BACKSPACE: c = MU_KEY_BACKSPACE; break;
case SDLK_TAB: c = MU_KEY_TAB; break;
case SDLK_UP: c = MU_KEY_UP; break;
case SDLK_DOWN: c = MU_KEY_DOWN; break;
case SDLK_LEFT: c = MU_KEY_LEFT; break;
case SDLK_RIGHT: c = MU_KEY_RIGHT; break;
case SDLK_SPACE: c = MU_KEY_SPACE; break;
}
if (!c)
break;
@@ -186,7 +198,7 @@ static void render_text(mu_Font font, const char *str, mu_Vec2 pos, mu_Color col
if (!*str)
return;
SDL_Color col = { color.r, color.g, color.b, color.a };
SDL_Surface *s = font_render_text(*(const FontSpec *)font, str, col);
SDL_Surface *s = font_render_text(*(const FontSpec *)font, str, col, true);
if (!s)
return;
SDL_Texture *t = SDL_CreateTextureFromSurface(gfx_renderer, s);
@@ -286,6 +298,27 @@ static void modal_render(void) {
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
}
// Turn SDL's text input on while a textbox has the keyboard focus, and keep the
// IME candidate window near it. The rect is in logical (view) coordinates.
static void update_text_input(void) {
static SDL_Rect last_rect;
if (!ctx->text_input) {
if (SDL_IsTextInputActive())
SDL_StopTextInput();
return;
}
if (!SDL_IsTextInputActive()) {
SDL_StartTextInput();
last_rect = (SDL_Rect){0, 0, 0, 0};
}
mu_Rect r = ctx->text_input_rect;
SDL_Rect wr = gfx_viewToWindowRect((SDL_Rect){r.x, r.y, r.w, r.h});
if (memcmp(&wr, &last_rect, sizeof(wr))) {
last_rect = wr;
SDL_SetTextInputRect(&wr);
}
}
bool modal_handle_event(const SDL_Event *e) {
if (!current_modal)
return false;
@@ -334,6 +367,7 @@ void modal_run(modal *m) {
mu_begin(ctx);
open = m->build(ctx, m);
mu_end(ctx);
update_text_input();
// Present only when the overlay changed.
mu_Id hash = mu_get_id(ctx, ctx->command_list.items, ctx->command_list.idx);
@@ -348,6 +382,8 @@ void modal_run(modal *m) {
current_modal = NULL;
free(ctx);
ctx = NULL;
if (SDL_IsTextInputActive())
SDL_StopTextInput();
gfx_requestRedraw(); // repaint once more to clear the overlay
// The gesture that opened/dismissed the dialog must not leave the engine
+5 -3
View File
@@ -167,9 +167,11 @@ void nact_init() {
sel_init();
nact->patch_ec = 1; // TODO: revisit
nact->patch_emen = 0;
nact->patch_g0 = 0;
// The EC bug was introduced in System 3.8. Older games expect the
// patch_ec=true behavior.
nact->patch_ec = !sl_is_s380;
nact->patch_emen = false;
nact->patch_g0 = false;
msg_msgHankakuMode = 0;
}
+4 -4
View File
@@ -97,10 +97,10 @@ typedef struct {
/* 選択肢関連 */
sel_t sel;
/* patch 関連 */
int patch_ec; /* see patch_ec command */
int patch_emen; /* see patch_emen command */
int patch_g0; /* see patch g0 command */
/* patch flags */
bool patch_ec;
bool patch_emen;
bool patch_g0;
/* ain 関連 */
S39AIN ain;
+6 -8
View File
@@ -182,15 +182,13 @@ static void extract_alpha(qnt_header *qnt, uint8_t *pic, uint8_t *b) {
w = qnt->width;
h = qnt->height;
i = 1;
if (w > 1) {
pic[0] = raw[0];
for (x = 1; x < w; x++) {
pic[x] = pic[x-1] - raw[i];
i++;
}
if (w%2) i++;
i = 0;
pic[0] = raw[i++];
for (x = 1; x < w; x++) {
pic[x] = pic[x-1] - raw[i];
i++;
}
if (w%2) i++;
if (h > 1) {
for (y = 1; y < h; y++) {
+32 -2
View File
@@ -22,8 +22,8 @@
#include <stdio.h>
#include <stdlib.h>
static void odd_size_test(void) {
FILE *fp = fopen("testdata/odd-size.qnt", "rb");
static uint8_t *read_file(const char *path) {
FILE *fp = fopen(path, "rb");
ASSERT_TRUE(fp);
fseek(fp, 0, SEEK_END);
@@ -32,6 +32,11 @@ static void odd_size_test(void) {
uint8_t *data = malloc(size);
ASSERT_EQUAL(fread(data, 1, size, fp), size);
fclose(fp);
return data;
}
static void odd_size_test(void) {
uint8_t *data = read_file("testdata/odd-size.qnt");
cgdata *cg = qnt_extract(data);
ASSERT_EQUAL(cg->width, 3);
@@ -58,6 +63,31 @@ static void odd_size_test(void) {
free(data);
}
static void width1_test(void) {
uint8_t *data = read_file("testdata/width1.qnt");
cgdata *cg = qnt_extract(data);
ASSERT_EQUAL(cg->width, 1);
ASSERT_EQUAL(cg->height, 3);
ASSERT_TRUE(cg->alpha);
const uint8_t expected[] = {
0x10, 0x20, 0x30,
0x40, 0x50, 0x60,
0x70, 0x80, 0x90,
};
ASSERT_TRUE(!memcmp(cg->pic, expected, sizeof(expected)));
const uint8_t expected_alpha[] = { 0xff, 0x80, 0x40 };
ASSERT_TRUE(!memcmp(cg->alpha, expected_alpha, sizeof(expected_alpha)));
free(cg->pic);
free(cg->alpha);
free(cg);
free(data);
}
void qnt_test(void) {
odd_size_test();
width1_test();
}
+2
View File
@@ -36,6 +36,7 @@
const uint8_t *sl_sco;
int sl_page;
int sl_index;
bool sl_is_s380;
static void pop_state(uint8_t tag);
@@ -102,6 +103,7 @@ bool sl_init(void) {
stack_top = stack_buf;
sl_jmpFar(0);
sl_is_s380 = !memcmp(sl_sco, "S380", 4);
return true;
}
+2
View File
@@ -54,6 +54,8 @@ struct stack_frame_info {
uint8_t *p; // for internal use
};
extern bool sl_is_s380;
// Use functions below instead of accessing these variables directly.
extern const uint8_t *sl_sco; // scenario page buffer
extern int sl_page; // current scenario page (0-based)
+8 -27
View File
@@ -303,7 +303,7 @@ static void init_selwindow() {
}
for (i = 0; i < regnum; i++) {
TRACE_MESSAGE("%d:%s\n", i +1, elm[i]);
ags_drawString(r.x +2, r.y + i * (sel.MsgFontSize +2) +1, elm[i], sel.MsgFontColor, sel.MsgFontSize, NULL);
ags_drawString(r.x +2, r.y + i * (sel.MsgFontSize +2) +2, elm[i], sel.MsgFontColor, sel.MsgFontSize, NULL);
}
ags_updateArea(saveArea.x, saveArea.y, saveArea.w, saveArea.h);
@@ -376,31 +376,12 @@ static void lineEncloseElement(SDL_Rect *r, int col, bool thick) {
static void encloseElement(int sw, int no) {
SDL_Rect *r = &workR[no];
if (sw == 0) { /* off */
if (sel.WinBackgroundTransparent != 255) {
ags_restoreRegion(saveimg2, r->x, r->y);
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
saveimg2 = NULL;
} else {
switch(sel.EncloseType) {
case 0:
lineEncloseElement(r, sel.WinBackgroundColor, true); break;
case 1:
lineEncloseElement(r, sel.WinBackgroundColor, false); break;
case 2:
ags_fillRectangle(r->x, r->y, r->w +2, r->h +2, sel.WinBackgroundColor);
ags_drawString(r->x +2, r->y +1, elm[no], sel.MsgFontColor, sel.MsgFontSize, NULL);
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
break;
default:
break;
}
}
} else { /* on */
if (sel.WinBackgroundTransparent != 255) {
saveimg2 = ags_saveRegion(r->x, r->y, r->w +2, r->h +2);
}
if (sw == 0) {
ags_restoreRegion(saveimg2, r->x, r->y);
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
saveimg2 = NULL;
} else {
saveimg2 = ags_saveRegion(r->x, r->y, r->w +2, r->h +2);
switch(sel.EncloseType) {
case 0:
lineEncloseElement(r, sel.WinFrameColor, true); break;
@@ -408,7 +389,7 @@ static void encloseElement(int sw, int no) {
lineEncloseElement(r, 255, false); break;
case 2:
ags_fillRectangle(r->x, r->y, r->w +2, r->h +2, sel.MsgFontColor);
ags_drawString(r->x +2, r->y +1, elm[no], sel.WinBackgroundColor, sel.MsgFontSize, NULL);
ags_drawString(r->x +2, r->y +2, elm[no], sel.WinBackgroundColor, sel.MsgFontSize, NULL);
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
break;
default:
+4
View File
@@ -20,6 +20,8 @@
#include <stdio.h>
#include <stdlib.h>
void font_test(void);
void cache_test(void);
void gameresource_test(void);
void hankaku_test(void);
void qnt_test(void);
@@ -41,6 +43,8 @@ void sys_message(int level, char *format, ...) {
}
int main() {
cache_test();
font_test();
gameresource_test();
variable_test();
hankaku_test();
-5
View File
@@ -83,12 +83,7 @@ void sys_error(char *format, ...) {
void sys_exit(int code) {
sys35_remove();
#ifdef __EMSCRIPTEN__
EM_ASM( xsystem35.shell.quit(); );
sys_sleep(1000000000);
#else
exit(code);
#endif
}
uint32_t sys_get_ticks(void) {
BIN
View File
Binary file not shown.
+3 -9
View File
@@ -87,7 +87,6 @@ static char fontface[FONTTYPEMAX];
static const char *savedir;
static bool font_noantialias;
static bool enable_zb = false;
static bool integer_scaling = false;
/* fullscreen on from command line */
static bool fs_on;
@@ -147,13 +146,13 @@ static void sys35_usage(bool verbose) {
static void sys35_init() {
int i;
nact_init();
sl_init();
nact_init();
v_init();
ags_init(render_driver, enable_zb);
ags_init(render_driver);
for (i = 0; i < FONTTYPEMAX; i++)
font_set_name_and_index(i, fontname_tt[i], fontface[i]);
@@ -264,8 +263,6 @@ static void sys35_ParseOption(int *argc, char **argv) {
fs_on = true;
} else if (0 == strcmp(argv[i], "-noantialias")) {
font_noantialias = true;
} else if (0 == strcmp(argv[i], "-enable_zb")) {
enable_zb = true;
} else if (0 == strcmp(argv[i], "-ttfont_gothic")) {
if (argv[i + 1] != NULL) {
fontname_tt[FONT_GOTHIC] = argv[i + 1];
@@ -332,9 +329,6 @@ static void check_profile() {
font_noantialias = !antialias;
}
/* Enable ZB command (font weight) */
get_boolean_profile("enable_zb", &enable_zb);
/* Audio buffer size */
param = get_profile("audio_buffer_size");
if (param) {
+46
View File
@@ -0,0 +1,46 @@
MS FILE,"patchec.adv":
; --- EC with a registered rectangle ---
; patchEC defaults to off, so EC clears a region 1px narrower and
; shorter than the rectangle registered by ES.
CF 100,100,40,30,7:
ES 1,3,100,100,40,30:
EC 1:
PT0 RND,120,115: ~AssertEquals RND,3,__LINE__:
PT0 RND,138,128: ~AssertEquals RND,3,__LINE__:
PT0 RND,139,115: ~AssertEquals RND,7,__LINE__:
PT0 RND,120,129: ~AssertEquals RND,7,__LINE__:
; patchEC 1 disables the adjustment, so EC clears the whole rectangle.
CF 100,100,40,30,7:
patchEC 1:
EC 1:
PT0 RND,139,115: ~AssertEquals RND,3,__LINE__:
PT0 RND,120,129: ~AssertEquals RND,3,__LINE__:
; Only eFlag == 1 turns the patch on; any other value restores the bug.
CF 100,100,40,30,7:
patchEC 2:
EC 1:
PT0 RND,139,115: ~AssertEquals RND,7,__LINE__:
patchEC 0:
; --- EC 0 clears the current view area, now with the same adjustment ---
ZZ5 D18:
WV 100,100,50,40:
CF 100,100,50,40,7:
EC 0:
PT0 RND,120,120: ~AssertEquals RND,0,__LINE__:
PT0 RND,148,138: ~AssertEquals RND,0,__LINE__:
PT0 RND,149,120: ~AssertEquals RND,7,__LINE__:
PT0 RND,120,139: ~AssertEquals RND,7,__LINE__:
CF 100,100,50,40,7:
patchEC 1:
EC 0:
PT0 RND,149,120: ~AssertEquals RND,0,__LINE__:
PT0 RND,120,139: ~AssertEquals RND,0,__LINE__:
patchEC 0:
WV 0,0,D18,D19:
%0:
+36
View File
@@ -0,0 +1,36 @@
MS FILE,"patchemen.adv":
ES 1,0,10,10,20,20:
ES 2,0,20,20,20,20:
; --- EM: point-in-rectangle test ---
; patchEMEN defaults to off, so the rectangle is treated as 1px
; narrower and shorter than registered by ES.
EM 1,RND,10,10: ~AssertEquals RND,1,__LINE__:
EM 1,RND,28,28: ~AssertEquals RND,1,__LINE__:
EM 1,RND,29,20: ~AssertEquals RND,0,__LINE__:
EM 1,RND,20,29: ~AssertEquals RND,0,__LINE__:
EM 1,RND,9,10: ~AssertEquals RND,0,__LINE__:
; patchEMEN 1 disables the adjustment.
patchEMEN 1:
EM 1,RND,29,29: ~AssertEquals RND,1,__LINE__:
EM 1,RND,30,20: ~AssertEquals RND,0,__LINE__:
; Only eFlag == 1 turns the patch on; any other value restores the bug.
patchEMEN 2:
EM 1,RND,29,29: ~AssertEquals RND,0,__LINE__:
patchEMEN 0:
; --- EN: number of the rectangle containing the point ---
EN RND,1,2,15,15: ~AssertEquals RND,1,__LINE__:
EN RND,1,2,25,25: ~AssertEquals RND,2,__LINE__:
EN RND,1,2,5,5: ~AssertEquals RND,0,__LINE__:
; The patch flag also applies to EN's edge handling.
EN RND,1,1,29,29: ~AssertEquals RND,0,__LINE__:
patchEMEN 1:
EN RND,1,1,29,29: ~AssertEquals RND,1,__LINE__:
patchEMEN 0:
%0:
+2
View File
@@ -16,6 +16,8 @@
%#math.adv:
%#variable.adv:
%#unitmap.adv:
%#patchec.adv:
%#patchemen.adv:
HH0,tests_passed: ' tests passed' R
HH0,tests_failed: ' tests failed' R
+2
View File
@@ -8,6 +8,8 @@ stack.adv
math.adv
variable.adv
unitmap.adv
patchec.adv
patchemen.adv
#DLLHeader
ShString.HEL
BIN
View File
Binary file not shown.
-7
View File
@@ -115,10 +115,3 @@
# per line, with '#' treated as comments.
#censor: censor_list.txt
# Enables the ZB command (change the font weight). It is disabled by default
# because it makes the text too bold with our default fonts.
# This option is experimental and may be removed in the future. See
# https://github.com/kichikuou/xsystem35-sdl2/issues/44 for details.
#enable_zb: Yes