Compare commits

...
84 Commits
Author SHA1 Message Date
kichikuou 50a6b17c5e Version 2.19.1 2026-08-02 12:41:18 +09:00
kichikuou 0d49619366 Fix unit map storage for 16-bit variables
Store unit map attributes as vmvar_t and derive VR/VW transfer sizes
from their element type. This prevents buffer overruns after vmvar_t
became uint16_t and fixes upward movement in Rance IV.

Add scenario coverage for both transfer directions and verify that
adjacent variables remain intact.
2026-08-02 12:34:40 +09:00
kichikuou da3cc5d900 ags: Consolidate display info accessors 2026-07-24 21:04:25 +09:00
kichikuou 1785e93fcc Refactor gfx API 2026-07-24 21:04:22 +09:00
kichikuou a77e849f82 Version 2.19.0 2026-07-19 12:53:09 +09:00
kichikuou 978dbdbc3b Tweak menu strings
Also, do not show "Mouse Movement" item on Android.
2026-07-19 12:46:03 +09:00
kichikuou e3c168c5a6 Android: Isolate game execution process
The launcher and GameActivity previously ran in the same process. Native
globals and library state therefore survived after returning to the
launcher, leaking static data from one game to the next game.

Run GameActivity in a private :game process and terminate it after SDL
shutdown completes, so every game starts with fresh native state. Keep
the launcher in the main process and refresh its game list from disk
when it resumes.
2026-07-19 09:09:21 +09:00
kichikuou 9f8c2af842 Emscripten: Fix ReferenceError for SE_VOLVAL_CH
EM_ASYNC_JS stringifies its function body without expanding macros. Pass
the BGM and sound-effect channel values through C wrapper functions so
the generated JavaScript receives numeric channel arguments.
2026-07-19 07:20:53 +09:00
kichikuou 5d8b1c5632 Change vmvar_t from int to uint16_t 2026-07-17 09:50:21 +09:00
kichikuou 30188c3a4f Match variable bounds with System 3.9
System 3.9 saturates compound addition and multiplication at 65535,
while an overflowing ascending loop stores the wrapped 16-bit result
after testing the wide value.

Update the scenario expectations and regenerated fixture to cover both
cases. The suite passes with xsystem35 and the original System39.exe.
2026-07-17 09:50:02 +09:00
kichikuou a627a8ea3d Add VM variable storage type
Define a dedicated `vmvar_t` type for values stored in variable pages
and propagate it through interfaces that expose those values. Keep the
type as int for now so this commit does not change runtime behavior.
This separates VM storage from ordinary int buffers and makes a later
conversion to 16-bit storage mechanically checkable.
2026-07-17 08:48:10 +09:00
kichikuou 0cf9c1fe8d Add variable semantics tests
Cover variable page allocation, array reference handling, and Shift-JIS
conversion in src_tests.

Extend the scenario suite with 16-bit boundary behavior for assignments,
loops, module results, and save/load round trips.
2026-07-17 08:11:01 +09:00
kichikuou 0887c44292 Run scenario tests headlessly
Register the existing scenario suite with CTest using SDL's dummy
video and audio drivers.
2026-07-16 20:53:19 +09:00
kichikuou 27a0c89a73 QNT: Add odd-size image test 2026-07-10 08:54:22 +09:00
kichikuou 5c827225ab QNT: Calculate decompression buffers exactly
Round image dimensions up to even values for QNT's 2x2 pixel blocks, and
use the resulting exact size for pixel and alpha decompression.

Verification against Only You Re Cross's QNT files confirmed that the
former 1164-byte discrepancy is odd-height padding, not extra zlib
output. Remove the obsolete fixed margin.
2026-07-10 08:54:19 +09:00
kichikuou bc37ba6256 Require SDL2 >=2.18.0 2026-07-09 09:50:44 +09:00
kichikuou ab4bc2d8d1 Move more modules into src_lib 2026-07-09 07:16:59 +09:00
kichikuou f26321c5e0 Android: Add some comments to CdImageReader 2026-07-05 16:34:16 +09:00
kichikuou 1d897c72d0 Android: Add CD image unit tests
Add synthetic ISO9660, CUE, CCD, and MDS fixtures for JVM unit tests.
Cover CD image sector mapping, audio extraction, install cleanup,
playlist generation, and output path validation.
2026-07-05 16:21:17 +09:00
kichikuou 172c76b4e2 Update android/README.md 2026-07-05 14:51:50 +09:00
kichikuou 100584f896 Android: Support CCD and MDS CD images
Extend the Android CD image reader to accept .bin/.img + .ccd and
.mdf/.mds pairs. CCD metadata is converted into the existing track
model, and MDS descriptors provide data and audio track offsets for
extraction.
2026-07-05 14:48:05 +09:00
kichikuou 975f618b53 Android: Support .img/.cue installs
Extend the Android CD image reader to parse .bin/.img + .cue images
and map MODE1 data tracks into the ISO9660 extractor. Audio tracks are
extracted as WAV files under GAMEDATA/cdda, and playlist.txt is
generated from the CUE track numbers.
2026-07-05 14:20:51 +09:00
kichikuou 0c8bab0c64 Android: Add ISO CD image installer
Add a CD image install path that accepts selected files from the
launcher and extracts GAMEDATA from ISO9660 images. The installer
reuses the existing install transaction cleanup and writes
game_directory.txt so installed games launch through the normal
GameActivity path.
2026-07-05 13:37:15 +09:00
kichikuou 0192fd3d8e Android: Split launcher storage and install logic
Move ZIP installation and file-store responsibilities out of Launcher.
Launcher now coordinates install state and delegates game scanning,
save-data operations, and ZIP extraction to focused helper classes.
2026-07-05 12:28:57 +09:00
kichikuou d69fc5c6a3 Android: Keep launcher install state in Launcher
Move ZIP install jobs from GlobalScope to a Launcher-owned coroutine
scope and store progress, success, and failure in InstallState. This
lets a recreated LauncherActivity render the current install state and
consume completed results once, even when observer callbacks happened
while the activity was absent.
2026-07-05 11:24:40 +09:00
kichikuou 68a39843e2 Android: Prevent ZIP path traversal in launcher 2026-07-05 10:16:48 +09:00
kichikuou e3d7b60785 Emscripten: Apply volume-valancer levels to the shell 2026-07-04 16:17:31 +09:00
kichikuou 2bb891ada8 Move WAI parser to audio_meta.c (renamed from bgi.c) 2026-07-04 16:17:28 +09:00
kichikuou f109fe11f5 Replace event_custom_handler with modal_handle_event 2026-07-01 17:57:14 +09:00
kichikuou 73b3377b77 Add option to mute audio while the window is unfocused
Disabled by default; enable via the mute_on_unfocus rc setting or the
-mute_on_unfocus flag.

Fixes #37.
2026-07-01 17:36:58 +09:00
kichikuou 9400572502 Rename menu_render_overlay to modal_render_overlay 2026-07-01 17:26:04 +09:00
kichikuou a1e063c4b2 Enable the volume controller dialog on Emscripten
Export volume_dialog_open() so the web shell can open it, and add
modal_run to ASYNCIFY_ADD since it drives an async loop.
2026-07-01 17:21:49 +09:00
kichikuou 602c55a5e5 Enable UI translation on platforms without libintl
Compile po/ja.po into a built-in catalog and select it by locale via
SDL_GetPreferredLocales(), so translations work without libintl.
2026-07-01 13:53:18 +09:00
kichikuou 9624192a41 Provide native input/menu implementations directly on Windows
Let win/dialog.c and win/menubar.c define input_modal_string,
input_modal_number, menu_open and menu_init directly instead of
dispatching to them through wrappers.
2026-07-01 10:53:14 +09:00
kichikuou 5c7436e2b1 Factor out the confirmation dialog in menu.c 2026-07-01 10:24:52 +09:00
kichikuou 6c9844697f Merge menu_emscripten.c into menu.c
Consolidate the SDL and Emscripten menu backends into a single menu.c.
2026-07-01 10:11:32 +09:00
kichikuou 210c0a13e4 Reword the quit/restart confirmation dialogs 2026-07-01 09:23:44 +09:00
kichikuou 3ebb1faece Use the SDL popup menu on Android
The popup menu opens with a three-finger tap. Add touch support to the
modal framework: a tap is replayed as a multi-frame mouse
hover->press->release so microui registers the click, and the press is
held so sliders can be dragged. Fix a microui command-buffer alignment
bug that crashed on 32-bit ARM.
2026-07-01 09:23:38 +09:00
kichikuou 286f150510 Android/Emscripten: Move input dialogs into input_modal.c
Consolidate the Android and Emscripten implementations of the string/
number input dialogs into input_modal.c, and split the input-dialog API
out of menu.h into input_modal.h.
2026-07-01 08:22:14 +09:00
kichikuou a194cf10a6 Reimplement the windowless string input on the modal framework
Move edit_string into input_modal.c as an inline modal dialog, sharing the
IME buffer handling and event handler with menu_inputstring.
2026-07-01 08:22:09 +09:00
kichikuou 9587d350ec Split string/number input dialogs out of menu_sdl.c into input_modal.c 2026-06-30 14:17:44 +09:00
kichikuou 02f62d51bb Add Restart to the SDL popup menu
Also localize the quit/restart confirmation dialogs.
2026-06-30 12:34:14 +09:00
kichikuou 54b98ec3e4 Translate UI strings and guard the .pot against staleness
Translate the menu/dialog strings that were left untranslated. Add a CI
step that fails when po/xsystem35.pot is out of sync with the sources.

Also remove unused autotools gettext files.
2026-06-30 12:20:51 +09:00
kichikuou 8cde827ee6 modal: Reset input state when a modal dialog closes
The click (or keypress) that opened a modal dialog leaves its button
"down" in the engine, and the modal consumes the matching release, so
afterwards IK kept reporting a stuck mouse button (or key). Forget all
held keys/buttons when a modal closes.
2026-06-30 10:04:19 +09:00
kichikuou a5d67b067c Use System39.exe-compatible format for Volume.sav
Also, save Volume.sav when the volume dialog is closed.
2026-06-29 17:18:49 +09:00
kichikuou 5cb70110b9 Apply BGM_VOLVAL_CH to MIDI and CD playback
MIDI and CD are background music, so they should share the same volume
control as BGM. Route them through the BGM volume valancer channel.
2026-06-29 16:17:13 +09:00
kichikuou e3dcadd5dd Allow the volume dialog to work without VolumeValancer
When a game defines no VolumeValancer[], fall back to two generic
channels so BGM and sound effects can be adjusted independently.

Volume controls are now always available, so drop the conditional menu
handling.
2026-06-29 15:52:08 +09:00
kichikuou ff31d03153 Implement mus_vol_set_valance()
Apply the System39.ini volume valancer levels to audio output, so the
Volume controller dialog actually adjusts per-channel volumes. PCM scales
its chunk volume by the channel mapped via the .wai file, and BGM scales
its music volume by valancer channel 0.

Fixes #62.
2026-06-29 11:44:22 +09:00
kichikuou 63886d7d00 Make the volume dialog's close button functional 2026-06-29 11:44:21 +09:00
kichikuou c119c2f969 Enable the volume controller dialog on Windows
Rename s39init.{c,h} to volume.{c,h}, move the dialog code there behind
volume_dialog_open(), and add a "Sound Settings" item to the Windows
menu.
2026-06-29 11:44:18 +09:00
kichikuou fb63d42ec1 Remove popupmenu_opened flag
With the blocking modal_run(), the game's main loop is frozen while a
menu is open, so the flag's input-suppression and VM-pause checks are
unreachable. Quit requested during a modal is now handled by closing the
modal and letting it propagate through the main loop.
2026-06-28 13:27:39 +09:00
kichikuou 6f631d073d menu_sdl: Use a light color scheme
Override microui's dark default palette with a light scheme (light gray
windows, dark text) in init_context().
2026-06-28 13:27:39 +09:00
kichikuou 49dca57fbd Call setlocale() so menu translations work
gettext only honors LANG / LC_* once setlocale() has been called. This
used to happen inside gtk_init(); now that GTK is gone, call
setlocale(LC_ALL, "") ourselves before bindtextdomain(), otherwise the
locale stays "C" and the menu is always shown in English.
2026-06-28 13:27:39 +09:00
kichikuou c8987da470 menu: Remove GTK menu backend
Linux and macOS now both build the pure-SDL + microui menu. Remove GTK3
detection, the ENABLE_GTK build branches, and the GTK menu sources.

Decouple gettext NLS from ENABLE_GTK so menu labels stay translatable on all
desktop builds, and update i18n to the new sources (POTFILES.in, .pot, ja.po).
2026-06-28 13:27:35 +09:00
kichikuou 0dfb3eb378 menu_sdl: Implement volume controller panel
Rewrite s39init.c GTK-free: keep the System39.ini / Volume.sav loading,
saving and mus_vol_set_valance() logic, and expose the channel table via
s39ini_available() / s39ini_channels() instead of driving a GtkWidget.

Add a microui volume panel to menu_sdl.c, offered as a "Volume" item in
the popup menu when the game defines volume channels.
2026-06-28 13:08:25 +09:00
kichikuou 269132fede menu_sdl: Implement the string-input dialog
The text field is hand-drawn and IME-aware: SDL_TEXTINPUT /
SDL_TEXTEDITING / BACKSPACE are handled directly (mirroring editor.c)
instead of using a microui textbox.
2026-06-28 13:07:42 +09:00
kichikuou 651fc21a19 menu_sdl: Implement the number-input dialog 2026-06-28 09:49:22 +09:00
kichikuou 03f86bc4d7 menu: Add pure SDL popup menu using microui
Vendor microui 2.02 under src/microui/ and use it to draw an in-game
popup menu as an overlay.

modal.c provides generic modal infrastructure: a loop that pumps SDL
events into microui and renders the command list via SDL_Renderer,
hooked into gfx_updateScreen(). menu_sdl.c builds the concrete popup
menu on top of it.
2026-06-28 09:49:19 +09:00
kichikuou 4ffab8e4e9 Fix compiler warning in cg.c 2026-06-27 16:18:09 +09:00
kichikuou 1593452146 menu: Signal number-input cancellation via the return value
It was inconsistent across backends. Now: true = confirmed,
false = cancelled.
2026-06-25 13:25:29 +09:00
kichikuou 50d05e5b56 font: Replace stateful font_select() with explicit parameter
The font module kept a global "currently selected font" set by
font_select() and implicitly used when drawing. Make it stateless by
introducing a FontSpec {type, weight, size} that font_render_text() /
font_draw_text() and gfx_drawString() take directly.

Make FontWeight a two-valued enum and normalize the 1-9 weight to it
in commandZB().

Removes font_select(), ags_setFont() and ags_setFontWithWeight().
2026-06-24 16:11:49 +09:00
kichikuou c4026f9eb1 Version 2.18.0 2026-06-23 19:18:38 +09:00
kichikuou 3318bebf6c fonts: Replace mincho font with IPA Mincho
Switch the bundled mincho font from a Source Han Serif JP subset to a
subset of IPA Mincho, which has monospace ASCII glyphs.

IPA Mincho's hhea ascent/descent already match its OS/2 typo metrics, so
adjust_metrics.py is no longer needed. However, IPA Mincho is distributed
under the IPA Font License v1.0, under which a subset is a "Derived
Program"; Article 3.1(4) forbids the derived font's name from containing
the licensed program's name ("IPA"). Add rename_font.py to rewrite the
name table accordingly, and replace licenses/mincho.txt with the IPA Font
License.
2026-06-23 17:40:22 +09:00
kichikuou d4ad329583 game_compatibility.md: Fix typo 2026-06-18 07:34:45 +09:00
kichikuou 2e828710ca gtk: Fix popup menu on Wayland
gtk_menu_popup() is deprecated since GTK 3.22. Since no GdkEvent is
available at the call site, use gtk_menu_popup_at_rect() with the
current pointer position obtained via gdk_device_get_position().
2026-05-03 16:37:28 +09:00
kichikuou fad4236458 CI: Fix emscripten build failure 2026-04-12 22:43:27 +09:00
kichikuou 77d62c7c13 CI: Update GitHub Actions 2026-04-12 22:43:24 +09:00
kichikuou a726d577ef modules: Move SACT/sactcg.c to lib/ and use it from NIGHTDLL 2026-03-17 11:09:47 +09:00
kichikuou 4a5cd4a1c1 dDemo/tDemo: Fix .alk filename case sensitivity
Instead of hardcoding "dDEMO.alk" / "tDEMO.alk", use the actual filename
stored in nact->files.alk[0] from the directory scan. This fixes loading
failures on case-sensitive filesystems where the actual file may be
named "dDemo.alk" or "tDemo.alk".

Also update gameresource.c to store .alk files without a numeric suffix
at index 0 (these games have only one .alk file).
2026-03-15 11:13:08 +09:00
kichikuou 022d6af4f1 Implement dDemo HLL (Daiakuji intro demo) 2026-03-08 10:09:01 +09:00
kichikuou df816e3708 README.md: Specify supported audio formats (MP3, Ogg) 2026-01-17 07:32:26 +09:00
kichikuou 2c9079c875 Version 2.17.1 2026-01-10 16:13:16 +09:00
kichikuou 54b8d6c993 android/README.md: Add Obtainium installation instructions 2026-01-10 14:57:43 +09:00
kichikuou f2fe6b9724 SACT: Ctrl key cancels SACT.Wait and SACT.TimerWait
This fixes an issue where the roulette in Rance 5D could not be fast
forwarded using the Ctrl key.
2026-01-09 16:16:13 +09:00
kichikuou 38dc9a7b0b Refactor sacttimer.c 2026-01-09 13:14:48 +09:00
kichikuou d2ac3889b5 QE/LE: Handle for paths with subdirectories correctly
This resolves issues where saving to and loading from paths with
subdirectories (e.g., "Save\file.asd") would fail.

The original problems were:
 1. Writing failure: A path mismatch between directory creation in
    commandQE and file writing in fc_open.
 2. Loading failure: fc_search could not handle paths containing
    directory separators, preventing saved files in subdirectories from
    being found.

This commit addresses these issues with the following changes:
 - fc_open now automatically creates parent directories when writing a
   file.
 - fc_search is updated to support two modes:
     - It performs a case-insensitive search for simple filenames
       (preserving original behavior).
     - It performs a case-sensitive existence check for full paths.

Fixes #74.
2026-01-09 08:31:40 +09:00
kichikuou 0342141358 Add is_available function to CD-ROM devices
Now `SI 2` command returns zero if no audio track is available.
2025-11-28 10:45:24 +09:00
kichikuou 8806bc9545 Version 2.17.0 2025-11-09 22:27:45 +09:00
kichikuou 5e62405f17 Move patch/ to misc/patch/ 2025-11-09 22:26:15 +09:00
kichikuou 714c7d44b5 Android: Update SDL to 2.32.10 2025-11-09 22:08:54 +09:00
kichikuou 0f6d4e2343 Add Streamer Mode with -censor option
This introduces "Streamer Mode" to mosaic specified NSFW images, making
the game suitable for streaming or public viewing. This feature is
enabled via the '-censor <file>' command-line option, which takes a file
containing a list of image numbers to be filtered.
2025-11-09 11:47:13 +09:00
kichikuou f1536a801a QE/LE: Handle backslashes in filenames
Rance 5D uses filename `Save\\Rance5dSystem.asd`. This caused issues:

- On Windows, saving would fail if the `Save` directory did not exist.
- On other platforms, files were created with literal backslashes in
  their names.

This change addresses these problems by:

- In commandQE, normalizing backslashes to forward slashes in the path
  and ensuring that the target directory exists by calling mkdir_p().
- In commandLE, first attempting to load files with normalized forward
  slashes. If that fails, it retries with the original path (containing
  backslashes) for compatibility with older saves.
2025-11-06 11:05:23 +09:00
kichikuou ab372b7873 Unify old and new command implementations
Consolidate logic for parsing string arguments by introducing a
parameter to handle different terminators.
2025-11-06 09:39:51 +09:00
kichikuou 2c02f40dc4 Move uppercase commands from cmd2F.c to cmd<first_letter>.c 2025-11-06 09:39:46 +09:00
224 changed files with 8960 additions and 4764 deletions
+3 -3
View File
@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Deps
run: sudo apt install ninja-build
@@ -22,7 +22,7 @@ jobs:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: xsystem35-apk
path: android/app/build/outputs/apk/release/app-release.apk
archive: false
+7 -4
View File
@@ -10,21 +10,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup cache
id: cache-system-libraries
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{env.EM_CACHE_FOLDER}}
key: ${{env.EM_VERSION}}-${{ runner.os }}
- name: Setup Emscripten toolchain
uses: mymindstorm/setup-emsdk@v14
uses: emscripten-core/setup-emsdk@v16
with:
version: ${{ env.EM_VERSION }}
actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
- name: Work around emscripten-core/emscripten#26534
run: npm install -g typescript@5
- name: Build
run: |
mkdir -p out/wasm
@@ -35,7 +38,7 @@ jobs:
mv src/xsystem35.* xsystem35/
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: xsystem35-wasm
path: out/wasm/xsystem35
+15 -2
View File
@@ -10,12 +10,12 @@ jobs:
name: Linux ${{ matrix.build-type }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Deps
run: |
sudo apt update
sudo apt install libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
- name: Build
run: |
@@ -27,3 +27,16 @@ jobs:
- name: Test
run: ctest --output-on-failure
working-directory: out/${{ matrix.build-type }}
- name: Verify xsystem35.pot is up to date
if: matrix.build-type == 'Debug'
run: |
xgettext --default-domain=xsystem35 --directory=. \
--keyword=_ --keyword=N_ \
--files-from=po/POTFILES.in \
--output=/tmp/fresh.pot
if ! diff <(grep '^msgid' po/xsystem35.pot | sort) \
<(grep '^msgid' /tmp/fresh.pot | sort); then
echo "::error::po/xsystem35.pot is out of date. Build the 'pot' target and commit the changes under po/."
exit 1
fi
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
${{ matrix.deps }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Copy licenses
run: |
@@ -64,7 +64,7 @@ jobs:
cp out/release/src/xsystem35.exe out/release/doc/*.html *.md COPYING xsys35rc.sample dist/
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.package }}
path: dist
+23
View File
@@ -1,5 +1,28 @@
# Changelog
## 2.19.1 - 2026-08-02
- Fixed an issue that prevented moving upward during battles in Rance 4.
## 2.19.0 - 2026-07-19
- Replaced the GTK-based popup menu and dialogs with a pure-SDL implementation.
- On Android, the popup menu opens with a three-finger tap.
- Android: Added installation directly from CD-ROM images.
- Implemented volume control panel.
- Added `mute_on_unfocus` option to mute audio while the window is unfocused.
## 2.18.0 - 2026-06-23
- Added support for the intro demo of Daiakuji.
- Replaced the bundled mincho font with IPA Mincho.
## 2.17.1 - 2026-01-10
- Fixed an issue where Rance 5D could not save files on non-Windows systems.
- Improved fast-forward behavior using the Ctrl key in Rance 5D.
- The `SI 2` command now returns 0 if a playlist file is not provided.
## 2.17.0 - 2025-11-09
- Added "Streamer Mode" to mosaic specified (NSFW) images. This is enabled via the `-censor <file>` command-line option.
- Windows: Fixed an issue where Rance 5D could not save files if the `Save` directory did not exist.
## 2.16.1 - 2025-08-28
- Fixed text rendering issue in SACT log (#71)
- Windows: The 32-bit version no longer supports the portmidi MIDI backend
+16 -14
View File
@@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
enable_testing()
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(XSYSTEM35_VERSION "2.16.1")
set(XSYSTEM35_VERSION "2.19.1")
include(CheckSymbolExists)
include(FetchContent)
@@ -87,12 +87,7 @@ else() # non-emscripten, non-android
add_library(zlib ALIAS ZLIB::ZLIB)
include(FindPkgConfig)
optional_pkg_check_modules(GTK3 IMPORTED_TARGET gtk+-3.0)
if (GTK3_FOUND)
set(ENABLE_GTK 1)
endif()
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
if (WIN32)
@@ -153,14 +148,19 @@ else() # non-emscripten, non-android
endif()
endif()
# Menu
# i18n support
#
# Translate via libintl if available; otherwise fall back to a built-in catalog
# compiled from the .po files.
#
# To add a language, create po/<lang>.po and list <lang> here.
set(NLS_LANGUAGES ja)
if (ENABLE_GTK)
# i18n support (currently only menus are translated)
if (NOT EMSCRIPTEN AND NOT ANDROID AND NOT WIN32)
include(FindIntl)
include(FindGettext)
if (Intl_FOUND AND GETTEXT_FOUND)
set(ENABLE_NLS 1)
set(HAVE_LIBINTL 1)
add_compile_definitions(LOCALEDIR="${CMAKE_INSTALL_PREFIX}/share/locale")
include_directories(${Intl_INCLUDE_DIRS})
link_libraries(${Intl_LIBRARIES})
@@ -227,9 +227,11 @@ add_subdirectory(fonts)
if (NOT ANDROID AND NOT EMSCRIPTEN)
add_subdirectory(doc)
endif()
if (ENABLE_NLS)
add_subdirectory(po)
endif()
add_subdirectory(po)
add_subdirectory(modules)
target_link_libraries(xsystem35 PRIVATE modules)
if (NOT ANDROID AND NOT EMSCRIPTEN)
add_subdirectory(test)
endif()
+13 -4
View File
@@ -16,9 +16,10 @@ the following features:
### Playing Audio Files as Virtual CD Music
Many System 3.x games feature music as audio tracks on the CD-ROM. xsystem35
can play music from audio files, eliminating the need to insert CDs. To use
ripped audio files, create a file named `playlist.txt` in the game directory
and list the paths to your tracks, one per line. For example:
can play music from audio files, eliminating the need to insert CDs. Supported
audio file formats are MP3 and Ogg. To use ripped audio files, create a file
named `playlist.txt` in the game directory and list the paths to your tracks,
one per line. For example:
```
# The first line is not used
@@ -60,6 +61,14 @@ and examine or modify game variables. There are two ways to use the debugger:
launch the debugger with a console interface. Type `help` to see a list of
available commands.
### Streamer Mode
xsystem35 introduces a "Streamer Mode" to make playing games with NSFW content
safer for streaming or public viewing. When enabled with the `-censor <file>`
option, images specified in the provided file will be automatically mosaiced.
The `misc/censor/` directory contains example censor list files for some games.
## Installation
Prebuilt packages for Windows and Android can be downloaded from the
@@ -96,7 +105,7 @@ See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
### Linux (Debian / Ubuntu)
```bash
$ sudo apt install build-essential cmake libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
+30 -2
View File
@@ -6,15 +6,41 @@ Minimum supported Android version: 5.0
You can download prebuilt APKs
[here](https://github.com/kichikuou/xsystem35-sdl2/releases).
### Installing with Obtainium
Alternatively, you can install `xsystem35-sdl2` using [Obtainium](https://github.com/ImranR98/Obtainium). Obtainium will detect new releases and allow you to update the app directly.
1. Install Obtainium on your Android device.
2. Click the badge below to add the app to Obtainium:
[<img src="https://raw.githubusercontent.com/ImranR98/Obtainium/main/assets/graphics/badge_obtainium.png" alt="Get it on Obtainium" height="60">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22io.github.kichikuou.xsystem35%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fkichikuou%2Fxsystem35-sdl2%22%2C%22author%22%3A%22kichikuou%22%2C%22name%22%3A%22xsystem35%22%2C%22categories%22%3A%5B%22games%22%5D%7D)
## Usage
### Basic Usage
You can install games either from a prepared ZIP file or directly from a CD-ROM
image.
#### Installing from a ZIP
1. Create a ZIP file containing all the game files and BGM files (see
[below](#preparing-a-zip) for details), and transfer it to your device.
2. Open the app. Tap the option menu (three dots in the top right corner) and
select "Install from ZIP".
3. Select the ZIP file you created in step 1.
4. The game will start. To simulate a right-click, tap the black bars on either
the left or right, or top or bottom of the screen.
4. The game will start.
#### Installing from a CD image
1. Transfer the CD image file(s) to your device.
2. Open the app. Tap the option menu and select "Install from CD image".
3. Select the image file and, when needed, the matching metadata file at the
same time. The supported combinations are:
- `.iso`
- `.bin` or `.img` with matching `.cue`
- `.bin` or `.img` with matching `.ccd`
- `.mdf` with matching `.mds`
In many Android file pickers, tapping a file immediately selects only that
one file. Long-press a file first to enter multi-select mode, then select
both the image file and its metadata file.
4. The game will start.
### Preparing a ZIP
- Include all files from the `GAMEDATA` folder (such as `.ALD` files and
@@ -33,6 +59,8 @@ Note: This ZIP format is also compatible with
[Kichikuou on Web](http://kichikuou.github.io/web/).
### Miscellaneous
- To simulate a right-click, tap the black bars on either the left or right,
or top or bottom of the screen.
- You can export or import save files via the game list's option menu.
- To uninstall a game, long-tap its title in the game list.
+2 -1
View File
@@ -17,7 +17,7 @@ android {
minSdkVersion 21
targetSdkVersion 34
versionCode 36
versionName "2.16.1" + gitRevision()
versionName "2.19.1" + gitRevision()
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
@@ -85,6 +85,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
testImplementation 'junit:junit:4.13.2'
}
static String gitRevision() {
+4 -3
View File
@@ -7,8 +7,8 @@ set(PROJECT_ROOT_DIR ../../..)
include(FetchContent)
FetchContent_Declare(
SDL
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.4/SDL2-2.32.4.tar.gz
URL_HASH SHA1=4fb68f43891ca4def414cc7cf0f3b2a053cb6aaf
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
)
FetchContent_Declare(
SDL_ttf
@@ -42,11 +42,12 @@ add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/main/assets)
file(MAKE_DIRECTORY ${ASSETS_DIR}/licenses)
file(COPY_FILE ${PROJECT_ROOT_DIR}/fonts/MTLc3m.ttf ${ASSETS_DIR}/MTLc3m.ttf)
file(COPY_FILE ${PROJECT_ROOT_DIR}/fonts/mincho.otf ${ASSETS_DIR}/mincho.otf)
file(COPY_FILE ${PROJECT_ROOT_DIR}/fonts/mincho.ttf ${ASSETS_DIR}/mincho.ttf)
file(COPY_FILE ${PROJECT_ROOT_DIR}/COPYING ${ASSETS_DIR}/licenses/xsystem35)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/MTLc3m.txt ${ASSETS_DIR}/licenses/MTLc3m)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mincho)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg)
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui)
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
+1
View File
@@ -78,6 +78,7 @@
<activity android:name=".GameActivity"
android:label="@string/app_name"
android:process=":game"
android:alwaysRetainTaskState="true"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:theme="@style/AppTheme"
@@ -0,0 +1,566 @@
/* 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
*
*/
package io.github.kichikuou.xsystem35
import android.content.ContentResolver
import android.os.ParcelFileDescriptor
import java.io.Closeable
import java.io.EOFException
import java.io.File
import java.io.FileInputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.Locale
internal class CdImageReader private constructor(
private val image: RandomAccessImage,
private val tracks: List<TrackInfo?>,
) : Closeable {
fun readSector(sector: Int, buffer: ByteArray) {
readDataFully(sector.toLong() * ISO_SECTOR_SIZE, buffer, 0, ISO_SECTOR_SIZE)
}
fun readDataFully(offset: Long, buffer: ByteArray, bufferOffset: Int, length: Int) {
// ISO9660 always sees logical 2048-byte data sectors even when the
// image stores each sector as a raw 2352-byte CD-ROM block.
var logicalOffset = offset
var outOffset = bufferOffset
var remaining = length
val sectorBuffer = ByteArray(ISO_SECTOR_SIZE)
while (remaining > 0) {
val sector = (logicalOffset / ISO_SECTOR_SIZE).toInt()
val offsetInSector = (logicalOffset % ISO_SECTOR_SIZE).toInt()
val count = minOf(remaining, ISO_SECTOR_SIZE - offsetInSector)
readSectorPayload(sector, sectorBuffer)
sectorBuffer.copyInto(buffer, outOffset, offsetInSector, offsetInSector + count)
logicalOffset += count
outOffset += count
remaining -= count
}
}
fun audioTracks(): List<Int> {
return tracks.mapIndexedNotNull { trackNumber, track ->
if (track?.isAudio == true) trackNumber else null
}
}
fun extractAudioTrack(trackNumber: Int, output: OutputStream) {
val track = tracks.getOrNull(trackNumber)
if (track?.isAudio != true) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val dataSize = track.numSectors.toLong() * RAW_AUDIO_SECTOR_SIZE
// CD-DA sectors are already 44.1kHz stereo 16-bit PCM; wrapping the
// raw sector stream in a WAV header makes it playable by SDL_mixer.
writeWaveHeader(output, dataSize)
val buffer = ByteArray(track.blockSize * 16)
var sector = 0
while (sector < track.numSectors) {
val sectorsToRead = minOf(16, track.numSectors - sector)
val bytesToRead = sectorsToRead * track.blockSize
image.readFully(track.offset + sector.toLong() * track.blockSize, buffer, 0, bytesToRead)
output.write(buffer, 0, sectorsToRead * RAW_AUDIO_SECTOR_SIZE)
sector += sectorsToRead
}
}
override fun close() {
image.close()
}
private fun readSectorPayload(sector: Int, buffer: ByteArray) {
val track = findDataTrack(sector)
val offset = track.offset + (sector - track.startSector).toLong() * track.blockSize + track.blockOffset
image.readFully(offset, buffer, 0, ISO_SECTOR_SIZE)
}
private fun findDataTrack(sector: Int): TrackInfo {
for (track in tracks) {
if (track == null || track.isAudio) {
continue
}
if (sector >= track.startSector && sector < track.startSector + track.numSectors) {
return track
}
}
throw InstallFailureException(R.string.invalid_iso9660_image)
}
companion object {
const val ISO_SECTOR_SIZE = 2048
private const val RAW_AUDIO_SECTOR_SIZE = 2352
private const val WAVE_HEADER_SIZE = 44
private const val MDS_MODE_AUDIO = 0xa9
private const val MDS_MODE_MODE1 = 0xaa
fun open(
contentResolver: ContentResolver,
files: List<SelectedInstallFile>,
tempDir: File,
): CdImageReader {
val selected = selectImageFiles(files)
val image = RandomAccessImage.open(contentResolver, selected.image, tempDir)
try {
if (selected.metadata == null) {
return CdImageReader(image, listOf(null, TrackInfo.iso(image.size)))
}
val metadataName = selected.metadata.displayName.lowercase(Locale.US)
val tracks = when {
metadataName.endsWith(".cue") -> {
val cueText = readMetadataText(contentResolver, selected.metadata)
parseCue(cueText, image.size)
}
metadataName.endsWith(".ccd") -> {
val ccdText = readMetadataText(contentResolver, selected.metadata)
parseCcd(ccdText, image.size)
}
metadataName.endsWith(".mds") -> {
val mdsBytes = readMetadataBytes(contentResolver, selected.metadata)
parseMds(mdsBytes)
}
else -> throw InstallFailureException(R.string.unsupported_cd_image)
}
return CdImageReader(image, tracks)
} catch (e: Exception) {
image.close()
throw e
}
}
internal fun openForFile(
imageFile: File,
metadataName: String? = null,
metadataBytes: ByteArray? = null,
): CdImageReader {
val image = RandomAccessImage.open(imageFile)
try {
if (metadataName == null) {
return CdImageReader(image, listOf(null, TrackInfo.iso(image.size)))
}
val tracks = when {
metadataName.lowercase(Locale.US).endsWith(".cue") -> {
parseCue(metadataBytes?.toString(Charsets.UTF_8) ?: "", image.size)
}
metadataName.lowercase(Locale.US).endsWith(".ccd") -> {
parseCcd(metadataBytes?.toString(Charsets.UTF_8) ?: "", image.size)
}
metadataName.lowercase(Locale.US).endsWith(".mds") -> {
parseMds(metadataBytes ?: ByteArray(0))
}
else -> throw InstallFailureException(R.string.unsupported_cd_image)
}
return CdImageReader(image, tracks)
} catch (e: Exception) {
image.close()
throw e
}
}
private fun selectImageFiles(files: List<SelectedInstallFile>): SelectedImageFiles {
if (files.isEmpty()) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val isoFiles = files.filter { it.displayName.lowercase(Locale.US).endsWith(".iso") }
if (isoFiles.size == 1 && files.size == 1) {
return SelectedImageFiles(isoFiles.single(), null)
}
val imageFiles = files.filter {
val name = it.displayName.lowercase(Locale.US)
name.endsWith(".bin") || name.endsWith(".img") || name.endsWith(".mdf")
}
val metadataFiles = files.filter {
val name = it.displayName.lowercase(Locale.US)
name.endsWith(".cue") || name.endsWith(".ccd") || name.endsWith(".mds")
}
if (imageFiles.size == 1 && metadataFiles.isEmpty() && files.size == 1 ||
imageFiles.isEmpty() && metadataFiles.size == 1 && files.size == 1
) {
throw InstallFailureException(R.string.missing_cd_image_metadata)
}
if (imageFiles.size != 1 || metadataFiles.size != 1 || files.size != 2) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val imageBase = imageFiles.single().baseName()
val metadataBase = metadataFiles.single().baseName()
if (imageBase != metadataBase) {
throw InstallFailureException(R.string.missing_cd_image_metadata)
}
if (!isSupportedMetadataForImage(imageFiles.single(), metadataFiles.single())) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
return SelectedImageFiles(imageFiles.single(), metadataFiles.single())
}
private fun isSupportedMetadataForImage(
image: SelectedInstallFile,
metadata: SelectedInstallFile,
): Boolean {
val imageName = image.displayName.lowercase(Locale.US)
val metadataName = metadata.displayName.lowercase(Locale.US)
return if (imageName.endsWith(".mdf")) {
metadataName.endsWith(".mds")
} else {
metadataName.endsWith(".cue") || metadataName.endsWith(".ccd")
}
}
private fun readMetadataText(
contentResolver: ContentResolver,
file: SelectedInstallFile,
): String {
return contentResolver.openInputStream(file.uri)?.bufferedReader()?.use {
it.readText()
} ?: throw InstallFailureException(R.string.cd_image_read_error)
}
private fun readMetadataBytes(
contentResolver: ContentResolver,
file: SelectedInstallFile,
): ByteArray {
return contentResolver.openInputStream(file.uri)?.use {
it.readBytes()
} ?: throw InstallFailureException(R.string.cd_image_read_error)
}
private fun parseCue(cueText: String, imageSize: Long): List<TrackInfo?> {
val cueTracks = mutableListOf<CueTrack?>()
var currentTrack: Int? = null
for (line in cueText.lines()) {
val fields = line.trim().split(Regex("\\s+"))
if (fields.isEmpty()) {
continue
}
when (fields[0].uppercase(Locale.US)) {
"TRACK" -> {
if (fields.size < 3) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
currentTrack = fields[1].toIntOrNull()
?: throw InstallFailureException(R.string.unsupported_cd_image)
while (cueTracks.size <= currentTrack) {
cueTracks.add(null)
}
cueTracks[currentTrack] = when (fields[2].uppercase(Locale.US)) {
"MODE1/2048" -> CueTrack(false, 2048, 0)
"MODE1/2352" -> CueTrack(false, 2352, 16)
"AUDIO" -> CueTrack(true, 2352, 0)
else -> throw InstallFailureException(R.string.unsupported_cd_image)
}
}
"INDEX" -> {
val trackNumber = currentTrack ?: continue
if (fields.size < 3) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val indexNumber = fields[1].toIntOrNull()
?: throw InstallFailureException(R.string.unsupported_cd_image)
cueTracks[trackNumber]?.index?.put(indexNumber, indexToSector(fields[2]))
}
}
}
return makeTrackInfo(cueTracks, imageSize)
}
private fun parseCcd(ccdText: String, imageSize: Long): List<TrackInfo?> {
val cueTracks = mutableListOf<CueTrack?>()
var currentTrack: Int? = null
for (line in ccdText.lines()) {
val trimmed = line.trim()
val trackMatch = Regex("""\[TRACK ([0-9]+)]""").matchEntire(trimmed)
if (trackMatch != null) {
currentTrack = trackMatch.groupValues[1].toInt()
while (cueTracks.size <= currentTrack) {
cueTracks.add(null)
}
cueTracks[currentTrack] = CueTrack(false, 2352, 16)
continue
}
val trackNumber = currentTrack ?: continue
val keyValue = trimmed.split("=", limit = 2)
if (keyValue.size != 2) {
continue
}
when (keyValue[0].uppercase(Locale.US)) {
"MODE" -> if (keyValue[1] == "0") {
cueTracks[trackNumber] = CueTrack(true, 2352, 0, cueTracks[trackNumber]?.index ?: mutableMapOf())
}
"INDEX 0" -> cueTracks[trackNumber]?.index?.put(
0,
keyValue[1].toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
)
"INDEX 1" -> cueTracks[trackNumber]?.index?.put(
1,
keyValue[1].toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
)
}
}
return makeTrackInfo(cueTracks, imageSize)
}
private fun parseMds(mdsBytes: ByteArray): List<TrackInfo?> {
if (mdsBytes.size < 0x70 ||
String(mdsBytes, 0, 16, Charsets.US_ASCII).trimEnd('\u0000') != "MEDIA DESCRIPTOR"
) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val entries = mdsBytes[0x62].toInt() and 0xff
if (0x70 + entries * 0x58 > mdsBytes.size) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
val tracks = MutableList<TrackInfo?>(100) { null }
for (i in 0 until entries) {
val trackOffset = 0x70 + i * 0x50
val extraOffset = 0x70 + entries * 0x50 + i * 8
val mode = mdsBytes[trackOffset].toInt() and 0xff
val trackNumber = mdsBytes[trackOffset + 0x04].toInt() and 0xff
val sectorSize = readLittleEndianShort(mdsBytes, trackOffset + 0x10)
// MDS offsets are treated as 32-bit file offsets, so images
// beyond 4GB are unsupported.
val imageOffset = readLittleEndianInt(mdsBytes, trackOffset + 0x28)
val sectors = readLittleEndianInt(mdsBytes, extraOffset + 0x04).toInt()
if (trackNumber >= tracks.size) {
continue
}
tracks[trackNumber] = when (mode) {
MDS_MODE_AUDIO -> TrackInfo(
isAudio = true,
offset = imageOffset,
blockSize = sectorSize,
blockOffset = 0,
startSector = 0,
numSectors = sectors,
)
MDS_MODE_MODE1 -> TrackInfo(
isAudio = false,
offset = imageOffset,
blockSize = sectorSize,
blockOffset = 16,
startSector = 0,
numSectors = sectors,
)
else -> null
}
}
if (tracks.getOrNull(1)?.isAudio != false) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
return tracks
}
private fun makeTrackInfo(cueTracks: List<CueTrack?>, imageSize: Long): List<TrackInfo?> {
val tracks = MutableList<TrackInfo?>(cueTracks.size) { null }
var offset = 0L
var startSector = 0
for (trackNumber in 1 until cueTracks.size) {
val cueTrack = cueTracks[trackNumber] ?: continue
val index1 = cueTrack.index[1]
?: throw InstallFailureException(R.string.unsupported_cd_image)
// INDEX 0 describes a pregap, but a zero INDEX 0 is treated as absent.
cueTrack.index[0]?.takeIf { it != 0 }?.let { index0 ->
val gap = index1 - index0
if (gap > 0) {
startSector += gap
offset += gap.toLong() * cueTrack.blockSize
}
}
val nextTrack = cueTracks.drop(trackNumber + 1).firstOrNull { it != null }
val nextStart = nextTrack?.let { it.index[0]?.takeIf { index -> index != 0 } ?: it.index[1] }
val numSectors = if (nextStart != null) {
nextStart - index1
} else {
((imageSize - offset) / cueTrack.blockSize).toInt()
}
if (numSectors <= 0) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
tracks[trackNumber] = TrackInfo(
isAudio = cueTrack.isAudio,
offset = offset,
blockSize = cueTrack.blockSize,
blockOffset = cueTrack.blockOffset,
startSector = startSector,
numSectors = numSectors,
)
startSector += numSectors
offset += numSectors.toLong() * cueTrack.blockSize
}
return tracks
}
private fun indexToSector(index: String): Int {
val parts = index.split(":").map {
it.toIntOrNull() ?: throw InstallFailureException(R.string.unsupported_cd_image)
}
if (parts.size != 3) {
throw InstallFailureException(R.string.unsupported_cd_image)
}
return parts[0] * 60 * 75 + parts[1] * 75 + parts[2]
}
private fun writeWaveHeader(output: OutputStream, dataSize: Long) {
val header = ByteArray(WAVE_HEADER_SIZE)
writeAscii(header, 0, "RIFF")
writeLittleEndianInt(header, 4, dataSize + 36)
writeAscii(header, 8, "WAVE")
writeAscii(header, 12, "fmt ")
writeLittleEndianInt(header, 16, 16)
writeLittleEndianShort(header, 20, 1)
writeLittleEndianShort(header, 22, 2)
writeLittleEndianInt(header, 24, 44100)
writeLittleEndianInt(header, 28, 44100 * 2 * 2)
writeLittleEndianShort(header, 32, 2 * 2)
writeLittleEndianShort(header, 34, 16)
writeAscii(header, 36, "data")
writeLittleEndianInt(header, 40, dataSize)
output.write(header)
}
private fun writeAscii(buffer: ByteArray, offset: Int, value: String) {
value.toByteArray(Charsets.US_ASCII).copyInto(buffer, offset)
}
private fun writeLittleEndianShort(buffer: ByteArray, offset: Int, value: Int) {
buffer[offset] = value.toByte()
buffer[offset + 1] = (value shr 8).toByte()
}
private fun writeLittleEndianInt(buffer: ByteArray, offset: Int, value: Long) {
buffer[offset] = value.toByte()
buffer[offset + 1] = (value shr 8).toByte()
buffer[offset + 2] = (value shr 16).toByte()
buffer[offset + 3] = (value shr 24).toByte()
}
private fun readLittleEndianShort(buffer: ByteArray, offset: Int): Int {
return (buffer[offset].toInt() and 0xff) or
((buffer[offset + 1].toInt() and 0xff) shl 8)
}
private fun readLittleEndianInt(buffer: ByteArray, offset: Int): Long {
return (buffer[offset].toLong() and 0xff) or
((buffer[offset + 1].toLong() and 0xff) shl 8) or
((buffer[offset + 2].toLong() and 0xff) shl 16) or
((buffer[offset + 3].toLong() and 0xff) shl 24)
}
}
}
private data class SelectedImageFiles(
val image: SelectedInstallFile,
val metadata: SelectedInstallFile?,
)
private data class CueTrack(
val isAudio: Boolean,
val blockSize: Int,
val blockOffset: Int,
val index: MutableMap<Int, Int> = mutableMapOf(),
)
private data class TrackInfo(
val isAudio: Boolean,
val offset: Long,
val blockSize: Int,
val blockOffset: Int,
val startSector: Int,
val numSectors: Int,
) {
companion object {
fun iso(imageSize: Long): TrackInfo {
return TrackInfo(
isAudio = false,
offset = 0,
blockSize = CdImageReader.ISO_SECTOR_SIZE,
blockOffset = 0,
startSector = 0,
numSectors = (imageSize / CdImageReader.ISO_SECTOR_SIZE).toInt(),
)
}
}
}
private class RandomAccessImage private constructor(
private val channel: FileChannel,
private val descriptor: ParcelFileDescriptor?,
private val tempFile: File?,
) : Closeable {
val size: Long = channel.size()
fun readFully(offset: Long, buffer: ByteArray, bufferOffset: Int, length: Int) {
val byteBuffer = ByteBuffer.wrap(buffer, bufferOffset, length)
var pos = offset
while (byteBuffer.hasRemaining()) {
val read = channel.read(byteBuffer, pos)
if (read < 0) {
throw EOFException("Unexpected end of CD image")
}
pos += read
}
}
override fun close() {
channel.close()
descriptor?.close()
tempFile?.delete()
}
companion object {
internal fun open(file: File): RandomAccessImage {
return RandomAccessImage(FileInputStream(file).channel, null, null)
}
fun open(
contentResolver: ContentResolver,
file: SelectedInstallFile,
tempDir: File,
): RandomAccessImage {
val descriptor = contentResolver.openFileDescriptor(file.uri, "r")
?: throw InstallFailureException(R.string.cd_image_read_error)
try {
val channel = FileInputStream(descriptor.fileDescriptor).channel
channel.size()
return RandomAccessImage(channel, descriptor, null)
} catch (e: Exception) {
descriptor.close()
}
// Some SAF providers expose the URI as a pipe. Copy those to an
// app-private temp file so the CD reader can use positional reads.
val tempFile = File.createTempFile("cdimage-", ".img", tempDir)
try {
contentResolver.openInputStream(file.uri)?.use { input ->
tempFile.outputStream().buffered().use { output ->
input.copyTo(output)
}
} ?: throw InstallFailureException(R.string.cd_image_read_error)
return RandomAccessImage(FileInputStream(tempFile).channel, null, tempFile)
} catch (e: Exception) {
tempFile.delete()
throw e
}
}
}
}
private fun SelectedInstallFile.baseName(): String {
return displayName.substringBeforeLast('.').lowercase(Locale.US)
}
@@ -20,6 +20,7 @@ package io.github.kichikuou.xsystem35
import android.app.AlertDialog
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Process
import android.text.InputType
import android.util.Log
import android.widget.EditText
@@ -56,6 +57,18 @@ class GameActivity : SDLActivity() {
midi.onActivityResume()
}
override fun onDestroy() {
try {
super.onDestroy()
} finally {
try {
midi.release()
} finally {
Process.killProcess(Process.myPid())
}
}
}
override fun getLibraries(): Array<String> {
return arrayOf("SDL2", "xsystem35")
}
@@ -75,7 +88,6 @@ class GameActivity : SDLActivity() {
return
}
File(gameRoot, Launcher.TITLE_FILE).writeText(str)
Launcher.updateGameList()
}
private fun textInputDialog(msg: String, oldVal: String, maxLen: Int, result: Array<String?>) {
@@ -154,11 +166,21 @@ class GameActivity : SDLActivity() {
}
private class MidiPlayer {
private enum class State {
STOPPED,
PLAYING,
PAUSED,
RELEASED,
}
private val player = MediaPlayer()
private var playing = false
private var playerPaused = false
private var state = State.STOPPED
fun start(path: String, loop: Boolean) {
if (state == State.RELEASED) {
return
}
state = State.STOPPED
try {
player.apply {
reset()
@@ -167,7 +189,7 @@ private class MidiPlayer {
prepare()
start()
}
playing = true
state = State.PLAYING
} catch (e: IOException) {
Log.e("midiStart", "Cannot play midi", e)
player.reset()
@@ -175,27 +197,41 @@ private class MidiPlayer {
}
fun stop() {
if (playing && player.isPlaying) {
player.stop()
playing = false
when (state) {
State.PLAYING, State.PAUSED -> {
player.stop()
state = State.STOPPED
}
State.STOPPED, State.RELEASED -> Unit
}
}
fun currentPosition(): Int {
return if (playing) player.currentPosition else 0
return when (state) {
State.PLAYING, State.PAUSED -> player.currentPosition
State.STOPPED, State.RELEASED -> 0
}
}
fun onActivityStop() {
if (playing && player.isPlaying) {
if (state == State.PLAYING && player.isPlaying) {
player.pause()
playerPaused = true
state = State.PAUSED
}
}
fun onActivityResume() {
if (playerPaused) {
if (state == State.PAUSED) {
player.start()
playerPaused = false
state = State.PLAYING
}
}
fun release() {
if (state == State.RELEASED) {
return
}
state = State.RELEASED
player.release()
}
}
@@ -0,0 +1,282 @@
/* 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
*
*/
package io.github.kichikuou.xsystem35
import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import android.util.Log
import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.UTFDataFormatException
import java.nio.charset.Charset
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
data class SelectedInstallFile(
val uri: Uri,
val displayName: String,
)
class GameInstaller(private val store: GameStore) {
suspend fun installZip(input: InputStream, progressCallback: suspend (String) -> Unit): File {
val dir = store.createDirForGame()
var committed = false
try {
val gameDir = extractFiles(input, dir, progressCallback)
committed = true
return gameDir
} finally {
if (!committed && !dir.deleteRecursively()) {
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
}
}
}
suspend fun installCdImage(
files: List<SelectedInstallFile>,
contentResolver: ContentResolver,
tempDir: File,
progressCallback: suspend (String) -> Unit
): File {
val dir = store.createDirForGame()
var committed = false
try {
val gameDir = extractCdImage(files, contentResolver, tempDir, dir, progressCallback)
committed = true
return gameDir
} finally {
if (!committed && !dir.deleteRecursively()) {
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
}
}
}
internal suspend fun installCdImageForTest(
cdImage: CdImageReader,
progressCallback: suspend (String) -> Unit
): File {
val dir = store.createDirForGame()
var committed = false
try {
val gameDir = extractCdImage(cdImage, dir, progressCallback)
committed = true
return gameDir
} finally {
if (!committed && !dir.deleteRecursively()) {
Log.w("launcher", "Failed to delete incomplete install directory: $dir")
}
}
}
private suspend fun extractFiles(
input: InputStream,
outDir: File,
progressCallback: suspend (String) -> Unit
): File {
val configWriter = GameConfigWriter()
val hadDecodeError = forEachZipEntrySuspending(input) { zipEntry, zip ->
Log.i("extractFiles", zipEntry.name)
if (zipEntry.isDirectory)
return@forEachZipEntrySuspending
val resolvedPath = resolveOutputPath(outDir, zipEntry.name)
val path = resolvedPath.file
path.parentFile?.mkdirs()
progressCallback(resolvedPath.relativePath)
FileOutputStream(path).buffered().use {
zip.copyTo(it)
}
configWriter.maybeAdd(resolvedPath.relativePath)
}
if (!configWriter.ready) {
if (hadDecodeError)
throw InstallFailureException(R.string.unsupported_zip)
throw InstallFailureException(R.string.cannot_find_ald)
}
configWriter.write(outDir)
return configWriter.gameDir?.let { File(outDir, it) } ?: outDir
}
private suspend fun extractCdImage(
files: List<SelectedInstallFile>,
contentResolver: ContentResolver,
tempDir: File,
outDir: File,
progressCallback: suspend (String) -> Unit
): File {
progressCallback("metadata parsing")
CdImageReader.open(contentResolver, files, tempDir).use { cdImage ->
return extractCdImage(cdImage, outDir, progressCallback)
}
}
private suspend fun extractCdImage(
cdImage: CdImageReader,
outDir: File,
progressCallback: suspend (String) -> Unit
): File {
val fs = Iso9660FileSystem(cdImage)
val gameData = fs.findGameDataDirectory()
?: throw InstallFailureException(R.string.cannot_find_game_data_directory)
var foundAld = false
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
val resolvedPath = resolveOutputPath(outDir, path)
resolvedPath.file.parentFile?.mkdirs()
progressCallback(resolvedPath.relativePath)
FileOutputStream(resolvedPath.file).buffered().use {
input.copyTo(it)
}
if (File(path).name.matches(""".*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE))) {
foundAld = true
}
}
if (!foundAld) {
throw InstallFailureException(R.string.cannot_find_ald)
}
File(outDir, Launcher.GAMEDIR_FILE).writeText("GAMEDATA")
extractAudioTracks(cdImage, File(outDir, "GAMEDATA"), progressCallback)
return File(outDir, "GAMEDATA")
}
private suspend fun extractAudioTracks(
cdImage: CdImageReader,
gameDir: File,
progressCallback: suspend (String) -> Unit
) {
val audioTracks = cdImage.audioTracks()
if (audioTracks.isEmpty()) {
return
}
val cddaDir = File(gameDir, "cdda")
cddaDir.mkdirs()
val playlist = arrayOfNulls<String>(audioTracks.maxOrNull() ?: 0)
for (track in audioTracks) {
val relativePath = "cdda/track%02d.wav".format(track)
val outputFile = File(gameDir, relativePath)
progressCallback(relativePath)
FileOutputStream(outputFile).buffered().use {
cdImage.extractAudioTrack(track, it)
}
playlist[track - 1] = relativePath
}
File(gameDir, Launcher.PLAYLIST_FILE).writeText(playlist.joinToString("\n") { it ?: "" }.trimEnd('\n'))
}
}
internal class InstallFailureException(val msgId: Int) : Exception()
// A helper class which generates GAMEDIR_FILE and PLAYLIST_FILE.
private class GameConfigWriter {
var ready = false
private set
var gameDir: String? = null
private set
private val aldRegex = """.*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE)
private val audioRegex = """((\d+).*|.*?(\d+))\.(wav|mp3|ogg)""".toRegex(RegexOption.IGNORE_CASE)
private val audioFiles: Array<String?> = arrayOfNulls(100)
fun maybeAdd(path: String) {
val name = File(path).name
aldRegex.matchEntire(name)?.let {
gameDir = File(path).parent
ready = true
}
audioRegex.matchEntire(name)?.let {
val track = it.groupValues[2].toIntOrNull() ?: it.groupValues[3].toInt()
if (0 < track && track <= audioFiles.size)
audioFiles[track - 1] = path
}
}
fun write(outDir: File) {
// Generate GAMEDIR_FILE
gameDir?.let {
File(outDir, Launcher.GAMEDIR_FILE).writeText(it)
}
// Generate PLAYLIST_FILE
val absGameDir = gameDir?.let { File(outDir, it) } ?: outDir
val playlistFile = File(absGameDir, Launcher.PLAYLIST_FILE)
if (!playlistFile.exists()) {
val prefixToRemove = gameDir?.let { "$it/" } ?: ""
val playlist = audioFiles.joinToString("\n") {
it?.removePrefix(prefixToRemove) ?: ""
}.trimEnd('\n')
playlistFile.writeText(playlist)
}
}
}
/**
* @property file Safe canonical output path.
* @property relativePath Path relative to the install root after canonicalization.
*/
internal data class ResolvedOutputPath(val file: File, val relativePath: String)
internal fun resolveOutputPath(baseDir: File, relativePath: String): ResolvedOutputPath {
if (File(relativePath).isAbsolute) {
throw IOException("Output path is absolute: $relativePath")
}
val canonicalBase = baseDir.canonicalFile
val file = File(canonicalBase, relativePath).canonicalFile
if (!isFileInsideDirectory(file, canonicalBase)) {
throw IOException("Output path is outside target directory: $relativePath")
}
val basePath = canonicalBase.path + File.separator
val canonicalRelativePath = file.path.removePrefix(basePath)
return ResolvedOutputPath(file, canonicalRelativePath)
}
private fun isFileInsideDirectory(file: File, directory: File): Boolean {
return file.path.startsWith(directory.path + File.separator)
}
internal fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit): Boolean =
runBlocking {
forEachZipEntrySuspending(input) { zipEntry, zip ->
action(zipEntry, zip)
}
}
internal suspend fun forEachZipEntrySuspending(
input: InputStream,
action: suspend (ZipEntry, ZipInputStream) -> Unit
): Boolean {
val zip = if (Build.VERSION.SDK_INT >= 24) {
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
} else {
ZipInputStream(input.buffered())
}
var hadDecodeError = false
zip.use {
while (true) {
try {
val zipEntry = zip.nextEntry ?: break
action(zipEntry, zip)
} catch (e: UTFDataFormatException) {
// Attempted to read Shift_JIS zip in Android < 7
Log.w("forEachZipEntry", "UTFDataFormatException: skipping a zip entry")
zip.closeEntry()
hadDecodeError = true
}
}
}
return hadDecodeError
}
@@ -0,0 +1,142 @@
/* 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
*
*/
package io.github.kichikuou.xsystem35
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.UTFDataFormatException
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class GameStore(private val rootDir: File) {
data class Entry(val path: File, val title: String, val timestamp: Long)
private val gameList = arrayListOf<Entry>()
val games: List<Entry>
get() = gameList
val titles: List<String>
get() = gameList.map(Entry::title)
init {
updateGameList()
}
fun uninstall(id: Int) {
gameList[id].path.deleteRecursively()
gameList.removeAt(id)
}
fun updateGameList() {
var saveDirFound = false
gameList.clear()
for (path in rootDir.listFiles() ?: emptyArray()) {
if (!path.isDirectory)
continue
if (path.name == Launcher.SAVE_DIR) {
saveDirFound = true
continue
}
try {
val gameDirFile = File(path, Launcher.GAMEDIR_FILE)
val gamePath = if (gameDirFile.exists()) File(path, gameDirFile.readText()) else path
val titleFile = File(gamePath, Launcher.TITLE_FILE)
val title = titleFile.readText()
gameList.add(Entry(gamePath, title, titleFile.lastModified()))
migratePlaylist(path)
} catch (e: IOException) {
// Incomplete game installation. Delete it.
path.deleteRecursively()
}
}
gameList.sortByDescending(Entry::timestamp)
if (!saveDirFound) {
File(rootDir, Launcher.SAVE_DIR).mkdir()
}
}
fun createDirForGame(): File {
var i = 0
while (true) {
val f = File(rootDir, i++.toString())
if (!f.exists() && f.mkdir()) {
return f
}
}
}
// Throws IOException
fun exportSaveData(output: OutputStream) {
ZipOutputStream(output.buffered()).use { zip ->
for (path in File(rootDir, Launcher.SAVE_DIR).listFiles() ?: emptyArray()) {
if (path.isDirectory || path.name.endsWith(".asd."))
continue
val pathInZip = "${Launcher.SAVE_DIR}/${path.name}"
Log.i("exportSaveData", pathInZip)
zip.putNextEntry(ZipEntry(pathInZip))
path.inputStream().buffered().use {
it.copyTo(zip)
}
}
}
}
fun importSaveData(input: InputStream): Int? {
try {
var imported = false
forEachZipEntry(input) { zipEntry, zip ->
// Process only files directly under save/
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/") ||
zipEntry.name.count{it == '/'} != 1)
return@forEachZipEntry
val path = resolveOutputPath(rootDir, zipEntry.name).file
Log.i("importSaveData", zipEntry.name)
FileOutputStream(path).buffered().use {
zip.copyTo(it)
}
imported = true
}
return if (imported) null else R.string.no_data_to_import
} catch (e: UTFDataFormatException) {
// Attempted to read Shift_JIS zip in Android < 7
return R.string.unsupported_zip
} catch (e: IOException) {
Log.e("launcher", "Failed to extract ZIP", e)
return R.string.zip_extraction_error
}
}
fun clearSaveData(): Boolean {
var success = true
File(rootDir, Launcher.SAVE_DIR).listFiles()?.forEach { file ->
if (!file.deleteRecursively()) success = false
}
return success
}
// Xsystem35-sdl2 2.3.0 - 2.11.1 used playlist2.txt. Rename it to playlist.txt.
private fun migratePlaylist(dir: File) {
val oldPlaylist = File(dir, Launcher.OLD_PLAYLIST_FILE)
if (oldPlaylist.exists()) {
oldPlaylist.renameTo(File(dir, Launcher.PLAYLIST_FILE))
}
}
}
@@ -0,0 +1,175 @@
/* 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
*
*/
package io.github.kichikuou.xsystem35
import java.io.InputStream
import java.nio.charset.Charset
import java.util.Locale
internal class Iso9660FileSystem(private val image: CdImageReader) {
private val descriptor = readBestVolumeDescriptor()
data class Entry(
val name: String,
val extent: Int,
val size: Int,
val isDirectory: Boolean,
)
fun findGameDataDirectory(): Entry? {
return listDirectory(descriptor.rootDirectory).firstOrNull {
it.isDirectory && it.name.uppercase(Locale.US) == "GAMEDATA"
}
}
suspend fun extractDirectory(
directory: Entry,
outputPath: String,
writeFile: suspend (String, InputStream) -> Unit,
) {
for (entry in listDirectory(directory)) {
val childPath = "$outputPath/${entry.name}"
if (entry.isDirectory) {
extractDirectory(entry, childPath, writeFile)
} else {
writeFile(childPath, fileInputStream(entry))
}
}
}
private fun listDirectory(directory: Entry): List<Entry> {
val bytes = ByteArray(directory.size)
image.readDataFully(
directory.extent.toLong() * CdImageReader.ISO_SECTOR_SIZE,
bytes,
0,
bytes.size
)
val entries = mutableListOf<Entry>()
var offset = 0
while (offset < bytes.size) {
val length = bytes[offset].toInt() and 0xff
if (length == 0) {
offset = ((offset / CdImageReader.ISO_SECTOR_SIZE) + 1) * CdImageReader.ISO_SECTOR_SIZE
continue
}
parseDirectoryRecord(bytes, offset, length)?.let { entries.add(it) }
offset += length
}
return entries
}
private fun parseDirectoryRecord(bytes: ByteArray, offset: Int, length: Int): Entry? {
if (length < 34) {
throw InstallFailureException(R.string.invalid_iso9660_image)
}
val nameLength = bytes[offset + 32].toInt() and 0xff
if (33 + nameLength > length) {
throw InstallFailureException(R.string.invalid_iso9660_image)
}
val rawName = bytes.copyOfRange(offset + 33, offset + 33 + nameLength)
if (rawName.size == 1 && (rawName[0].toInt() == 0 || rawName[0].toInt() == 1)) {
return null
}
val name = descriptor.decodeName(rawName)
val extent = readLittleEndianInt(bytes, offset + 2)
val size = readLittleEndianInt(bytes, offset + 10)
val flags = bytes[offset + 25].toInt() and 0xff
return Entry(name, extent, size, flags and 0x02 != 0)
}
private fun fileInputStream(entry: Entry): InputStream {
return object : InputStream() {
private var pos = 0
override fun read(): Int {
val buffer = ByteArray(1)
val read = read(buffer, 0, 1)
return if (read < 0) -1 else buffer[0].toInt() and 0xff
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
if (pos >= entry.size) {
return -1
}
val count = minOf(length, entry.size - pos)
image.readDataFully(
entry.extent.toLong() * CdImageReader.ISO_SECTOR_SIZE + pos,
buffer,
offset,
count
)
pos += count
return count
}
}
}
private fun readBestVolumeDescriptor(): VolumeDescriptor {
var primary: VolumeDescriptor? = null
var joliet: VolumeDescriptor? = null
val sector = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
var sectorNumber = 0x10
while (true) {
image.readSector(sectorNumber++, sector)
if (String(sector, 1, 5, Charsets.US_ASCII) != "CD001") {
throw InstallFailureException(R.string.invalid_iso9660_image)
}
when (sector[0].toInt() and 0xff) {
1 -> primary = VolumeDescriptor(parseRootDirectory(sector), false)
2 -> if (isJolietDescriptor(sector)) {
joliet = VolumeDescriptor(parseRootDirectory(sector), true)
}
255 -> return joliet ?: primary
?: throw InstallFailureException(R.string.invalid_iso9660_image)
}
}
}
private fun parseRootDirectory(sector: ByteArray): Entry {
return Entry(
name = "",
extent = readLittleEndianInt(sector, 156 + 2),
size = readLittleEndianInt(sector, 156 + 10),
isDirectory = true,
)
}
private fun isJolietDescriptor(sector: ByteArray): Boolean {
return sector[88] == 0x25.toByte() &&
sector[89] == 0x2f.toByte() &&
(sector[90] == 0x40.toByte() || sector[90] == 0x43.toByte() || sector[90] == 0x45.toByte())
}
private data class VolumeDescriptor(
val rootDirectory: Entry,
val joliet: Boolean,
) {
fun decodeName(rawName: ByteArray): String {
val charset = if (joliet) Charsets.UTF_16BE else Charset.forName("Shift_JIS")
return String(rawName, charset).substringBefore(";")
}
}
}
private fun readLittleEndianInt(bytes: ByteArray, offset: Int): Int {
return (bytes[offset].toInt() and 0xff) or
((bytes[offset + 1].toInt() and 0xff) shl 8) or
((bytes[offset + 2].toInt() and 0xff) shl 16) or
((bytes[offset + 3].toInt() and 0xff) shl 24)
}
@@ -17,18 +17,15 @@
*/
package io.github.kichikuou.xsystem35
import android.os.Build
import android.content.ContentResolver
import android.util.Log
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.*
import java.nio.charset.Charset
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
private var gLauncher: Launcher? = null
@@ -39,7 +36,16 @@ interface LauncherObserver {
fun onInstallFailure(msgId: Int)
}
class Launcher private constructor(private val rootDir: File) {
sealed class InstallState {
object Idle : InstallState()
data class Installing(val progress: String?) : InstallState()
data class Succeeded(val path: File, val archiveName: String?) : InstallState()
data class Failed(val msgId: Int) : InstallState()
}
data class InstallResult(val path: File, val archiveName: String?)
class Launcher private constructor(rootDir: File) {
companion object {
const val SAVE_DIR = "save"
const val TITLE_FILE = "title.txt"
@@ -54,234 +60,118 @@ class Launcher private constructor(private val rootDir: File) {
return gLauncher!!
}
fun updateGameList() {
gLauncher?.updateGameList()
}
}
data class Entry(val path: File, val title: String, val timestamp: Long)
val games = arrayListOf<Entry>()
private val store = GameStore(rootDir)
private val installer = GameInstaller(store)
val games: List<GameStore.Entry>
get() = store.games
val titles: List<String>
get() = games.map(Entry::title)
get() = store.titles
var observer: LauncherObserver? = null
var isInstalling = false
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var installJob: Job? = null
var installState: InstallState = InstallState.Idle
private set
init {
updateGameList()
}
@OptIn(DelicateCoroutinesApi::class)
fun install(input: InputStream, archiveName: String?) {
val dir = createDirForGame()
isInstalling = true
GlobalScope.launch(Dispatchers.Main) {
try {
val gameDir = withContext(Dispatchers.IO) {
extractFiles(input, dir) { msg ->
GlobalScope.launch(Dispatchers.Main) {
observer?.onInstallProgress(msg)
fun installZip(input: InputStream, archiveName: String?) {
if (installJob?.isActive == true) {
input.close()
return
}
startInstallJob {
val gameDir = withContext(Dispatchers.IO) {
input.use {
installer.installZip(it) { msg ->
withContext(Dispatchers.Main) {
setInstallProgress(msg)
}
}
}
observer?.onInstallSuccess(gameDir, archiveName)
} catch (e: InstallFailureException) {
observer?.onInstallFailure(e.msgId)
} catch (e: Exception) {
Log.e("launcher", "Failed to extract ZIP", e)
observer?.onInstallFailure(R.string.zip_extraction_error)
}
isInstalling = false
InstallResult(gameDir, archiveName)
}
}
fun installCdImage(
files: List<SelectedInstallFile>,
contentResolver: ContentResolver,
tempDir: File,
) {
if (installJob?.isActive == true) {
return
}
startInstallJob {
val gameDir = withContext(Dispatchers.IO) {
installer.installCdImage(files, contentResolver, tempDir) { msg ->
withContext(Dispatchers.Main) {
setInstallProgress(msg)
}
}
}
InstallResult(gameDir, null)
}
}
private fun startInstallJob(block: suspend () -> InstallResult) {
if (installJob?.isActive == true) {
return
}
installState = InstallState.Installing(null)
installJob = scope.launch {
try {
val result = block()
setInstallSucceeded(result)
} catch (e: InstallFailureException) {
setInstallFailed(e.msgId)
} catch (e: Exception) {
Log.e("launcher", "Failed to install game", e)
setInstallFailed(R.string.install_error)
}
}
}
fun consumeInstallResult() {
if (installState is InstallState.Succeeded || installState is InstallState.Failed) {
installState = InstallState.Idle
}
}
private fun setInstallProgress(progress: String) {
installState = InstallState.Installing(progress)
observer?.onInstallProgress(progress)
}
private fun setInstallSucceeded(result: InstallResult) {
installState = InstallState.Succeeded(result.path, result.archiveName)
observer?.onInstallSuccess(result.path, result.archiveName)
}
private fun setInstallFailed(msgId: Int) {
installState = InstallState.Failed(msgId)
observer?.onInstallFailure(msgId)
}
fun uninstall(id: Int) {
games[id].path.deleteRecursively()
games.removeAt(id)
store.uninstall(id)
observer?.onGameListChange()
}
private fun updateGameList() {
var saveDirFound = false
games.clear()
for (path in rootDir.listFiles() ?: emptyArray()) {
if (!path.isDirectory)
continue
if (path.name == SAVE_DIR) {
saveDirFound = true
continue
}
try {
val gameDirFile = File(path, GAMEDIR_FILE)
val gamePath = if (gameDirFile.exists()) File(path, gameDirFile.readText()) else path
val titleFile = File(gamePath, TITLE_FILE)
val title = titleFile.readText()
games.add(Entry(gamePath, title, titleFile.lastModified()))
migratePlaylist(path)
} catch (e: IOException) {
// Incomplete game installation. Delete it.
path.deleteRecursively()
}
}
games.sortByDescending(Entry::timestamp)
if (!saveDirFound) {
File(rootDir, SAVE_DIR).mkdir()
}
fun refreshGameList() {
store.updateGameList()
observer?.onGameListChange()
}
private fun createDirForGame(): File {
var i = 0
while (true) {
val f = File(rootDir, i++.toString())
if (!f.exists() && f.mkdir()) {
return f
}
}
}
// Throws IOException
fun exportSaveData(output: OutputStream) {
ZipOutputStream(output.buffered()).use { zip ->
for (path in File(rootDir, SAVE_DIR).listFiles() ?: emptyArray()) {
if (path.isDirectory || path.name.endsWith(".asd."))
continue
val pathInZip = "${SAVE_DIR}/${path.name}"
Log.i("exportSaveData", pathInZip)
zip.putNextEntry(ZipEntry(pathInZip))
path.inputStream().buffered().use {
it.copyTo(zip)
}
}
}
store.exportSaveData(output)
}
fun importSaveData(input: InputStream): Int? {
try {
var imported = false
forEachZipEntry(input) { zipEntry, zip ->
// Process only files directly under save/
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/") ||
zipEntry.name.count{it == '/'} != 1)
return@forEachZipEntry
Log.i("importSaveData", zipEntry.name)
FileOutputStream(File(rootDir, zipEntry.name)).buffered().use {
zip.copyTo(it)
}
imported = true
}
return if (imported) null else R.string.no_data_to_import
} catch (e: UTFDataFormatException) {
// Attempted to read Shift_JIS zip in Android < 7
return R.string.unsupported_zip
} catch (e: IOException) {
Log.e("launcher", "Failed to extract ZIP", e)
return R.string.zip_extraction_error
}
}
private fun extractFiles(input: InputStream, outDir: File, progressCallback: (String) -> Unit): File {
val configWriter = GameConfigWriter()
val hadDecodeError = forEachZipEntry(input) { zipEntry, zip ->
Log.i("extractFiles", zipEntry.name)
val path = File(outDir, zipEntry.name)
if (zipEntry.isDirectory)
return@forEachZipEntry
path.parentFile?.mkdirs()
progressCallback(zipEntry.name)
FileOutputStream(path).buffered().use {
zip.copyTo(it)
}
configWriter.maybeAdd(zipEntry.name)
}
if (!configWriter.ready) {
if (hadDecodeError)
throw InstallFailureException(R.string.unsupported_zip)
throw InstallFailureException(R.string.cannot_find_ald)
}
configWriter.write(outDir)
return configWriter.gameDir?.let { File(outDir, it) } ?: outDir
}
// Xsystem35-sdl2 2.3.0 - 2.11.1 used playlist2.txt. Rename it to playlist.txt.
private fun migratePlaylist(dir: File) {
val oldPlaylist = File(dir, OLD_PLAYLIST_FILE)
if (oldPlaylist.exists()) {
oldPlaylist.renameTo(File(dir, PLAYLIST_FILE))
}
}
class InstallFailureException(val msgId: Int) : Exception()
// A helper class which generates GAMEDIR_FILE and PLAYLIST_FILE.
private class GameConfigWriter {
var ready = false
private set
var gameDir: String? = null
private set
private val aldRegex = """.*?s[a-z]\.ald""".toRegex(RegexOption.IGNORE_CASE)
private val audioRegex = """((\d+).*|.*?(\d+))\.(wav|mp3|ogg)""".toRegex(RegexOption.IGNORE_CASE)
private val audioFiles: Array<String?> = arrayOfNulls(100)
fun maybeAdd(path: String) {
val name = File(path).name
aldRegex.matchEntire(name)?.let {
gameDir = File(path).parent
ready = true
}
audioRegex.matchEntire(name)?.let {
val track = it.groupValues[2].toIntOrNull() ?: it.groupValues[3].toInt()
if (0 < track && track <= audioFiles.size)
audioFiles[track - 1] = path
}
}
fun write(outDir: File) {
// Generate GAMEDIR_FILE
gameDir?.let {
File(outDir, GAMEDIR_FILE).writeText(it)
}
// Generate PLAYLIST_FILE
val absGameDir = gameDir?.let { File(outDir, it) } ?: outDir
val playlistFile = File(absGameDir, PLAYLIST_FILE)
if (!playlistFile.exists()) {
val prefixToRemove = gameDir?.let { "$it/" } ?: ""
val playlist = audioFiles.joinToString("\n") {
it?.removePrefix(prefixToRemove) ?: ""
}.trimEnd('\n')
playlistFile.writeText(playlist)
}
}
return store.importSaveData(input)
}
fun clearSaveData(): Boolean {
var success = true
File(rootDir, SAVE_DIR).listFiles()?.forEach { file ->
if (!file.deleteRecursively()) success = false
}
return success
return store.clearSaveData()
}
}
private fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit): Boolean {
val zip = if (Build.VERSION.SDK_INT >= 24) {
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))
} else {
ZipInputStream(input.buffered())
}
var hadDecodeError = false
zip.use {
while (true) {
try {
val zipEntry = zip.nextEntry ?: break
action(zipEntry, zip)
} catch (e: UTFDataFormatException) {
// Attempted to read Shift_JIS zip in Android < 7
Log.w("forEachZipEntry", "UTFDataFormatException: skipping a zip entry")
zip.closeEntry()
hadDecodeError = true
}
}
}
return hadDecodeError
}
@@ -33,10 +33,10 @@ import android.widget.Toast
import java.io.*
private const val CONTENT_TYPE_ZIP = "application/zip"
private const val INSTALL_REQUEST = 1
private const val ZIP_INSTALL_REQUEST = 1
private const val SAVEDATA_EXPORT_REQUEST = 2
private const val SAVEDATA_IMPORT_REQUEST = 3
private const val STATE_PROGRESS_TEXT = "progressText"
private const val CD_IMAGE_INSTALL_REQUEST = 4
class LauncherActivity : Activity(), LauncherObserver {
private lateinit var launcher: Launcher
@@ -48,9 +48,7 @@ class LauncherActivity : Activity(), LauncherObserver {
launcher = Launcher.getInstance(filesDir)
launcher.observer = this
if (launcher.isInstalling) {
showProgressDialog(savedInstanceState)
}
renderInstallState(launcher.installState)
onGameListChange()
val listView = findViewById<ListView>(R.id.list)
@@ -70,11 +68,9 @@ class LauncherActivity : Activity(), LauncherObserver {
super.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
progressDialog?.let {
outState.putCharSequence(STATE_PROGRESS_TEXT, it.findViewById<TextView>(R.id.text).text)
}
super.onSaveInstanceState(outState)
override fun onResume() {
super.onResume()
launcher.refreshGameList()
}
private fun onListItemClick(position: Int) {
@@ -100,7 +96,15 @@ class LauncherActivity : Activity(), LauncherObserver {
R.id.install_from_zip -> {
val i = Intent(Intent.ACTION_GET_CONTENT)
i.type = CONTENT_TYPE_ZIP
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), INSTALL_REQUEST)
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), ZIP_INSTALL_REQUEST)
true
}
R.id.install_from_cd_image -> {
val i = Intent(Intent.ACTION_GET_CONTENT)
i.type = "*/*"
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_cd_image_files)),
CD_IMAGE_INSTALL_REQUEST)
true
}
R.id.export_savedata -> {
@@ -150,14 +154,20 @@ class LauncherActivity : Activity(), LauncherObserver {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode != RESULT_OK)
return
val uri = data?.data ?: return
when (requestCode) {
INSTALL_REQUEST -> {
ZIP_INSTALL_REQUEST -> {
val uri = data?.data ?: return
val input = contentResolver.openInputStream(uri) ?: return
showProgressDialog()
launcher.install(input, getArchiveName(uri))
launcher.installZip(input, getArchiveName(uri))
renderInstallState(launcher.installState)
}
CD_IMAGE_INSTALL_REQUEST -> {
val files = getSelectedInstallFiles(data)
launcher.installCdImage(files, contentResolver, cacheDir)
renderInstallState(launcher.installState)
}
SAVEDATA_EXPORT_REQUEST -> try {
val uri = data?.data ?: return
launcher.exportSaveData(contentResolver.openOutputStream(uri)!!)
Toast.makeText(this, R.string.save_data_export_success, Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
@@ -165,6 +175,7 @@ class LauncherActivity : Activity(), LauncherObserver {
errorDialog(R.string.save_data_export_error)
}
SAVEDATA_IMPORT_REQUEST -> {
val uri = data?.data ?: return
val input = contentResolver.openInputStream(uri) ?: return
val errMsgId = launcher.importSaveData(input)
if (errMsgId == null) {
@@ -184,17 +195,15 @@ class LauncherActivity : Activity(), LauncherObserver {
}
override fun onInstallProgress(path: String) {
progressDialog?.findViewById<TextView>(R.id.text)?.text = getString(R.string.install_progress, path)
renderInstallState(launcher.installState)
}
override fun onInstallSuccess(path: File, archiveName: String?) {
dismissProgressDialog()
startGame(path, archiveName)
renderInstallState(launcher.installState)
}
override fun onInstallFailure(msgId: Int) {
dismissProgressDialog()
errorDialog(msgId)
renderInstallState(launcher.installState)
}
private fun startGame(path: File, archiveName: String?) {
@@ -210,16 +219,35 @@ class LauncherActivity : Activity(), LauncherObserver {
launcher.uninstall(id)
}
private fun showProgressDialog(savedInstanceState: Bundle? = null) {
progressDialog = Dialog(this)
progressDialog!!.apply {
setTitle(R.string.install_dialog_title)
setCancelable(false)
setContentView(R.layout.progress_dialog)
savedInstanceState?.let {
findViewById<TextView>(R.id.text)?.text = it.getCharSequence(STATE_PROGRESS_TEXT)
private fun renderInstallState(state: InstallState) {
when (state) {
InstallState.Idle -> dismissProgressDialog()
is InstallState.Installing -> showProgressDialog(state.progress)
is InstallState.Succeeded -> {
dismissProgressDialog()
startGame(state.path, state.archiveName)
launcher.consumeInstallResult()
}
show()
is InstallState.Failed -> {
dismissProgressDialog()
errorDialog(state.msgId)
launcher.consumeInstallResult()
}
}
}
private fun showProgressDialog(progress: String? = null) {
if (progressDialog == null) {
progressDialog = Dialog(this)
progressDialog!!.apply {
setTitle(R.string.install_dialog_title)
setCancelable(false)
setContentView(R.layout.progress_dialog)
show()
}
}
progress?.let {
progressDialog?.findViewById<TextView>(R.id.text)?.text = getString(R.string.install_progress, it)
}
}
@@ -236,13 +264,33 @@ class LauncherActivity : Activity(), LauncherObserver {
}
private fun getArchiveName(uri: Uri): String? {
val fname = getDisplayName(uri) ?: return null
return if (fname.endsWith(".zip", true)) fname.dropLast(4) else fname
}
private fun getSelectedInstallFiles(data: Intent?): List<SelectedInstallFile> {
val files = mutableListOf<SelectedInstallFile>()
val clipData = data?.clipData
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
files.add(SelectedInstallFile(uri, getDisplayName(uri) ?: uri.lastPathSegment ?: ""))
}
} else {
data?.data?.let { uri ->
files.add(SelectedInstallFile(uri, getDisplayName(uri) ?: uri.lastPathSegment ?: ""))
}
}
return files
}
private fun getDisplayName(uri: Uri): String? {
val cursor = contentResolver.query(uri, null, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val column = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (column >= 0) {
val fname = it.getString(column)
return if (fname.endsWith(".zip", true)) fname.dropLast(4) else fname
return it.getString(column)
}
}
}
@@ -14,10 +14,11 @@ class LicensesMenuActivity : Activity() {
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("Source Han Serif", "mincho", "https://github.com/adobe-fonts/source-han-serif/"),
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
)
override fun onCreate(savedInstanceState: Bundle?) {
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 32;
private static final int SDL_MICRO_VERSION = 4;
private static final int SDL_MICRO_VERSION = 10;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -3,6 +3,9 @@
<item
android:id="@+id/install_from_zip"
android:title="@string/action_install_from_zip" />
<item
android:id="@+id/install_from_cd_image"
android:title="@string/action_install_from_cd_image" />
<item
android:id="@+id/export_savedata"
android:title="@string/action_export_save_data" />
@@ -18,4 +21,4 @@
<item
android:id="@+id/licenses"
android:title="@string/action_licenses" />
</menu>
</menu>
@@ -4,17 +4,24 @@
<string name="action_export_save_data">セーブデータをエクスポート</string>
<string name="action_help">使い方</string>
<string name="action_import_save_data">セーブデータをインポート</string>
<string name="action_install_from_cd_image">CDイメージからインストール</string>
<string name="action_install_from_zip">ZIPからインストール</string>
<string name="action_licenses">オープンソースライセンス</string>
<string name="action_clear_save_data">セーブデータをクリア</string>
<string name="cancel">キャンセル</string>
<string name="cannot_find_ald">System 3.x のファイル (*.ald) が見つかりません。</string>
<string name="cannot_find_game_data_directory">CDイメージ内に GAMEDATA ディレクトリが見つかりません。</string>
<string name="cd_image_read_error">選択された CD イメージファイルを読み取れません。</string>
<string name="choose_a_file">ファイルを選択</string>
<string name="choose_cd_image_files">CDイメージファイルを選択</string>
<string name="clear_save_data_confirm">セーブデータをすべて削除します。元には戻せません。本当によろしいですか?</string>
<string name="confirm">確認</string>
<string name="error_dialog_title">エラー</string>
<string name="install_dialog_title">インストール中…</string>
<string name="install_error">ゲームのインストールに失敗しました。</string>
<string name="install_progress">%s を展開中</string>
<string name="invalid_iso9660_image">有効な ISO9660 イメージではありません。</string>
<string name="missing_cd_image_metadata">CD イメージファイルと対応するメタデータファイルを一緒に選択してください。ファイル選択画面ではファイルを長押しすると複数選択モードに入れます。</string>
<string name="no_data_to_import">ZIP アーカイブ中に save/ フォルダが見つかりません。</string>
<string name="save_data_export_error">セーブデータのエクスポートに失敗しました。</string>
<string name="save_data_export_success">エクスポートに成功しました。</string>
@@ -22,6 +29,7 @@
<string name="save_data_clear_success">セーブデータをクリアしました。</string>
<string name="save_data_clear_error">セーブデータのクリアに失敗しました。</string>
<string name="uninstall_dialog_message">「%s」をアンインストールしますか?</string>
<string name="unsupported_cd_image">この CD イメージ形式にはまだ対応していません。現在は .iso、.bin/.img + .cue/.ccd、.mdf/.mds に対応しています。</string>
<string name="unsupported_zip">この形式の ZIP はサポートしていません。</string>
<string name="zip_extraction_error">ZIP の展開に失敗しました。</string>
<string name="usage">
@@ -41,4 +49,4 @@
<p>3. この画面の右上のメニューボタンをタップして「ZIPからインストール」を選択し、転送したZIPを選びます。</p>
]]>
</string>
</resources>
</resources>
@@ -4,16 +4,23 @@
<string name="action_export_save_data">Export Save Files</string>
<string name="action_help">Help</string>
<string name="action_import_save_data">Import Save Files</string>
<string name="action_install_from_cd_image">Install from CD image</string>
<string name="action_install_from_zip">Install from ZIP</string>
<string name="action_licenses">Open source licenses</string>
<string name="cancel">Cancel</string>
<string name="cannot_find_ald">Cannot find System 3.x game files (*.ald).</string>
<string name="cannot_find_game_data_directory">Cannot find GAMEDATA directory in the CD image.</string>
<string name="cd_image_read_error">Cannot read the selected CD image file.</string>
<string name="choose_a_file">Choose a file</string>
<string name="choose_cd_image_files">Choose CD image files</string>
<string name="clear_save_data_confirm">Are you sure you want to clear all save files? This cannot be undone.</string>
<string name="confirm">Confirm</string>
<string name="error_dialog_title">Error</string>
<string name="install_dialog_title">Installing…</string>
<string name="install_error">Failed to install game.</string>
<string name="install_progress">Extracting %s</string>
<string name="invalid_iso9660_image">This file is not a valid ISO9660 image.</string>
<string name="missing_cd_image_metadata">Select a matching metadata file with the CD image file. In the file picker, long-press a file to enter multi-select mode.</string>
<string name="no_data_to_import">No save/ folder in the ZIP archive.</string>
<string name="ok">OK</string>
<string name="save_data_clear_error">Failed to clear save files.</string>
@@ -22,6 +29,7 @@
<string name="save_data_import_success">Imported successfully.</string>
<string name="save_data_export_success">Exported successfully.</string>
<string name="uninstall_dialog_message">Uninstall \"%s\"?</string>
<string name="unsupported_cd_image">This CD image format is not supported yet. Currently only .iso, .bin/.img + .cue/.ccd, and .mdf/.mds are supported.</string>
<string name="unsupported_zip">This type of ZIP is not supported.</string>
<string name="zip_extraction_error">Failed to extract ZIP.</string>
<string name="usage">
@@ -0,0 +1,714 @@
/* 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
*
*/
package io.github.kichikuou.xsystem35
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.charset.Charset
class Iso9660FileSystemTest {
@get:Rule
val tmp = TemporaryFolder()
@Test
fun extractsGameDataDirectoryFromPrimaryDescriptor() = runBlocking {
val iso = SyntheticIsoBuilder()
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
.addGameDataFile("SUB/FILE.TXT;1", "text".toByteArray())
.writeTo(tmp.newFile("game.iso"))
CdImageReader.openForFile(iso).use { reader ->
val fs = Iso9660FileSystem(reader)
val gameData = fs.findGameDataDirectory()
?: throw AssertionError("GAMEDATA directory was not found")
val extracted = mutableMapOf<String, ByteArray>()
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
extracted[path] = input.readBytes()
}
assertArrayEquals("ald".toByteArray(), extracted["GAMEDATA/SA.ALD"])
assertArrayEquals("text".toByteArray(), extracted["GAMEDATA/SUB/FILE.TXT"])
}
}
@Test
fun decodesPrimaryDescriptorNamesAsShiftJis() = runBlocking {
val name = "表.TXT;1"
val iso = SyntheticIsoBuilder()
.addGameDataFile(name, "sjis".toByteArray())
.writeTo(tmp.newFile("sjis.iso"))
CdImageReader.openForFile(iso).use { reader ->
val fs = Iso9660FileSystem(reader)
val gameData = fs.findGameDataDirectory()
?: throw AssertionError("GAMEDATA directory was not found")
val extracted = mutableMapOf<String, ByteArray>()
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
extracted[path] = input.readBytes()
}
assertArrayEquals("sjis".toByteArray(), extracted["GAMEDATA/表.TXT"])
}
}
@Test
fun prefersJolietDescriptorOverPrimaryDescriptor() = runBlocking {
val iso = SyntheticIsoBuilder()
.addGameDataFile("PRIMARY.TXT;1", "primary".toByteArray())
.addJolietGameDataFile("日本語.TXT;1", "joliet".toByteArray())
.writeTo(tmp.newFile("joliet.iso"))
CdImageReader.openForFile(iso).use { reader ->
val fs = Iso9660FileSystem(reader)
val gameData = fs.findGameDataDirectory()
?: throw AssertionError("GAMEDATA directory was not found")
val extracted = mutableMapOf<String, ByteArray>()
fs.extractDirectory(gameData, "GAMEDATA") { path, input ->
extracted[path] = input.readBytes()
}
assertFalse(extracted.containsKey("GAMEDATA/PRIMARY.TXT"))
assertArrayEquals("joliet".toByteArray(), extracted["GAMEDATA/日本語.TXT"])
}
}
@Test
fun rejectsInvalidVolumeDescriptorSignature() {
val iso = tmp.newFile("invalid.iso")
iso.writeBytes(ByteArray(18 * CdImageReader.ISO_SECTOR_SIZE))
val e = assertInstallFailure(R.string.invalid_iso9660_image) {
CdImageReader.openForFile(iso).use { Iso9660FileSystem(it) }
}
assertEquals(R.string.invalid_iso9660_image, e.msgId)
}
}
class CdImageReaderTest {
@get:Rule
val tmp = TemporaryFolder()
@Test
fun readsIsoSectorAt2048ByteOffset() {
val sector0 = sectorFilledWith(0x10)
val sector1 = sectorFilledWith(0x20)
val image = tmp.newFile("plain.iso")
image.writeBytes(sector0 + sector1)
CdImageReader.openForFile(image).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
reader.readSector(1, buffer)
assertArrayEquals(sector1, buffer)
assertTrue(reader.audioTracks().isEmpty())
}
}
@Test
fun cueMode12048ReadsDataSector() {
val payload = sectorFilledWith(0x31)
val image = tmp.newFile("mode2048.bin")
image.writeBytes(payload)
CdImageReader.openForFile(image, "mode2048.cue", cue("MODE1/2048").toByteArray()).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
reader.readSector(0, buffer)
assertArrayEquals(payload, buffer)
}
}
@Test
fun cueMode12352ReadsPayloadAtOffset16() {
val payload = sectorFilledWith(0x42)
val image = tmp.newFile("mode2352.bin")
image.writeBytes(rawDataSector(payload, 0x11))
CdImageReader.openForFile(image, "mode2352.cue", cue("MODE1/2352").toByteArray()).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
reader.readSector(0, buffer)
assertArrayEquals(payload, buffer)
}
}
@Test
fun cuePregapAdvancesDataTrackStart() {
val pregapPayload = sectorFilledWith(0x21)
val dataPayload = sectorFilledWith(0x22)
val image = tmp.newFile("pregap.bin")
image.writeBytes(rawDataSector(pregapPayload, 0x01) + rawDataSector(dataPayload, 0x02))
val metadata = """
FILE "pregap.bin" BINARY
TRACK 01 MODE1/2352
INDEX 00 00:00:00
INDEX 01 00:00:01
""".trimIndent()
CdImageReader.openForFile(image, "pregap.cue", metadata.toByteArray()).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
reader.readSector(1, buffer)
assertArrayEquals(dataPayload, buffer)
}
}
@Test
fun extractsCueAudioTrackAsWav() {
val dataPayload = sectorFilledWith(0x33)
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { it.toByte() }
val image = tmp.newFile("audio.bin")
image.writeBytes(rawDataSector(dataPayload, 0) + audio)
val metadata = """
FILE "audio.bin" BINARY
TRACK 01 MODE1/2352
INDEX 01 00:00:00
TRACK 02 AUDIO
INDEX 01 00:00:01
""".trimIndent()
CdImageReader.openForFile(image, "audio.cue", metadata.toByteArray()).use { reader ->
val output = ByteArrayOutputStream()
assertEquals(listOf(2), reader.audioTracks())
reader.extractAudioTrack(2, output)
val wav = output.toByteArray()
assertEquals("RIFF", wav.decodeAscii(0, 4))
assertEquals(RAW_AUDIO_SECTOR_SIZE + 36, wav.readLeInt(4))
assertEquals("WAVE", wav.decodeAscii(8, 4))
assertEquals("data", wav.decodeAscii(36, 4))
assertEquals(RAW_AUDIO_SECTOR_SIZE, wav.readLeInt(40))
assertEquals(44100, wav.readLeInt(24))
assertEquals(2, wav.readLeShort(22))
assertEquals(16, wav.readLeShort(34))
assertArrayEquals(audio, wav.copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
}
}
@Test
fun rejectsUnsupportedCueMode() {
val image = tmp.newFile("unsupported.bin")
image.writeBytes(ByteArray(CdImageReader.ISO_SECTOR_SIZE))
assertInstallFailure(R.string.unsupported_cd_image) {
CdImageReader.openForFile(image, "unsupported.cue", cue("MODE2/2352").toByteArray())
}
}
@Test
fun ccdUsesMode0ForAudioAndMode1PayloadOffsetForData() {
val dataPayload = sectorFilledWith(0x52)
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (255 - it).toByte() }
val image = tmp.newFile("disc.img")
image.writeBytes(rawDataSector(dataPayload, 0x08) + audio)
val metadata = """
[TRACK 1]
MODE=1
INDEX 1=0
[TRACK 2]
MODE=0
INDEX 1=1
""".trimIndent()
CdImageReader.openForFile(image, "disc.ccd", metadata.toByteArray()).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
val output = ByteArrayOutputStream()
reader.readSector(0, buffer)
reader.extractAudioTrack(2, output)
assertArrayEquals(dataPayload, buffer)
assertEquals(listOf(2), reader.audioTracks())
assertArrayEquals(audio, output.toByteArray().copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
}
}
@Test
fun mdsMapsModeAAToDataAndModeA9ToAudio() {
val dataPayload = sectorFilledWith(0x61)
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (it * 3).toByte() }
val image = tmp.newFile("disc.mdf")
image.writeBytes(rawDataSector(dataPayload, 0x09) + audio)
CdImageReader.openForFile(image, "disc.mds", mds(track1Sectors = 1, track2Sectors = 1)).use { reader ->
val buffer = ByteArray(CdImageReader.ISO_SECTOR_SIZE)
val output = ByteArrayOutputStream()
reader.readSector(0, buffer)
reader.extractAudioTrack(2, output)
assertArrayEquals(dataPayload, buffer)
assertEquals(listOf(2), reader.audioTracks())
assertArrayEquals(audio, output.toByteArray().copyOfRange(44, 44 + RAW_AUDIO_SECTOR_SIZE))
}
}
@Test
fun rejectsMdsWithoutMediaDescriptorSignature() {
val image = tmp.newFile("bad.mdf")
image.writeBytes(ByteArray(RAW_AUDIO_SECTOR_SIZE))
assertInstallFailure(R.string.unsupported_cd_image) {
CdImageReader.openForFile(image, "bad.mds", ByteArray(0x70))
}
}
}
class GameInstallerCdImageTest {
@get:Rule
val tmp = TemporaryFolder()
@Test
fun installsDataOnlyIso() = runBlocking {
val root = tmp.newFolder("root")
val iso = SyntheticIsoBuilder()
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
.addGameDataFile("TITLE.TXT;1", "title".toByteArray())
.writeTo(tmp.newFile("game.iso"))
CdImageReader.openForFile(iso).use { reader ->
val gameDir = GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
assertEquals(File(root, "0/GAMEDATA").canonicalFile, gameDir.canonicalFile)
assertEquals("GAMEDATA", File(root, "0/${Launcher.GAMEDIR_FILE}").readText())
assertEquals("ald", File(gameDir, "SA.ALD").readText())
assertEquals("title", File(gameDir, "TITLE.TXT").readText())
}
}
@Test
fun installsCueAudioTrackAndPlaylist() = runBlocking {
val root = tmp.newFolder("root")
val isoSector = SyntheticIsoBuilder()
.addGameDataFile("SA.ALD;1", "ald".toByteArray())
.toByteArray()
val dataSectors = isoSector.toList().chunked(CdImageReader.ISO_SECTOR_SIZE)
.joinToByteArray { rawDataSector(it.toByteArray(), 0) }
val audio = ByteArray(RAW_AUDIO_SECTOR_SIZE) { (it and 0x7f).toByte() }
val image = tmp.newFile("audio.bin")
image.writeBytes(dataSectors + audio)
val dataSectorCount = isoSector.size / CdImageReader.ISO_SECTOR_SIZE
val metadata = """
FILE "audio.bin" BINARY
TRACK 01 MODE1/2352
INDEX 01 00:00:00
TRACK 02 AUDIO
INDEX 01 ${dataSectorCount.toMsf()}
""".trimIndent()
CdImageReader.openForFile(image, "audio.cue", metadata.toByteArray()).use { reader ->
val gameDir = GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
val wav = File(gameDir, "cdda/track02.wav")
assertTrue(wav.exists())
assertEquals("\ncdda/track02.wav", File(gameDir, Launcher.PLAYLIST_FILE).readText())
}
}
@Test
fun rejectsIsoWithoutGameDataDirectoryAndDeletesIncompleteInstall() = runBlocking {
val root = tmp.newFolder("root")
val iso = SyntheticIsoBuilder(includeGameData = false).writeTo(tmp.newFile("nogamedata.iso"))
CdImageReader.openForFile(iso).use { reader ->
assertInstallFailure(R.string.cannot_find_game_data_directory) {
runBlocking {
GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
}
}
}
assertFalse(File(root, "0").exists())
}
@Test
fun rejectsIsoWithoutAldAndDeletesIncompleteInstall() = runBlocking {
val root = tmp.newFolder("root")
val iso = SyntheticIsoBuilder()
.addGameDataFile("README.TXT;1", "readme".toByteArray())
.writeTo(tmp.newFile("noald.iso"))
CdImageReader.openForFile(iso).use { reader ->
assertInstallFailure(R.string.cannot_find_ald) {
runBlocking {
GameInstaller(GameStore(root)).installCdImageForTest(reader) {}
}
}
}
assertFalse(File(root, "0").exists())
}
@Test
fun resolveOutputPathRejectsTraversal() {
val base = tmp.newFolder("out")
assertThrowsIOException {
resolveOutputPath(base, "../evil")
}
assertThrowsIOException {
resolveOutputPath(base, File(base.parentFile, "evil").absolutePath)
}
}
}
private class SyntheticIsoBuilder(
private val includeGameData: Boolean = true,
) {
private val primaryFiles = mutableMapOf<String, ByteArray>()
private val jolietFiles = mutableMapOf<String, ByteArray>()
fun addGameDataFile(path: String, content: ByteArray): SyntheticIsoBuilder {
primaryFiles[path] = content
return this
}
fun addJolietGameDataFile(path: String, content: ByteArray): SyntheticIsoBuilder {
jolietFiles[path] = content
return this
}
fun writeTo(file: File): File {
file.writeBytes(toByteArray())
return file
}
fun toByteArray(): ByteArray {
val sectors = MutableList(80) { ByteArray(CdImageReader.ISO_SECTOR_SIZE) }
var nextSector = 30
val primaryTree = buildTree(primaryFiles, Charset.forName("Shift_JIS"))
val primaryRoot = writeTree(sectors, primaryTree, ROOT_SECTOR, GAMEDATA_SECTOR, nextSector, false)
nextSector = primaryRoot.nextSector
writeVolumeDescriptor(sectors[16], 1, ROOT_SECTOR, CdImageReader.ISO_SECTOR_SIZE, false)
var terminatorSector = 17
if (jolietFiles.isNotEmpty()) {
val jolietTree = buildTree(jolietFiles, Charsets.UTF_16BE)
writeTree(sectors, jolietTree, JOLIET_ROOT_SECTOR, JOLIET_GAMEDATA_SECTOR, nextSector, true)
writeVolumeDescriptor(sectors[17], 2, JOLIET_ROOT_SECTOR, CdImageReader.ISO_SECTOR_SIZE, true)
terminatorSector = 18
}
writeTerminator(sectors[terminatorSector])
return sectors.flattenToByteArray()
}
private fun buildTree(files: Map<String, ByteArray>, charset: Charset): DirectoryNode {
val root = DirectoryNode("")
if (!includeGameData) {
return root
}
val gameData = DirectoryNode("GAMEDATA")
root.directories["GAMEDATA"] = gameData
for ((path, content) in files) {
var directory = gameData
val parts = path.split("/")
for (part in parts.dropLast(1)) {
directory = directory.directories.getOrPut(part.substringBefore(";")) {
DirectoryNode(part.substringBefore(";"))
}
}
val rawName = parts.last().toByteArray(charset)
directory.files.add(FileNode(rawName, content))
}
return root
}
private fun writeTree(
sectors: MutableList<ByteArray>,
root: DirectoryNode,
rootSector: Int,
gameDataSector: Int,
firstFileSector: Int,
joliet: Boolean,
): TreeWriteResult {
var nextSector = firstFileSector
root.extent = rootSector
root.size = CdImageReader.ISO_SECTOR_SIZE
if (includeGameData) {
root.directories["GAMEDATA"]!!.extent = gameDataSector
root.directories["GAMEDATA"]!!.size = CdImageReader.ISO_SECTOR_SIZE
}
assignDirectorySectors(root, gameDataSector + 1)
nextSector = writeFileContents(sectors, root, nextSector)
writeDirectorySector(sectors[rootSector], root, root, joliet)
if (includeGameData) {
writeDirectorySectors(sectors, root.directories["GAMEDATA"]!!, root, joliet)
}
return TreeWriteResult(nextSector)
}
private fun assignDirectorySectors(directory: DirectoryNode, nextDirectorySector: Int): Int {
var sector = nextDirectorySector
for (child in directory.directories.values) {
if (child.name == "GAMEDATA") {
sector = assignDirectorySectors(child, sector)
continue
}
child.extent = sector++
child.size = CdImageReader.ISO_SECTOR_SIZE
sector = assignDirectorySectors(child, sector)
}
return sector
}
private fun writeFileContents(
sectors: MutableList<ByteArray>,
directory: DirectoryNode,
firstFileSector: Int,
): Int {
var sector = firstFileSector
for (child in directory.directories.values) {
sector = writeFileContents(sectors, child, sector)
}
for (file in directory.files) {
file.extent = sector
file.size = file.content.size
val sectorCount = (file.content.size + CdImageReader.ISO_SECTOR_SIZE - 1) /
CdImageReader.ISO_SECTOR_SIZE
for (i in 0 until sectorCount) {
file.content.copyInto(
sectors[sector + i],
0,
i * CdImageReader.ISO_SECTOR_SIZE,
minOf(file.content.size, (i + 1) * CdImageReader.ISO_SECTOR_SIZE)
)
}
sector += maxOf(1, sectorCount)
}
return sector
}
private fun writeDirectorySectors(
sectors: MutableList<ByteArray>,
directory: DirectoryNode,
parent: DirectoryNode,
joliet: Boolean,
) {
writeDirectorySector(sectors[directory.extent], directory, parent, joliet)
for (child in directory.directories.values) {
writeDirectorySectors(sectors, child, directory, joliet)
}
}
private fun writeDirectorySector(
sector: ByteArray,
directory: DirectoryNode,
parent: DirectoryNode,
joliet: Boolean,
) {
var offset = 0
offset += writeRecord(sector, offset, byteArrayOf(0), directory.extent, directory.size, true)
offset += writeRecord(sector, offset, byteArrayOf(1), parent.extent, parent.size, true)
for (child in directory.directories.values) {
val name = if (joliet) child.name.toByteArray(Charsets.UTF_16BE) else child.name.toByteArray()
offset += writeRecord(sector, offset, name, child.extent, child.size, true)
}
for (file in directory.files) {
offset += writeRecord(sector, offset, file.rawName, file.extent, file.size, false)
}
}
private fun writeVolumeDescriptor(
sector: ByteArray,
type: Int,
rootExtent: Int,
rootSize: Int,
joliet: Boolean,
) {
sector[0] = type.toByte()
"CD001".toByteArray(Charsets.US_ASCII).copyInto(sector, 1)
sector[6] = 1
if (joliet) {
sector[88] = 0x25
sector[89] = 0x2f
sector[90] = 0x45
}
writeRecord(sector, 156, byteArrayOf(0), rootExtent, rootSize, true)
}
private fun writeTerminator(sector: ByteArray) {
sector[0] = 255.toByte()
"CD001".toByteArray(Charsets.US_ASCII).copyInto(sector, 1)
sector[6] = 1
}
private fun writeRecord(
buffer: ByteArray,
offset: Int,
rawName: ByteArray,
extent: Int,
size: Int,
isDirectory: Boolean,
): Int {
val length = 33 + rawName.size + if (rawName.size % 2 == 0) 1 else 0
buffer[offset] = length.toByte()
buffer.writeLeInt(offset + 2, extent)
buffer.writeLeInt(offset + 10, size)
buffer[offset + 25] = if (isDirectory) 0x02 else 0x00
buffer[offset + 32] = rawName.size.toByte()
rawName.copyInto(buffer, offset + 33)
return length
}
companion object {
private const val ROOT_SECTOR = 20
private const val GAMEDATA_SECTOR = 21
private const val JOLIET_ROOT_SECTOR = 24
private const val JOLIET_GAMEDATA_SECTOR = 25
}
}
private data class DirectoryNode(
val name: String,
val directories: MutableMap<String, DirectoryNode> = linkedMapOf(),
val files: MutableList<FileNode> = mutableListOf(),
var extent: Int = 0,
var size: Int = 0,
)
private data class FileNode(
val rawName: ByteArray,
val content: ByteArray,
var extent: Int = 0,
var size: Int = 0,
)
private data class TreeWriteResult(val nextSector: Int)
private const val RAW_AUDIO_SECTOR_SIZE = 2352
private fun cue(mode: String): String = """
FILE "image.bin" BINARY
TRACK 01 $mode
INDEX 01 00:00:00
""".trimIndent()
private fun mds(track1Sectors: Int, track2Sectors: Int): ByteArray {
val bytes = ByteArray(0x70 + 2 * 0x50 + 2 * 8)
"MEDIA DESCRIPTOR".toByteArray(Charsets.US_ASCII).copyInto(bytes, 0)
bytes[0x62] = 2
val track1 = 0x70
bytes[track1] = 0xaa.toByte()
bytes[track1 + 0x04] = 1
bytes.writeLeShort(track1 + 0x10, RAW_AUDIO_SECTOR_SIZE)
bytes.writeLeInt(track1 + 0x28, 0)
bytes.writeLeInt(0x70 + 2 * 0x50 + 0x04, track1Sectors)
val track2 = 0x70 + 0x50
bytes[track2] = 0xa9.toByte()
bytes[track2 + 0x04] = 2
bytes.writeLeShort(track2 + 0x10, RAW_AUDIO_SECTOR_SIZE)
bytes.writeLeInt(track2 + 0x28, track1Sectors * RAW_AUDIO_SECTOR_SIZE)
bytes.writeLeInt(0x70 + 2 * 0x50 + 8 + 0x04, track2Sectors)
return bytes
}
private fun sectorFilledWith(value: Int): ByteArray {
return ByteArray(CdImageReader.ISO_SECTOR_SIZE) { value.toByte() }
}
private fun rawDataSector(payload: ByteArray, prefixValue: Int): ByteArray {
val sector = ByteArray(RAW_AUDIO_SECTOR_SIZE) { prefixValue.toByte() }
payload.copyInto(sector, 16, 0, CdImageReader.ISO_SECTOR_SIZE)
return sector
}
private fun Int.toMsf(): String {
val minutes = this / (60 * 75)
val seconds = this / 75 % 60
val frames = this % 75
return "%02d:%02d:%02d".format(minutes, seconds, frames)
}
private fun Iterable<Byte>.toByteArray(): ByteArray {
val result = ByteArray(count())
forEachIndexed { index, byte -> result[index] = byte }
return result
}
private fun List<ByteArray>.flattenToByteArray(): ByteArray {
val result = ByteArray(sumOf { it.size })
var offset = 0
for (bytes in this) {
bytes.copyInto(result, offset)
offset += bytes.size
}
return result
}
private fun List<List<Byte>>.joinToByteArray(transform: (List<Byte>) -> ByteArray): ByteArray {
return map(transform).flattenToByteArray()
}
private fun ByteArray.decodeAscii(offset: Int, length: Int): String {
return String(this, offset, length, Charsets.US_ASCII)
}
private fun ByteArray.readLeShort(offset: Int): Int {
return (this[offset].toInt() and 0xff) or ((this[offset + 1].toInt() and 0xff) shl 8)
}
private fun ByteArray.readLeInt(offset: Int): Int {
return readLeShort(offset) or (readLeShort(offset + 2) shl 16)
}
private fun ByteArray.writeLeShort(offset: Int, value: Int) {
this[offset] = value.toByte()
this[offset + 1] = (value shr 8).toByte()
}
private fun ByteArray.writeLeInt(offset: Int, value: Int) {
writeLeShort(offset, value)
writeLeShort(offset + 2, value shr 16)
}
private fun assertInstallFailure(expectedMsgId: Int, block: () -> Unit): InstallFailureException {
try {
block()
} catch (e: InstallFailureException) {
assertEquals(expectedMsgId, e.msgId)
return e
}
fail("Expected InstallFailureException")
throw AssertionError()
}
private fun assertThrowsIOException(block: () -> Unit) {
try {
block()
} catch (e: java.io.IOException) {
return
}
fail("Expected IOException")
}
+2 -3
View File
@@ -4,18 +4,17 @@
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
#cmakedefine ENABLE_GTK @ENABLE_GTK@
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
#cmakedefine ENABLE_MIDI_PORTMIDI @ENABLE_MIDI_PORTMIDI@
#cmakedefine ENABLE_NLS @ENABLE_NLS@
#cmakedefine DEFAULT_PLAYLIST_PATH "@DEFAULT_PLAYLIST_PATH@"
#cmakedefine JOY_DEVICE "@JOY_DEVICE@"
#cmakedefine HAVE_LIBINTL @HAVE_LIBINTL@
#cmakedefine HAVE_GETLOGIN @HAVE_GETLOGIN@
#cmakedefine HAVE_MMAP @HAVE_MMAP@
#cmakedefine HAVE_SIGACTION @HAVE_SIGACTION@
#cmakedefine HAVE_WEBP @HAVE_WEBP@
#define DEFAULT_GOTHIC_TTF "@DEFAULT_FONT_PATH@MTLc3m.ttf"
#define DEFAULT_MINCHO_TTF "@DEFAULT_FONT_PATH@mincho.otf"
#define DEFAULT_MINCHO_TTF "@DEFAULT_FONT_PATH@mincho.ttf"
+8
View File
@@ -49,6 +49,11 @@ finds System 3.x game files (*.ALD) from the current directory.
Suppress text hook on specified scenario pages. _list_ is a comma-separated
list of page numbers. Used to suppress system messages, etc.
*-censor* _file_::
Enable Streamer Mode, which mosaics specific images to make them safe for
streaming. The _file_ is a text file containing a list of image numbers
to censor, one per line. Lines starting with '#' are treated as comments.
*-renderer* _name_::
Use the SDL rendering driver named _name_.
@@ -93,6 +98,9 @@ finds System 3.x game files (*.ALD) from the current directory.
*-noimagecursor*::
Disable custom mouse cursor images.
*-mute_on_unfocus*::
Mute audio while the game window is not in focus.
*-version*::
Print the version number and exit.
+5 -4
View File
@@ -1,6 +1,6 @@
install(FILES
MTLc3m.ttf
mincho.otf
mincho.ttf
DESTINATION share/xsystem35/fonts)
# pyftsubset is included in fonttools.
@@ -9,8 +9,9 @@ find_program(PYFTSUBSET_FOUND pyftsubset)
if (PYFTSUBSET_FOUND)
add_custom_target(fonts
COMMAND uchars > uchars.txt
COMMAND wget -O SourceHanSerifJP-Regular.otf https://github.com/adobe-fonts/source-han-serif/blob/release/SubsetOTF/JP/SourceHanSerifJP-Regular.otf?raw=true
COMMAND pyftsubset SourceHanSerifJP-Regular.otf --text-file=uchars.txt --output-file=subset.otf
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/adjust_metrics.py subset.otf ${CMAKE_CURRENT_SOURCE_DIR}/mincho.otf
COMMAND wget -O ipam.zip https://moji.or.jp/wp-content/ipafont/IPAfont/ipam00303.zip
COMMAND unzip -o -j ipam.zip ipam00303/ipam.ttf
COMMAND pyftsubset ipam.ttf --text-file=uchars.txt --output-file=subset.ttf
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/rename_font.py subset.ttf ${CMAKE_CURRENT_SOURCE_DIR}/mincho.ttf
)
endif()
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env python3
import sys
from fontTools.ttLib import TTFont
if len(sys.argv) != 3:
print('Usage: adjust_metrics.py infile outfile')
exit(1)
font = TTFont(sys.argv[1])
hhea = font['hhea']
os2 = font['OS/2']
if hhea.ascent != os2.sTypoAscender:
print(f'Changing hhea.ascent from {hhea.ascent} to {os2.sTypoAscender}')
hhea.ascent = os2.sTypoAscender
if hhea.descent != os2.sTypoDescender:
print(f'Changing hhea.descent from {hhea.descent} to {os2.sTypoDescender}')
hhea.descent = os2.sTypoDescender
font.save(sys.argv[2])
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# Rename the font's internal name records.
#
# IPA明朝 is distributed under the IPA Font License v1.0. A subset of it is a
# "Derived Program" under that license, and Article 3.1(4) forbids using or
# including the Licensed Program's name ("IPA"/"IPAMincho") as the program,
# font, or file name of the Derived Program. pyftsubset preserves the original
# name table, so rename it here to a name that does not contain "IPA".
import sys
from fontTools.ttLib import TTFont
if len(sys.argv) != 3:
print('Usage: rename_font.py infile outfile')
exit(1)
FAMILY = 'xsystem35 Mincho'
PSNAME = 'xsystem35-Mincho'
font = TTFont(sys.argv[1])
name = font['name']
# Rewrite the family / full / typographic-family / PostScript / unique-ID
# records in place, preserving each record's platform, encoding and language.
for rec in list(name.names):
if rec.nameID in (1, 4, 16):
value = FAMILY
elif rec.nameID in (3, 6):
value = PSNAME
else:
continue
name.setName(value, rec.nameID, rec.platformID, rec.platEncID, rec.langID)
font.save(sys.argv[2])
+2 -2
View File
@@ -42,7 +42,7 @@ Game Compatibility
| これD・P・S? | Supported | From 20世紀アリス |
| 夜が来る! | Unsupported | |
| Only you ~リ・クルス~ | Supported | |
| 大悪司 | Supported | Opening demo is not supported |
| 大悪司 | Supported | |
| 王子さまLv1 | Supported | |
| 王子さま1ヵ月後 | Supported | From 王子さまLV1.5 |
| 白鳳の子分叩き | Supported | From 王子さまLV1.5 |
@@ -62,7 +62,7 @@ Game Compatibility
| 翡翠の眸 | Unsupported | |
| 学園KING | Supported | From アリスCD |
| 学園KING 番外編 | Supported | From アリスCD |
| 蒼海にちて | Supported | From アリスCD |
| 蒼海にちて | Supported | From アリスCD |
| ぶろぶろ | Supported | From アリスCD |
| 裸ぶろぶろ狂 | Supported | From アリスCD |
| メイドのススメ | Supported | From アリスCD |
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2024 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+117 -92
View File
@@ -1,92 +1,117 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------
IPA Font License Agreement v1.0 <Japanese/English>
--------------------------------------------------
IPAフォントライセンスv1.0
許諾者は、この使用許諾(以下「本契約」といいます。)に定める条件の下で、許諾プログラム(1条に定義するところによります。)を提供します。受領者(1条に定義するところによります。)が、許諾プログラムを使用し、複製し、または頒布する行為、その他、本契約に定める権利の利用を行った場合、受領者は本契約に同意したものと見なします。
第1条 用語の定義
本契約において、次の各号に掲げる用語は、当該各号に定めるところによります。
1.「デジタル・フォント・プログラム」とは、フォントを含み、レンダリングしまたは表示するために用いられるコンピュータ・プログラムをいいます。
2.「許諾プログラム」とは、許諾者が本契約の下で許諾するデジタル・フォント・プログラムをいいます。
3.「派生プログラム」とは、許諾プログラムの一部または全部を、改変し、加除修正等し、入れ替え、その他翻案したデジタル・フォント・プログラムをいい、許諾プログラムの一部もしくは全部から文字情報を取り出し、またはデジタル・ドキュメント・ファイルからエンベッドされたフォントを取り出し、取り出された文字情報をそのまま、または改変をなして新たなデジタル・フォント・プログラムとして製作されたものを含みます。
4.「デジタル・コンテンツ」とは、デジタル・データ形式によってエンド・ユーザに提供される制作物のことをいい、動画・静止画等の映像コンテンツおよびテレビ番組等の放送コンテンツ、ならびに文字テキスト、画像、図形等を含んで構成された制作物を含みます。
5.「デジタル・ドキュメント・ファイル」とは、PDFファイルその他、各種ソフトウェア・プログラムによって製作されたデジタル・コンテンツであって、その中にフォントを表示するために許諾プログラムの全部または一部が埋め込まれた(エンベッドされた)ものをいいます。フォントが「エンベッドされた」とは、当該フォントが埋め込まれた特定の「デジタル・ドキュメント・ファイル」においてのみ表示されるために使用されている状態を指し、その特定の「デジタル・ドキュメント・ファイル」以外でフォントを表示するために使用できるデジタル・フォント・プログラムに含まれている場合と区別されます。
6.「コンピュータ」とは、本契約においては、サーバを含みます。
7.「複製その他の利用」とは、複製、譲渡、頒布、貸与、公衆送信、上映、展示、翻案その他の利用をいいます。
8.「受領者」とは、許諾プログラムを本契約の下で受領した人をいい、受領者から許諾プログラムを受領した人を含みます。
第2条 使用許諾の付与
許諾者は受領者に対し、本契約の条項に従い、すべての国で、許諾プログラムを使用することを許諾します。ただし、許諾プログラムに存在する一切の権利はすべて許諾者が保有しています。本契約は、本契約で明示的に定められている場合を除き、いかなる意味においても、許諾者が保有する許諾プログラムに関する一切の権利および、いかなる商標、商号、もしくはサービス・マークに関する権利をも受領者に移転するものではありません。
1.受領者は本契約に定める条件に従い、許諾プログラムを任意の数のコンピュータにインストールし、当該コンピュータで使用することができます。
2.受領者はコンピュータにインストールされた許諾プログラムをそのまま、または改変を行ったうえで、印刷物およびデジタル・コンテンツにおいて、文字テキスト表現等として使用することができます。
3.受領者は前項の定めに従い作成した印刷物およびデジタル・コンテンツにつき、その商用・非商用の別、および放送、通信、各種記録メディアなどの媒体の形式を問わず、複製その他の利用をすることができます。
4.受領者がデジタル・ドキュメント・ファイルからエンベッドされたフォントを取り出して派生プログラムを作成した場合には、かかる派生プログラムは本契約に定める条件に従う必要があります。
5.許諾プログラムのエンベッドされたフォントがデジタル・ドキュメント・ファイル内のデジタル・コンテンツをレンダリングするためにのみ使用される場合において、受領者が当該デジタル・ドキュメント・ファイルを複製その他の利用をする場合には、受領者はかかる行為に関しては本契約の下ではいかなる義務をも負いません。
6.受領者は、3条2項の定めに従い、商用・非商用を問わず、許諾プログラムをそのままの状態で改変することなく複製して第三者への譲渡し、公衆送信し、その他の方法で再配布することができます(以下、「再配布」といいます。)。
7.受領者は、上記の許諾プログラムについて定められた条件と同様の条件に従って、派生プログラムを作成し、使用し、複製し、再配布することができます。ただし、受領者が派生プログラムを再配布する場合には、3条1項の定めに従うものとします。
第3条 制限
前条により付与された使用許諾は、以下の制限に服します。
1.派生プログラムが前条4項及び7項に基づき再配布される場合には、以下の全ての条件を満たさなければなりません。
 (1)派生プログラムを再配布する際には、下記もまた、当該派生プログラムと一緒に再配布され、オンラインで提供され、または、郵送費・媒体及び取扱手数料の合計を超えない実費と引き換えに媒体を郵送する方法により提供されなければなりません。
  (a)派生プログラムの写し; および
  (b)派生プログラムを作成する過程でフォント開発プログラムによって作成された追加のファイルであって派生プログラムをさらに加工するにあたって利用できるファイルが存在すれば、当該ファイル
 (2)派生プログラムの受領者が、派生プログラムを、このライセンスの下で最初にリリースされた許諾プログラム(以下、「オリジナル・プログラム」といいます。)に置き換えることができる方法を再配布するものとします。かかる方法は、オリジナル・ファイルからの差分ファイルの提供、または、派生プログラムをオリジナル・プログラムに置き換える方法を示す指示の提供などが考えられます。
 (3)派生プログラムを、本契約書に定められた条件の下でライセンスしなければなりません。
 (4)派生プログラムのプログラム名、フォント名またはファイル名として、許諾プログラムが用いているのと同一の名称、またはこれを含む名称を使用してはなりません。
 (5)本項の要件を満たすためにオンラインで提供し、または媒体を郵送する方法で提供されるものは、その提供を希望するいかなる者によっても提供が可能です。
2.受領者が前条6項に基づき許諾プログラムを再配布する場合には、以下の全ての条件を満たさなければなりません。
 (1)許諾プログラムの名称を変更してはなりません。
 (2)許諾プログラムに加工その他の改変を加えてはなりません。
 (3)本契約の写しを許諾プログラムに添付しなければなりません。
3.許諾プログラムは、現状有姿で提供されており、許諾プログラムまたは派生プログラムについて、許諾者は一切の明示または黙示の保証(権利の所在、非侵害、商品性、特定目的への適合性を含むがこれに限られません)を行いません。いかなる場合にも、その原因を問わず、契約上の責任か厳格責任か過失その他の不法行為責任かにかかわらず、また事前に通知されたか否かにかかわらず、許諾者は、許諾プログラムまたは派生プログラムのインストール、使用、複製その他の利用または本契約上の権利の行使によって生じた一切の損害(直接・間接・付随的・特別・拡大・懲罰的または結果的損害)(商品またはサービスの代替品の調達、システム障害から生じた損害、現存するデータまたはプログラムの紛失または破損、逸失利益を含むがこれに限られません)について責任を負いません。
4.許諾プログラムまたは派生プログラムのインストール、使用、複製その他の利用に関して、許諾者は技術的な質問や問い合わせ等に対する対応その他、いかなるユーザ・サポートをも行う義務を負いません。
第4条 契約の終了
1.本契約の有効期間は、受領者が許諾プログラムを受領した時に開始し、受領者が許諾プログラムを何らかの方法で保持する限り続くものとします。
2.前項の定めにかかわらず、受領者が本契約に定める各条項に違反したときは、本契約は、何らの催告を要することなく、自動的に終了し、当該受領者はそれ以後、許諾プログラムおよび派生プログラムを一切使用しまたは複製その他の利用をすることができないものとします。ただし、かかる契約の終了は、当該違反した受領者から許諾プログラムまたは派生プログラムの配布を受けた受領者の権利に影響を及ぼすものではありません。
第5条 準拠法
1.IPAは、本契約の変更バージョンまたは新しいバージョンを公表することができます。その場合には、受領者は、許諾プログラムまたは派生プログラムの使用、複製その他の利用または再配布にあたり、本契約または変更後の契約のいずれかを選択することができます。その他、上記に記載されていない条項に関しては日本の著作権法および関連法規に従うものとします。
2.本契約は、日本法に基づき解釈されます。
----------
IPA Font License Agreement v1.0
The Licensor provides the Licensed Program (as defined in Article 1 below) under the terms of this license agreement (“Agreement”). Any use, reproduction or distribution of the Licensed Program, or any exercise of rights under this Agreement by a Recipient (as defined in Article 1 below) constitutes the Recipient's acceptance of this Agreement.
Article 1 (Definitions)
1.“Digital Font Program” shall mean a computer program containing, or used to render or display fonts.
2.“Licensed Program” shall mean a Digital Font Program licensed by the Licensor under this Agreement.
3.“Derived Program” shall mean a Digital Font Program created as a result of a modification, addition, deletion, replacement or any other adaptation to or of a part or all of the Licensed Program, and includes a case where a Digital Font Program newly created by retrieving font information from a part or all of the Licensed Program or Embedded Fonts from a Digital Document File with or without modification of the retrieved font information.
4.“Digital Content” shall mean products provided to end users in the form of digital data, including video content, motion and/or still pictures, TV programs or other broadcasting content and products consisting of character text, pictures, photographic images, graphic symbols and/or the like.
5.“Digital Document File” shall mean a PDF file or other Digital Content created by various software programs in which a part or all of the Licensed Program becomes embedded or contained in the file for the display of the font (“Embedded Fonts”). Embedded Fonts are used only in the display of characters in the particular Digital Document File within which they are embedded, and shall be distinguished from those in any Digital Font Program, which may be used for display of characters outside that particular Digital Document File.
6.“Computer” shall include a server in this Agreement.
7.“Reproduction and Other Exploitation” shall mean reproduction, transfer, distribution, lease, public transmission, presentation, exhibition, adaptation and any other exploitation.
8.“Recipient” shall mean anyone who receives the Licensed Program under this Agreement, including one that receives the Licensed Program from a Recipient.
Article 2 (Grant of License)
The Licensor grants to the Recipient a license to use the Licensed Program in any and all countries in accordance with each of the provisions set forth in this Agreement. However, any and all rights underlying in the Licensed Program shall be held by the Licensor. In no sense is this Agreement intended to transfer any right relating to the Licensed Program held by the Licensor except as specifically set forth herein or any right relating to any trademark, trade name, or service mark to the Recipient.
1.The Recipient may install the Licensed Program on any number of Computers and use the same in accordance with the provisions set forth in this Agreement.
2.The Recipient may use the Licensed Program, with or without modification in printed materials or in Digital Content as an expression of character texts or the like.
3.The Recipient may conduct Reproduction and Other Exploitation of the printed materials and Digital Content created in accordance with the preceding Paragraph, for commercial or non-commercial purposes and in any form of media including but not limited to broadcasting, communication and various recording media.
4.If any Recipient extracts Embedded Fonts from a Digital Document File to create a Derived Program, such Derived Program shall be subject to the terms of this agreement.
5.If any Recipient performs Reproduction or Other Exploitation of a Digital Document File in which Embedded Fonts of the Licensed Program are used only for rendering the Digital Content within such Digital Document File then such Recipient shall have no further obligations under this Agreement in relation to such actions.
6.The Recipient may reproduce the Licensed Program as is without modification and transfer such copies, publicly transmit or otherwise redistribute the Licensed Program to a third party for commercial or non-commercial purposes (“Redistribute”), in accordance with the provisions set forth in Article 3 Paragraph 2.
7.The Recipient may create, use, reproduce and/or Redistribute a Derived Program under the terms stated above for the Licensed Program: provided, that the Recipient shall follow the provisions set forth in Article 3 Paragraph 1 when Redistributing the Derived Program.
Article 3 (Restriction)
The license granted in the preceding Article shall be subject to the following restrictions:
1.If a Derived Program is Redistributed pursuant to Paragraph 4 and 7 of the preceding Article, the following conditions must be met :
 (1)The following must be also Redistributed together with the Derived Program, or be made available online or by means of mailing mechanisms in exchange for a cost which does not exceed the total costs of postage, storage medium and handling fees:
  (a)a copy of the Derived Program; and
  (b)any additional file created by the font developing program in the course of creating the Derived Program that can be used for further modification of the Derived Program, if any.
 (2)It is required to also Redistribute means to enable recipients of the Derived Program to replace the Derived Program with the Licensed Program first released under this License (the “Original Program”). Such means may be to provide a difference file from the Original Program, or instructions setting out a method to replace the Derived Program with the Original Program.
 (3)The Recipient must license the Derived Program under the terms and conditions of this Agreement.
 (4)No one may use or include the name of the Licensed Program as a program name, font name or file name of the Derived Program.
 (5)Any material to be made available online or by means of mailing a medium to satisfy the requirements of this paragraph may be provided, verbatim, by any party wishing to do so.
2.If the Recipient Redistributes the Licensed Program pursuant to Paragraph 6 of the preceding Article, the Recipient shall meet all of the following conditions:
 (1)The Recipient may not change the name of the Licensed Program.
 (2)The Recipient may not alter or otherwise modify the Licensed Program.
 (3)The Recipient must attach a copy of this Agreement to the Licensed Program.
3.THIS LICENSED PROGRAM IS PROVIDED BY THE LICENSOR “AS IS” AND ANY EXPRESSED OR IMPLIED WARRANTY AS TO THE LICENSED PROGRAM OR ANY DERIVED PROGRAM, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXTENDED, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO; PROCUREMENT OF SUBSTITUTED GOODS OR SERVICE; DAMAGES ARISING FROM SYSTEM FAILURE; LOSS OR CORRUPTION OF EXISTING DATA OR PROGRAM; LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE INSTALLATION, USE, THE REPRODUCTION OR OTHER EXPLOITATION OF THE LICENSED PROGRAM OR ANY DERIVED PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
4.The Licensor is under no obligation to respond to any technical questions or inquiries, or provide any other user support in connection with the installation, use or the Reproduction and Other Exploitation of the Licensed Program or Derived Programs thereof.
Article 4 (Termination of Agreement)
1.The term of this Agreement shall begin from the time of receipt of the Licensed Program by the Recipient and shall continue as long as the Recipient retains any such Licensed Program in any way.
2.Notwithstanding the provision set forth in the preceding Paragraph, in the event of the breach of any of the provisions set forth in this Agreement by the Recipient, this Agreement shall automatically terminate without any notice. In the case of such termination, the Recipient may not use or conduct Reproduction and Other Exploitation of the Licensed Program or a Derived Program: provided that such termination shall not affect any rights of any other Recipient receiving the Licensed Program or the Derived Program from such Recipient who breached this Agreement.
Article 5 (Governing Law)
1.IPA may publish revised and/or new versions of this License. In such an event, the Recipient may select either this Agreement or any subsequent version of the Agreement in using, conducting the Reproduction and Other Exploitation of, or Redistributing the Licensed Program or a Derived Program. Other matters not specified above shall be subject to the Copyright Law of Japan and other related laws and regulations of Japan.
2.This Agreement shall be construed under the laws of Japan.
+21
View File
@@ -0,0 +1,21 @@
# Censor Lists for Streamer Mode
This directory contains sample censor lists for various games, intended for use
with xsystem35's Streamer Mode.
## Usage
To use a censor list, specify its path with the `-censor` command-line option
or in your `.xsys35rc` file. For example:
```bash
xsystem35 -censor misc/censor/kichikuou.txt
```
The list files contain image numbers (integers), one per line. Lines starting
with `#` are treated as comments.
## Contributing
We welcome contributions of censor lists for other games! If you have created
a censor list that you'd like to share, please open a Pull Request.
+164
View File
@@ -0,0 +1,164 @@
# Censor list for 鬼畜王ランス (Kichikuou Rance)
27
70
93
101
102
103
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
123
124
125
126
127
128
129
130
131
132
133
134
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
160
161
162
163
164
165
166
167
168
169
171
172
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
193
195
196
197
198
199
200
201
203
204
205
206
207
208
209
210
214
215
216
217
218
219
221
222
224
225
226
227
228
229
230
231
232
233
234
235
239
299
300
361
362
363
375
376
377
393
422
461
466
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
517
844
845
846
847
848
851
852
853
854
855
+1 -1
View File
@@ -12,7 +12,7 @@ static void Init() {
int p1 = getCaliValue(); /* ISys3x */
int p2 = getCaliValue(); /* IWinMsg */
int p3 = getCaliValue(); /* ITimer */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+1 -2
View File
@@ -15,7 +15,6 @@ add_library(modules STATIC
MsgSkip/MsgSkip.c
NIGHTDLL/NIGHTDLL.c
NIGHTDLL/nt_scenario.c
NIGHTDLL/sactcg.c
NIGHTDLL/sactstring.c
NIGHTDLL/sprite.c
NIGHTDLL/sprite_draw.c
@@ -30,7 +29,6 @@ add_library(modules STATIC
RandMT/RandMT.c
S3xMusic/S3xMusic.c
SACT/SACT.c
SACT/sactcg.c
SACT/sactsound.c
SACT/sacttimer.c
SACT/sactstring.c
@@ -68,6 +66,7 @@ add_library(modules STATIC
lib/drawtext.c
lib/list.c
lib/graph_blend_amap.c
lib/sactcg.c
nDEMO/nDEMO.c
nDEMOE/nDEMOE.c
oDEMO/oDEMO.c
+6 -6
View File
@@ -15,8 +15,8 @@ static void Init() {
static void ExistKeyFile() {
const char *p1 = sl_getString(0);
int *p2 = getCaliVariable();
int *var = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
@@ -25,8 +25,8 @@ static void ExistKeyFile() {
static void CheckProtectFile() {
const char *p1 = sl_getString(0);
int *p2 = getCaliVariable();
int *var = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
@@ -35,8 +35,8 @@ static void CheckProtectFile() {
static void CreateKeyFile() {
const char *p1 = sl_getString(0);
int *p2 = getCaliVariable();
int *var = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+13 -13
View File
@@ -135,7 +135,7 @@ static void Create() {
height: surface の高さ
bpp : surface の深さ(24bppのみサポート)
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int width = getCaliValue();
int height = getCaliValue();
int bpp = getCaliValue();
@@ -162,7 +162,7 @@ static void CreatePixelOnly() {
height: surface の高さ
bpp : surface の深さ(24bpp only)
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int width = getCaliValue();
int height = getCaliValue();
int bpp = getCaliValue();
@@ -188,7 +188,7 @@ static void CreateAMapOnly() {
width : surface の幅
height: surface の高さ
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int width = getCaliValue();
int height = getCaliValue();
surface_t *s;
@@ -214,7 +214,7 @@ static void IsSurface() {
var : 結果を返す変数。surface ならば 1, !surface ならば 0
*/
int p1 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = sf_get(p1) ? 1 : 0;
@@ -229,7 +229,7 @@ static void IsPixel() {
var : 結果を返す変数。pixel ならば 1, !pixel ならば 0
*/
int p1 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -251,7 +251,7 @@ static void IsAlpha() {
var : 結果を返す変数。alpha ならば 1, !alpha ならば 0
*/
int p1 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -273,7 +273,7 @@ static void GetWidth() {
var : 結果を返す変数。
*/
int p1 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -295,7 +295,7 @@ static void GetHeight() {
var : 結果を返す変数。
*/
int p1 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -310,7 +310,7 @@ static void GetHeight() {
}
static void GetCreatedSurface() { /* not used ? */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
TRACE_UNIMPLEMENTED("Gpx.GetCreatedSurface %p:", var);
}
@@ -322,7 +322,7 @@ static void LoadCG() {
var : 作成した surface の番号を返す変数
p1 : 読み込む CG の番号
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int p1 = getCaliValue();
*var = load_cg_main(p1 -1);
@@ -331,14 +331,14 @@ static void LoadCG() {
}
static void GetCGPosX() { /* not useed ? */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int p1 = getCaliValue();
TRACE_UNIMPLEMENTED("Gpx.GetCgPosX %p,%d:", var, p1);
}
static void GetCGPosY() { /* not useed ? */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int p1 = getCaliValue();
TRACE_UNIMPLEMENTED("Gpx.GetCgPosY %p,%d:", var, p1);
@@ -1117,7 +1117,7 @@ static void EffectCopy() {
int sw = getCaliValue();
int sh = getCaliValue();
int time = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
TRACE("Gpx.EffectCopy %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%p:", no, dx, dy, ss1, sx1, sy1, ss2, sx2, sy2, sw, sh, time, var);
+1 -1
View File
@@ -17,7 +17,7 @@ void gpx_effect(int no,
surface_t *src, int sx, int sy,
int width, int height,
int time,
int *endtype) {
vmvar_t *endtype) {
surface_t *write = nact->ags.dib;
if (!gr_clip(dst, &dx, &dy, &width, &height, write, &wx, &wy)) return;
if (!gr_clip(src, &sx, &sy, &width, &height, write, &wx, &wy)) return;
+2 -1
View File
@@ -1,6 +1,7 @@
#ifndef __GLEFFECTCOPY_H__
#define __GLEFFECTCOPY_H__
#include "portab.h"
#include "surface.h"
extern void gpx_effect(int no,
@@ -9,6 +10,6 @@ extern void gpx_effect(int no,
surface_t *src, int sx, int sy,
int width, int height,
int time,
int *endtype);
vmvar_t *endtype);
#endif /* __GLEFFECTCOPY_H__ */
+2 -2
View File
@@ -53,7 +53,7 @@ static void RandMTGet() {
var: 結果を返す変数
*/
int num = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
if (num == 0 || num == 1) {
*var = num;
@@ -83,7 +83,7 @@ static void RandMTGetNumTable() {
var: 乱数を格納する変数
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = (int)(genrand() * numtblmax) + 1;
+4 -4
View File
@@ -17,7 +17,7 @@ static bool valid;
static int action;
static void Init() {
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue(); /* ISys3x */
int p3 = getCaliValue();
int p4 = getCaliValue();
@@ -30,7 +30,7 @@ static void Init() {
}
static void Start() {
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue();
char *fname_utf8 = sjis2utf(svar_get(p2));
@@ -52,7 +52,7 @@ static void SetValid() {
}
static void GetValid() {
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
*p1 = valid;
@@ -68,7 +68,7 @@ static void SetAction() {
}
static void GetAction() {
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
*p1 = action;
+26 -27
View File
@@ -22,7 +22,7 @@ night_t nightprv;
static void Init(void) { /* 0 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int p1 = getCaliValue(); /* ISys3xCG */
int p2 = getCaliValue(); /* ISys3xDIB */
int p3 = getCaliValue(); /* ISys3xMsgString */
@@ -46,7 +46,7 @@ static void NIGHTDLL_reset(void) {
if (night.sp[i])
nt_sp_free(night.sp[i]);
}
nt_scg_freeall();
scg_freeall();
memset(&night, 0, sizeof(night));
}
@@ -56,8 +56,7 @@ static void InitGame() { /* 1 */
nact->msgout = ntmsg_add;
nact->ags.eventcb = ntev_callback;
nact->callback = ntev_main;
nt_gr_init();
ntmsg_init();
nt_sstr_init();
@@ -214,7 +213,7 @@ static void SetSelMode(void) { /* 18 */
// キー入力待ち後、改ページ
static void AnalyzeMessage(void) { /* 19 */
int *var = getCaliVariable(); // 入力されたキー
vmvar_t *var = getCaliVariable(); // 入力されたキー
*var = ntmsg_ana();
@@ -298,7 +297,7 @@ static void ScreenCG(void) { /* 27 */
}
static void RunGameMain(void) { /* 28 */
int *p1 = getCaliVariable(); // result
vmvar_t *p1 = getCaliVariable(); // result
int p2 = getCaliValue(); // month
int p3 = getCaliValue(); // day
int p4 = getCaliValue(); // day of week
@@ -314,7 +313,7 @@ static void RunGameMain(void) { /* 28 */
}
static void CheckNewGame(void) { /* 29 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
*p1 = 0;
@@ -348,9 +347,9 @@ static void SetDate(void) { /* 33 */
}
static void GetDate(void) { /* 34 */
int *p1 = getCaliVariable(); // month
int *p2 = getCaliVariable(); // day
int *p3 = getCaliVariable(); // day of weeek
vmvar_t *p1 = getCaliVariable(); // month
vmvar_t *p2 = getCaliVariable(); // day
vmvar_t *p3 = getCaliVariable(); // day of weeek
*p1 = night.Month;
*p2 = night.Day;
@@ -364,7 +363,7 @@ static void SelectGameLevel(void) { /* 35 */
}
static void RunEventDungeon(void) { /* 36 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue();
*p1 = 1;
@@ -479,7 +478,7 @@ static void RunSoundMode(void) { /* 48 */
}
static void RunMapEditor(void) { /* 49 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.RunMapEditor %p:", p1);
}
@@ -496,21 +495,21 @@ static void VisualListAdd(void) { /* 51 */
}
static void GetLocalCountCG(void) { /* 52 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetLocalCountCG %p,%d:", p1, p2);
}
static void PlayMemory(void) { /* 53 */
int *p1 = getCaliVariable(); // 回想ページ
int *p2 = getCaliVariable(); // 回想RESULT
vmvar_t *p1 = getCaliVariable(); // 回想ページ
vmvar_t *p2 = getCaliVariable(); // 回想RESULT
TRACE_UNIMPLEMENTED("NIGHTDLL.PlayMemory %p,%p:", p1, p2);
}
static void GetEventFlagTotal(void) { /* 54 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetEventFlagTotal %p,%d:", p1, p2);
@@ -529,25 +528,25 @@ static void GetPlayerName(void) { /* 56 */
}
static void SaveGame(void) { /* 57 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.SaveGame %p:", p1);
}
static void LoadGame(void) { /* 58 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.LoadGame %p:", p1);
}
static void ExistSaveData(void) { /* 59 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.ExistSaveData %p:", p1);
}
static void ExistStartData(void) { /* 60 */
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.ExistStartData %p:", p1);
}
@@ -581,13 +580,13 @@ static void DebugScenario(void) { /* 66 */
}
static void GetDLLTime(void) { /* 67 */
int *p1 = getCaliVariable();
int *p2 = getCaliVariable();
int *p3 = getCaliVariable();
int *p4 = getCaliVariable();
int *p5 = getCaliVariable();
int *p6 = getCaliVariable();
int *p7 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
vmvar_t *p3 = getCaliVariable();
vmvar_t *p4 = getCaliVariable();
vmvar_t *p5 = getCaliVariable();
vmvar_t *p6 = getCaliVariable();
vmvar_t *p7 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetDLLTime %p,%p,%p,%p,%p,%p,%p:", p1, p2, p3, p4, p5, p6, p7);
}
-4
View File
@@ -37,10 +37,6 @@ struct _night {
int waitskiplv;
int waitkey;
// graph
// cginfo_t *cg[CGMAX];
SDL_Rect updaterect;
FontType fonttype;
-16
View File
@@ -53,11 +53,6 @@ static void cb_waitkey_selection(agsevent_t *e) {
}
void ntev_callback(agsevent_t *e) {
// menu open中は無視
if (nact->popupmenu_opened) {
return;
}
if (e->type == AGSEVENT_KEY_PRESS && e->code == KEY_CTRL) {
night.waitskiplv = 2;
night.waitkey = e->code;
@@ -90,16 +85,5 @@ void ntev_callback(agsevent_t *e) {
default:
return;
}
}
/*
system35のメインループからで呼ばれるコールバック
*/
void ntev_main() {
// デフォルトのコールバックのうち、ここで必要なものだけ処理。
if (nact->popupmenu_opened) {
menu_gtkmainiteration();
if (nact->is_quit) sys_exit(0);
}
}
-1
View File
@@ -2,7 +2,6 @@
#define __NT_EVENT_H__
extern void ntev_callback(agsevent_t *e);
extern void ntev_main();
#endif /* __NT_EVENT_H__ */
+3 -3
View File
@@ -82,7 +82,7 @@ void ntmsg_init() {
nt_sp_add_updatelist(sp);
// 文字背景CG作成
nt_scg_create(CGNO_MSGFR_BG,
scg_create(CGNO_MSGFR_BG,
main_surface->w, main_surface->h,
0, 0, 0, 255);
// night.cg[CGNO_MSGFR_BG]->refcnt++;
@@ -92,9 +92,9 @@ void ntmsg_init() {
nt_sp_add_updatelist(sp);
// キー入力アニメーションCG作成
nt_scg_cut(CGNO_MSGHAK_1, CGNO_MSGHAK_LCG, 0, 0, 9, 9);
scg_cut(CGNO_MSGHAK_1, CGNO_MSGHAK_LCG, 0, 0, 9, 9);
//night.cg[CGNO_MSGHAK_1]->refcnt++;
nt_scg_cut(CGNO_MSGHAK_2, CGNO_MSGHAK_LCG, 0, 9, 9, 9);
scg_cut(CGNO_MSGHAK_2, CGNO_MSGHAK_LCG, 0, 9, 9, 9);
//night.cg[CGNO_MSGHAK_2]->refcnt++;
sp = night.sp[SPNO_MSG_KEYANIM] = nt_sp_new(SPNO_MSG_KEYANIM, CGNO_MSGHAK_1, CGNO_MSGHAK_2, 0, SPRITE_ANIME);
sp->u.anime.interval = HAKANIM_INTERVAL;
+10 -12
View File
@@ -55,19 +55,17 @@ static void ntmain(struct _scoadr inadr) {
while (!nact->is_quit && !is_yield_requested()) {
scheduler_on_command();
//SACT_DEBUG("%d:%x", sl_getPage(), sl_getIndex());
if (!nact->popupmenu_opened) {
exec_command();
if (sl_getPage() == inadr.page &&
sl_getIndex() == inadr.index) {
// ~E%05dからの戻り
if (nact->fnc_return_value == 0) {
return;
} else {
scono = nact->fnc_return_value;
}
curadr = scene2adr(scono);
sl_callFar2(curadr.page -1, curadr.index);
exec_command();
if (sl_getPage() == inadr.page &&
sl_getIndex() == inadr.index) {
// ~E%05dからの戻り
if (nact->fnc_return_value == 0) {
return;
} else {
scono = nact->fnc_return_value;
}
curadr = scene2adr(scono);
sl_callFar2(curadr.page -1, curadr.index);
}
nact->callback();
}
-146
View File
@@ -1,146 +0,0 @@
/*
* sactcg.c: CG作成
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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
*
*/
/* $Id: sactcg.c,v 1.2 2003/11/16 15:29:52 chikama Exp $ */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "portab.h"
#include "nact.h"
#include "system.h"
#include "ags.h"
#include "cg.h"
#include "sactcg.h"
#define CGMAX 65536
static cginfo_t *cgs[CGMAX];
#define SPCG_ASSERT_NO(no) \
if ((no) > (CGMAX -1)) { \
WARNING("no is too large (should be %d < %d)", (no), CGMAX); \
return; \
} \
static cginfo_t *nt_scg_new(enum cgtype type, int no, SDL_Surface *sf) {
cginfo_t *info = calloc(1, sizeof(cginfo_t));
info->type = type;
info->no = no;
info->sf = sf;
info->refcnt = 1;
nt_scg_free(no);
cgs[no] = info;
return info;
}
/*
cgの読み込み
指定の番号のCGをリンクファイルから読み込んだり、
CG_xxxで作成したCGを参照する
@param no: 読み込むCG番号
*/
static cginfo_t *nt_scg_get(int no) {
if (no >= (CGMAX -1)) {
WARNING("no is too large (should be %d < %d)", (no), CGMAX);
return NULL;
}
if (cgs[no] != NULL)
return cgs[no];
SDL_Surface *sf = cg_load_as_sdlsurface(no - 1);
if (!sf) {
WARNING("load fail (%d)", no -1);
return NULL;
}
return nt_scg_new(CG_LINKED, no, sf);
}
cginfo_t *nt_scg_addref(int no) {
cginfo_t *info = nt_scg_get(no);
if (info)
info->refcnt++;
return info;
}
void nt_scg_deref(cginfo_t *cg) {
if (--cg->refcnt > 0)
return;
if (cg->sf)
SDL_FreeSurface(cg->sf);
free(cg);
}
// 指定の大きさ、色の矩形の CG を作成
void nt_scg_create(int wNumCG, int wWidth, int wHeight, int wR, int wG, int wB, int wBlendRate) {
SPCG_ASSERT_NO(wNumCG);
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, wR, wG, wB, wBlendRate));
nt_scg_new(CG_SET, wNumCG, sf);
}
// CGの一部を切りぬいたCGを作成
void nt_scg_cut(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int wHeight) {
SPCG_ASSERT_NO(wNumDstCG);
SPCG_ASSERT_NO(wNumSrcCG);
cginfo_t *src = nt_scg_get(wNumSrcCG);
if (!src)
return;
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, src->sf->format->BitsPerPixel, src->sf->format->format);
SDL_BlitSurface(src->sf, &(SDL_Rect){wX, wY, wWidth, wHeight}, sf, NULL);
nt_scg_new(CG_SET, wNumDstCG, sf);
}
// 全てのCGの開放
void nt_scg_freeall(void) {
int i;
for (i = 1; i < CGMAX; i++) {
nt_scg_free(i);
}
}
/**
* 指定の番号の CG をオブジェクトリストから消し、オブジェクトがどこからも参照
* されていない(参照数が0の)場合のみ、オブジェクトを削除
*/
void nt_scg_free(int no) {
SPCG_ASSERT_NO(no);
cginfo_t *cg = cgs[no];
if (!cg) return;
nt_scg_deref(cg);
cgs[no] = NULL;
}
+8 -8
View File
@@ -16,9 +16,9 @@ sprite_t *nt_sp_new(int no, int cg1, int cg2, int cg3, int type) {
sp->no = no;
sp->type = type;
sp->cg1 = cg1 ? nt_scg_addref(cg1) : NULL;
sp->cg2 = cg2 ? nt_scg_addref(cg2) : NULL;
sp->cg3 = cg3 ? nt_scg_addref(cg3) : NULL;
sp->cg1 = cg1 ? scg_addref(cg1) : NULL;
sp->cg2 = cg2 ? scg_addref(cg2) : NULL;
sp->cg3 = cg3 ? scg_addref(cg3) : NULL;
sp->curcg = sp->cg1;
sp->show = true;
@@ -71,9 +71,9 @@ sprite_t *nt_sp_msg_new(int no, int x, int y, int width, int height) {
void nt_sp_free(sprite_t *sp) {
if (sp == NULL) return;
if (sp->cg1) nt_scg_deref(sp->cg1);
if (sp->cg2) nt_scg_deref(sp->cg2);
if (sp->cg3) nt_scg_deref(sp->cg3);
if (sp->cg1) scg_deref(sp->cg1);
if (sp->cg2) scg_deref(sp->cg2);
if (sp->cg3) scg_deref(sp->cg3);
if (sp->type == SPRITE_MSG) {
SDL_FreeSurface(sp->u.msg.canvas);
@@ -102,9 +102,9 @@ void nt_sp_set_cg(sprite_t *sp, int no) {
if (sp == NULL) return;
if (sp->curcg)
nt_scg_deref(sp->curcg);
scg_deref(sp->curcg);
cg = no ? nt_scg_addref(no) : NULL;
cg = no ? scg_addref(no) : NULL;
if (cg == NULL) {
sp->width = 0;
+1 -20
View File
@@ -4,6 +4,7 @@
#include "portab.h"
#include <SDL_rect.h>
#include "sactcg.h"
#define DEFAULT_UPDATE nt_sp_draw
@@ -21,7 +22,6 @@
#define SPNO_MSG_KEYANIM 12 // メッセージキー入力アニメーション
#define SPNO_MSG_ICON_MUTE 13 // メッセージウィンドアイコン
#define CGMAX 65536
// 0-9999: reserve for Link CG
// 2051: メッセージウィンド枠CG
#define CGNO_MSGFRAME_LCG 2051
@@ -40,25 +40,6 @@
#define CGNO_MSGFR_BG 10002
// CG_XX で作るCGの種類
enum cgtype {
CG_NOTUSED = 0,
CG_LINKED = 1,
CG_SET = 2,
CG_REVERSE = 3,
CG_STRETCH = 4
};
// cgに関する情報
struct _cginfo {
enum cgtype type; // CGの種類, 0: 未使用, 1:リンクされている, ...
int no; // CGの番号
struct SDL_Surface *sf;
int refcnt; // 参照カウンタ。0になったら開放してもよい。
};
typedef struct _cginfo cginfo_t;
enum spritetype {
SPRITE_NORMAL = 0,
SPRITE_ANIME = 5,
+1 -1
View File
@@ -90,7 +90,7 @@ static void Init() {
int p1 = getCaliValue(); /* ISys3x */
int p2 = getCaliValue(); /* IWinMsg */
int p3 = getCaliValue(); /* ITimer */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+2 -2
View File
@@ -51,7 +51,7 @@ static void Get() {
var: 結果を返す変数
*/
int num = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
if (num == 0 || num == 1) {
*var = num;
@@ -65,7 +65,7 @@ static void Get() {
static void GetNoOverlap() {
int min = getCaliValue();
int n = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = (int)(genrand() * n) + min;
+9 -9
View File
@@ -42,7 +42,7 @@ static void CreateChannel(void) {
static void Prepare(void) {
int ch = getCaliValue();
int no = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
if (ch == 0) {
current_no = no;
@@ -97,7 +97,7 @@ static void PlayPosSample(void) {
static void IsPlay(void) {
int ch = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
if (ch == 0) {
*result = current_no && musbgm_isplaying(current_no);
@@ -127,10 +127,10 @@ static void Restart(void) {
static void GetPlayPos(void) {
int ch = getCaliValue();
int *hour = getCaliVariable();
int *min = getCaliVariable();
int *sec = getCaliVariable();
int *msec = getCaliVariable();
vmvar_t *hour = getCaliVariable();
vmvar_t *min = getCaliVariable();
vmvar_t *sec = getCaliVariable();
vmvar_t *msec = getCaliVariable();
if (ch == 0) {
int time = musbgm_getpos(current_no);
*hour = time / 360000;
@@ -149,8 +149,8 @@ static void GetPlayPos(void) {
static void GetPlayPosSample(void) {
int p1 = getCaliValue();
int *var1 = getCaliVariable();
int *var2 = getCaliVariable();
vmvar_t *var1 = getCaliVariable();
vmvar_t *var2 = getCaliVariable();
TRACE_UNIMPLEMENTED("S3xMusic.GetPlayPosSample %d, %p, %p:", p1, var1, var2);
}
@@ -192,7 +192,7 @@ static void FadeVolume(void) {
static void IsFade(void) {
int ch = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
*result = 0;
TRACE_UNIMPLEMENTED("S3xMusic.IsFade %d => %d:", ch, *result);
}
+58 -58
View File
@@ -636,7 +636,7 @@ static void QuakeSprite() {
*/
static void QuerySpriteIsExist() {
int wNum = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = sp_exists(wNum) ? 1 : 0;
@@ -654,10 +654,10 @@ static void QuerySpriteIsExist() {
*/
static void QuerySpriteInfo() {
int wNum = getCaliValue();
int *vType = getCaliVariable();
int *vCG1 = getCaliVariable();
int *vCG2 = getCaliVariable();
int *vCG3 = getCaliVariable();
vmvar_t *vType = getCaliVariable();
vmvar_t *vCG1 = getCaliVariable();
vmvar_t *vCG2 = getCaliVariable();
vmvar_t *vCG3 = getCaliVariable();
sp_query_info(wNum, vType, vCG1, vCG2, vCG3);
@@ -672,7 +672,7 @@ static void QuerySpriteInfo() {
*/
static void QuerySpriteShow() {
int wNum = getCaliValue();
int *vShow = getCaliVariable();
vmvar_t *vShow = getCaliVariable();
sp_query_show(wNum, vShow);
@@ -688,8 +688,8 @@ static void QuerySpriteShow() {
*/
static void QuerySpritePos() {
int wNum = getCaliValue();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
sp_query_pos(wNum, vX, vY);
@@ -705,8 +705,8 @@ static void QuerySpritePos() {
*/
static void QuerySpriteSize() {
int wNum = getCaliValue();
int *vWidth = getCaliVariable();
int *vHeight = getCaliVariable();
vmvar_t *vWidth = getCaliVariable();
vmvar_t *vHeight = getCaliVariable();
sp_query_size(wNum, vWidth, vHeight);
@@ -722,8 +722,8 @@ static void QuerySpriteSize() {
*/
static void QueryTextPos() {
int wNum = getCaliValue();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
sp_query_textpos(wNum, vX, vY);
@@ -762,7 +762,7 @@ static void CG_Reset() {
*/
static void CG_QueryType() {
int wNumCG = getCaliValue();
int *vType = getCaliVariable();
vmvar_t *vType = getCaliVariable();
*vType = scg_querytype(wNumCG);
@@ -778,8 +778,8 @@ static void CG_QueryType() {
*/
static void CG_QuerySize() {
int wNumCG = getCaliValue();
int *vWidth = getCaliVariable();
int *vHeight = getCaliVariable();
vmvar_t *vWidth = getCaliVariable();
vmvar_t *vHeight = getCaliVariable();
scg_querysize(wNumCG, vWidth, vHeight);
@@ -794,7 +794,7 @@ static void CG_QuerySize() {
*/
static void CG_QueryBpp() {
int wNumCG = getCaliValue();
int *vBpp = getCaliVariable();
vmvar_t *vBpp = getCaliVariable();
*vBpp = scg_querybpp(wNumCG);
@@ -809,7 +809,7 @@ static void CG_QueryBpp() {
*/
static void CG_ExistAlphaMap() {
int wNumCG = getCaliValue();
int *vMask = getCaliVariable();
vmvar_t *vMask = getCaliVariable();
*vMask = scg_existalphamap(wNumCG) ? 1 : 0;
@@ -1021,7 +1021,7 @@ static void CG_PartCopy() {
* @param vKey: 入力されたキー
*/
static void WaitKeySimple() {
int *vKey = getCaliVariable();
vmvar_t *vKey = getCaliVariable();
TRACE("SACT.WaitKeySimple %d:", vKey);
@@ -1071,10 +1071,10 @@ static void WaitKeyMessage() {
* @param vRsv2: 予約
*/
static void WaitKeySprite() {
int *vOK = getCaliVariable();
int *vRND = getCaliVariable();
int *vRsv1 = getCaliVariable();
int *vRsv2 = getCaliVariable();
vmvar_t *vOK = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
vmvar_t *vRsv1 = getCaliVariable();
vmvar_t *vRsv2 = getCaliVariable();
TRACE("SACT.WaitKeySprite %p,%p,%p,%p:", vOK, vRND, vRsv1, vRsv2);
@@ -1092,7 +1092,7 @@ static void WaitKeySprite() {
static void PeekKey() {
static int prevKeyCode = NUM_KEYCODES;
int nKeyCode = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
// This function is called successively with different nKeyCodes.
// Only the first call hits the scheduler.
@@ -1121,8 +1121,8 @@ static void WaitMsgSkipKeyUp() {
* @param wTime: タイムアウト時間 (1/100sec)
*/
static void WaitKeySimpleTimeOut() {
int *vRND = getCaliVariable();
int *vD03 = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
vmvar_t *vD03 = getCaliVariable();
int wTime = getCaliValue();
sact.waittype = KEYWAIT_SIMPLE;
@@ -1153,11 +1153,11 @@ static void WaitKeySimpleTimeOut() {
* @param wTime: タイムアウト時間 (1/100sec)
*/
static void WaitKeySpriteTimeOut() {
int *vOK = getCaliVariable();
int *vRND = getCaliVariable();
int *vD01 = getCaliVariable();
int *vD02 = getCaliVariable();
int *vD03 = getCaliVariable();
vmvar_t *vOK = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
vmvar_t *vD01 = getCaliVariable();
vmvar_t *vD02 = getCaliVariable();
vmvar_t *vD03 = getCaliVariable();
int wTime = getCaliValue();
sp_keywait(vOK, vRND, vD01, vD02, vD03, wTime);
@@ -1171,7 +1171,7 @@ static void WaitKeySpriteTimeOut() {
* @param vSkip:
*/
static void QueryMessageSkip() {
int *vSkip = getCaliVariable();
vmvar_t *vSkip = getCaliVariable();
*vSkip = msgskip_isSkipping() ? 1 : 0;
@@ -1219,7 +1219,7 @@ static void MessageOutput() {
int wMessageSpeed = getCaliValue();
int wMessageLineSpace = getCaliValue();
int wMessageAlign = 0;
int *vMessageLength = NULL;
vmvar_t *vMessageLength = NULL;
if (sact.version >= 110) {
wMessageAlign = getCaliValue();
@@ -1266,7 +1266,7 @@ static void MessageOutputEx() {
int wRubySize = getCaliValue();
int wRubyFont = getCaliValue();
int wRubyLineSpace = getCaliValue();
int *vLength = NULL;
vmvar_t *vLength = NULL;
if (sact.version >= 120) {
vLength = getCaliVariable();
@@ -1311,7 +1311,7 @@ static void MessageClear() {
* @param wResult: 結果を返す変数
*/
static void MessageIsEmpty() {
int *wResult = getCaliVariable();
vmvar_t *wResult = getCaliVariable();
*wResult = smsg_is_empty() ? 1 : 0;
@@ -1325,7 +1325,7 @@ static void MessageIsEmpty() {
* @param nTopStringNum: バッファを取得する文字列変数の最初
*/
static void MessagePeek() {
int *vCount = getCaliVariable();
vmvar_t *vCount = getCaliVariable();
int nTopStringNum = getCaliValue();
*vCount = smsg_peek(nTopStringNum);
@@ -1388,7 +1388,7 @@ static void MenuAdd() {
* @param nAlign: 行そろえ (0:左, 1:中央, 2: 右) (1.1~)
*/
static void MenuOpen() {
int *wMenuResult = getCaliVariable();
vmvar_t *wMenuResult = getCaliVariable();
int wNum = getCaliValue();
int wChoiceSize = getCaliValue();
int wMenuOutSpc = getCaliValue();
@@ -1464,7 +1464,7 @@ static void Numeral_SetCG() {
static void Numeral_GetCG() {
int nNum = getCaliValue();
int nIndex = getCaliValue();
int *vCG = getCaliVariable();
vmvar_t *vCG = getCaliVariable();
sp_num_getcg(nNum, nIndex, vCG);
@@ -1497,8 +1497,8 @@ static void Numeral_SetPos() {
*/
static void Numeral_GetPos() {
int nNum = getCaliValue();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
sp_num_getpos(nNum, vX, vY);
@@ -1528,7 +1528,7 @@ static void Numeral_SetSpan() {
*/
static void Numeral_GetSpan() {
int nNum = getCaliValue();
int *vSpan = getCaliVariable();
vmvar_t *vSpan = getCaliVariable();
sp_num_getspan(nNum, vSpan);
@@ -1596,7 +1596,7 @@ static void TimerSet() {
*/
static void TimerGet() {
int wTimerID = getCaliValue();
int *vRND = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
*vRND = stimer_get(wTimerID);
@@ -1613,10 +1613,10 @@ static void TimerWait() {
int wTimerID = getCaliValue();
int wCount = getCaliValue();
while (wCount > stimer_get(wTimerID) && !nact->is_quit) {
sys_keywait(10, KEYWAIT_NONCANCELABLE);
}
int msec = (wCount - stimer_get(wTimerID)) * 10;
if (msec > 0)
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE);
TRACE("SACT.TimerWait %d,%d:", wTimerID, wCount);
}
@@ -1627,9 +1627,9 @@ static void TimerWait() {
*/
static void Wait() {
int wCount = getCaliValue();
sys_keywait(wCount*10, KEYWAIT_NONCANCELABLE);
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE);
TRACE("SACT.Wait %d:", wCount);
}
@@ -1696,7 +1696,7 @@ static void SoundWait() {
*/
static void SoundWaitKey() {
int wNum = getCaliValue();
int *vKey = getCaliVariable();
vmvar_t *vKey = getCaliVariable();
ssnd_waitkey(wNum, vKey);
@@ -1797,7 +1797,7 @@ static void SpriteSoundOB() {
*/
static void MusicCheck() {
int wNum = getCaliValue();
int *vRND = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
*vRND = ald_exists(DRIFILE_BGM, wNum - 1) ? 1 : 0;
@@ -1812,7 +1812,7 @@ static void MusicCheck() {
*/
static void MusicGetLength() {
int wNum = getCaliValue();
int *vRND = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
*vRND = musbgm_getlen(wNum);
@@ -1827,7 +1827,7 @@ static void MusicGetLength() {
*/
static void MusicGetPos() {
int wNum = getCaliValue();
int *vRND = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
*vRND = musbgm_getpos(wNum);
@@ -1937,7 +1937,7 @@ static void MusicWaitPos() {
*/
static void SoundGetLinkNum() {
int wNum = getCaliValue();
int *vRND = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
*vRND = ssnd_getlinknum(wNum);
@@ -1957,7 +1957,7 @@ static void SoundGetLinkNum() {
* pos = ((pos2-pos1) / (val2-val1)) * (val-val1) + pos1
*/
static void ChartPos() {
int *pos = getCaliVariable();
vmvar_t *pos = getCaliVariable();
int pos1 = getCaliValue();
int pos2 = getCaliValue();
int val1 = getCaliValue();
@@ -2002,7 +2002,7 @@ static void Maze_Create() {
* SACT.Maze_Get (1.0~)
*/
static void Maze_Get() {
int *p1 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
int p2 = getCaliValue();
int p3 = getCaliValue();
@@ -2013,7 +2013,7 @@ static void Maze_Get() {
* SACT.EncryptWORD (1.0~)
*/
static void EncryptWORD() {
int *array = getCaliVariable();
vmvar_t *array = getCaliVariable();
int num = getCaliValue();
int key = getCaliValue();
@@ -2026,7 +2026,7 @@ static void EncryptWORD() {
* SACT.DecryptWORD (1.0~)
*/
static void DecryptWORD() {
int *array = getCaliVariable();
vmvar_t *array = getCaliVariable();
int num = getCaliValue();
int key = getCaliValue();
@@ -2092,7 +2092,7 @@ static void XMenuRegister() {
*/
static void XMenuGetNum() {
int nRegiNum = getCaliValue();
int *vMenuID = getCaliVariable();
vmvar_t *vMenuID = getCaliVariable();
*vMenuID = spxm_getnum(nRegiNum);
+2 -28
View File
@@ -30,14 +30,11 @@
#include "list.h"
#include "ags.h"
#include "sacttimer.h"
#include "variable.h"
#include "sactcg.h"
// スプライトの最大数
#define SPRITEMAX 21845
// CGの最大数
#define CGMAX 63336
// メッセージの最大長さ
#define MSGBUFMAX 257*10
@@ -59,24 +56,6 @@ typedef struct {
char *dst; // 置き換え文字列
} strexchange_t;
// CG_XX で作るCGの種類
enum cgtype {
CG_NOTUSED = 0,
CG_LINKED = 1,
CG_SET = 2,
CG_REVERSE = 3,
CG_STRETCH = 4
};
// cgに関する情報
struct _cginfo {
enum cgtype type; // CGの種類, 0: 未使用, 1:リンクされている, ...
int no; // CGの番号
struct SDL_Surface *sf;
int refcnt; // 参照カウンタ。0になったら開放してもよい。
};
typedef struct _cginfo cginfo_t;
// スプライトのタイプ
enum spritetype {
SPRITE_NORMAL = 0,
@@ -209,9 +188,7 @@ struct _sact {
SList *sp_quake; // Quakeで揺らすスプライトのリスト
SList *updatelist; // 再描画するスプライトのリスト
cginfo_t *cg[CGMAX]; // cgまたはCG_xxで作った CG
// 座標系の原点
SDL_Point origin;
@@ -249,9 +226,6 @@ struct _sact {
SDL_Rect updaterect; // 更新が必要なspriteの領域の和
// sact timer
stimer_t timer[65536];
// DnDに関するもの
sprite_t *draggedsp; // drag中のスプライト
bool dropped; // スプライトがドロップされたかどうか
+1 -1
View File
@@ -25,7 +25,7 @@
#include "portab.h"
// グラフ用チャート作成
void schart_pos(int *pos, int pos1, int pos2, int val1, int val2, int val) {
void schart_pos(vmvar_t *pos, int pos1, int pos2, int val1, int val2, int val) {
if (val1 == val2) {
*pos = 0;
} else {
+3 -1
View File
@@ -24,6 +24,8 @@
#ifndef __SACTCHART_H__
#define __SACTCHART_H__
void schart_pos(int *pos, int pos1, int pos2, int val1, int val2, int val);
#include "portab.h"
void schart_pos(vmvar_t *pos, int pos1, int pos2, int val1, int val2, int val);
#endif
+1 -1
View File
@@ -31,7 +31,7 @@
適当でいいとおもう
*/
void scryp_encrypt_word(int *array, int num, int key) {
void scryp_encrypt_word(vmvar_t *array, int num, int key) {
WARNING("NOT IMPLEMENTED");
}
+3 -1
View File
@@ -24,7 +24,9 @@
#ifndef __SACTCRYPT_H__
#define __SACTCRYPT_H__
void scryp_encrypt_word(int *array, int num, int key);
#include "portab.h"
void scryp_encrypt_word(vmvar_t *array, int num, int key);
void scryp_decrypt_word(int *array, int num, int key);
void scryp_encrypt_str(int strno, int key);
void scryp_decrypt_str(int strno, int key);
+4 -8
View File
@@ -85,8 +85,8 @@ static void draw_log() {
// ページ位置情報
len = snprintf(pinfo, sizeof(pinfo) -1, "%d/%d", curline, list_length(sact.log));
ags_setFont(FONT_GOTHIC, INDEX_FONTSIZE);
gfx_drawString(main_surface->w - INDEX_FONTSIZE * len / 2, 0, pinfo, 255);
gfx_drawString(main_surface->w - INDEX_FONTSIZE * len / 2, 0, pinfo, 255,
(FontSpec){ .size = INDEX_FONTSIZE });
// 表示始め位置
node = list_nth(sact.log, list_length(sact.log) - curline);
@@ -97,12 +97,8 @@ 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 {
if (cur < 6) {
ags_setFont(FONT_MINCHO, FONTSIZE);
} else {
ags_setFont(FONT_GOTHIC, FONTSIZE);
}
gfx_drawString(0, y, str, 255);
FontSpec font_spec = { .type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC, .size = FONTSIZE };
gfx_drawString(0, y, str, 255, font_spec);
}
y += FONTSIZE;
cur--;
+1 -1
View File
@@ -112,7 +112,7 @@ void ssnd_wait(int no) {
}
// 指定の効果音が終了するか、キーが押されるまで待つ
void ssnd_waitkey(int no, int *res) {
void ssnd_waitkey(int no, vmvar_t *res) {
int slot = slt_find(no);
if (slot == -1) {
+3 -1
View File
@@ -24,11 +24,13 @@
#ifndef __SACTSOUND_H__
#define __SACTSOUND_H__
#include "portab.h"
void ssnd_init(void);
void ssnd_play(int no);
void ssnd_stop(int no, int fadetime);
void ssnd_wait(int no);
void ssnd_waitkey(int no, int *res);
void ssnd_waitkey(int no, vmvar_t *res);
void ssnd_prepare(int no);
void ssnd_prepareLRrev(int no);
void ssnd_playLRrev(int no);
+20 -21
View File
@@ -22,36 +22,35 @@
/* $Id: sacttimer.c,v 1.1 2003/04/22 16:29:52 chikama Exp $ */
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include "portab.h"
#include "sact.h"
#include "sacttimer.h"
#include "system.h"
#define MAX_TIMER 10
#define TICKS_PER_CENTISECOND 10
uint32_t ticks_base[MAX_TIMER];
/*
sact timer subsystem 初期化
*/
void stimer_init(void) {
stimer_reset(0, 0);
memset(ticks_base, 0, sizeof(ticks_base));
}
// 指定IDのタイマーのリセット
void stimer_reset(int id, int val) {
gettimeofday(&(sact.timer[id].tv_base), NULL);
sact.timer[id].val = val;
if (id < 0 || id >= MAX_TIMER) {
WARNING("Invalid timer ID: %d", id);
return;
}
ticks_base[id] = sys_get_ticks() - (val * TICKS_PER_CENTISECOND);
}
// 指定IDのタイマーの取得
int stimer_get(int id) {
long sec, usec, usec2;
struct timeval tv;
struct timeval tv_base = sact.timer[id].tv_base;
int division = 10;
gettimeofday(&tv, NULL);
sec = tv.tv_sec - tv_base.tv_sec;
usec = tv.tv_usec - tv_base.tv_usec;
usec2 = sec * (1000l/division)+ usec / 1000l /division;
return sact.timer[id].val + usec2;
if (id < 0 || id >= MAX_TIMER) {
WARNING("Invalid timer ID: %d", id);
return 0;
}
uint32_t ticks = sys_get_ticks();
uint32_t diff = ticks - ticks_base[id];
return diff / TICKS_PER_CENTISECOND;
}
-10
View File
@@ -24,16 +24,6 @@
#ifndef __SACTTIMER_H__
#define __SACTTIMER_H__
#include <sys/time.h>
#include <unistd.h>
struct _stimer {
int val;
struct timeval tv_base;
};
typedef struct _stimer stimer_t;
void stimer_init(void);
void stimer_reset(int id, int val);
int stimer_get(int id);
+8 -8
View File
@@ -455,7 +455,7 @@ bool sp_exists(int wNum) {
}
// スプライトのタイプと何番のCGがセットされているかの取得
bool sp_query_info(int wNum, int *vtype, int *vcg1, int *vcg2, int *vcg3) {
bool sp_query_info(int wNum, vmvar_t *vtype, vmvar_t *vcg1, vmvar_t *vcg2, vmvar_t *vcg3) {
sprite_t *sp;
if (wNum >= SPRITEMAX) goto errexit;
@@ -479,7 +479,7 @@ bool sp_query_info(int wNum, int *vtype, int *vcg1, int *vcg2, int *vcg3) {
}
// スプライトの表示状態の取得
bool sp_query_show(int wNum, int *vShow) {
bool sp_query_show(int wNum, vmvar_t *vShow) {
if (wNum >= SPRITEMAX) goto errexit;
if (sact.sp[wNum]->type == SPRITE_NONE) goto errexit;
@@ -492,7 +492,7 @@ bool sp_query_show(int wNum, int *vShow) {
}
// スプライトの表示位置の取得
bool sp_query_pos(int wNum, int *vx, int *vy) {
bool sp_query_pos(int wNum, vmvar_t *vx, vmvar_t *vy) {
if (wNum >= SPRITEMAX) goto errexit;
if (sact.sp[wNum]->type == SPRITE_NONE) goto errexit;
@@ -507,7 +507,7 @@ bool sp_query_pos(int wNum, int *vx, int *vy) {
}
// スプライトの大きさの取得
bool sp_query_size(int wNum, int *vw, int *vh) {
bool sp_query_size(int wNum, vmvar_t *vw, vmvar_t *vh) {
sprite_t *sp;
if (wNum >= SPRITEMAX) goto errexit;
@@ -528,7 +528,7 @@ bool sp_query_size(int wNum, int *vw, int *vh) {
}
// テキストスプライトの現在の文字表示位置の取得
bool sp_query_textpos(int wNum, int *vx, int *vy) {
bool sp_query_textpos(int wNum, vmvar_t *vx, vmvar_t *vy) {
if (wNum >= SPRITEMAX) goto errexit;
sprite_t *sp = sact.sp[wNum];
if (sp->type != SPRITE_MSG) goto errexit;
@@ -551,7 +551,7 @@ void sp_num_setcg(int nNum, int nIndex, int nCG) {
}
// NumeralXXXのCGの取得
void sp_num_getcg(int nNum, int nIndex, int *vCG) {
void sp_num_getcg(int nNum, int nIndex, vmvar_t *vCG) {
SP_ASSERT_NO(nNum);
*vCG = sact.sp[nNum]->numeral.cg[nIndex];
@@ -566,7 +566,7 @@ void sp_num_setpos(int nNum, int nX, int nY) {
}
// NumeralXXXの位置の取得
void sp_num_getpos(int nNum, int *vX, int *vY) {
void sp_num_getpos(int nNum, vmvar_t *vX, vmvar_t *vY) {
SP_ASSERT_NO(nNum);
*vX = sact.sp[nNum]->numeral.pos.x;
@@ -581,7 +581,7 @@ void sp_num_setspan(int nNum, int nSpan) {
}
// NumeralXXXのスパンの取得
void sp_num_getspan(int nNum, int *vSpan) {
void sp_num_getspan(int nNum, vmvar_t *vSpan) {
SP_ASSERT_NO(nNum);
*vSpan = sact.sp[nNum]->numeral.span;
+11 -10
View File
@@ -24,6 +24,7 @@
#ifndef __SPRITE_H__
#define __SPRITE_H__
#include "portab.h"
#include "sact.h"
#define DEFAULT_UPDATE sp_draw
@@ -51,17 +52,17 @@ void sp_set_animeinterval(int wNum, int wTime);
bool sp_is_insprite(sprite_t *sp, int x, int y);
void sp_set_blendrate(int wNum, int wCount, int rate);
bool sp_exists(int wNum);
bool sp_query_info(int wNum, int *vtype, int *vcg1, int *vcg2, int *vcg3);
bool sp_query_show(int wNum, int *vShow);
bool sp_query_pos(int wNum, int *vx, int *vy);
bool sp_query_size(int wNum, int *vw, int *vh);
bool sp_query_textpos(int wNum, int *vx, int *vy);
bool sp_query_info(int wNum, vmvar_t *vtype, vmvar_t *vcg1, vmvar_t *vcg2, vmvar_t *vcg3);
bool sp_query_show(int wNum, vmvar_t *vShow);
bool sp_query_pos(int wNum, vmvar_t *vx, vmvar_t *vy);
bool sp_query_size(int wNum, vmvar_t *vw, vmvar_t *vh);
bool sp_query_textpos(int wNum, vmvar_t *vx, vmvar_t *vy);
void sp_num_setcg(int nNum, int nIndex, int nCG);
void sp_num_getcg(int nNum, int nIndex, int *vCG);
void sp_num_getcg(int nNum, int nIndex, vmvar_t *vCG);
void sp_num_setpos(int nNum, int nX, int nY);
void sp_num_getpos(int nNum, int *vX, int *vY);
void sp_num_getpos(int nNum, vmvar_t *vX, vmvar_t *vY);
void sp_num_setspan(int nNum, int nSpan);
void sp_num_getspan(int nNUm, int *vSpan);
void sp_num_getspan(int nNUm, vmvar_t *vSpan);
void sp_exp_clear(void);
void sp_exp_add(int nNumSP1, int nNumSP2);
void sp_exp_del(int wNum);
@@ -85,7 +86,7 @@ void sp_draw_dmap(void* data, void* userdata);
// in sprite_msg.c
void smsg_add(const char *msg);
void smsg_newline(int wNum, int size);
void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wFont, int wSpeed, int wLineSpace, int wAlign, int wRSize, int wRFont, int wRLineSpace, int *wLength);
void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wFont, int wSpeed, int wLineSpace, int wAlign, int wRSize, int wRFont, int wRLineSpace, vmvar_t *wLength);
void smsg_clear(int wNum);
bool smsg_is_empty();
int smsg_peek(int nTopStringNum);
@@ -133,7 +134,7 @@ void sp_eupdate(int type, int time, int key);
void sp_quake_sprite(int wType, int wAmplitudeX, int wAmplitude, int wCount, int cancel);
// in sprite_keywait.c
void sp_keywait(int *vOK, int *vRND, int *vRsv1, int *vRsv2, int *vRsv3, int timeout);
void sp_keywait(vmvar_t *vOK, vmvar_t *vRND, vmvar_t *vRsv1, vmvar_t *vRsv2, vmvar_t *vRsv3, int timeout);
// in screen_quake.c
void sp_quake_screen(int type, int p1, int p2, int time, int cancel);
-5
View File
@@ -426,11 +426,6 @@ static void cb_waitkey_backlog(agsevent_t *e) {
X|SDL のイベントディスパッチャからくる最初の場所
*/
void spev_callback(agsevent_t *e) {
// menu open中は無視
if (nact->popupmenu_opened) {
return;
}
if (sact.waittype != KEYWAIT_BACKLOG) {
if (e->type == AGSEVENT_KEY_PRESS && e->code == KEY_CTRL) {
sact.waitskiplv = 2;
+1 -2
View File
@@ -113,7 +113,7 @@ static bool waitcond(int endtime) {
@param vD03: タイムアウトした場合=1, しない場合=0
@param wTime: タイムアウト時間 (1/100sec)
*/
void sp_keywait(int *vOK, int *vRND, int *vD01, int *vD02, int *vD03, int timeout) {
void sp_keywait(vmvar_t *vOK, vmvar_t *vRND, vmvar_t *vD01, vmvar_t *vD02, vmvar_t *vD03, int timeout) {
int curtime, endtime;
// とりあえず全更新
@@ -171,4 +171,3 @@ void sp_keywait(int *vOK, int *vRND, int *vD01, int *vD02, int *vD03, int timeou
sact.waittype = KEYWAIT_NONE;
}
-2
View File
@@ -98,8 +98,6 @@ static int move_cb(sprite_t *sp, agsevent_t *e) {
// 新しい場所のupdate
sp_updateme(sp);
update++;
} else {
usleep(1);
}
return update;
+2 -1
View File
@@ -31,6 +31,7 @@
#include "system.h"
#include "ags.h"
#include "nact.h"
#include "variable.h"
#include "input.h"
#include "sact.h"
#include "sprite.h"
@@ -235,7 +236,7 @@ void smsg_newline(int wNum, int size) {
@param wRLineSpace: ルビと本文の文字間隔
@param vLength: ???
*/
void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wFont, int wSpeed, int wLineSpace, int wAlign, int wRSize, int wRFont, int wRLineSpace, int *wLength) {
void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wFont, int wSpeed, int wLineSpace, int wAlign, int wRSize, int wRFont, int wRLineSpace, vmvar_t *wLength) {
char *msg;
sprite_t *sp;
int len = 0; // 処理した文字数?
+1
View File
@@ -30,6 +30,7 @@
#include "portab.h"
#include "nact.h"
#include "variable.h"
#include "ags.h"
#include "input.h"
#include "sact.h"
-7
View File
@@ -117,11 +117,4 @@ void spev_main() {
e.type = AGSEVENT_TIMER;
tevent_callback(&e);
// デフォルトのコールバックのうち、ここで必要なものだけ
// 処理。(VAコマンドcallbackはなし)
if (nact->popupmenu_opened) {
menu_gtkmainiteration();
if (nact->is_quit) sys_exit(0);
}
}
+83 -83
View File
@@ -43,10 +43,10 @@ static void GetAtArray(void) { /* 0 */
type: 演算の種類
vResult: 演算結果を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int type = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i, j;
TRACE("ShArray.GetAtArray %p,%d,%d,%p:", vAry, cnt, type, vResult);
@@ -88,8 +88,8 @@ static void AddAtArray(void) { /* 1 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -114,8 +114,8 @@ static void SubAtArray(void) { /* 2 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -140,8 +140,8 @@ static void MulAtArray(void) { /* 3 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -166,8 +166,8 @@ static void DivAtArray(void) { /* 4 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -196,8 +196,8 @@ static void MinAtArray(void) { /* 5 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -219,8 +219,8 @@ static void MaxAtArray(void) { /* 6 */
vAry2: 配列2
cnt : 個数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -242,7 +242,7 @@ static void AndNumArray(void) { /* 7 */
cnt : 個数
val : ANDをとる値
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -263,7 +263,7 @@ static void OrNumArray(void) { /* 8 */
cnt : 個数
val : ORをとる値
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -284,7 +284,7 @@ static void XorNumArray(void) { /* 9 */
cnt : 個数
val : XORをとる値
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -307,10 +307,10 @@ static void SetEquArray(void) { /* 10 */
val : 比較する値
vResults : 結果を格納する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetEquArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -331,10 +331,10 @@ static void SetNotArray(void) { /* 11 */
val : 比較する値
vResults : 結果を格納する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetNotArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -354,10 +354,10 @@ static void SetLowArray(void) { /* 12 */
val : 閾値
vResult: 結果を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetLowArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -377,10 +377,10 @@ static void SetHighArray(void) { /* 13 */
val : 閾値
vResults: 結果を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetHighArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -403,11 +403,11 @@ static void SetRangeArray(void) { /* 14 */
min < vAry < max の時 vResults = 1;
それ以外 vResults = 0;
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetRangeArray %p,%d,%d,%d,%p:", vAry, cnt, min, max, vResults);
@@ -430,11 +430,11 @@ static void SetAndEquArray(void) { /* 15 */
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int mask = getCaliValue();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.SetAndEquArray: %p,%d,%d,%d,%p:", vAry, mask, cnt, val, vResults);
@@ -455,10 +455,10 @@ static void AndEquArray(void) { /* 16 */
val : 比較する値
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndEquArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -479,10 +479,10 @@ static void AndNotArray(void) { /* 17 */
val : 比較する値
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndNotArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -503,10 +503,10 @@ static void AndLowArray(void) { /* 18 */
min : 最小値
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndLowArray: %d,%d,%d,%d:", vAry, cnt, min, vResults);
@@ -527,10 +527,10 @@ static void AndHighArray(void) { /* 19 */
max : 最小値
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int max = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndHighArray: %p,%d,%d,%p:", vAry, cnt, max, vResults);
@@ -552,11 +552,11 @@ static void AndRangeArray(void) { /* 20 */
max: 最大値
vResults: 結果を返す配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndRangeArray %d,%d,%d,%d,%d:", vAry, cnt, min, max, vResults);
@@ -579,11 +579,11 @@ static void AndAndEquArray(void) { /* 21 */
vResults : 結果を代入する配列
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int mask = getCaliValue();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.AndAndEquArray: %d,%d,%d,%d,%d:", vAry, mask, cnt, val, vResults);
@@ -612,10 +612,10 @@ static void OrNotArray(void) { /* 23 */
val : 比較する値
vResults: 結果を書き込む変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResults = getCaliVariable();
vmvar_t *vResults = getCaliVariable();
int i;
TRACE("ShArray.OrNotArray %p,%d,%d,%p:", vAry, cnt, val, vResults);
@@ -674,10 +674,10 @@ static void EnumEquArray(void) { /* 28 */
val : 比較する値
vResult: 一致する個数を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.EnumEquArray %p,%d,%d,%p:", vAry, cnt, val, vResult);
@@ -713,12 +713,12 @@ static void EnumEquNotArray2(void) { /* 30 */
val2: 配列2と比較する値
vResult: 条件に一致する数を返す変数
*/
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int val1 = getCaliValue();
int val2 = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.EnumEquNotArray2 %p,%p,%d,%d,%d,%p:", vAry1, vAry2, cnt, val1, val2, vResult);
@@ -742,10 +742,10 @@ static void EnumNotArray(void) { /* 31 */
val: 比較する値
vResult: 等しくないものの数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.EnumNotArray %p, %d, %d, %p:", vAry, cnt, val, vResult);
@@ -799,11 +799,11 @@ static void EnumRangeArray(void) { /* 35 */
max : 最大値
vResult: 一致した数を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.EnumRangeArray %d,%d,%d,%d,%d:", vAry, cnt, min, max, vResult);
@@ -830,11 +830,11 @@ static void GrepEquArray(void) { /* 36 */
vMatch: 一致したインデックス
vResult: 一つでも val と同じ値があれば 1
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.GrepEquArray %p,%d,%d,%p,%p:", vAry, cnt, val, vMatch, vResult);
@@ -862,11 +862,11 @@ static void GrepNotArray(void) { /* 37 */
vMatch: 一致するindex
vResult: 一つでも val と同じ値があれば 1
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int *vMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.GrepNotArray %p,%d,%d,%p,%p:", vAry, cnt, val, vMatch, vResult);
@@ -930,11 +930,11 @@ static void GrepLowArray(void) { /* 41 */
vMatch: 一致したインデックス
vResult: 一つでも val と同じ値があれば 1
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int *vMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.GrepLowArray: %p,%d,%d,%p,%p:", vAry, cnt, min, vMatch, vResult);
@@ -962,11 +962,11 @@ static void GrepHighArray(void) { /* 42 */
vMatch: 一致したインデックス
vResult: 一つでも val と同じ値があれば 1
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int max = getCaliValue();
int *vMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.GrepHighArray: %p,%d,%d,%p,%p:", vAry, cnt, max, vMatch, vResult);
@@ -995,12 +995,12 @@ static void GrepRangeArray(void) { /* 43 */
vMatch: 一致したインデックス
vResult: 一つでも val と同じ値があれば 1
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *vMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
TRACE("ShArray.GrepRangeArray %p,%d,%d,%d,%p,%p:", vAry, cnt, max, min, vMatch, vResult);
@@ -1031,13 +1031,13 @@ static void GrepLowOrderArray(void) { /* 44 */
vLastMatch: 最小値を示す配列のindex
vResult: 最小値が見つかれば 1, 見つからなければ 0
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *v1 = getCaliVariable();
int *vLastMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *v1 = getCaliVariable();
vmvar_t *vLastMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i, j, k = 0;
TRACE("ShArray.GrepLowOrderArray %p,%d,%d,%d,%p,%p,%p:", vAry, cnt, min, max, v1, vLastMatch, vResult);
@@ -1082,13 +1082,13 @@ static void GrepHighOrderArray(void) { /* 45 */
vLastMatch: 最大値を示す配列のindex
vResult: 最大値が見つかれば 1, 見つからなければ 0
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
int *v1 = getCaliVariable();
int *vLastMatch = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *v1 = getCaliVariable();
vmvar_t *vLastMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i, j, k = 0;
TRACE("ShArray.GrepHighOrderArray %p,%d,%d,%d,%p,%p,%p:", vAry, cnt, min, max, v1, vLastMatch, vResult);
@@ -1119,7 +1119,7 @@ static void GrepHighOrderArray(void) { /* 45 */
}
static void ChangeEquArray(void) { /* 46 */
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int src = getCaliValue();
int dst = getCaliValue();
@@ -1172,7 +1172,7 @@ static void ChangeRangeArray(void) { /* 50 */
max : 最大値
val : 置き換える値
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
@@ -1202,12 +1202,12 @@ static void CopyArrayToRect(void) { /* 51 */
dw : コピー先 width
dh : コピー先 height
*/
int *vSrc = getCaliVariable();
vmvar_t *vSrc = getCaliVariable();
int sw = getCaliValue();
int sh = getCaliValue();
int sx = getCaliValue();
int sy = getCaliValue();
int *vDst = getCaliVariable();
vmvar_t *vDst = getCaliVariable();
int dw = getCaliValue();
int dh = getCaliValue();
int x, y;
@@ -1236,10 +1236,10 @@ static void CopyRectToArray(void) { /* 52 */
dx : コピー先 x
dy : コピー先 y
*/
int *vSrc = getCaliVariable();
vmvar_t *vSrc = getCaliVariable();
int sw = getCaliValue();
int sh = getCaliValue();
int *vDst = getCaliVariable();
vmvar_t *vDst = getCaliVariable();
int dw = getCaliValue();
int dh = getCaliValue();
int dx = getCaliValue();
@@ -1266,10 +1266,10 @@ static void ChangeSecretArray(void) { /* 53 */
type: 機能番号
vResult: 結果を返す変数
*/
int *vAry = getCaliVariable();
vmvar_t *vAry = getCaliVariable();
int cnt = getCaliValue();
int type = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
static uint16_t key[4] = { 0x7A7A, 0xADAD, 0xBCBC, 0xCECE }; /* key */
TRACE("ShArray.ChangeSecretArray %p,%d,%d,%p:", vAry, cnt, type, vResult);
+19 -19
View File
@@ -50,7 +50,7 @@ static int64_t div64(int64_t a1, int64_t a2) {
return a1 / a2;
}
static int64_t get32(int *var) {
static int64_t get32(vmvar_t *var) {
return var[0] + mul64(var[1], 0x10000);
}
@@ -74,7 +74,7 @@ static void SetIntNum16(void) { /* 1 */
var:
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator = mul64(*var, numbase);
@@ -82,7 +82,7 @@ static void SetIntNum16(void) { /* 1 */
}
static void SetIntNum32(void) { /* 2 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator = mul64(get32(var), numbase);
@@ -95,7 +95,7 @@ static void GetIntNum16(void) { /* 3 */
var:
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int64_t i;
i = div64(accumulator, numbase);
@@ -110,7 +110,7 @@ static void GetIntNum16(void) { /* 3 */
}
static void GetIntNum32(void) { /* 4 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int64_t i = div64(accumulator, numbase);
var[0] = i & 0xFFFF;
@@ -126,7 +126,7 @@ static void AddIntNum16(void) { /* 5 */
var:
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator += mul64(*var, numbase);
@@ -134,7 +134,7 @@ static void AddIntNum16(void) { /* 5 */
}
static void AddIntNum32(void) { /* 6 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator += mul64(get32(var), numbase);
@@ -148,7 +148,7 @@ static void SubIntNum16(void) { /* 7 */
}
static void SubIntNum32(void) { /* 8 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator -= mul64(get32(var), numbase);
@@ -161,7 +161,7 @@ static void MulIntNum16(void) { /* 9 */
var:
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
accumulator *= mul64(*var, numbase);
@@ -180,7 +180,7 @@ static void DivIntNum16(void) { /* 11 */
var:
*/
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int64_t i;
i = mul64(*var, numbase);
@@ -204,9 +204,9 @@ static void CmpIntNum16(void) { /* 13 */
}
static void CmpIntNum32(void) { /* 14 */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int op = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
int64_t val = mul64(get32(var), numbase);
@@ -230,8 +230,8 @@ static void GetLengthNum16(void) { /* 15 */
var:
vResult:
*/
int *var = getCaliVariable();
int *vResult = getCaliVariable();
vmvar_t *var = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
if (*var >= 10000) {
*vResult = 5;
@@ -270,7 +270,7 @@ static void NumToRate(void) { /* 17 */
int p2 = getCaliValue();
int p3 = getCaliValue();
int flag = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
i = (p1 * p3) / p2;
@@ -302,7 +302,7 @@ static void NumToRateNum(void) { /* 18 */
int p2 = getCaliValue();
int p3 = getCaliValue();
int flag = getCaliValue();
int *vResult = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int i;
i = (p1 * p2) / p3;
@@ -333,7 +333,7 @@ static void SetRandomSeed() {
static void GetRandomNumA() {
int num = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
if (num == 0 || num == 1) {
*var = num;
@@ -351,7 +351,7 @@ static void NumToBit() {
var:
*/
int beki = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int i, j = 1;
if (beki < 17) {
@@ -379,7 +379,7 @@ static void BitToNum() {
var:
*/
int val = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int i;
TRACE("ShCalc.BitToNum %d,%p:", val, var);
+12 -12
View File
@@ -59,8 +59,8 @@ struct animsrc {
static struct animsrc src[SLOT]; /* アニメーション各コマ転送元 */
struct _s0 {
int *dst_p1;
int *dst_p2;
vmvar_t *dst_p1;
vmvar_t *dst_p2;
int dw_1000A188;
};
static struct _s0 s0[SLOT];
@@ -84,7 +84,7 @@ struct _s2 {
};
static struct _s2 s2[SLOT];
static int* add_p5[SLOT]; /* どこまでアニメーションのコマが進んだか */
static vmvar_t *add_p5[SLOT]; /* どこまでアニメーションのコマが進んだか */
static void copy_sprite(int sx, int sy, int width, int height, int dx, int dy, int r, int g, int b) {
SDL_Surface *sf = nact->ags.dib->sdl_surface;
@@ -115,8 +115,8 @@ static void ChangeEquColor() {
int p2 = getCaliValue();
int p3 = getCaliValue();
int p4 = getCaliValue();
int *p5 = getCaliVariable();
int *p6 = getCaliVariable();
vmvar_t *p5 = getCaliVariable();
vmvar_t *p6 = getCaliVariable();
int p7 = getCaliValue(); /* ISurface */
TRACE_UNIMPLEMENTED("ShGraph.ChangeEquColor %d,%d,%d,%d,%p,%p,%d:", p1, p2, p3, p4, p5, p6, p7);
@@ -137,8 +137,8 @@ static void ChangeNotColor() {
int y0 = getCaliValue();
int width = getCaliValue();
int height = getCaliValue();
int *src = getCaliVariable(); /* r, g, b */
int *dst = getCaliVariable(); /* r, g, b */
vmvar_t *src = getCaliVariable(); /* r, g, b */
vmvar_t *dst = getCaliVariable(); /* r, g, b */
int p7 = getCaliValue(); /* ISurface */
surface_t *dib;
int x, y;
@@ -216,7 +216,7 @@ static void ResetAnimeData() {
}
memset(s1, 0, sizeof(struct _s1) * SLOT);
memset(add_p5, 0, sizeof(int *) * SLOT);
memset(add_p5, 0, sizeof(vmvar_t *) * SLOT);
}
@@ -240,7 +240,7 @@ static void SetAnimeSrc() {
int h = getCaliValue();
int uw = getCaliValue();
int uh = getCaliValue();
int *pal = getCaliVariable();
vmvar_t *pal = getCaliVariable();
int r, g, b;
TRACE("ShGraph.SetAnimeSrc %d,%d,%d,%d,%d,%d,%d,%p:", no, x0, y0, w, h, uw, uh, pal);
@@ -276,8 +276,8 @@ static void SetAnimeDst() {
p6: (h) 10000
*/
int no = getCaliValue();
int *p1 = getCaliVariable();
int *p2 = getCaliVariable();
vmvar_t *p1 = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
int p3 = getCaliValue();
int p4 = getCaliValue();
int p5 = getCaliValue();
@@ -313,7 +313,7 @@ static void AddAnimeData() {
int p2 = getCaliValue();
int p3 = getCaliValue();
int p4 = getCaliValue();
int *p5 = getCaliVariable();
vmvar_t *p5 = getCaliVariable();
int p6 = getCaliValue();
int i;
+8 -9
View File
@@ -36,7 +36,7 @@
#include "xsystem35.h"
#include "modules.h"
#include "input.h"
#include "menu.h"
#include "input_modal.h"
// キー変換テーブル
#define KEYMAP_MAX 8
@@ -47,7 +47,7 @@ static void OutputMessageBox(void) { /* 0 */
int p2 = getCaliValue();
int title = getCaliValue();
int msg = getCaliValue();
int *res = getCaliVariable();
vmvar_t *res = getCaliVariable();
int ISys3xSystem = getCaliValue();
char *title_utf8 = toUTF8(svar_get(title));
@@ -64,10 +64,10 @@ static void OutputMessageBox(void) { /* 0 */
static void InputListNum(void) { /* 1 */
int flags = getCaliValue();
int title = getCaliValue();
int *val = getCaliVariable();
vmvar_t *val = getCaliVariable();
int minval = getCaliValue();
int maxval = getCaliValue();
int *res = getCaliVariable();
vmvar_t *res = getCaliVariable();
int ISys3xSystem = getCaliValue();
INPUTNUM_PARAM ni_param = {
@@ -77,12 +77,11 @@ static void InputListNum(void) { /* 1 */
.title = toUTF8(svar_get(title)),
};
menu_inputnumber(&ni_param);
if (ni_param.value < 0) {
*res = 0;
} else {
if (input_modal_number(&ni_param)) {
*val = (uint16_t)ni_param.value;
*res = 1;
} else {
*res = 0;
}
free(ni_param.title);
@@ -152,7 +151,7 @@ static void SetKeyStatus(void) {
*/
static void GetKeyStatus(void) {
int no = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
int i;
if (no >= KEYMAP_MAX) {
+3 -3
View File
@@ -268,7 +268,7 @@ static void wavPlayRing() {
*/
int start = getCaliValue();
int cnt = getCaliValue();
int *cur = getCaliVariable();
vmvar_t *cur = getCaliVariable();
mus_wav_play(start + (*cur % cnt), 1);
*cur = (*cur + 1) % cnt;
@@ -321,7 +321,7 @@ static void wavIsPlay() {
*result: 0!0
*/
int slot = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
*result = mus_wav_get_playposition(slot);
@@ -338,7 +338,7 @@ static void wavIsPlayRange() {
*/
int slot = getCaliValue();
int range = getCaliValue();
int *result = getCaliVariable();
vmvar_t *result = getCaliVariable();
int i, ret = 0;
for (i = slot; i < (slot + range); i++) {
+2 -2
View File
@@ -127,7 +127,7 @@ static void SetStringNum16(void) {
p2:
*/
int st = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
const char *str = svar_get(st);
char _dst[100];
char *dst = _dst;
@@ -160,7 +160,7 @@ static void SetStringNum16(void) {
static void SetStringNum32(void) {
int p1 = getCaliValue();
int *p2 = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
TRACE_UNIMPLEMENTED("ShString.SetStringNum32: %d,%p:", p1, p2);
}
+920 -27
View File
@@ -1,8 +1,8 @@
// #define DDEMODEV
#include "config.h"
#include <stdio.h>
#include <math.h>
#include <SDL.h>
#include "portab.h"
#include "system.h"
@@ -11,53 +11,946 @@
#include "nact.h"
#include "input.h"
#include "music.h"
#include "alk.h"
#include "cg.h"
#include "gfx.h"
#include "gfx_private.h"
#ifdef DDEMODEV
#include "dDemo.h"
static struct ddemo dd;
#include "scene.c"
#endif
// Perspective projection parameters
#define PROJ_SCALE_X 1.2993f // cot(30°) / (4/3)
#define PROJ_SCALE_Y 1.7321f // cot(30°) = √3
#define PROJ_W_DENOM 0.5000625f
static inline float lerpf(float a, float b, float t) { return a + (b - a) * t; }
static void render_quad(SDL_Texture *tex, SDL_Vertex verts[4]) {
static const int indices[] = {0, 1, 2, 0, 2, 3};
SDL_RenderGeometry(gfx_renderer, tex, verts, 4, indices, 6);
}
static void fade_overlay(Uint8 r, Uint8 g, Uint8 b, Uint8 alpha) {
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(gfx_renderer, r, g, b, alpha);
SDL_RenderFillRect(gfx_renderer, NULL);
}
#define NR_SCENES 13
static const int scene_duration[] = {
5400,
4752,
24963,
4000,
20000,
4000,
21000,
4000,
6402,
4000,
10370,
31613,
10338,
};
#define NR_TEXTURES 86
static struct {
bool loop;
bool key_cancel;
SDL_Texture *images[NR_TEXTURES];
} dd;
// For each corner, if a coordinate is out of [0, view_w-1] x [0, view_h-1],
// shift all other corners by the overshoot amount, then hard-clamp everything
// to the screen boundary.
static void clamp_rect_to_screen(int xs[4], int ys[4]) {
for (int i = 0; i < 4; i++) {
if (xs[i] < 0) {
for (int j = 0; j < 4; j++) if (j != i) xs[j] -= xs[i];
xs[i] = 0;
}
if (ys[i] < 0) {
for (int j = 0; j < 4; j++) if (j != i) ys[j] -= ys[i];
ys[i] = 0;
}
if (xs[i] >= view_w - 1) {
for (int j = 0; j < 4; j++) if (j != i) xs[j] += (view_w - 1 - xs[i]);
xs[i] = view_w - 1;
}
if (ys[i] >= view_h - 1) {
for (int j = 0; j < 4; j++) if (j != i) ys[j] += (view_h - 1 - ys[i]);
ys[i] = view_h - 1;
}
}
for (int i = 0; i < 4; i++) {
if (xs[i] < 0) xs[i] = 0;
if (xs[i] >= view_w) xs[i] = view_w - 1;
if (ys[i] < 0) ys[i] = 0;
if (ys[i] >= view_h) ys[i] = view_h - 1;
}
}
static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
if (!nact->files.alk[0]) {
WARNING("dDEMO.alk not found");
return;
}
alk_t *alk = alk_new(nact->files.alk[0]); // dDEMO.alk
if (!alk) {
WARNING("Cannot open %s", nact->files.alk[0]);
*var = 0;
return;
}
if (alk->datanum < NR_TEXTURES) {
WARNING("dDEMO.alk has %d entries, expected %d", alk->datanum, NR_TEXTURES);
alk_free(alk);
*var = 0;
return;
}
for (int i = 0; i < NR_TEXTURES; i++) {
bool load_as_alpha_map = i < 18 || i > 60;
SDL_Surface *sf = cg_load_as_sdlsurface_from_data(alk->entries[i].data, alk->entries[i].size, false, load_as_alpha_map);
if (!sf) {
WARNING("failed to load image %d in dDEMO.alk", i);
dd.images[i] = NULL;
continue;
}
dd.images[i] = SDL_CreateTextureFromSurface(gfx_renderer, sf);
SDL_FreeSurface(sf);
}
*var = 1;
#ifdef DDEMODEV
dd.alk = alk_new("/home/masaki-c/game/daiakuji/dDemo.alk");
#endif
TRACE_UNIMPLEMENTED("dDemo.Init %d,%d,%d,%p:", p1, p2, p3, var);
TRACE("dDemo.Init %d,%d,%d,%p:", p1, p2, p3, var);
}
static void SetKeyCancelFlag() {
int cancelflag = getCaliValue();
TRACE_UNIMPLEMENTED("dDemo.SetKeyCancelFlag %d:", cancelflag);
dd.key_cancel = cancelflag != 0;
TRACE("dDemo.SetKeyCancelFlag %d:", cancelflag);
}
static void SetLoopFlag() {
/* Loop Flag */
int loopflag = getCaliValue(); /* 0 なら無限繰り返し */
TRACE_UNIMPLEMENTED("dDemo.SetLoopFlag %d:", loopflag);
int loopflag = getCaliValue();
dd.loop = loopflag != 0;
TRACE("dDemo.SetLoopFlag %d:", loopflag);
}
static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
float angle_deg, float scale, int offset_x, int offset_y) {
if (!scroll_bg) return;
int bg_w, bg_h;
SDL_QueryTexture(scroll_bg, NULL, NULL, &bg_w, &bg_h);
int xs[4], ys[4];
const float angle = angle_deg * (float)M_PI / 180.0f;
const float hw = view_w * 0.5f, hh = view_h * 0.5f;
const float cosA = cosf(angle), sinA = sinf(angle);
const float cxs[4] = {-hw, hw, hw, -hw};
const float cys[4] = {-hh, -hh, hh, hh};
for (int i = 0; i < 4; i++) {
xs[i] = (int)((cosA * cxs[i] - sinA * cys[i]) * scale + hw) + offset_x;
ys[i] = (int)((sinA * cxs[i] + cosA * cys[i]) * scale + hh) + offset_y;
}
clamp_rect_to_screen(xs, ys);
float t = (float)elapsed / (float)duration;
float w = (float)view_w, h = (float)view_h;
float uv[4][2];
uv[0][0] = 0.0f;
uv[0][1] = lerpf(0.0f, (float)ys[0], t);
uv[1][0] = lerpf(w - 1.0f, (float)xs[1], t);
uv[1][1] = lerpf(0.0f, (float)ys[1], t);
uv[2][0] = lerpf(w - 1.0f, (float)xs[2], t);
uv[2][1] = lerpf(h - 1.0f, (float)ys[2], t);
uv[3][0] = lerpf(0.0f, (float)xs[3], t);
uv[3][1] = lerpf(h - 1.0f, (float)ys[3], t);
for (int i = 0; i < 4; i++) {
uv[i][0] /= (float)bg_w;
uv[i][1] /= (float)bg_h;
}
SDL_Vertex verts[4];
static int y_toggle = 0;
// Pass 1: Forward mapping layer
for (int i = 0; i < 4; i++) {
verts[i].position.x = (i == 1 || i == 2) ? w : 0;
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].tex_coord.x = uv[i][0];
verts[i].tex_coord.y = uv[i][1];
}
SDL_SetTextureBlendMode(scroll_bg, SDL_BLENDMODE_NONE);
render_quad(scroll_bg, verts);
// Pass 2: 180° rotated mapping layer
y_toggle = (y_toggle + 1) & 1;
for (int i = 0; i < 4; i++) {
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 128};
}
// Swap mapping: TL<->BR, TR<->BL for 180° rotation effect
verts[0].tex_coord.x = uv[2][0]; verts[0].tex_coord.y = uv[2][1];
verts[1].tex_coord.x = uv[3][0]; verts[1].tex_coord.y = uv[3][1];
verts[2].tex_coord.x = uv[0][0]; verts[2].tex_coord.y = uv[0][1];
verts[3].tex_coord.x = uv[1][0]; verts[3].tex_coord.y = uv[1][1];
SDL_SetTextureBlendMode(scroll_bg, SDL_BLENDMODE_BLEND);
render_quad(scroll_bg, verts);
}
static void render_scene0(int elapsed) {
SDL_Texture *scroll_bg = dd.images[79];
SDL_Texture *sprite = dd.images[77];
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
const int slide_end = 4700;
const int duration = scene_duration[0]; // 5400
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
// Source coordinates are linear-interpolated between identity and a transformed state.
// Two layers (identity and 180° rotated) are blended together.
render_affine_bg(scroll_bg, elapsed, duration, 40.0f, 0.55f, -20, 100);
// --- Sprite and UI elements ---
// 2. Sprite (ALK 77) with 3-echo horizontal slide-in.
// Each copy starts wider/further left and converges to the final position.
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
int dest_y = (view_h - spr_h) / 2;
if (elapsed < slide_end) {
SDL_SetTextureAlphaMod(sprite, 0x80);
const int a0_vals[] = {640, 480, 320};
for (int i = 0; i < 3; i++) {
int a0 = a0_vals[i];
int dest_w = a0 - ((a0 - spr_w) * elapsed) / slide_end;
if (dest_w <= 0) continue;
SDL_Rect dst = {view_w - 2 * dest_w, dest_y, dest_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
} else {
SDL_SetTextureAlphaMod(sprite, 0xd0);
SDL_Rect dst = {view_w - 2 * spr_w, dest_y, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
// 3. Horizontal accent line (grows rightward over 4700ms)
int line_w = elapsed < slide_end ? 639 * elapsed / slide_end + 1 : 640;
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xc0, 0xc0, 0xc0, 0xff);
SDL_Rect hline = {0, (spr_h + view_h) / 2 - 4, line_w, 2};
SDL_RenderFillRect(gfx_renderer, &hline);
// 4. Vertical accent line (right edge of sprite, grows downward)
int line_h = elapsed < slide_end ? 479 * elapsed / slide_end + 1 : 480;
SDL_Rect vline = {view_w - spr_w, 0, 2, line_h};
SDL_RenderFillRect(gfx_renderer, &vline);
}
static void render_scene1(int elapsed) {
SDL_Texture *scroll_bg = dd.images[78];
SDL_Texture *sprite = dd.images[76];
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
const int duration = scene_duration[1]; // 4752
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
render_affine_bg(scroll_bg, elapsed, duration, -45.0f, 0.1f, 0, 200);
// Foreground (ALK 76): centered, fades in over first 3000ms.
// graph_BlendAlphaMapColorAlpha (alpha 0->255) for elapsed < 3000,
// graph_BlendAlphaMapColor (full opacity) for elapsed >= 3000.
{
int alpha = elapsed < 3000 ? (elapsed * 255) / 3000 : 255;
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(sprite, (Uint8)alpha);
SDL_Rect dst = {(view_w - spr_w) / 2, (view_h - spr_h) / 2, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
// Fade to black: graph_FillAlphaColor(0,0,0), starts at 4000ms.
if (elapsed > 4000) {
int fade_alpha = ((elapsed - 4000) * 255) / (duration - 4000);
fade_overlay(0, 0, 0, (Uint8)SDL_min(fade_alpha, 255));
}
}
// 3D rendering for render_scene2_4_6.
//
// Background quad (model space, XY plane):
// v0=(-1, -0.73446, 0) v1=(+1, -0.73446, 0)
// v2=(+1, +0.73446, 0) v3=(-1, +0.73446, 0)
// Object border quad (slightly oversized):
// v0=(-1.00885, -0.74655, 0) ... v3=(-1.00885, +0.74655, 0)
//
// Animation phases (phaseDuration = (sceneDuration - 1000) / 8):
// Phase 0 [0..1pd): approach from Z=-9 to Z=-3.75, Y-rot -90°→0°, tex1
// Phase 1 [1..3pd): static at Z=-3.75, rot=0°, tex1
// Phase 2 [3..4pd): recede from Z=-3.75 to Z=-9, Y-rot 0°→90°, tex1
// Phase 3 [4..5pd): approach from Z=-9 to Z=-3.75, Y-rot -90°→0°, tex2
// Phase 4 [5..7pd): static at Z=-3.75, rot=0°, tex2
// Phase 5 [7..8pd): recede from Z=-3.75 to Z=-9, Y-rot 0°→90°, tex2
//
// All phases also have an X-swing: tx = sin(π*t) * swing_amp
// approach swing_amp = -1.5, recede = +1.5
//
// Projection: same parameters as render_scene12_3d.
static void render_scene_2_4_6_3d(int elapsed, int phase_dur, SDL_Texture *tex1, SDL_Texture *tex2) {
static const float bg_mv[4][3] = {
{-1.00000f, -0.73446f, 0.0f},
{+1.00000f, -0.73446f, 0.0f},
{+1.00000f, +0.73446f, 0.0f},
{-1.00000f, +0.73446f, 0.0f},
};
static const float obj_mv[4][3] = {
{-1.00885f, -0.74655f, 0.0f},
{+1.00885f, -0.74655f, 0.0f},
{+1.00885f, +0.74655f, 0.0f},
{-1.00885f, +0.74655f, 0.0f},
};
static const float muv[4][2] = {
{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f},
};
float tx, tz, rot_y_deg;
SDL_Texture *tex;
if (elapsed < phase_dur) {
// Phase 0: approach with X-swing, tex1
float t = (float)elapsed / (float)phase_dur;
tx = sinf(t * (float)M_PI) * (-1.5f);
tz = lerpf(-9.0f, -3.75f, t);
rot_y_deg = lerpf(-90.0f, 0.0f, t);
tex = tex1;
} else if (elapsed < phase_dur * 3) {
// Phase 1: static, tex1
tx = 0.0f; tz = -3.75f; rot_y_deg = 0.0f;
tex = tex1;
} else if (elapsed < phase_dur * 4) {
// Phase 2: recede with X-swing, tex1
float t = (float)(elapsed - phase_dur * 3) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * 1.5f;
tz = lerpf(-3.75f, -9.0f, t);
rot_y_deg = lerpf(0.0f, 90.0f, t);
tex = tex1;
} else if (elapsed < phase_dur * 5) {
// Phase 3: approach with X-swing, tex2
float t = (float)(elapsed - phase_dur * 4) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * (-1.5f);
tz = lerpf(-9.0f, -3.75f, t);
rot_y_deg = lerpf(-90.0f, 0.0f, t);
tex = tex2;
} else if (elapsed < phase_dur * 7) {
// Phase 4: static, tex2
tx = 0.0f; tz = -3.75f; rot_y_deg = 0.0f;
tex = tex2;
} else if (elapsed < phase_dur * 8) {
// Phase 5: recede with X-swing, tex2
float t = (float)(elapsed - phase_dur * 7) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * 1.5f;
tz = lerpf(-3.75f, -9.0f, t);
rot_y_deg = lerpf(0.0f, 90.0f, t);
tex = tex2;
} else {
return;
}
if (!tex) return;
float angle_rad = rot_y_deg * (float)M_PI / 180.0f;
float cos_a = cosf(angle_rad);
float sin_a = sinf(angle_rad);
const float half_w = (float)view_w * 0.5f;
const float half_h = (float)view_h * 0.5f;
SDL_Vertex bg_verts[4], obj_verts[4];
for (int i = 0; i < 4; i++) {
for (int pass = 0; pass < 2; pass++) {
const float (*mv)[3] = (pass == 0) ? bg_mv : obj_mv;
SDL_Vertex *v = (pass == 0) ? &bg_verts[i] : &obj_verts[i];
float rx = cos_a * mv[i][0] + sin_a * mv[i][2];
float ry = mv[i][1];
float rz = -sin_a * mv[i][0] + cos_a * mv[i][2];
float wx = rx + tx;
float wy = ry;
float wz = rz + tz;
float w = -wz * PROJ_W_DENOM;
if (w <= 0.0f) w = 1e-5f;
v->position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
v->position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
v->tex_coord.x = muv[i][0];
v->tex_coord.y = muv[i][1];
}
}
// Object border quad: solid color RGB(244,221,181) = BGR(0xb5,0xdd,0xf4),
// drawn first as a backdrop. The bg quad drawn on top leaves only the thin
// border ring visible.
for (int i = 0; i < 4; i++) obj_verts[i].color = (SDL_Color){0xf4, 0xdd, 0xb5, 0xff};
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
render_quad(NULL, obj_verts);
// Background quad: textured, opaque, drawn on top (covers center of border quad)
for (int i = 0; i < 4; i++) bg_verts[i].color = (SDL_Color){255, 255, 255, 255};
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
render_quad(tex, bg_verts);
}
// Scenes 2, 4, and 6 share the same structure:
// - Fixed background (ALK[9]) full-screen blit
// - 3D quad animation (see render_scene_2_4_6_3d above)
// - Sprite + shadow scroll linearly left-to-right over the full scene duration
//
// sprite_idx: ALK index of the scrolling sprite (shadow is at sprite_idx + 73)
// tex1_idx: ALK index of the 3D quad texture for phases 0-2
// tex2_idx: ALK index of the 3D quad texture for phases 3-5
static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int tex1_idx, int tex2_idx) {
const int duration = scene_duration[scene_idx];
SDL_Texture *bg = dd.images[9];
SDL_Texture *sprite = dd.images[sprite_idx];
SDL_Texture *sprite_shadow = dd.images[sprite_idx + 73];
// 1. Background: full-screen blit, no blending
if (bg) {
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_SetTextureColorMod(bg, 0xff, 0xff, 0xff);
SDL_RenderCopy(gfx_renderer, bg, NULL, NULL);
}
// 2. 3D rotating quad animation (8-phase timing)
{
int phase_dur = (duration - 1000) / 8;
SDL_Texture *tex1 = dd.images[tex1_idx];
SDL_Texture *tex2 = dd.images[tex2_idx];
render_scene_2_4_6_3d(elapsed, phase_dur, tex1, tex2);
}
// 3. Scrolling sprite: x moves from -sprite_w (off-screen left) to view_w (off-screen right)
int sprite_w = 0, sprite_h = 0;
if (sprite)
SDL_QueryTexture(sprite, NULL, NULL, &sprite_w, &sprite_h);
int x = sprite_w ? ((sprite_w + view_w) * elapsed) / duration - sprite_w : 0;
// Shadow drawn first (color-modded to black), sprite on top (full color)
if (sprite_shadow) {
int sw, sh;
SDL_QueryTexture(sprite_shadow, NULL, NULL, &sw, &sh);
SDL_SetTextureBlendMode(sprite_shadow, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite_shadow, 0, 0, 0);
SDL_Rect dst = {x, 0, sw, sh};
SDL_RenderCopy(gfx_renderer, sprite_shadow, NULL, &dst);
}
if (sprite) {
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite, 0xff, 0xff, 0xff);
SDL_Rect dst = {x, 0, sprite_w, sprite_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
}
// Scenes 3, 5, 7, and 9 share the same structure:
// - Main image (image_idx) fades in and out over 4 seconds
// - ALK[9] overlay drawn at 50% alpha with an animated quad-warp throughout
//
// Timing of main image:
// 0250ms: black (no image)
// 2501000ms: fade in (alpha 0→255)
// 10003000ms: full opacity
// 30004000ms: fade out (alpha 255→0)
//
// Overlay quad-warp: source corners rotate clockwise along image edges over the full duration.
// At t=0: source = full image (no warp).
// At t=1: each corner has shifted 1/3 of its edge length, creating a rotation-like distortion.
static void render_scene_3_5_7_9(int elapsed, int image_idx) {
SDL_Texture *main_img = dd.images[image_idx];
SDL_Texture *overlay = dd.images[9];
if (main_img) {
int alpha;
if (elapsed < 250)
alpha = 0;
else if (elapsed < 1000)
alpha = ((elapsed - 250) * 255) / 750;
else if (elapsed < 3000)
alpha = 255;
else
alpha = ((4000 - elapsed) * 255) / 1000;
alpha = SDL_max(0, SDL_min(255, alpha));
if (alpha > 0) {
SDL_SetTextureBlendMode(main_img, alpha < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(main_img, (Uint8)alpha);
SDL_RenderCopy(gfx_renderer, main_img, NULL, NULL);
}
}
// Overlay: 50% alpha blend with animated quad-warp.
// UV corner positions (normalized): corners shift by t/3 along each edge.
if (overlay) {
float t3 = (float)elapsed / (4000.0f * 3.0f); // 0 → 1/3
float w = (float)view_w, h = (float)view_h;
SDL_Vertex verts[4] = {
/* TL */ {{0.f, 0.f}, {255, 255, 255, 128}, {t3, 0.f }},
/* TR */ {{w, 0.f}, {255, 255, 255, 128}, {1.0f, t3 }},
/* BR */ {{w, h }, {255, 255, 255, 128}, {1.0f - t3, 1.0f }},
/* BL */ {{0.f, h }, {255, 255, 255, 128}, {0.f, 1.0f-t3}},
};
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
render_quad(overlay, verts);
}
}
// Scene 8 is a slideshow of 6 images (ALK 8085) with crossfade transitions.
// Total duration (scene_duration[8] = 6402ms) is divided into 7 equal segments:
// Segment 1: fade in image[0] (black → full)
// Segments 26: crossfade image[i-1] → image[i]
// Segment 7: hold image[5] at full opacity
static void render_scene8(int elapsed) {
const int duration = scene_duration[8];
const int seg = duration / 7;
SDL_Texture **imgs = dd.images + 80; // ALK[80..85]
if (elapsed < seg) {
// Segment 1: fade in image[0] from black
int alpha = seg > 0 ? (elapsed * 255) / seg : 255;
if (imgs[0]) {
SDL_SetTextureBlendMode(imgs[0], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[0], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[0], NULL, NULL);
}
} else if (elapsed < 6 * seg) {
// Segments 26: crossfade image[idx] → image[idx+1]
int idx = elapsed / seg - 1; // 0..4
int alpha = ((elapsed - (idx + 1) * seg) * 255) / seg;
if (imgs[idx]) {
SDL_SetTextureBlendMode(imgs[idx], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[idx], 255);
SDL_RenderCopy(gfx_renderer, imgs[idx], NULL, NULL);
}
if (imgs[idx + 1]) {
SDL_SetTextureBlendMode(imgs[idx + 1], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[idx + 1], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[idx + 1], NULL, NULL);
}
} else {
// Segment 7: hold image[5] at full opacity
if (imgs[5]) {
SDL_SetTextureBlendMode(imgs[5], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[5], 255);
SDL_RenderCopy(gfx_renderer, imgs[5], NULL, NULL);
}
}
}
// Scene 10 is a 10.37s animation with 3 phases:
// images[18..31]: surfaces1, 14 animation frames (type A)
// images[32..59]: surfaces2, 28 animation frames (type B/C pairs per slot)
// images[10]: surface3, static full-screen image for zoom/fade
//
// Phase 1 (06453ms): slideshow of 14 frames × 461ms each.
// Within each frame, 3 sub-images are shown in sequence:
// 0152ms: images[18+frame] (surfaces1[frame])
// 153306ms: images[32+frame*2] (surfaces2[frame*2])
// 307460ms: images[32+frame*2+1] (surfaces2[frame*2+1])
//
// Phase 2 (64549609ms): ZoomBlend images[10] zooms in from a tiny
// bottom-centered region (16 × view_h/4) to full screen with pow(4) ease-in.
//
// Phase 3 (961010370ms): images[10] fades out to black (alpha 255→0).
static void render_scene10(int elapsed) {
const int duration = scene_duration[10];
SDL_Texture *surface3 = dd.images[10];
if (elapsed < 6454) {
// Phase 1: frame animation
int frame = elapsed / 461;
int phase = elapsed % 461;
int idx;
if (phase < 153)
idx = 18 + frame;
else if (phase < 307)
idx = 32 + frame * 2;
else
idx = 32 + frame * 2 + 1;
SDL_Texture *tex = dd.images[idx];
if (tex) {
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(tex, 255);
SDL_SetTextureColorMod(tex, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < 9610) {
// Phase 2: zoom in
int h4 = view_h / 4;
float progress = 1.0f - (float)(elapsed - 6454) / (float)(9610 - 6454);
float ease = powf(progress, 4.0f);
float src_w = lerpf((float)view_w, 16.0f, ease);
float src_h = lerpf((float)view_h, (float)h4, ease);
SDL_Rect src_rect = {
(int)((view_w - src_w) * 0.5f),
(int)(view_h - src_h),
(int)src_w,
(int)src_h,
};
if (surface3) {
SDL_SetTextureBlendMode(surface3, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(surface3, 255);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, &src_rect, NULL);
}
} else {
// Phase 3: fade out to black
int blend_alpha = (elapsed - 9610) * 255 / (duration - 9610);
int combined = 255 - SDL_min(blend_alpha, 255);
if (surface3) {
SDL_SetTextureBlendMode(surface3,
combined < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(surface3, (Uint8)combined);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, NULL, NULL);
}
}
}
// Scene 11 is a complex layered animation with 8 segments over 31.6s:
// Background (ALK 60) scrolls upward over the full scene duration.
// The scene duration (31613ms) is divided into 8 equal segments (~3951ms each):
// Segment 0: background only
// Segments 1, 3, 5: one of 3 images (ALK 63, 62, 61) slides top-to-bottom at 50% alpha
// Segments 2, 4, 6: one of 3 overlay images (ALK 69, 70, 71) pingpong-fades in/out
// Segment 7: white fade out
static void render_scene11(int elapsed) {
const int duration = scene_duration[11]; // 31613
const int unit = duration / 8; // ~3951
SDL_Texture *bg = dd.images[60];
// Slide images (alpha maps, drawn at 50% alpha, pass top-to-bottom)
SDL_Texture **slide = dd.images + 61; // ALK[61..63]
// Overlay images (alpha maps, pingpong blend)
SDL_Texture **overlay = dd.images + 69; // ALK[69..71]
// Background: scrolls from bottom to top of source image over full duration.
// srcY = (bg_h - view_h) * (duration - elapsed) / duration
if (bg) {
int bg_w, bg_h;
SDL_QueryTexture(bg, NULL, NULL, &bg_w, &bg_h);
int scroll = (int)((long long)(bg_h - view_h) * elapsed / duration);
int src_y = SDL_max(0, (bg_h - view_h) - scroll);
SDL_Rect src = {0, src_y, SDL_min(view_w, bg_w), view_h};
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg, &src, NULL);
}
if (elapsed < unit) {
// Segment 0: background only, nothing extra
} else if (elapsed < unit * 2) {
// Segment 1: slide[2] (ALK 63) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[2];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 3) {
// Segment 2: overlay[0] (ALK 69) pingpong fade
SDL_Texture *tex = overlay[0];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 4) {
// Segment 3: slide[1] (ALK 62) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[1];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 3) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 5) {
// Segment 4: overlay[1] (ALK 70) pingpong fade
SDL_Texture *tex = overlay[1];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 6) {
// Segment 5: slide[0] (ALK 61) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[0];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 5) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 7) {
// Segment 6: overlay[2] (ALK 71) pingpong fade
SDL_Texture *tex = overlay[2];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else {
// Segment 7: white fade out
int fade_alpha = (duration > unit * 7)
? ((elapsed - unit * 7) * 255) / (duration - unit * 7)
: 255;
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(fade_alpha, 255));
}
}
// Scene 12 is a complex 10.37s animation with multiple layers and a 3D spinning quad:
// ALK[64]: background image A
// ALK[65]: background image B
// ALK[66]: overlay image
// ALK[67]: 3D quad texture
//
// Timeline (relative to scene start):
// 02076ms: fade in bg_a from white (white overlay 255→0)
// 20762226ms: bg_a static
// 22263826ms: crossfade bg_a → bg_b
// 38263976ms: bg_b static
// 39767803ms: bg_b wave distortion (TODO) + bg_b at α=0xB0 + fade to white
// 78039397ms: white bg + 3D rotating quad + overlay fade in (0→255)
// 939710338ms: overlay static
//
// 3D rendering (78039397ms):
// A rhombus-shaped textured quad in the XY plane at z=-2.4 (world space),
// spinning around the Y axis (2 full rotations over 1594ms).
// Projection: vertical FoV=60°, aspect=4:3, near=0.25, far=2000.
// Vertex positions (model space):
// v0=(0, -0.51524, 0) UV:(0,0)
// v1=(0.28806, 0.22720, 0) UV:(1,0)
// v2=(0, 0.51524, 0) UV:(1,1)
// v3=(-0.28806, -0.22720, 0) UV:(0,1)
static void render_scene12_3d(int elapsed) {
SDL_Texture *tex = dd.images[67];
if (!tex) return;
// Model-space vertices (x, y, z) forming a rhombus in the XY plane.
static const float mv[4][3] = {
{ 0.00000f, -0.51524f, 0.0f }, // v0
{ 0.28806f, 0.22720f, 0.0f }, // v1
{ 0.00000f, 0.51524f, 0.0f }, // v2
{ -0.28806f, -0.22720f, 0.0f }, // v3
};
// Normalized texture UV per vertex
static const float muv[4][2] = {
{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f},
};
// Y-rotation: 2 full rotations over the 1594ms 3D phase.
float t = (float)(elapsed - 7803);
float angle = t * (4.0f * (float)M_PI / 1594.0f);
float cos_a = cosf(angle);
float sin_a = sinf(angle);
// Perspective projection parameters: see PROJ_SCALE_X/Y, PROJ_W_DENOM
const float half_w = (float)view_w * 0.5f;
const float half_h = (float)view_h * 0.5f;
SDL_Vertex verts[4];
for (int i = 0; i < 4; i++) {
// World transform: Y-rotate first, then translate(+0.01, 0, -2.4).
float rx = cos_a * mv[i][0] + sin_a * mv[i][2];
float ry = mv[i][1];
float rz = -sin_a * mv[i][0] + cos_a * mv[i][2];
float wx = rx + 0.01f;
float wy = ry;
float wz = rz - 2.4f;
// Perspective divide: w = -wz * PROJ_W_DENOM (wz is negative → w positive)
float w = -wz * PROJ_W_DENOM;
if (w <= 0.0f) w = 1e-5f;
// NDC → screen
verts[i].position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
verts[i].position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].tex_coord.x = muv[i][0];
verts[i].tex_coord.y = muv[i][1];
}
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
render_quad(tex, verts);
}
static void render_scene12(int elapsed) {
SDL_Texture *bg_a = dd.images[64];
SDL_Texture *bg_b = dd.images[65];
SDL_Texture *overlay = dd.images[66];
if (elapsed < 2076) {
// 02076ms: bg_a with white fade-in overlay
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
int white_alpha = 0xff - (elapsed * 0xff) / 2076;
fade_overlay(0xff, 0xff, 0xff, (Uint8)white_alpha);
} else if (elapsed < 2226) {
// 20762226ms: bg_a static
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
} else if (elapsed < 3826) {
// 22263826ms: crossfade bg_a → bg_b
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
if (bg_b) {
int alpha = ((elapsed - 2226) * 0xff) / 1600;
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(bg_b, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 3976) {
// 38263976ms: bg_b static
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 7803) {
// 39767803ms: TODO wave distortion on bg_b; for now just show bg_b,
// then fade to white
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
int white_alpha = ((elapsed - 3976) * 0xff) / 3827;
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(white_alpha, 0xff));
} else if (elapsed < 9397) {
// 78039397ms: white bg + 3D rotating quad + overlay fade-in
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderFillRect(gfx_renderer, NULL);
render_scene12_3d(elapsed);
if (overlay) {
int alpha = ((elapsed - 7803) * 0xff) / 1594;
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(overlay, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
}
} else {
// 939710338ms: overlay static
if (overlay) {
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
}
}
}
static void Run() {
TRACE_UNIMPLEMENTED("dDemo.Run:");
#ifdef DDEMODEV
TRACE("dDemo.Run:");
muscd_start(13, 1);
// ddemo_scene();
while(0 == sys_getInputInfo()) {
usleep(1000 * 100);
uint32_t start = sys_get_ticks();
int current_scene = 0;
while (!nact->is_quit && !(sys_getInputInfo() && dd.key_cancel)) {
uint32_t now = sys_get_ticks();
if (current_scene < NR_SCENES && now - start >= (uint32_t)scene_duration[current_scene]) {
current_scene++;
if (current_scene < NR_SCENES) {
start = now;
} else if (dd.loop) {
muscd_stop();
current_scene = 0;
start = now;
muscd_start(13, 1);
}
}
if (current_scene >= NR_SCENES) {
break;
}
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 255);
SDL_RenderClear(gfx_renderer);
switch (current_scene) {
case 0:
render_scene0(now - start);
break;
case 1:
render_scene1(now - start);
break;
case 2:
render_scene_2_4_6(now - start, 2, 0, 3, 4);
break;
case 3:
render_scene_3_5_7_9(now - start, 11);
break;
case 4:
render_scene_2_4_6(now - start, 4, 1, 5, 6);
break;
case 5:
render_scene_3_5_7_9(now - start, 12);
break;
case 6:
render_scene_2_4_6(now - start, 6, 2, 7, 8);
break;
case 7:
render_scene_3_5_7_9(now - start, 13);
break;
case 8:
render_scene8(now - start);
break;
case 9:
render_scene_3_5_7_9(now - start, 14);
break;
case 10:
render_scene10(now - start);
break;
case 11:
render_scene11(now - start);
break;
case 12:
render_scene12(now - start);
break;
}
SDL_RenderPresent(gfx_renderer);
sys_wait_vsync();
}
muscd_stop();
#endif
for (int i = 0; i < NR_TEXTURES; i++) {
if (dd.images[i])
SDL_DestroyTexture(dd.images[i]);
dd.images[i] = NULL;
}
}
static const ModuleFunc functions[] = {
+1 -1
View File
@@ -13,7 +13,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+1 -1
View File
@@ -13,7 +13,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+1 -2
View File
@@ -66,8 +66,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) {
ags_setFont(ftype, fsize);
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b);
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, (FontSpec){ .type = ftype, .size = fsize });
if (glyph == NULL) return 0;
SDL_Rect rect = {x, y, glyph->w, glyph->h};
+37 -27
View File
@@ -33,26 +33,35 @@
#include "nact.h"
#include "system.h"
#include "ags.h"
#include "sact.h"
#include "variable.h"
#include "sactcg.h"
#include "cg.h"
#include "gfx.h"
#define CGMAX 65536
static cginfo_t **cg_store;
#define SPCG_ASSERT_NO(no) \
if ((no) > (CGMAX -1)) { \
WARNING("no is too large (should be %d < %d)", (no), CGMAX); \
return; \
} \
static cginfo_t *scg_new(enum cgtype type, int no, SDL_Surface *sf) {
static void scg_ensure_store(void) {
if (!cg_store)
cg_store = calloc(CGMAX, sizeof(cginfo_t *));
}
static cginfo_t *scg_new(enum sactcg_type type, int no, SDL_Surface *sf) {
cginfo_t *info = calloc(1, sizeof(cginfo_t));
info->type = type;
info->no = no;
info->sf = sf;
info->refcnt = 1;
scg_ensure_store();
scg_free(no);
sact.cg[no] = info;
cg_store[no] = info;
return info;
}
@@ -71,8 +80,8 @@ static cginfo_t *scg_get(int no) {
return NULL;
}
if (sact.cg[no] != NULL)
return sact.cg[no];
if (cg_store && cg_store[no])
return cg_store[no];
SDL_Surface *sf = cg_load_as_sdlsurface(no - 1);
if (!sf) {
@@ -211,8 +220,7 @@ void scg_create_text(int wNumCG, int wSize, int wR, int wG, int wB, int wText) {
// 勝手に出ていいのかな?
if (svar_length(wText) == 0) return;
ags_setFont(FONT_GOTHIC, wSize);
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB);
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, (FontSpec){ .size = wSize });
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
@@ -236,8 +244,7 @@ void scg_create_textnum(int wNumCG, int wSize, int wR, int wG, int wB, int wFigs
}
sprintf(s, ss, wValue);
ags_setFont(FONT_GOTHIC, wSize);
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB);
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, (FontSpec){ .size = wSize });
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
@@ -292,11 +299,13 @@ void scg_partcopy(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int
// 全てのCGの開放
void scg_freeall(void) {
int i;
for (i = 1; i < CGMAX; i++) {
if (!cg_store)
return;
for (int i = 1; i < CGMAX; i++) {
scg_free(i);
}
free(cg_store);
cg_store = NULL;
}
/**
@@ -306,28 +315,29 @@ void scg_freeall(void) {
void scg_free(int no) {
SPCG_ASSERT_NO(no);
cginfo_t *cg = sact.cg[no];
if (!cg_store) return;
cginfo_t *cg = cg_store[no];
if (!cg) return;
scg_deref(cg);
sact.cg[no] = NULL;
cg_store[no] = NULL;
}
// CGの種類を取得
int scg_querytype(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return CG_NOTUSED;
if (sact.cg[wNumCG] == NULL) return CG_NOTUSED;
return sact.cg[wNumCG]->type;
if (!cg_store || !cg_store[wNumCG]) return CG_NOTUSED;
return cg_store[wNumCG]->type;
}
// CGの大きさを取得
bool scg_querysize(int wNumCG, int *w, int *h) {
bool scg_querysize(int wNumCG, vmvar_t *w, vmvar_t *h) {
if (wNumCG >= (CGMAX -1)) goto errexit;
if (sact.cg[wNumCG] == NULL) goto errexit;
if (sact.cg[wNumCG]->sf == NULL) goto errexit;
if (!cg_store || !cg_store[wNumCG]) goto errexit;
if (cg_store[wNumCG]->sf == NULL) goto errexit;
*w = sact.cg[wNumCG]->sf->w;
*h = sact.cg[wNumCG]->sf->h;
*w = cg_store[wNumCG]->sf->w;
*h = cg_store[wNumCG]->sf->h;
return true;
@@ -339,15 +349,15 @@ bool scg_querysize(int wNumCG, int *w, int *h) {
// CGのBPPを取得
int scg_querybpp(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return 0;
if (sact.cg[wNumCG] == NULL) return 0;
if (sact.cg[wNumCG]->sf == NULL) return 0;
return sact.cg[wNumCG]->sf->format->BitsPerPixel;
if (!cg_store || !cg_store[wNumCG]) return 0;
if (cg_store[wNumCG]->sf == NULL) return 0;
return cg_store[wNumCG]->sf->format->BitsPerPixel;
}
// CGの alphamap が存在するかを取得
bool scg_existalphamap(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return false;
if (sact.cg[wNumCG] == NULL) return false;
if (sact.cg[wNumCG]->sf == NULL) return false;
return SDL_ISPIXELFORMAT_ALPHA(sact.cg[wNumCG]->sf->format->format);
if (!cg_store || !cg_store[wNumCG]) return false;
if (cg_store[wNumCG]->sf == NULL) return false;
return SDL_ISPIXELFORMAT_ALPHA(cg_store[wNumCG]->sf->format->format);
}
+16 -2
View File
@@ -25,7 +25,21 @@
#define __SACTCG_H__
#include "portab.h"
#include "sact.h"
enum sactcg_type {
CG_NOTUSED = 0,
CG_LINKED = 1,
CG_SET = 2,
CG_REVERSE = 3,
CG_STRETCH = 4
};
typedef struct {
enum sactcg_type type;
int no;
struct SDL_Surface *sf;
int refcnt;
} cginfo_t;
cginfo_t *scg_addref(int no);
void scg_deref(cginfo_t *cg);
@@ -41,7 +55,7 @@ void scg_partcopy(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int
void scg_freeall(void);
void scg_free(int cg);
int scg_querytype(int wNumCG);
bool scg_querysize(int wNumCG, int *w, int *h);
bool scg_querysize(int wNumCG, vmvar_t *w, vmvar_t *h);
int scg_querybpp(int wNumCG);
bool scg_existalphamap(int wNumCG);
+1 -1
View File
@@ -12,7 +12,7 @@ static void Init() {
int p1 = getCaliValue(); /* ISys3x */
int p2 = getCaliValue(); /* IWinMsg */
int p3 = getCaliValue(); /* ITimer */
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 0;
+1 -1
View File
@@ -12,7 +12,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
TRACE_UNIMPLEMENTED("nDEMOE.Init %p:", var);
}
+1 -1
View File
@@ -13,7 +13,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
+12 -12
View File
@@ -83,9 +83,9 @@ static void MakeMapDraw() {
int dstY = getCaliValue();
int posX = getCaliValue();
int posY = getCaliValue();
int *a1 = getCaliVariable();
int *a2 = getCaliVariable();
int *a3 = getCaliVariable();
vmvar_t *a1 = getCaliVariable();
vmvar_t *a2 = getCaliVariable();
vmvar_t *a3 = getCaliVariable();
for (int y = 0; y < window_height; y++) {
for (int x = 0; x < window_width; x++) {
@@ -178,9 +178,9 @@ static void TempMapLoadToShadow() {
WARNING("non-array destination variable");
return;
}
int *a1 = v_resolveRef(&r1);
int *a2 = v_resolveRef(&r2);
int *a3 = v_resolveRef(&r3);
vmvar_t *a1 = v_resolveRef(&r1);
vmvar_t *a2 = v_resolveRef(&r2);
vmvar_t *a3 = v_resolveRef(&r3);
uint16_t *p = mapdata[map];
for (int i = 0; i < size; i++)
*a1++ = SDL_SwapLE16(*p++);
@@ -207,9 +207,9 @@ static void TempMapSaveToShadow() {
WARNING("non-array source variable");
return;
}
int *a1 = v_resolveRef(&r1);
int *a2 = v_resolveRef(&r2);
int *a3 = v_resolveRef(&r3);
vmvar_t *a1 = v_resolveRef(&r1);
vmvar_t *a2 = v_resolveRef(&r2);
vmvar_t *a3 = v_resolveRef(&r3);
uint16_t *p = mapdata[map];
for (int i = 0; i < size; i++)
*p++ = SDL_SwapLE16(*a1++);
@@ -296,13 +296,13 @@ static void TempMapFileLoad() {
}
static void CalcMoveDiffer() {
int *dx = getCaliVariable();
int *dy = getCaliVariable();
vmvar_t *dx = getCaliVariable();
vmvar_t *dy = getCaliVariable();
int moveL = getCaliValue();
int moveU = getCaliValue();
int moveR = getCaliValue();
int moveD = getCaliValue();
int *pt = getCaliVariable();
vmvar_t *pt = getCaliVariable();
int duration = getCaliValue();
int t = min(*pt, duration);
+7 -3
View File
@@ -41,7 +41,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
int *var = getCaliVariable();
vmvar_t *var = getCaliVariable();
*var = 1;
@@ -62,9 +62,13 @@ static void SetLoopFlag() {
}
static void Run() {
alk_t *alk = alk_new("tDEMO.alk");
if (!nact->files.alk[0]) {
WARNING("tDEMO.alk not found");
return;
}
alk_t *alk = alk_new(nact->files.alk[0]); // tDEMO.alk
if (!alk) {
WARNING("Cannot open tDEMO.alk");
WARNING("Cannot open %s", nact->files.alk[0]);
return;
}
+41 -10
View File
@@ -1,11 +1,42 @@
# This creates a target "translations"
gettext_create_translations(xsystem35.pot ALL ja.po)
# Translation catalogs, built from the po/<lang>.po files listed in
# NLS_LANGUAGES. On platforms with libintl they are compiled to .mo files and
# loaded at runtime; otherwise they are compiled into the executable as a
# built-in catalog (see src/nls.c).
add_custom_target(pot
xgettext --default-domain=${PACKAGE} --directory=${CMAKE_SOURCE_DIR}
--keyword=_ --keyword=N_
--files-from=${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in
--copyright-holder='Masaki Chikama'
--output=${CMAKE_CURRENT_SOURCE_DIR}/xsystem35.pot
)
add_dependencies(translations pot)
if (HAVE_LIBINTL)
# Compile the .po files into .mo catalogs for libintl.
list(TRANSFORM NLS_LANGUAGES APPEND ".po" OUTPUT_VARIABLE po_files)
gettext_create_translations(xsystem35.pot ALL ${po_files})
# The `pot` target re-extracts strings from POTFILES.in into xsystem35.pot.
# A normal build does not re-extract, so after adding/changing a _()/N_()
# string build this target and translate the new entries. CI fails if
# xsystem35.pot is out of date.
add_custom_target(pot
xgettext --default-domain=${PACKAGE} --directory=${PROJECT_SOURCE_DIR}
--keyword=_ --keyword=N_
--files-from=${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in
--output=${CMAKE_CURRENT_SOURCE_DIR}/xsystem35.pot
)
else()
# Generate a header with the built-in translation tables (nls_catalog.h),
# included by src/nls.c. xsystem35 is made to depend on it and to look in
# this build directory for the header.
set(po_files "")
foreach (lang IN LISTS NLS_LANGUAGES)
list(APPEND po_files ${CMAKE_CURRENT_SOURCE_DIR}/${lang}.po)
endforeach()
set(nls_header ${CMAKE_CURRENT_BINARY_DIR}/nls_catalog.h)
add_custom_command(
OUTPUT ${nls_header}
COMMAND ${CMAKE_COMMAND}
-DPO_DIR=${CMAKE_CURRENT_SOURCE_DIR}
"-DLANGUAGES=${NLS_LANGUAGES}"
-DOUTPUT=${nls_header}
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_nls_catalog.cmake
DEPENDS ${po_files} ${CMAKE_CURRENT_SOURCE_DIR}/generate_nls_catalog.cmake
VERBATIM)
add_custom_target(generate_nls_catalog DEPENDS ${nls_header})
add_dependencies(xsystem35 generate_nls_catalog)
target_include_directories(xsystem35 PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
endif()
-1
View File
@@ -1 +0,0 @@
ja
-317
View File
@@ -1,317 +0,0 @@
# Makefile for PO directory in any package using GNU gettext.
# Copyright (C) 1995-1997, 2000-2002 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
#
# This file can be copied and used freely without restrictions. It can
# be used in projects which are not available under the GNU General Public
# License but which still want to provide support for the GNU gettext
# functionality.
# Please note that the actual code of GNU gettext is covered by the GNU
# General Public License and is *not* in the public domain.
PACKAGE = @PACKAGE@
VERSION = @VERSION@
SHELL = /bin/sh
@SET_MAKE@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
VPATH = @srcdir@
prefix = @prefix@
exec_prefix = @exec_prefix@
datadir = @datadir@
localedir = $(datadir)/locale
gettextsrcdir = $(datadir)/gettext/po
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
MKINSTALLDIRS = @MKINSTALLDIRS@
mkinstalldirs = $(SHELL) `case "$(MKINSTALLDIRS)" in /*) echo "$(MKINSTALLDIRS)" ;; *) echo "$(top_builddir)/$(MKINSTALLDIRS)" ;; esac`
GMSGFMT = @GMSGFMT@
MSGFMT = @MSGFMT@
XGETTEXT = @XGETTEXT@
MSGMERGE = msgmerge
MSGMERGE_UPDATE = @MSGMERGE@ --update
MSGINIT = msginit
MSGCONV = msgconv
MSGFILTER = msgfilter
POFILES = @POFILES@
GMOFILES = @GMOFILES@
UPDATEPOFILES = @UPDATEPOFILES@
DUMMYPOFILES = @DUMMYPOFILES@
DISTFILES.common = Makefile.in.in Makevars remove-potcdate.sin \
$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3)
DISTFILES = $(DISTFILES.common) POTFILES.in $(DOMAIN).pot \
$(POFILES) $(GMOFILES) \
$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)
POTFILES = \
CATALOGS = @CATALOGS@
# Makevars gets inserted here. (Don't remove this line!)
.SUFFIXES:
.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-update
.po.mo:
@echo "$(MSGFMT) -c -o $@ $<"; \
$(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
.po.gmo:
@lang=`echo $* | sed -e 's,.*/,,'`; \
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o $${lang}.gmo $${lang}.po"; \
cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
.sin.sed:
sed -e '/^#/d' $< > t-$@
mv t-$@ $@
all: all-@USE_NLS@
all-yes: $(CATALOGS)
all-no:
# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
# otherwise packages like GCC can not be built if only parts of the source
# have been downloaded.
$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed
$(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
--add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) \
--files-from=$(srcdir)/POTFILES.in \
--copyright-holder='$(COPYRIGHT_HOLDER)'
test ! -f $(DOMAIN).po || { \
if test -f $(srcdir)/$(DOMAIN).pot; then \
sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \
sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \
if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \
rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \
else \
rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \
mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
fi; \
else \
mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
fi; \
}
$(srcdir)/$(DOMAIN).pot:
$(MAKE) $(DOMAIN).pot-update
$(POFILES): $(srcdir)/$(DOMAIN).pot
@lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
echo "$${cdcmd}$(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot"; \
cd $(srcdir) && $(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot
install: install-exec install-data
install-exec:
install-data: install-data-@USE_NLS@
if test "$(PACKAGE)" = "gettext"; then \
$(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \
for file in $(DISTFILES.common); do \
$(INSTALL_DATA) $(srcdir)/$$file \
$(DESTDIR)$(gettextsrcdir)/$$file; \
done; \
else \
: ; \
fi
install-data-no: all
install-data-yes: all
$(mkinstalldirs) $(DESTDIR)$(datadir)
@catalogs='$(CATALOGS)'; \
for cat in $$catalogs; do \
cat=`basename $$cat`; \
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
dir=$(localedir)/$$lang/LC_MESSAGES; \
$(mkinstalldirs) $(DESTDIR)$$dir; \
if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \
$(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \
echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \
for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
if test -n "$$lc"; then \
if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
(cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
for file in *; do \
if test -f $$file; then \
ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
fi; \
done); \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
else \
if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
:; \
else \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
fi; \
fi; \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \
fi; \
done; \
done
install-strip: install
installdirs: installdirs-exec installdirs-data
installdirs-exec:
installdirs-data: installdirs-data-@USE_NLS@
if test "$(PACKAGE)" = "gettext"; then \
$(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \
else \
: ; \
fi
installdirs-data-no:
installdirs-data-yes:
$(mkinstalldirs) $(DESTDIR)$(datadir)
@catalogs='$(CATALOGS)'; \
for cat in $$catalogs; do \
cat=`basename $$cat`; \
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
dir=$(localedir)/$$lang/LC_MESSAGES; \
$(mkinstalldirs) $(DESTDIR)$$dir; \
for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
if test -n "$$lc"; then \
if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
(cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
for file in *; do \
if test -f $$file; then \
ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
fi; \
done); \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
else \
if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
:; \
else \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
fi; \
fi; \
fi; \
done; \
done
# Define this as empty until I found a useful application.
installcheck:
uninstall: uninstall-exec uninstall-data
uninstall-exec:
uninstall-data: uninstall-data-@USE_NLS@
if test "$(PACKAGE)" = "gettext"; then \
for file in $(DISTFILES.common); do \
rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
done; \
else \
: ; \
fi
uninstall-data-no:
uninstall-data-yes:
catalogs='$(CATALOGS)'; \
for cat in $$catalogs; do \
cat=`basename $$cat`; \
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
done; \
done
check: all
dvi info tags TAGS ID:
mostlyclean:
rm -f remove-potcdate.sed
rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po
rm -fr *.o
clean: mostlyclean
distclean: clean
rm -f Makefile Makefile.in POTFILES *.mo
maintainer-clean: distclean
@echo "This command is intended for maintainers to use;"
@echo "it deletes files that may require special tools to rebuild."
rm -f $(GMOFILES)
distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
dist distdir:
$(MAKE) update-po
@$(MAKE) dist2
# This is a separate target because 'update-po' must be executed before.
dist2: $(DISTFILES)
dists="$(DISTFILES)"; \
if test -f $(srcdir)/ChangeLog; then dists="$$dists ChangeLog"; fi; \
if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \
for file in $$dists; do \
if test -f $$file; then \
cp -p $$file $(distdir); \
else \
cp -p $(srcdir)/$$file $(distdir); \
fi; \
done
update-po: Makefile
$(MAKE) $(DOMAIN).pot-update
$(MAKE) $(UPDATEPOFILES)
$(MAKE) update-gmo
# General rule for updating PO files.
.nop.po-update:
@lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; fi; \
tmpdir=`pwd`; \
echo "$$lang:"; \
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
echo "$${cdcmd}$(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \
cd $(srcdir); \
if $(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$tmpdir/$$lang.new.po; then \
if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
rm -f $$tmpdir/$$lang.new.po; \
else \
if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
:; \
else \
echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
exit 1; \
fi; \
fi; \
else \
echo "msgmerge for $$lang.po failed!" 1>&2; \
rm -f $$tmpdir/$$lang.new.po; \
fi
$(DUMMYPOFILES):
update-gmo: Makefile $(GMOFILES)
@:
Makefile: Makefile.in.in $(top_builddir)/config.status POTFILES.in
cd $(top_builddir) \
&& CONFIG_FILES=$(subdir)/$@.in CONFIG_HEADERS= \
$(SHELL) ./config.status
force:
# Tell versions [3.59,3.63) of GNU make not to export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
-41
View File
@@ -1,41 +0,0 @@
# Makefile variables for PO directory in any package using GNU gettext.
# Usually the message domain is the same as the package name.
DOMAIN = $(PACKAGE)
# These two variables depend on the location of this directory.
subdir = po
top_builddir = ..
# These options get passed to xgettext.
XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
# This is the copyright holder that gets inserted into the header of the
# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
# package. (Note that the msgstr strings, extracted from the package's
# sources, belong to the copyright holder of the package.) Translators are
# expected to transfer the copyright for their translations to this person
# or entity, or to disclaim their copyright. The empty string stands for
# the public domain; in this case the translators are expected to disclaim
# their copyright.
COPYRIGHT_HOLDER = Masaki Chikama
# This is the email address or URL to which the translators shall report
# bugs in the untranslated strings:
# - Strings which are not entire sentences, see the maintainer guidelines
# in the GNU gettext documentation, section 'Preparing Strings'.
# - Strings which use unclear terms or require additional context to be
# understood.
# - Strings which make invalid assumptions about notation of date, time or
# money.
# - Pluralisation problems.
# - Incorrect English spelling.
# - Incorrect formatting.
# It can be your email address, or a mailing list address where translators
# can write to without being subscribed, or the URL of a web page through
# which the translators can contact you.
MSGID_BUGS_ADDRESS = chikama@nabal.aist-nara.ac.jp
# This is the list of locale categories, beyond LC_MESSAGES, for which the
# message catalogs shall be used. It is usually empty.
EXTRA_LOCALE_CATEGORIES =
+2 -4
View File
@@ -1,5 +1,3 @@
./src/menu.c
./src/menu_gui.c
./src/menu_gui_volval.c
./src/s39init.c
./src/input_modal.c
./src/volume.c
+68
View File
@@ -0,0 +1,68 @@
# Generate a C header with the built-in translation catalogs, for the NLS used
# on platforms without libintl. It defines one table per language plus a
# registry (nls_catalogs) mapping language codes to tables, all as file-local
# statics, and is included by src/nls.c.
#
# .po and C share the same string-escape conventions (\n, \", \\, ...), so the
# quoted bodies are copied through verbatim and multi-line entries become
# adjacent C string literals, which the compiler concatenates.
#
# Expected variables: PO_DIR, LANGUAGES (list), OUTPUT
set(tables "")
set(registry "")
foreach(lang IN LISTS LANGUAGES)
file(STRINGS "${PO_DIR}/${lang}.po" lines ENCODING UTF-8)
set(entries "")
set(cur_id "")
set(cur_str "")
set(state none)
# Appends the current msgid/msgstr pair to `entries`, skipping the header
# entry (empty msgid).
macro(flush_entry)
if(NOT cur_id STREQUAL "" AND NOT cur_id STREQUAL "\"\"")
string(APPEND entries "\t{ ${cur_id}, ${cur_str} },\n")
endif()
endmacro()
foreach(line IN LISTS lines)
if(line MATCHES "^msgid \"(.*)\"$")
flush_entry()
set(cur_id "\"${CMAKE_MATCH_1}\"")
set(cur_str "")
set(state id)
elseif(line MATCHES "^msgstr \"(.*)\"$")
set(cur_str "\"${CMAKE_MATCH_1}\"")
set(state str)
elseif(line MATCHES "^\"(.*)\"$")
if(state STREQUAL id)
set(cur_id "${cur_id} \"${CMAKE_MATCH_1}\"")
elseif(state STREQUAL str)
set(cur_str "${cur_str} \"${CMAKE_MATCH_1}\"")
endif()
else()
set(state none)
endif()
endforeach()
flush_entry()
string(APPEND tables
"static const struct nls_entry nls_table_${lang}[] = {\n"
"${entries}"
"\t{ 0, 0 }, // terminator\n"
"};\n\n")
string(APPEND registry "\t{ \"${lang}\", nls_table_${lang} },\n")
endforeach()
file(WRITE "${OUTPUT}"
"// Generated from the .po files. Do not edit.\n"
"#include \"nls.h\"\n"
"\n"
"${tables}"
"static const struct nls_catalog nls_catalogs[] = {\n"
"${registry}"
"\t{ 0, 0 }, // terminator\n"
"};\n")

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