Compare commits

..
Author SHA1 Message Date
kichikuouandClaude Opus 4.8 dac0345e02 [NOT FOR MERGE] Add opt-in virtual mouse pointer for touch devices
Provide a trackpad-style on-screen cursor for touch environments (mainly
Android), where touching the screen previously jumped the pointer to the
touched location (absolute) and SDL hardware cursors are invisible.

When enabled, a finger drag moves an arrow cursor relatively, a tap
left-clicks (held briefly so polling games detect it), two fingers
right-click, and a stationary long press starts a left-button drag. Real
mouse motion is ignored (including the spurious startup (0,0) event), and
program-driven cursor moves are followed via the internal pointer location
instead of warping the OS cursor.

The feature is disabled by default and enabled via the -virtualpointer
command-line option, the virtualpointer profile setting, or the "Virtual
mouse pointer" toggle in the Android launcher (persisted in
SharedPreferences and passed to the engine as -virtualpointer).

The gesture handling lives in event.c and the cursor rendering in the new
platform-independent virtual_pointer module, so it works on any touch
target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:29:45 +09:00
211 changed files with 4656 additions and 8743 deletions
+1 -14
View File
@@ -15,7 +15,7 @@ jobs:
- name: Install Deps
run: |
sudo apt update
sudo apt install libsdl2-dev libfreetype-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
sudo apt install libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
- name: Build
run: |
@@ -27,16 +27,3 @@ 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
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
msystem: ${{ matrix.sys }}
pacboy: >-
SDL2:p
freetype:p
SDL2_ttf:p
SDL2_mixer:p
libwebp:p
${{ matrix.deps }}
-24
View File
@@ -1,29 +1,5 @@
# Changelog
## 2.20.0 - 2026-09-11
- Improved font rendering to more closely match the original engine. On
Windows, MS Gothic and MS Mincho are now used by default when available.
- The experimental `enable_zb` option has been removed. (#44)
- SACT: Message skipping now also skips text animation, effects, waits, and
related input waits.
- SACT: BGM now honors the loop count specified by the game.
- SACT: Fixed timer handling in 楽園行.
- The popup menu and dialogs can now be operated with the keyboard.
- Fixed mouse coordinates changing unexpectedly when automatic mouse movement
is disabled in System 3.8/3.9 games.
- Android: The status bar now stays hidden after returning to the game. (#81)
- Debugger: Added an `info cache` command for viewing cache usage and hit rates.
## 2.19.1 - 2026-08-02
- Fixed an issue that prevented moving upward during battles in Rance 4.
## 2.19.0 - 2026-07-19
- Replaced the GTK-based popup menu and dialogs with a pure-SDL implementation.
- On Android, the popup menu opens with a three-finger tap.
- 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.
+22 -25
View File
@@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
enable_testing()
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
set(XSYSTEM35_VERSION "2.20.0")
set(XSYSTEM35_VERSION "2.18.0")
include(CheckSymbolExists)
include(FetchContent)
@@ -55,8 +55,6 @@ check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
if (EMSCRIPTEN)
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
function(add_emscripten_library name option)
add_library(${name} INTERFACE)
set_target_properties(${name} PROPERTIES
@@ -65,14 +63,14 @@ if (EMSCRIPTEN)
endfunction()
add_emscripten_library(zlib -sUSE_ZLIB=1)
add_emscripten_library(sdl2 -sUSE_SDL=2)
add_emscripten_library(freetype2 -sUSE_FREETYPE=1)
add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2)
set(DEFAULT_FONT_PATH /fonts/)
fetch_webp()
set(HAVE_WEBP 1)
elseif (ANDROID)
add_library(sdl2 ALIAS SDL2)
add_library(freetype2 ALIAS freetype)
add_library(sdl2_ttf ALIAS SDL2_ttf)
add_library(sdl2_mixer ALIAS SDL2_mixer)
find_library(ndk_log log)
find_library(ndk_zlib z)
@@ -89,18 +87,24 @@ else() # non-emscripten, non-android
add_library(zlib ALIAS ZLIB::ZLIB)
include(FindPkgConfig)
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
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(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
if (WIN32)
add_static_library(sdl2 SDL2)
add_static_library(freetype2 FREETYPE)
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
target_link_libraries(freetype2 INTERFACE -lstdc++)
add_static_library(sdl2_ttf SDL2TTF)
# Workaround for linking error
set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES
$<LINK_GROUP:RESCAN,${SDL2TTF_STATIC_LIBRARIES} -lstdc++>)
add_static_library(sdl2_mixer SDL2MIXER)
else()
add_library(sdl2 ALIAS PkgConfig::SDL2)
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
endif()
@@ -149,19 +153,14 @@ else() # non-emscripten, non-android
endif()
endif()
# 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)
# Menu
if (NOT EMSCRIPTEN AND NOT ANDROID AND NOT WIN32)
if (ENABLE_GTK)
# i18n support (currently only menus are translated)
include(FindIntl)
include(FindGettext)
if (Intl_FOUND AND GETTEXT_FOUND)
set(HAVE_LIBINTL 1)
set(ENABLE_NLS 1)
add_compile_definitions(LOCALEDIR="${CMAKE_INSTALL_PREFIX}/share/locale")
include_directories(${Intl_INCLUDE_DIRS})
link_libraries(${Intl_LIBRARIES})
@@ -228,11 +227,9 @@ add_subdirectory(fonts)
if (NOT ANDROID AND NOT EMSCRIPTEN)
add_subdirectory(doc)
endif()
add_subdirectory(po)
if (ENABLE_NLS)
add_subdirectory(po)
endif()
add_subdirectory(modules)
target_link_libraries(xsystem35 PRIVATE modules)
if (NOT ANDROID AND NOT EMSCRIPTEN)
add_subdirectory(test)
endif()
+3 -3
View File
@@ -105,7 +105,7 @@ See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
### Linux (Debian / Ubuntu)
```bash
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
$ sudo apt install build-essential cmake libgtk-3-dev 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 ../../
@@ -117,7 +117,7 @@ $ make && make install
[Homebrew](https://brew.sh/) is required.
```bash
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi cjson asciidoctor
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor
$ mkdir -p out/debug
$ cd out/debug
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
@@ -129,7 +129,7 @@ $ make && make install
[MSYS2](https://www.msys2.org) is required.
```bash
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
$ mkdir -p out/debug
$ cd out/debug
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
+2 -22
View File
@@ -16,31 +16,13 @@ Alternatively, you can install `xsystem35-sdl2` using [Obtainium](https://github
## 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.
#### 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.
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.
### Preparing a ZIP
- Include all files from the `GAMEDATA` folder (such as `.ALD` files and
@@ -59,8 +41,6 @@ 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.
+1 -2
View File
@@ -17,7 +17,7 @@ android {
minSdkVersion 21
targetSdkVersion 34
versionCode 36
versionName "2.20.0" + gitRevision()
versionName "2.18.0" + gitRevision()
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
@@ -85,7 +85,6 @@ 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() {
+10 -13
View File
@@ -11,10 +11,9 @@ FetchContent_Declare(
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
)
FetchContent_Declare(
freetype
URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz
https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz
URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678
SDL_ttf
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz
URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d
)
FetchContent_Declare(
SDL_mixer
@@ -22,12 +21,9 @@ FetchContent_Declare(
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
)
# Only FreeType's own rasterizer is needed, none of its optional dependencies.
set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE)
set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE)
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE)
set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE)
set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE)
set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE)
set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
@@ -37,7 +33,7 @@ set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE)
set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE)
set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE)
FetchContent_MakeAvailable(SDL freetype SDL_mixer)
FetchContent_MakeAvailable(SDL SDL_ttf SDL_mixer)
# The main CMakeLists.txt of xsystem35
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
@@ -51,7 +47,8 @@ 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 ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz)
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
-2
View File
@@ -66,7 +66,6 @@
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
-->
<meta-data android:name="SDL_ENV.SDL_IOS_ORIENTATIONS" android:value="LandscapeLeft LandscapeRight"/>
<activity android:name=".LauncherActivity"
android:exported="true">
@@ -79,7 +78,6 @@
<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"
@@ -1,566 +0,0 @@
/* 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,7 +20,6 @@ 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
@@ -45,8 +44,6 @@ class GameActivity : SDLActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT)!!)
// Workaround for https://github.com/libsdl-org/SDL/issues/8995
SDLActivity.setWindowStyle(true)
}
override fun onStop() {
@@ -59,26 +56,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")
}
override fun getArguments(): Array<String> {
return arrayOf(
val args = mutableListOf(
"-gamedir", intent.getStringExtra(EXTRA_GAME_ROOT)!!,
"-savedir", intent.getStringExtra(EXTRA_SAVE_DIRECTORY)!!)
val prefs = getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE)
if (prefs.getBoolean(Launcher.PREF_VIRTUAL_POINTER, false))
args.add("-virtualpointer")
return args.toTypedArray()
}
override fun setTitle(title: CharSequence?) {
@@ -90,6 +79,7 @@ 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?>) {
@@ -168,21 +158,11 @@ class GameActivity : SDLActivity() {
}
private class MidiPlayer {
private enum class State {
STOPPED,
PLAYING,
PAUSED,
RELEASED,
}
private val player = MediaPlayer()
private var state = State.STOPPED
private var playing = false
private var playerPaused = false
fun start(path: String, loop: Boolean) {
if (state == State.RELEASED) {
return
}
state = State.STOPPED
try {
player.apply {
reset()
@@ -191,7 +171,7 @@ private class MidiPlayer {
prepare()
start()
}
state = State.PLAYING
playing = true
} catch (e: IOException) {
Log.e("midiStart", "Cannot play midi", e)
player.reset()
@@ -199,41 +179,27 @@ private class MidiPlayer {
}
fun stop() {
when (state) {
State.PLAYING, State.PAUSED -> {
player.stop()
state = State.STOPPED
}
State.STOPPED, State.RELEASED -> Unit
if (playing && player.isPlaying) {
player.stop()
playing = false
}
}
fun currentPosition(): Int {
return when (state) {
State.PLAYING, State.PAUSED -> player.currentPosition
State.STOPPED, State.RELEASED -> 0
}
return if (playing) player.currentPosition else 0
}
fun onActivityStop() {
if (state == State.PLAYING && player.isPlaying) {
if (playing && player.isPlaying) {
player.pause()
state = State.PAUSED
playerPaused = true
}
}
fun onActivityResume() {
if (state == State.PAUSED) {
if (playerPaused) {
player.start()
state = State.PLAYING
playerPaused = false
}
}
fun release() {
if (state == State.RELEASED) {
return
}
state = State.RELEASED
player.release()
}
}
@@ -1,282 +0,0 @@
/* 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
}
@@ -1,142 +0,0 @@
/* 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))
}
}
}
@@ -1,175 +0,0 @@
/* 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,15 +17,18 @@
*/
package io.github.kichikuou.xsystem35
import android.content.ContentResolver
import android.os.Build
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.GlobalScope
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
@@ -36,18 +39,11 @@ interface LauncherObserver {
fun onInstallFailure(msgId: Int)
}
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) {
class Launcher private constructor(private val rootDir: File) {
companion object {
const val SAVE_DIR = "save"
const val PREFS_NAME = "settings"
const val PREF_VIRTUAL_POINTER = "virtual_pointer"
const val TITLE_FILE = "title.txt"
const val GAMEDIR_FILE = "game_directory.txt"
const val PLAYLIST_FILE = "playlist.txt"
@@ -60,118 +56,234 @@ class Launcher private constructor(rootDir: File) {
return gLauncher!!
}
fun updateGameList() {
gLauncher?.updateGameList()
}
}
private val store = GameStore(rootDir)
private val installer = GameInstaller(store)
val games: List<GameStore.Entry>
get() = store.games
data class Entry(val path: File, val title: String, val timestamp: Long)
val games = arrayListOf<Entry>()
val titles: List<String>
get() = store.titles
get() = games.map(Entry::title)
var observer: LauncherObserver? = null
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var installJob: Job? = null
var installState: InstallState = InstallState.Idle
var isInstalling = false
private set
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)
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)
}
}
}
}
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)
observer?.onInstallSuccess(gameDir, archiveName)
} catch (e: InstallFailureException) {
setInstallFailed(e.msgId)
observer?.onInstallFailure(e.msgId)
} catch (e: Exception) {
Log.e("launcher", "Failed to install game", e)
setInstallFailed(R.string.install_error)
Log.e("launcher", "Failed to extract ZIP", e)
observer?.onInstallFailure(R.string.zip_extraction_error)
}
isInstalling = false
}
}
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) {
store.uninstall(id)
games[id].path.deleteRecursively()
games.removeAt(id)
observer?.onGameListChange()
}
fun refreshGameList() {
store.updateGameList()
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()
}
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) {
store.exportSaveData(output)
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)
}
}
}
}
fun importSaveData(input: InputStream): Int? {
return store.importSaveData(input)
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)
}
}
}
fun clearSaveData(): Boolean {
return store.clearSaveData()
var success = true
File(rootDir, SAVE_DIR).listFiles()?.forEach { file ->
if (!file.deleteRecursively()) success = false
}
return success
}
}
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 ZIP_INSTALL_REQUEST = 1
private const val INSTALL_REQUEST = 1
private const val SAVEDATA_EXPORT_REQUEST = 2
private const val SAVEDATA_IMPORT_REQUEST = 3
private const val CD_IMAGE_INSTALL_REQUEST = 4
private const val STATE_PROGRESS_TEXT = "progressText"
class LauncherActivity : Activity(), LauncherObserver {
private lateinit var launcher: Launcher
@@ -48,7 +48,9 @@ class LauncherActivity : Activity(), LauncherObserver {
launcher = Launcher.getInstance(filesDir)
launcher.observer = this
renderInstallState(launcher.installState)
if (launcher.isInstalling) {
showProgressDialog(savedInstanceState)
}
onGameListChange()
val listView = findViewById<ListView>(R.id.list)
@@ -68,9 +70,11 @@ class LauncherActivity : Activity(), LauncherObserver {
super.onDestroy()
}
override fun onResume() {
super.onResume()
launcher.refreshGameList()
override fun onSaveInstanceState(outState: Bundle) {
progressDialog?.let {
outState.putCharSequence(STATE_PROGRESS_TEXT, it.findViewById<TextView>(R.id.text).text)
}
super.onSaveInstanceState(outState)
}
private fun onListItemClick(position: Int) {
@@ -88,6 +92,9 @@ class LauncherActivity : Activity(), LauncherObserver {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.launcher_menu, menu)
val prefs = getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE)
menu.findItem(R.id.virtual_pointer).isChecked =
prefs.getBoolean(Launcher.PREF_VIRTUAL_POINTER, false)
return true
}
@@ -96,15 +103,7 @@ 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)), 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)
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_a_file)), INSTALL_REQUEST)
true
}
R.id.export_savedata -> {
@@ -147,6 +146,13 @@ class LauncherActivity : Activity(), LauncherObserver {
startActivity(intent)
true
}
R.id.virtual_pointer -> {
val enabled = !item.isChecked
item.isChecked = enabled
getSharedPreferences(Launcher.PREFS_NAME, MODE_PRIVATE).edit()
.putBoolean(Launcher.PREF_VIRTUAL_POINTER, enabled).apply()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -154,20 +160,14 @@ 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) {
ZIP_INSTALL_REQUEST -> {
val uri = data?.data ?: return
INSTALL_REQUEST -> {
val input = contentResolver.openInputStream(uri) ?: return
launcher.installZip(input, getArchiveName(uri))
renderInstallState(launcher.installState)
}
CD_IMAGE_INSTALL_REQUEST -> {
val files = getSelectedInstallFiles(data)
launcher.installCdImage(files, contentResolver, cacheDir)
renderInstallState(launcher.installState)
showProgressDialog()
launcher.install(input, getArchiveName(uri))
}
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) {
@@ -175,7 +175,6 @@ 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) {
@@ -195,15 +194,17 @@ class LauncherActivity : Activity(), LauncherObserver {
}
override fun onInstallProgress(path: String) {
renderInstallState(launcher.installState)
progressDialog?.findViewById<TextView>(R.id.text)?.text = getString(R.string.install_progress, path)
}
override fun onInstallSuccess(path: File, archiveName: String?) {
renderInstallState(launcher.installState)
dismissProgressDialog()
startGame(path, archiveName)
}
override fun onInstallFailure(msgId: Int) {
renderInstallState(launcher.installState)
dismissProgressDialog()
errorDialog(msgId)
}
private fun startGame(path: File, archiveName: String?) {
@@ -219,35 +220,16 @@ class LauncherActivity : Activity(), LauncherObserver {
launcher.uninstall(id)
}
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()
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)
}
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)
show()
}
}
@@ -264,33 +246,13 @@ 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) {
return it.getString(column)
val fname = it.getString(column)
return if (fname.endsWith(".zip", true)) fname.dropLast(4) else fname
}
}
}
@@ -12,9 +12,10 @@ class LicensesMenuActivity : Activity() {
Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"),
Entry("SDL", "SDL", "https://www.libsdl.org/"),
Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"),
Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"),
Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"),
Entry("microui", "microui", "https://github.com/rxi/microui"),
Entry("FreeType", "freetype", "https://freetype.org/"),
Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"),
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
)
@@ -3,9 +3,6 @@
<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" />
@@ -15,10 +12,14 @@
<item
android:id="@+id/clear_savedata"
android:title="@string/action_clear_save_data" />
<item
android:id="@+id/virtual_pointer"
android:checkable="true"
android:title="@string/action_virtual_pointer" />
<item
android:id="@+id/help"
android:title="@string/action_help" />
<item
android:id="@+id/licenses"
android:title="@string/action_licenses" />
</menu>
</menu>
@@ -4,24 +4,18 @@
<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="action_virtual_pointer">仮想マウスポインタ</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>
@@ -29,7 +23,6 @@
<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">
@@ -49,4 +42,4 @@
<p>3. この画面の右上のメニューボタンをタップして「ZIPからインストール」を選択し、転送したZIPを選びます。</p>
]]>
</string>
</resources>
</resources>
+1 -8
View File
@@ -4,23 +4,17 @@
<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="action_virtual_pointer">Virtual mouse pointer</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>
@@ -29,7 +23,6 @@
<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">
@@ -1,714 +0,0 @@
/* 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")
}
+4 -1
View File
@@ -1,14 +1,17 @@
#cmakedefine PACKAGE "@PACKAGE@"
#define VERSION "@XSYSTEM35_VERSION@"
#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@
-3
View File
@@ -98,9 +98,6 @@ 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.
-19
View File
@@ -1,19 +0,0 @@
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.
+2 -2
View File
@@ -12,8 +12,8 @@ or in your `.xsys35rc` file. For example:
xsystem35 -censor misc/censor/kichikuou.txt
```
The list files contain image numbers (integers), one per line. A `#` starts a
comment that continues to the end of the line.
The list files contain image numbers (integers), one per line. Lines starting
with `#` are treated as comments.
## Contributing
-30
View File
@@ -1,30 +0,0 @@
# Censor list for ランス -光をもとめて- (Rance: The Quest for Hikari)
# Images marked with a * might be allowed in an 18+ stream.
5
6
8
22
26
27
29
32
39
40
41
51
56
57
75
76
77
78 # *
80
82
89
91
96 # *
97 # *
98
99
101
208
-59
View File
@@ -1,59 +0,0 @@
# Censor list for ランスII (Rance II)
# Images marked with a * might be allowed in an 18+ stream.
26
39 # *
40
41
77
78
86
98
103
104
121
122
123
124
130
131
132
133
134
140
141
142
148
156
157
158
159
161
166
171
172
185
187
191
195
197
200
201
234
236
245
246
247
272
274
275
276
278
280
281
282
284
289
292
299
300
302
-121
View File
@@ -1,121 +0,0 @@
# Censor list for Rance 3
# Images marked with a * might be allowed in an 18+ stream.
1 # *
2 # *
3
6
8
9
32 # *
53 # *
54
70
71
72
73
75
77 # *
78
79
112 # *
113
114
115
116
117
118 # *
150
151
152
153
154
155
166
167
180
181
183
184
185
186
187
188
189
190
195
196
208
209
210
211
212
213
215
216
217
218
219
231
232
233
234
235
236
238
239
260
261
263
264
265
266
268
269
330
331
332
334
335
336
337
338
339
420
421
422
423
424
425
426
427
428
429
440
441
442
443
444
445
447
473
474
475
476
477
479
519 # *
521 # *
523
524
525
526
530
533 # *
534
535
536
537
570
571
572
581
-168
View File
@@ -1,168 +0,0 @@
# Censor list for Rance IV
# Images marked with a * might be allowed in an 18+ stream.
1
2
10
20
26
28
29
30 # *
31
32
33
34
35
36
37
38
39 # *
40
41
42
43
44
45
46
48
49
75
76
77
78
79
122
126
140
142
148
149
162 # *
163
164
165 # *
166
167
168
169 # *
170
171
172
176
177
178
185
186
188
189
205
207
217 # *
220
221
222
223
228
241
242
251
252
253
254
260
261
262 # *
264 # *
265
266
267
268
269
280
281
283
284
285
286 # *
287
288
292
294
299
370
371
372
373
376
378
382
384
385 # *
386
387
388
389
414 # *
416
428 # *
531 # *
532
533
534 # *
535
550
551
552
553
554
555
562 # *
565
615
625
626
711
723
1055
1056
1057
1058
1059
1060
1061
1062
1545 # *
1546 # *
1547 # *
1548 # *
1549 # *
1653 # *
1654 # *
1655 # *
1656 # *
1657 # *
1658 # *
1659 # *
1660 # *
1661 # *
1662 # *
1663 # *
1664 # *
1665 # *
1728
1729
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
+1 -1
View File
@@ -12,7 +12,7 @@ static void Init() {
int p1 = getCaliValue(); /* ISys3x */
int p2 = getCaliValue(); /* IWinMsg */
int p3 = getCaliValue(); /* ITimer */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+6 -6
View File
@@ -15,8 +15,8 @@ static void Init() {
static void ExistKeyFile() {
const char *p1 = sl_getString(0);
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
int *p2 = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
@@ -25,8 +25,8 @@ static void ExistKeyFile() {
static void CheckProtectFile() {
const char *p1 = sl_getString(0);
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
int *p2 = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
@@ -35,8 +35,8 @@ static void CheckProtectFile() {
static void CreateKeyFile() {
const char *p1 = sl_getString(0);
vmvar_t *p2 = getCaliVariable();
vmvar_t *var = getCaliVariable();
int *p2 = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+13 -13
View File
@@ -135,7 +135,7 @@ static void Create() {
height: surface の高さ
bpp : surface の深さ(24bppのみサポート)
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int width = getCaliValue();
int height = getCaliValue();
int bpp = getCaliValue();
@@ -162,7 +162,7 @@ static void CreatePixelOnly() {
height: surface の高さ
bpp : surface の深さ(24bpp only)
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int width = getCaliValue();
int height = getCaliValue();
int bpp = getCaliValue();
@@ -188,7 +188,7 @@ static void CreateAMapOnly() {
width : surface の幅
height: surface の高さ
*/
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = sf_get(p1) ? 1 : 0;
@@ -229,7 +229,7 @@ static void IsPixel() {
var : 結果を返す変数。pixel ならば 1, !pixel ならば 0
*/
int p1 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -251,7 +251,7 @@ static void IsAlpha() {
var : 結果を返す変数。alpha ならば 1, !alpha ならば 0
*/
int p1 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -273,7 +273,7 @@ static void GetWidth() {
var : 結果を返す変数。
*/
int p1 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -295,7 +295,7 @@ static void GetHeight() {
var : 結果を返す変数。
*/
int p1 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
surface_t *s;
s = sf_get(p1);
@@ -310,7 +310,7 @@ static void GetHeight() {
}
static void GetCreatedSurface() { /* not used ? */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
TRACE_UNIMPLEMENTED("Gpx.GetCreatedSurface %p:", var);
}
@@ -322,7 +322,7 @@ static void LoadCG() {
var : 作成した surface の番号を返す変数
p1 : 読み込む CG の番号
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int p1 = getCaliValue();
*var = load_cg_main(p1 -1);
@@ -331,14 +331,14 @@ static void LoadCG() {
}
static void GetCGPosX() { /* not useed ? */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int p1 = getCaliValue();
TRACE_UNIMPLEMENTED("Gpx.GetCgPosX %p,%d:", var, p1);
}
static void GetCGPosY() { /* not useed ? */
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *var = getCaliVariable();
int *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,
vmvar_t *endtype) {
int *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;
+1 -2
View File
@@ -1,7 +1,6 @@
#ifndef __GLEFFECTCOPY_H__
#define __GLEFFECTCOPY_H__
#include "portab.h"
#include "surface.h"
extern void gpx_effect(int no,
@@ -10,6 +9,6 @@ extern void gpx_effect(int no,
surface_t *src, int sx, int sy,
int width, int height,
int time,
vmvar_t *endtype);
int *endtype);
#endif /* __GLEFFECTCOPY_H__ */
+2 -2
View File
@@ -53,7 +53,7 @@ static void RandMTGet() {
var: 結果を返す変数
*/
int num = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
if (num == 0 || num == 1) {
*var = num;
@@ -83,7 +83,7 @@ static void RandMTGetNumTable() {
var: 乱数を格納する変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = (int)(genrand() * numtblmax) + 1;
+4 -4
View File
@@ -17,7 +17,7 @@ static bool valid;
static int action;
static void Init() {
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue(); /* ISys3x */
int p3 = getCaliValue();
int p4 = getCaliValue();
@@ -30,7 +30,7 @@ static void Init() {
}
static void Start() {
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue();
char *fname_utf8 = sjis2utf(svar_get(p2));
@@ -52,7 +52,7 @@ static void SetValid() {
}
static void GetValid() {
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
*p1 = valid;
@@ -68,7 +68,7 @@ static void SetAction() {
}
static void GetAction() {
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
*p1 = action;
+26 -25
View File
@@ -22,7 +22,7 @@ night_t nightprv;
static void Init(void) { /* 0 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int p1 = getCaliValue(); /* ISys3xCG */
int p2 = getCaliValue(); /* ISys3xDIB */
int p3 = getCaliValue(); /* ISys3xMsgString */
@@ -56,7 +56,8 @@ 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();
@@ -213,7 +214,7 @@ static void SetSelMode(void) { /* 18 */
// キー入力待ち後、改ページ
static void AnalyzeMessage(void) { /* 19 */
vmvar_t *var = getCaliVariable(); // 入力されたキー
int *var = getCaliVariable(); // 入力されたキー
*var = ntmsg_ana();
@@ -297,7 +298,7 @@ static void ScreenCG(void) { /* 27 */
}
static void RunGameMain(void) { /* 28 */
vmvar_t *p1 = getCaliVariable(); // result
int *p1 = getCaliVariable(); // result
int p2 = getCaliValue(); // month
int p3 = getCaliValue(); // day
int p4 = getCaliValue(); // day of week
@@ -313,7 +314,7 @@ static void RunGameMain(void) { /* 28 */
}
static void CheckNewGame(void) { /* 29 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
*p1 = 0;
@@ -347,9 +348,9 @@ static void SetDate(void) { /* 33 */
}
static void GetDate(void) { /* 34 */
vmvar_t *p1 = getCaliVariable(); // month
vmvar_t *p2 = getCaliVariable(); // day
vmvar_t *p3 = getCaliVariable(); // day of weeek
int *p1 = getCaliVariable(); // month
int *p2 = getCaliVariable(); // day
int *p3 = getCaliVariable(); // day of weeek
*p1 = night.Month;
*p2 = night.Day;
@@ -363,7 +364,7 @@ static void SelectGameLevel(void) { /* 35 */
}
static void RunEventDungeon(void) { /* 36 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue();
*p1 = 1;
@@ -478,7 +479,7 @@ static void RunSoundMode(void) { /* 48 */
}
static void RunMapEditor(void) { /* 49 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.RunMapEditor %p:", p1);
}
@@ -495,21 +496,21 @@ static void VisualListAdd(void) { /* 51 */
}
static void GetLocalCountCG(void) { /* 52 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetLocalCountCG %p,%d:", p1, p2);
}
static void PlayMemory(void) { /* 53 */
vmvar_t *p1 = getCaliVariable(); // 回想ページ
vmvar_t *p2 = getCaliVariable(); // 回想RESULT
int *p1 = getCaliVariable(); // 回想ページ
int *p2 = getCaliVariable(); // 回想RESULT
TRACE_UNIMPLEMENTED("NIGHTDLL.PlayMemory %p,%p:", p1, p2);
}
static void GetEventFlagTotal(void) { /* 54 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetEventFlagTotal %p,%d:", p1, p2);
@@ -528,25 +529,25 @@ static void GetPlayerName(void) { /* 56 */
}
static void SaveGame(void) { /* 57 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.SaveGame %p:", p1);
}
static void LoadGame(void) { /* 58 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.LoadGame %p:", p1);
}
static void ExistSaveData(void) { /* 59 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.ExistSaveData %p:", p1);
}
static void ExistStartData(void) { /* 60 */
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.ExistStartData %p:", p1);
}
@@ -580,13 +581,13 @@ static void DebugScenario(void) { /* 66 */
}
static void GetDLLTime(void) { /* 67 */
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();
int *p1 = getCaliVariable();
int *p2 = getCaliVariable();
int *p3 = getCaliVariable();
int *p4 = getCaliVariable();
int *p5 = getCaliVariable();
int *p6 = getCaliVariable();
int *p7 = getCaliVariable();
TRACE_UNIMPLEMENTED("NIGHTDLL.GetDLLTime %p,%p,%p,%p,%p,%p,%p:", p1, p2, p3, p4, p5, p6, p7);
}
+16
View File
@@ -53,6 +53,11 @@ 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;
@@ -85,5 +90,16 @@ 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,6 +2,7 @@
#define __NT_EVENT_H__
extern void ntev_callback(agsevent_t *e);
extern void ntev_main();
#endif /* __NT_EVENT_H__ */
+12 -10
View File
@@ -55,17 +55,19 @@ static void ntmain(struct _scoadr inadr) {
while (!nact->is_quit && !is_yield_requested()) {
scheduler_on_command();
//SACT_DEBUG("%d:%x", sl_getPage(), sl_getIndex());
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;
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);
}
curadr = scene2adr(scono);
sl_callFar2(curadr.page -1, curadr.index);
}
nact->callback();
}
+1 -1
View File
@@ -90,7 +90,7 @@ static void Init() {
int p1 = getCaliValue(); /* ISys3x */
int p2 = getCaliValue(); /* IWinMsg */
int p3 = getCaliValue(); /* ITimer */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+2 -2
View File
@@ -51,7 +51,7 @@ static void Get() {
var: 結果を返す変数
*/
int num = getCaliValue();
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *result = getCaliVariable();
int *result = getCaliVariable();
if (ch == 0) {
current_no = no;
@@ -97,7 +97,7 @@ static void PlayPosSample(void) {
static void IsPlay(void) {
int ch = getCaliValue();
vmvar_t *result = getCaliVariable();
int *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();
vmvar_t *hour = getCaliVariable();
vmvar_t *min = getCaliVariable();
vmvar_t *sec = getCaliVariable();
vmvar_t *msec = getCaliVariable();
int *hour = getCaliVariable();
int *min = getCaliVariable();
int *sec = getCaliVariable();
int *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();
vmvar_t *var1 = getCaliVariable();
vmvar_t *var2 = getCaliVariable();
int *var1 = getCaliVariable();
int *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();
vmvar_t *result = getCaliVariable();
int *result = getCaliVariable();
*result = 0;
TRACE_UNIMPLEMENTED("S3xMusic.IsFade %d => %d:", ch, *result);
}
+60 -69
View File
@@ -33,7 +33,6 @@
#include "portab.h"
#include "system.h"
#include "ald_manager.h"
#include "audio_meta.h"
#include "input.h"
#include "msgskip.h"
#include "xsystem35.h"
@@ -287,11 +286,8 @@ static void DrawEffect() {
if (sact.version >= 110) {
wEffectkey = getCaliValue();
}
if (sact.waitskiplv > 1 || msgskip_isSkipping())
sp_update_all(true);
else
sp_eupdate(wType, wEffectTime, wEffectkey);
sp_eupdate(wType, wEffectTime, wEffectkey);
TRACE("SACT.DrawEffect %d,%d,%d:", wType, wEffectTime, wEffectkey);
}
@@ -308,10 +304,7 @@ static void DrawEffectAlphaMap() {
int wEffectTime = getCaliValue();
int wEffectKey = getCaliValue();
if (sact.waitskiplv > 1 || msgskip_isSkipping())
sp_update_all(true);
else
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
TRACE("SACT.DrawEffectAlphaMap %d,%d,%d:", nIndexAlphaMap, wEffectTime, wEffectKey);
}
@@ -629,10 +622,9 @@ static void QuakeSprite() {
if (sact.version >= 110) {
nfKeyEnable = getCaliValue();
}
if (!msgskip_isSkipping())
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
TRACE("SACT.QuakeSprite %d,%d,%d,%d:", wType, wAmplitudeX, wAmplitudeY, wCount);
}
@@ -644,7 +636,7 @@ static void QuakeSprite() {
*/
static void QuerySpriteIsExist() {
int wNum = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = sp_exists(wNum) ? 1 : 0;
@@ -662,10 +654,10 @@ static void QuerySpriteIsExist() {
*/
static void QuerySpriteInfo() {
int wNum = getCaliValue();
vmvar_t *vType = getCaliVariable();
vmvar_t *vCG1 = getCaliVariable();
vmvar_t *vCG2 = getCaliVariable();
vmvar_t *vCG3 = getCaliVariable();
int *vType = getCaliVariable();
int *vCG1 = getCaliVariable();
int *vCG2 = getCaliVariable();
int *vCG3 = getCaliVariable();
sp_query_info(wNum, vType, vCG1, vCG2, vCG3);
@@ -680,7 +672,7 @@ static void QuerySpriteInfo() {
*/
static void QuerySpriteShow() {
int wNum = getCaliValue();
vmvar_t *vShow = getCaliVariable();
int *vShow = getCaliVariable();
sp_query_show(wNum, vShow);
@@ -696,8 +688,8 @@ static void QuerySpriteShow() {
*/
static void QuerySpritePos() {
int wNum = getCaliValue();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
sp_query_pos(wNum, vX, vY);
@@ -713,8 +705,8 @@ static void QuerySpritePos() {
*/
static void QuerySpriteSize() {
int wNum = getCaliValue();
vmvar_t *vWidth = getCaliVariable();
vmvar_t *vHeight = getCaliVariable();
int *vWidth = getCaliVariable();
int *vHeight = getCaliVariable();
sp_query_size(wNum, vWidth, vHeight);
@@ -730,8 +722,8 @@ static void QuerySpriteSize() {
*/
static void QueryTextPos() {
int wNum = getCaliValue();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
sp_query_textpos(wNum, vX, vY);
@@ -770,7 +762,7 @@ static void CG_Reset() {
*/
static void CG_QueryType() {
int wNumCG = getCaliValue();
vmvar_t *vType = getCaliVariable();
int *vType = getCaliVariable();
*vType = scg_querytype(wNumCG);
@@ -786,8 +778,8 @@ static void CG_QueryType() {
*/
static void CG_QuerySize() {
int wNumCG = getCaliValue();
vmvar_t *vWidth = getCaliVariable();
vmvar_t *vHeight = getCaliVariable();
int *vWidth = getCaliVariable();
int *vHeight = getCaliVariable();
scg_querysize(wNumCG, vWidth, vHeight);
@@ -802,7 +794,7 @@ static void CG_QuerySize() {
*/
static void CG_QueryBpp() {
int wNumCG = getCaliValue();
vmvar_t *vBpp = getCaliVariable();
int *vBpp = getCaliVariable();
*vBpp = scg_querybpp(wNumCG);
@@ -817,7 +809,7 @@ static void CG_QueryBpp() {
*/
static void CG_ExistAlphaMap() {
int wNumCG = getCaliValue();
vmvar_t *vMask = getCaliVariable();
int *vMask = getCaliVariable();
*vMask = scg_existalphamap(wNumCG) ? 1 : 0;
@@ -1029,7 +1021,7 @@ static void CG_PartCopy() {
* @param vKey: 入力されたキー
*/
static void WaitKeySimple() {
vmvar_t *vKey = getCaliVariable();
int *vKey = getCaliVariable();
TRACE("SACT.WaitKeySimple %d:", vKey);
@@ -1079,10 +1071,10 @@ static void WaitKeyMessage() {
* @param vRsv2: 予約
*/
static void WaitKeySprite() {
vmvar_t *vOK = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
vmvar_t *vRsv1 = getCaliVariable();
vmvar_t *vRsv2 = getCaliVariable();
int *vOK = getCaliVariable();
int *vRND = getCaliVariable();
int *vRsv1 = getCaliVariable();
int *vRsv2 = getCaliVariable();
TRACE("SACT.WaitKeySprite %p,%p,%p,%p:", vOK, vRND, vRsv1, vRsv2);
@@ -1100,7 +1092,7 @@ static void WaitKeySprite() {
static void PeekKey() {
static int prevKeyCode = NUM_KEYCODES;
int nKeyCode = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *vResult = getCaliVariable();
// This function is called successively with different nKeyCodes.
// Only the first call hits the scheduler.
@@ -1129,8 +1121,8 @@ static void WaitMsgSkipKeyUp() {
* @param wTime: タイムアウト時間 (1/100sec)
*/
static void WaitKeySimpleTimeOut() {
vmvar_t *vRND = getCaliVariable();
vmvar_t *vD03 = getCaliVariable();
int *vRND = getCaliVariable();
int *vD03 = getCaliVariable();
int wTime = getCaliValue();
sact.waittype = KEYWAIT_SIMPLE;
@@ -1161,11 +1153,11 @@ static void WaitKeySimpleTimeOut() {
* @param wTime: タイムアウト時間 (1/100sec)
*/
static void WaitKeySpriteTimeOut() {
vmvar_t *vOK = getCaliVariable();
vmvar_t *vRND = getCaliVariable();
vmvar_t *vD01 = getCaliVariable();
vmvar_t *vD02 = getCaliVariable();
vmvar_t *vD03 = getCaliVariable();
int *vOK = getCaliVariable();
int *vRND = getCaliVariable();
int *vD01 = getCaliVariable();
int *vD02 = getCaliVariable();
int *vD03 = getCaliVariable();
int wTime = getCaliValue();
sp_keywait(vOK, vRND, vD01, vD02, vD03, wTime);
@@ -1179,7 +1171,7 @@ static void WaitKeySpriteTimeOut() {
* @param vSkip:
*/
static void QueryMessageSkip() {
vmvar_t *vSkip = getCaliVariable();
int *vSkip = getCaliVariable();
*vSkip = msgskip_isSkipping() ? 1 : 0;
@@ -1227,7 +1219,7 @@ static void MessageOutput() {
int wMessageSpeed = getCaliValue();
int wMessageLineSpace = getCaliValue();
int wMessageAlign = 0;
vmvar_t *vMessageLength = NULL;
int *vMessageLength = NULL;
if (sact.version >= 110) {
wMessageAlign = getCaliValue();
@@ -1274,7 +1266,7 @@ static void MessageOutputEx() {
int wRubySize = getCaliValue();
int wRubyFont = getCaliValue();
int wRubyLineSpace = getCaliValue();
vmvar_t *vLength = NULL;
int *vLength = NULL;
if (sact.version >= 120) {
vLength = getCaliVariable();
@@ -1319,7 +1311,7 @@ static void MessageClear() {
* @param wResult: 結果を返す変数
*/
static void MessageIsEmpty() {
vmvar_t *wResult = getCaliVariable();
int *wResult = getCaliVariable();
*wResult = smsg_is_empty() ? 1 : 0;
@@ -1333,7 +1325,7 @@ static void MessageIsEmpty() {
* @param nTopStringNum: バッファを取得する文字列変数の最初
*/
static void MessagePeek() {
vmvar_t *vCount = getCaliVariable();
int *vCount = getCaliVariable();
int nTopStringNum = getCaliValue();
*vCount = smsg_peek(nTopStringNum);
@@ -1396,7 +1388,7 @@ static void MenuAdd() {
* @param nAlign: 行そろえ (0:左, 1:中央, 2: 右) (1.1~)
*/
static void MenuOpen() {
vmvar_t *wMenuResult = getCaliVariable();
int *wMenuResult = getCaliVariable();
int wNum = getCaliValue();
int wChoiceSize = getCaliValue();
int wMenuOutSpc = getCaliValue();
@@ -1472,7 +1464,7 @@ static void Numeral_SetCG() {
static void Numeral_GetCG() {
int nNum = getCaliValue();
int nIndex = getCaliValue();
vmvar_t *vCG = getCaliVariable();
int *vCG = getCaliVariable();
sp_num_getcg(nNum, nIndex, vCG);
@@ -1505,8 +1497,8 @@ static void Numeral_SetPos() {
*/
static void Numeral_GetPos() {
int nNum = getCaliValue();
vmvar_t *vX = getCaliVariable();
vmvar_t *vY = getCaliVariable();
int *vX = getCaliVariable();
int *vY = getCaliVariable();
sp_num_getpos(nNum, vX, vY);
@@ -1536,7 +1528,7 @@ static void Numeral_SetSpan() {
*/
static void Numeral_GetSpan() {
int nNum = getCaliValue();
vmvar_t *vSpan = getCaliVariable();
int *vSpan = getCaliVariable();
sp_num_getspan(nNum, vSpan);
@@ -1604,7 +1596,7 @@ static void TimerSet() {
*/
static void TimerGet() {
int wTimerID = getCaliValue();
vmvar_t *vRND = getCaliVariable();
int *vRND = getCaliVariable();
*vRND = stimer_get(wTimerID);
@@ -1623,7 +1615,7 @@ static void TimerWait() {
int msec = (wCount - stimer_get(wTimerID)) * 10;
if (msec > 0)
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE);
TRACE("SACT.TimerWait %d,%d:", wTimerID, wCount);
}
@@ -1636,7 +1628,7 @@ static void TimerWait() {
static void Wait() {
int wCount = getCaliValue();
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE);
TRACE("SACT.Wait %d:", wCount);
}
@@ -1704,7 +1696,7 @@ static void SoundWait() {
*/
static void SoundWaitKey() {
int wNum = getCaliValue();
vmvar_t *vKey = getCaliVariable();
int *vKey = getCaliVariable();
ssnd_waitkey(wNum, vKey);
@@ -1805,7 +1797,7 @@ static void SpriteSoundOB() {
*/
static void MusicCheck() {
int wNum = getCaliValue();
vmvar_t *vRND = getCaliVariable();
int *vRND = getCaliVariable();
*vRND = ald_exists(DRIFILE_BGM, wNum - 1) ? 1 : 0;
@@ -1820,7 +1812,7 @@ static void MusicCheck() {
*/
static void MusicGetLength() {
int wNum = getCaliValue();
vmvar_t *vRND = getCaliVariable();
int *vRND = getCaliVariable();
*vRND = musbgm_getlen(wNum);
@@ -1835,7 +1827,7 @@ static void MusicGetLength() {
*/
static void MusicGetPos() {
int wNum = getCaliValue();
vmvar_t *vRND = getCaliVariable();
int *vRND = getCaliVariable();
*vRND = musbgm_getpos(wNum);
@@ -1853,9 +1845,8 @@ static void MusicPlay() {
int wNum = getCaliValue();
int wFadeTime = getCaliValue();
int wVolume = getCaliValue();
bgi_t *bgi = bgi_find(wNum);
musbgm_play(wNum, wFadeTime, wVolume, bgi ? bgi->loopno : 0);
musbgm_play(wNum, wFadeTime, wVolume, 0);
TRACE("SACT.MusicPlay %d,%d,%d:", wNum, wFadeTime, wVolume);
}
@@ -1946,7 +1937,7 @@ static void MusicWaitPos() {
*/
static void SoundGetLinkNum() {
int wNum = getCaliValue();
vmvar_t *vRND = getCaliVariable();
int *vRND = getCaliVariable();
*vRND = ssnd_getlinknum(wNum);
@@ -1966,7 +1957,7 @@ static void SoundGetLinkNum() {
* pos = ((pos2-pos1) / (val2-val1)) * (val-val1) + pos1
*/
static void ChartPos() {
vmvar_t *pos = getCaliVariable();
int *pos = getCaliVariable();
int pos1 = getCaliValue();
int pos2 = getCaliValue();
int val1 = getCaliValue();
@@ -2011,7 +2002,7 @@ static void Maze_Create() {
* SACT.Maze_Get (1.0~)
*/
static void Maze_Get() {
vmvar_t *p1 = getCaliVariable();
int *p1 = getCaliVariable();
int p2 = getCaliValue();
int p3 = getCaliValue();
@@ -2022,7 +2013,7 @@ static void Maze_Get() {
* SACT.EncryptWORD (1.0~)
*/
static void EncryptWORD() {
vmvar_t *array = getCaliVariable();
int *array = getCaliVariable();
int num = getCaliValue();
int key = getCaliValue();
@@ -2035,7 +2026,7 @@ static void EncryptWORD() {
* SACT.DecryptWORD (1.0~)
*/
static void DecryptWORD() {
vmvar_t *array = getCaliVariable();
int *array = getCaliVariable();
int num = getCaliValue();
int key = getCaliValue();
@@ -2101,7 +2092,7 @@ static void XMenuRegister() {
*/
static void XMenuGetNum() {
int nRegiNum = getCaliValue();
vmvar_t *vMenuID = getCaliVariable();
int *vMenuID = getCaliVariable();
*vMenuID = spxm_getnum(nRegiNum);
+1 -1
View File
@@ -25,7 +25,7 @@
#include "portab.h"
// グラフ用チャート作成
void schart_pos(vmvar_t *pos, int pos1, int pos2, int val1, int val2, int val) {
void schart_pos(int *pos, int pos1, int pos2, int val1, int val2, int val) {
if (val1 == val2) {
*pos = 0;
} else {
+1 -3
View File
@@ -24,8 +24,6 @@
#ifndef __SACTCHART_H__
#define __SACTCHART_H__
#include "portab.h"
void schart_pos(vmvar_t *pos, int pos1, int pos2, int val1, int val2, int val);
void schart_pos(int *pos, int pos1, int pos2, int val1, int val2, int val);
#endif
+1 -1
View File
@@ -31,7 +31,7 @@
適当でいいとおもう
*/
void scryp_encrypt_word(vmvar_t *array, int num, int key) {
void scryp_encrypt_word(int *array, int num, int key) {
WARNING("NOT IMPLEMENTED");
}
+1 -3
View File
@@ -24,9 +24,7 @@
#ifndef __SACTCRYPT_H__
#define __SACTCRYPT_H__
#include "portab.h"
void scryp_encrypt_word(vmvar_t *array, int num, int key);
void scryp_encrypt_word(int *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);
+1 -5
View File
@@ -97,11 +97,7 @@ static void draw_log() {
if (0 == strcmp(str, "\n")) {
SDL_BlitSurface(hline, NULL, main_surface, &(SDL_Rect){0, y + FONTSIZE/2, main_surface->w, 3});
} else {
FontSpec font_spec = {
.type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC,
.weight = cur < 6 ? FONT_WEIGHT_NORMAL : FONT_WEIGHT_BOLD,
.size = FONTSIZE
};
FontSpec font_spec = { .type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC, .size = FONTSIZE };
gfx_drawString(0, y, str, 255, font_spec);
}
y += FONTSIZE;
+2 -3
View File
@@ -30,7 +30,6 @@
#include "system.h"
#include "nact.h"
#include "input.h"
#include "msgskip.h"
#include "sactsound.h"
#include "music.h"
#include "sact.h"
@@ -113,7 +112,7 @@ void ssnd_wait(int no) {
}
// 指定の効果音が終了するか、キーが押されるまで待つ
void ssnd_waitkey(int no, vmvar_t *res) {
void ssnd_waitkey(int no, int *res) {
int slot = slt_find(no);
if (slot == -1) {
@@ -121,7 +120,7 @@ void ssnd_waitkey(int no, vmvar_t *res) {
return;
}
if (sact.waitskiplv > 1 || msgskip_isSkipping()) {
if (sact.waitskiplv > 1) {
*res = SYS35KEY_RET;
return;
}
+1 -3
View File
@@ -24,13 +24,11 @@
#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, vmvar_t *res);
void ssnd_waitkey(int no, int *res);
void ssnd_prepare(int no);
void ssnd_prepareLRrev(int no);
void ssnd_playLRrev(int no);
+1 -1
View File
@@ -28,7 +28,7 @@
#include "sacttimer.h"
#include "system.h"
#define MAX_TIMER 128
#define MAX_TIMER 10
#define TICKS_PER_CENTISECOND 10
uint32_t ticks_base[MAX_TIMER];
+8 -8
View File
@@ -455,7 +455,7 @@ bool sp_exists(int wNum) {
}
// スプライトのタイプと何番のCGがセットされているかの取得
bool sp_query_info(int wNum, vmvar_t *vtype, vmvar_t *vcg1, vmvar_t *vcg2, vmvar_t *vcg3) {
bool sp_query_info(int wNum, int *vtype, int *vcg1, int *vcg2, int *vcg3) {
sprite_t *sp;
if (wNum >= SPRITEMAX) goto errexit;
@@ -479,7 +479,7 @@ bool sp_query_info(int wNum, vmvar_t *vtype, vmvar_t *vcg1, vmvar_t *vcg2, vmvar
}
// スプライトの表示状態の取得
bool sp_query_show(int wNum, vmvar_t *vShow) {
bool sp_query_show(int wNum, int *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, vmvar_t *vShow) {
}
// スプライトの表示位置の取得
bool sp_query_pos(int wNum, vmvar_t *vx, vmvar_t *vy) {
bool sp_query_pos(int wNum, int *vx, int *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, vmvar_t *vx, vmvar_t *vy) {
}
// スプライトの大きさの取得
bool sp_query_size(int wNum, vmvar_t *vw, vmvar_t *vh) {
bool sp_query_size(int wNum, int *vw, int *vh) {
sprite_t *sp;
if (wNum >= SPRITEMAX) goto errexit;
@@ -528,7 +528,7 @@ bool sp_query_size(int wNum, vmvar_t *vw, vmvar_t *vh) {
}
// テキストスプライトの現在の文字表示位置の取得
bool sp_query_textpos(int wNum, vmvar_t *vx, vmvar_t *vy) {
bool sp_query_textpos(int wNum, int *vx, int *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, vmvar_t *vCG) {
void sp_num_getcg(int nNum, int nIndex, int *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, vmvar_t *vX, vmvar_t *vY) {
void sp_num_getpos(int nNum, int *vX, int *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, vmvar_t *vSpan) {
void sp_num_getspan(int nNum, int *vSpan) {
SP_ASSERT_NO(nNum);
*vSpan = sact.sp[nNum]->numeral.span;
+10 -11
View File
@@ -24,7 +24,6 @@
#ifndef __SPRITE_H__
#define __SPRITE_H__
#include "portab.h"
#include "sact.h"
#define DEFAULT_UPDATE sp_draw
@@ -52,17 +51,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, 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);
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);
void sp_num_setcg(int nNum, int nIndex, int nCG);
void sp_num_getcg(int nNum, int nIndex, vmvar_t *vCG);
void sp_num_getcg(int nNum, int nIndex, int *vCG);
void sp_num_setpos(int nNum, int nX, int nY);
void sp_num_getpos(int nNum, vmvar_t *vX, vmvar_t *vY);
void sp_num_getpos(int nNum, int *vX, int *vY);
void sp_num_setspan(int nNum, int nSpan);
void sp_num_getspan(int nNUm, vmvar_t *vSpan);
void sp_num_getspan(int nNUm, int *vSpan);
void sp_exp_clear(void);
void sp_exp_add(int nNumSP1, int nNumSP2);
void sp_exp_del(int wNum);
@@ -86,7 +85,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, vmvar_t *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, int *wLength);
void smsg_clear(int wNum);
bool smsg_is_empty();
int smsg_peek(int nTopStringNum);
@@ -134,7 +133,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(vmvar_t *vOK, vmvar_t *vRND, vmvar_t *vRsv1, vmvar_t *vRsv2, vmvar_t *vRsv3, int timeout);
void sp_keywait(int *vOK, int *vRND, int *vRsv1, int *vRsv2, int *vRsv3, int timeout);
// in screen_quake.c
void sp_quake_screen(int type, int p1, int p2, int time, int cancel);
+5
View File
@@ -39,6 +39,11 @@
@param cancel: キー抜け(0:なし, 1:あり)
*/
void sp_eupdate(int type, int time, int cancel) {
if (sact.waitskiplv > 1) {
sp_update_all(true);
return;
}
sp_update_all(false);
enum effect_type effect = from_sact_effect(type);
+5
View File
@@ -426,6 +426,11 @@ 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;
+2 -1
View File
@@ -113,7 +113,7 @@ static bool waitcond(int endtime) {
@param vD03: タイムアウトした場合=1, しない場合=0
@param wTime: タイムアウト時間 (1/100sec)
*/
void sp_keywait(vmvar_t *vOK, vmvar_t *vRND, vmvar_t *vD01, vmvar_t *vD02, vmvar_t *vD03, int timeout) {
void sp_keywait(int *vOK, int *vRND, int *vD01, int *vD02, int *vD03, int timeout) {
int curtime, endtime;
// とりあえず全更新
@@ -171,3 +171,4 @@ void sp_keywait(vmvar_t *vOK, vmvar_t *vRND, vmvar_t *vD01, vmvar_t *vD02, vmvar
sact.waittype = KEYWAIT_NONE;
}
+9 -18
View File
@@ -30,11 +30,9 @@
#include "portab.h"
#include "system.h"
#include "ags.h"
#include "gfx.h"
#include "nact.h"
#include "variable.h"
#include "input.h"
#include "msgskip.h"
#include "sact.h"
#include "sprite.h"
#include "drawtext.h"
@@ -238,12 +236,11 @@ 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, vmvar_t *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, int *wLength) {
char *msg;
sprite_t *sp;
int len = 0; // 処理した文字数?
bool has_batched_output = false;
bool flush_batched_output = wSpeed > 0;
bool needupdate = false;
SDL_Rect uparea = {0,0,0,0};
// wRSize == 0 -> ルビ無し(SACT.MessageOutputからの呼出)
@@ -253,7 +250,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
if (!is_messagesprite(wNum)) return;
// MessageSkip中は文字送り速度を最大に
if (sact.waitskiplv > 1 || msgskip_isSkipping()) wSpeed = 0;
if (sact.waitskiplv > 1) wSpeed = 0;
// shortcut
sp = sact.sp[wNum];
@@ -305,7 +302,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
mbuf,
wColorR, wColorG, wColorB);
has_batched_output = true;
needupdate = true;
append_to_log(mbuf);
@@ -316,7 +313,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
cw,
wSize + wRSize + wRLineSpace);
sp_update_clipped();
has_batched_output = false;
needupdate = false;
// keywait
delta = sys_get_ticks() - wcnt;
@@ -336,17 +333,11 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
// バッファリング中の文字のクリア
sact.msgbuf[0] = '\0';
// Register the update area after rendering without waits.
if (has_batched_output) {
// Waitなしの出力は最後にupdate
if (needupdate) {
uparea.w = sp->width;
uparea.h = min(sp->height - uparea.y,
sp->u.msg.dspcur.y - uparea.y + wSize + wRSize + wRLineSpace);
uparea.h = min(sp->height, uparea.y - sp->u.msg.dspcur.y + wLineSpace + wLineSpace + wRSize);
sp_updateme_part(sp, uparea.x, uparea.y, uparea.w, uparea.h);
// Present now if waiting was skipped.
if (flush_batched_output) {
sp_update_clipped();
gfx_updateScreen();
}
}
// ????
@@ -417,7 +408,7 @@ int smsg_keywait(int wNum1, int wNum2, int msglen) {
struct markinfo minfo[6];
int i = 0, j, maxstep;
if (sact.waitskiplv > 0 || msgskip_isSkipping()) {
if (sact.waitskiplv > 0) {
sys_getInputInfo();
return 0;
}
+7
View File
@@ -117,4 +117,11 @@ 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: 演算結果を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int type = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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 : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -114,8 +114,8 @@ static void SubAtArray(void) { /* 2 */
vAry2: 配列2
cnt : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -140,8 +140,8 @@ static void MulAtArray(void) { /* 3 */
vAry2: 配列2
cnt : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -166,8 +166,8 @@ static void DivAtArray(void) { /* 4 */
vAry2: 配列2
cnt : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -196,8 +196,8 @@ static void MinAtArray(void) { /* 5 */
vAry2: 配列2
cnt : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -219,8 +219,8 @@ static void MaxAtArray(void) { /* 6 */
vAry2: 配列2
cnt : 個数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int i;
@@ -242,7 +242,7 @@ static void AndNumArray(void) { /* 7 */
cnt : 個数
val : ANDをとる値
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -263,7 +263,7 @@ static void OrNumArray(void) { /* 8 */
cnt : 個数
val : ORをとる値
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -284,7 +284,7 @@ static void XorNumArray(void) { /* 9 */
cnt : 個数
val : XORをとる値
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
int i;
@@ -307,10 +307,10 @@ static void SetEquArray(void) { /* 10 */
val : 比較する値
vResults : 結果を格納する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を格納する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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: 結果を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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: 結果を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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;
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int mask = getCaliValue();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int max = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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: 結果を返す配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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 : 結果を代入する配列
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int mask = getCaliValue();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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: 結果を書き込む変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResults = getCaliVariable();
int *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: 一致する個数を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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: 条件に一致する数を返す変数
*/
vmvar_t *vAry1 = getCaliVariable();
vmvar_t *vAry2 = getCaliVariable();
int *vAry1 = getCaliVariable();
int *vAry2 = getCaliVariable();
int cnt = getCaliValue();
int val1 = getCaliValue();
int val2 = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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: 等しくないものの数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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: 一致した数を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *vMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int val = getCaliValue();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *vMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *vMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int max = getCaliValue();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *vMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *vMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *vMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *v1 = getCaliVariable();
vmvar_t *vLastMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *v1 = getCaliVariable();
int *vLastMatch = getCaliVariable();
int *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
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
vmvar_t *v1 = getCaliVariable();
vmvar_t *vLastMatch = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *v1 = getCaliVariable();
int *vLastMatch = getCaliVariable();
int *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 */
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int src = getCaliValue();
int dst = getCaliValue();
@@ -1172,7 +1172,7 @@ static void ChangeRangeArray(void) { /* 50 */
max : 最大値
val : 置き換える値
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int min = getCaliValue();
int max = getCaliValue();
@@ -1202,12 +1202,12 @@ static void CopyArrayToRect(void) { /* 51 */
dw : コピー先 width
dh : コピー先 height
*/
vmvar_t *vSrc = getCaliVariable();
int *vSrc = getCaliVariable();
int sw = getCaliValue();
int sh = getCaliValue();
int sx = getCaliValue();
int sy = getCaliValue();
vmvar_t *vDst = getCaliVariable();
int *vDst = getCaliVariable();
int dw = getCaliValue();
int dh = getCaliValue();
int x, y;
@@ -1236,10 +1236,10 @@ static void CopyRectToArray(void) { /* 52 */
dx : コピー先 x
dy : コピー先 y
*/
vmvar_t *vSrc = getCaliVariable();
int *vSrc = getCaliVariable();
int sw = getCaliValue();
int sh = getCaliValue();
vmvar_t *vDst = getCaliVariable();
int *vDst = getCaliVariable();
int dw = getCaliValue();
int dh = getCaliValue();
int dx = getCaliValue();
@@ -1266,10 +1266,10 @@ static void ChangeSecretArray(void) { /* 53 */
type: 機能番号
vResult: 結果を返す変数
*/
vmvar_t *vAry = getCaliVariable();
int *vAry = getCaliVariable();
int cnt = getCaliValue();
int type = getCaliValue();
vmvar_t *vResult = getCaliVariable();
int *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(vmvar_t *var) {
static int64_t get32(int *var) {
return var[0] + mul64(var[1], 0x10000);
}
@@ -74,7 +74,7 @@ static void SetIntNum16(void) { /* 1 */
var: 数値の入った変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator = mul64(*var, numbase);
@@ -82,7 +82,7 @@ static void SetIntNum16(void) { /* 1 */
}
static void SetIntNum32(void) { /* 2 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator = mul64(get32(var), numbase);
@@ -95,7 +95,7 @@ static void GetIntNum16(void) { /* 3 */
var: 数値をいれる変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int64_t i;
i = div64(accumulator, numbase);
@@ -110,7 +110,7 @@ static void GetIntNum16(void) { /* 3 */
}
static void GetIntNum32(void) { /* 4 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int64_t i = div64(accumulator, numbase);
var[0] = i & 0xFFFF;
@@ -126,7 +126,7 @@ static void AddIntNum16(void) { /* 5 */
var: 足す数の入った変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator += mul64(*var, numbase);
@@ -134,7 +134,7 @@ static void AddIntNum16(void) { /* 5 */
}
static void AddIntNum32(void) { /* 6 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator += mul64(get32(var), numbase);
@@ -148,7 +148,7 @@ static void SubIntNum16(void) { /* 7 */
}
static void SubIntNum32(void) { /* 8 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator -= mul64(get32(var), numbase);
@@ -161,7 +161,7 @@ static void MulIntNum16(void) { /* 9 */
var: 掛ける数の入った変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
accumulator *= mul64(*var, numbase);
@@ -180,7 +180,7 @@ static void DivIntNum16(void) { /* 11 */
var: 足す数の入った変数
*/
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int64_t i;
i = mul64(*var, numbase);
@@ -204,9 +204,9 @@ static void CmpIntNum16(void) { /* 13 */
}
static void CmpIntNum32(void) { /* 14 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int op = getCaliValue();
vmvar_t *result = getCaliVariable();
int *result = getCaliVariable();
int64_t val = mul64(get32(var), numbase);
@@ -230,8 +230,8 @@ static void GetLengthNum16(void) { /* 15 */
var: 数値
vResult: 数値の桁数を返す変数
*/
vmvar_t *var = getCaliVariable();
vmvar_t *vResult = getCaliVariable();
int *var = getCaliVariable();
int *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();
vmvar_t *vResult = getCaliVariable();
int *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();
vmvar_t *vResult = getCaliVariable();
int *vResult = getCaliVariable();
int i;
i = (p1 * p2) / p3;
@@ -333,7 +333,7 @@ static void SetRandomSeed() {
static void GetRandomNumA() {
int num = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
if (num == 0 || num == 1) {
*var = num;
@@ -351,7 +351,7 @@ static void NumToBit() {
var: 値を返す変数
*/
int beki = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
int i, j = 1;
if (beki < 17) {
@@ -379,7 +379,7 @@ static void BitToNum() {
var: 値を返す変数
*/
int val = getCaliValue();
vmvar_t *var = getCaliVariable();
int *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 {
vmvar_t *dst_p1;
vmvar_t *dst_p2;
int *dst_p1;
int *dst_p2;
int dw_1000A188;
};
static struct _s0 s0[SLOT];
@@ -84,7 +84,7 @@ struct _s2 {
};
static struct _s2 s2[SLOT];
static vmvar_t *add_p5[SLOT]; /* どこまでアニメーションのコマが進んだか */
static int* 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();
vmvar_t *p5 = getCaliVariable();
vmvar_t *p6 = getCaliVariable();
int *p5 = getCaliVariable();
int *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();
vmvar_t *src = getCaliVariable(); /* r, g, b */
vmvar_t *dst = getCaliVariable(); /* r, g, b */
int *src = getCaliVariable(); /* r, g, b */
int *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(vmvar_t *) * SLOT);
memset(add_p5, 0, sizeof(int *) * SLOT);
}
@@ -240,7 +240,7 @@ static void SetAnimeSrc() {
int h = getCaliValue();
int uw = getCaliValue();
int uh = getCaliValue();
vmvar_t *pal = getCaliVariable();
int *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();
vmvar_t *p1 = getCaliVariable();
vmvar_t *p2 = getCaliVariable();
int *p1 = getCaliVariable();
int *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();
vmvar_t *p5 = getCaliVariable();
int *p5 = getCaliVariable();
int p6 = getCaliValue();
int i;
+9 -8
View File
@@ -36,7 +36,7 @@
#include "xsystem35.h"
#include "modules.h"
#include "input.h"
#include "input_modal.h"
#include "menu.h"
// キー変換テーブル
#define KEYMAP_MAX 8
@@ -47,7 +47,7 @@ static void OutputMessageBox(void) { /* 0 */
int p2 = getCaliValue();
int title = getCaliValue();
int msg = getCaliValue();
vmvar_t *res = getCaliVariable();
int *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();
vmvar_t *val = getCaliVariable();
int *val = getCaliVariable();
int minval = getCaliValue();
int maxval = getCaliValue();
vmvar_t *res = getCaliVariable();
int *res = getCaliVariable();
int ISys3xSystem = getCaliValue();
INPUTNUM_PARAM ni_param = {
@@ -77,11 +77,12 @@ static void InputListNum(void) { /* 1 */
.title = toUTF8(svar_get(title)),
};
if (input_modal_number(&ni_param)) {
menu_inputnumber(&ni_param);
if (ni_param.value < 0) {
*res = 0;
} else {
*val = (uint16_t)ni_param.value;
*res = 1;
} else {
*res = 0;
}
free(ni_param.title);
@@ -151,7 +152,7 @@ static void SetKeyStatus(void) {
*/
static void GetKeyStatus(void) {
int no = getCaliValue();
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *cur = getCaliVariable();
int *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();
vmvar_t *result = getCaliVariable();
int *result = getCaliVariable();
*result = mus_wav_get_playposition(slot);
@@ -338,7 +338,7 @@ static void wavIsPlayRange() {
*/
int slot = getCaliValue();
int range = getCaliValue();
vmvar_t *result = getCaliVariable();
int *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();
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *p2 = getCaliVariable();
int *p2 = getCaliVariable();
TRACE_UNIMPLEMENTED("ShString.SetStringNum32: %d,%p:", p1, p2);
}
+1 -1
View File
@@ -94,7 +94,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
if (!nact->files.alk[0]) {
WARNING("dDEMO.alk not found");
+1 -1
View File
@@ -13,7 +13,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+1 -1
View File
@@ -13,7 +13,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+5 -8
View File
@@ -31,11 +31,8 @@
#include "ags.h"
#include "font.h"
static FontSpec dt_spec = {
.type = FONT_GOTHIC,
.weight = FONT_WEIGHT_BOLD,
.size = 12
};
static FontType ftype;
static int fsize; // フォントの大きさ
/**
* 次に描く文字のフォントの種類と大きさを設定
@@ -51,8 +48,8 @@ void dt_setfont(FontType type, int size) {
}
#endif
dt_spec.type = type;
dt_spec.size = size;
ftype = type;
fsize = size;
}
/**
@@ -69,7 +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) {
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, dt_spec);
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};
+15 -21
View File
@@ -35,7 +35,6 @@
#include "ags.h"
#include "variable.h"
#include "sactcg.h"
#include "ald_manager.h"
#include "cg.h"
#include "gfx.h"
@@ -221,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;
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, spec);
SDL_Surface *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);
@@ -246,8 +244,7 @@ void scg_create_textnum(int wNumCG, int wSize, int wR, int wG, int wB, int wFigs
}
sprintf(s, ss, wValue);
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, spec);
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);
@@ -329,27 +326,24 @@ void scg_free(int no) {
// CGの種類を取得
int scg_querytype(int wNumCG) {
if (wNumCG >= (CGMAX -1)) return CG_NOTUSED;
if (cg_store && cg_store[wNumCG])
return cg_store[wNumCG]->type;
// Linked CGs are valid even before they are loaded into cg_store.
return ald_is_linked(DRIFILE_CG, wNumCG - 1) ? CG_LINKED : CG_NOTUSED;
if (!cg_store || !cg_store[wNumCG]) return CG_NOTUSED;
return cg_store[wNumCG]->type;
}
// CGの大きさを取得
bool scg_querysize(int wNumCG, vmvar_t *w, vmvar_t *h) {
*w = *h = 0;
bool scg_querysize(int wNumCG, int *w, int *h) {
if (wNumCG >= (CGMAX -1)) goto errexit;
if (!cg_store || !cg_store[wNumCG]) goto errexit;
if (cg_store[wNumCG]->sf == NULL) goto errexit;
if (wNumCG >= (CGMAX - 1))
return false;
cginfo_t *cg = cg_store ? cg_store[wNumCG] : NULL;
if (!cg && ald_is_linked(DRIFILE_CG, wNumCG - 1))
cg = scg_get(wNumCG);
if (!cg || !cg->sf)
return false;
*w = cg->sf->w;
*h = cg->sf->h;
*w = cg_store[wNumCG]->sf->w;
*h = cg_store[wNumCG]->sf->h;
return true;
errexit:
*w = *h = 0;
return false;
}
// CGのBPPを取得
+1 -1
View File
@@ -55,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, vmvar_t *w, vmvar_t *h);
bool scg_querysize(int wNumCG, int *w, int *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 */
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 0;
+1 -1
View File
@@ -12,7 +12,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *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();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+12 -12
View File
@@ -83,9 +83,9 @@ static void MakeMapDraw() {
int dstY = getCaliValue();
int posX = getCaliValue();
int posY = getCaliValue();
vmvar_t *a1 = getCaliVariable();
vmvar_t *a2 = getCaliVariable();
vmvar_t *a3 = getCaliVariable();
int *a1 = getCaliVariable();
int *a2 = getCaliVariable();
int *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;
}
vmvar_t *a1 = v_resolveRef(&r1);
vmvar_t *a2 = v_resolveRef(&r2);
vmvar_t *a3 = v_resolveRef(&r3);
int *a1 = v_resolveRef(&r1);
int *a2 = v_resolveRef(&r2);
int *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;
}
vmvar_t *a1 = v_resolveRef(&r1);
vmvar_t *a2 = v_resolveRef(&r2);
vmvar_t *a3 = v_resolveRef(&r3);
int *a1 = v_resolveRef(&r1);
int *a2 = v_resolveRef(&r2);
int *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() {
vmvar_t *dx = getCaliVariable();
vmvar_t *dy = getCaliVariable();
int *dx = getCaliVariable();
int *dy = getCaliVariable();
int moveL = getCaliValue();
int moveU = getCaliValue();
int moveR = getCaliValue();
int moveD = getCaliValue();
vmvar_t *pt = getCaliVariable();
int *pt = getCaliVariable();
int duration = getCaliValue();
int t = min(*pt, duration);
+1 -1
View File
@@ -41,7 +41,7 @@ static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
int *var = getCaliVariable();
*var = 1;
+10 -41
View File
@@ -1,42 +1,11 @@
# 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).
# This creates a target "translations"
gettext_create_translations(xsystem35.pot ALL ja.po)
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()
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)
+1
View File
@@ -0,0 +1 @@
ja
+317
View File
@@ -0,0 +1,317 @@
# 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
@@ -0,0 +1,41 @@
# 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 =
+4 -2
View File
@@ -1,3 +1,5 @@
./src/menu.c
./src/input_modal.c
./src/volume.c
./src/menu_gui.c
./src/menu_gui_volval.c
./src/s39init.c
-68
View File
@@ -1,68 +0,0 @@
# 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")
BIN
View File
Binary file not shown.
+119 -50
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: xsystem35 1.7.3pre3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-19 12:40+0900\n"
"PO-Revision-Date: 2026-06-14 17:09+0900\n"
"POT-Creation-Date: 2019-07-14 18:38+0900\n"
"PO-Revision-Date: 2004-08-24 14:40+0900\n"
"Last-Translator: CHIKAMA Masaki <chika@8ne.sakura.ne.jp>\n"
"Language-Team: Japanese <chika@8ne.sakura.ne.jp>\n"
"Language: ja\n"
@@ -17,74 +17,143 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/menu.c:136 src/menu.c:139
msgid "Message Skip"
msgstr "文字送り"
#: src/menu_gui.c:127 src/menu_gui.c:137 src/menu_gui.c:147
msgid "0"
msgstr ""
#: src/menu.c:144
msgid "Mouse Movement"
msgstr "マウスカーソル移動"
#: src/menu_gui.c:374 src/menu_gui.c:658
msgid "About"
msgstr "情報"
#: src/menu.c:148 src/volume.c:198
msgid "Sound Settings"
msgstr "サウンド設定"
#: src/menu_gui.c:593
msgid "CDROM-audio"
msgstr "CDROMオーディオ"
#: src/menu.c:153 src/menu.c:221
msgid "Restart"
msgstr "再起動"
#: src/menu.c:158 src/menu.c:216
msgid "Quit"
msgstr "終了"
#: src/menu.c:197 src/input_modal.c:292 src/input_modal.c:485
#: src/menu_gui.c:171 src/menu_gui.c:266
msgid "Cancel"
msgstr "キャンセル"
#: src/menu.c:203
msgid "Any unsaved progress will be lost."
msgstr "保存していない進行状況は失われます。"
#: src/menu_gui.c:306 src/menu_gui.c:673
msgid "Exit"
msgstr "終了"
#: src/menu.c:216
msgid "Quit game?"
msgstr "ゲームを終了しますか?"
#: src/menu_gui.c:317
msgid "Exit System35 ?"
msgstr "System35を終了しますか?"
#: src/menu.c:221
msgid "Restart game?"
msgstr "ゲームを再起動しますか?"
#: src/menu_gui.c:400
msgid "Information1"
msgstr "情報1"
#: src/input_modal.c:268
msgid "Enter a string"
msgstr "文字を入力してください"
#: src/menu_gui.c:410
msgid "Information2"
msgstr "情報2"
#: src/input_modal.c:280
#: src/menu_gui.c:420
msgid "Information3"
msgstr "情報3"
#: src/menu_gui.c:60
msgid "InputNumber"
msgstr "数値入力"
#: src/menu_gui.c:214
msgid "InputString"
msgstr "文字列入力"
#: src/menu.c:69
#, c-format
msgid "Up to %d characters"
msgid "MAX %d charater"
msgstr "最大 %d 文字"
#: src/input_modal.c:288 src/input_modal.c:478 src/input_modal.c:483
#: src/menu_gui.c:241
msgid "MAX charater"
msgstr "最大文字数"
#: src/menu_gui.c:621
msgid "MIDI-audio"
msgstr "MIDIオーディオ"
#: src/menu_gui.c:764
#, fuzzy
msgid "MessageBox"
msgstr "メッセージスキップ"
#: src/menu_gui.c:485
msgid "MessageSkip"
msgstr "メッセージスキップ"
#: src/menu_gui.c:774
#, fuzzy
msgid "Messge"
msgstr "メッセージスキップ"
#: src/menu_gui.c:521
msgid "MouseAutoMove"
msgstr "マウスカーソル移動"
#: src/menu_gui.c:338
msgid "No"
msgstr "いいえ"
#: src/menu_gui.c:250
msgid "Notice) HANKAKU is not available"
msgstr "註)全角文字のみ有効"
#: src/menu_gui.c:505 src/menu_gui.c:541 src/menu_gui.c:585 src/menu_gui.c:613
#: src/menu_gui.c:641
msgid "OFF"
msgstr "無効"
#: src/menu_gui.c:164 src/menu_gui.c:273 src/menu_gui.c:426 src/menu_gui.c:781
msgid "OK"
msgstr "OK"
#: src/input_modal.c:434
msgid "Enter a number"
msgstr "数値を入力してください"
#: src/menu_gui.c:497 src/menu_gui.c:533 src/menu_gui.c:577 src/menu_gui.c:605
#: src/menu_gui.c:633
msgid "ON"
msgstr "有効"
#: src/volume.c:106
msgid "BGM"
msgstr "BGM"
#: src/menu_gui.c:565
msgid "PCM-audio"
msgstr "PCMオーディオ"
#: src/volume.c:107
msgid "Sound effects"
msgstr "効果音"
#: src/menu_gui.c:557 src/menu_gui_volval.c:43
msgid "VolumeValance"
msgstr "ボリュームバランス"
#: src/volume.c:189
#: src/menu_gui.c:331
msgid "Yes"
msgstr "はい"
#: src/menu_gui.c:97 src/menu_gui.c:745
msgid "default"
msgstr "既定値"
#: src/menu_gui_volval.c:122
msgid "large"
msgstr "大"
#: src/menu_gui.c:117
msgid "max"
msgstr "最大値"
#: src/menu_gui.c:107
msgid "min"
msgstr "最小値"
#: src/menu_gui_volval.c:89
msgid "mute"
msgstr "ミュート"
#: src/volume.c:223
msgid "Close"
msgstr "閉じる"
#: src/menu_gui_volval.c:114
msgid "small"
msgstr ""
#~ msgid "Volume"
#~ msgstr "ボリューム"
#: src/menu_gui.c:225
msgid "title"
msgstr "タイトル"
#: src/menu_gui_volval.c:41
msgid "window1"
msgstr ""
+124 -54
View File
@@ -1,5 +1,5 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# Copyright (C) YEAR Masaki Chikama
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-19 12:40+0900\n"
"POT-Creation-Date: 2019-07-14 18:38+0900\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,71 +17,141 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/menu.c:136 src/menu.c:139
msgid "Message Skip"
msgstr ""
#: src/menu.c:144
msgid "Mouse Movement"
msgstr ""
#: src/menu.c:148 src/volume.c:198
msgid "Sound Settings"
msgstr ""
#: src/menu.c:153 src/menu.c:221
msgid "Restart"
msgstr ""
#: src/menu.c:158 src/menu.c:216
msgid "Quit"
msgstr ""
#: src/menu.c:197 src/input_modal.c:292 src/input_modal.c:485
msgid "Cancel"
msgstr ""
#: src/menu.c:203
msgid "Any unsaved progress will be lost."
msgstr ""
#: src/menu.c:216
msgid "Quit game?"
msgstr ""
#: src/menu.c:221
msgid "Restart game?"
msgstr ""
#: src/input_modal.c:268
msgid "Enter a string"
msgstr ""
#: src/input_modal.c:280
#: src/menu.c:69
#, c-format
msgid "Up to %d characters"
msgid "MAX %d charater"
msgstr ""
#: src/input_modal.c:288 src/input_modal.c:478 src/input_modal.c:483
#: src/menu_gui.c:60
msgid "InputNumber"
msgstr ""
#: src/menu_gui.c:97 src/menu_gui.c:745
msgid "default"
msgstr ""
#: src/menu_gui.c:107
msgid "min"
msgstr ""
#: src/menu_gui.c:117
msgid "max"
msgstr ""
#: src/menu_gui.c:127 src/menu_gui.c:137 src/menu_gui.c:147
msgid "0"
msgstr ""
#: src/menu_gui.c:164 src/menu_gui.c:273 src/menu_gui.c:426 src/menu_gui.c:781
msgid "OK"
msgstr ""
#: src/input_modal.c:434
msgid "Enter a number"
#: src/menu_gui.c:171 src/menu_gui.c:266
msgid "Cancel"
msgstr ""
#: src/volume.c:106
msgid "BGM"
#: src/menu_gui.c:214
msgid "InputString"
msgstr ""
#: src/volume.c:107
msgid "Sound effects"
#: src/menu_gui.c:225
msgid "title"
msgstr ""
#: src/volume.c:189
#: src/menu_gui.c:241
msgid "MAX charater"
msgstr ""
#: src/menu_gui.c:250
msgid "Notice) HANKAKU is not available"
msgstr ""
#: src/menu_gui.c:306 src/menu_gui.c:673
msgid "Exit"
msgstr ""
#: src/menu_gui.c:317
msgid "Exit System35 ?"
msgstr ""
#: src/menu_gui.c:331
msgid "Yes"
msgstr ""
#: src/menu_gui.c:338
msgid "No"
msgstr ""
#: src/menu_gui.c:374 src/menu_gui.c:658
msgid "About"
msgstr ""
#: src/menu_gui.c:400
msgid "Information1"
msgstr ""
#: src/menu_gui.c:410
msgid "Information2"
msgstr ""
#: src/menu_gui.c:420
msgid "Information3"
msgstr ""
#: src/menu_gui.c:485
msgid "MessageSkip"
msgstr ""
#: src/menu_gui.c:497 src/menu_gui.c:533 src/menu_gui.c:577 src/menu_gui.c:605
#: src/menu_gui.c:633
msgid "ON"
msgstr ""
#: src/menu_gui.c:505 src/menu_gui.c:541 src/menu_gui.c:585 src/menu_gui.c:613
#: src/menu_gui.c:641
msgid "OFF"
msgstr ""
#: src/menu_gui.c:521
msgid "MouseAutoMove"
msgstr ""
#: src/menu_gui.c:557 src/menu_gui_volval.c:43
msgid "VolumeValance"
msgstr ""
#: src/menu_gui.c:565
msgid "PCM-audio"
msgstr ""
#: src/menu_gui.c:593
msgid "CDROM-audio"
msgstr ""
#: src/menu_gui.c:621
msgid "MIDI-audio"
msgstr ""
#: src/menu_gui.c:764
msgid "MessageBox"
msgstr ""
#: src/menu_gui.c:774
msgid "Messge"
msgstr ""
#: src/menu_gui_volval.c:41
msgid "window1"
msgstr ""
#: src/menu_gui_volval.c:89
msgid "mute"
msgstr ""
#: src/volume.c:223
msgid "Close"
#: src/menu_gui_volval.c:114
msgid "small"
msgstr ""
#: src/menu_gui_volval.c:122
msgid "large"
msgstr ""
+43 -58
View File
@@ -1,27 +1,8 @@
# src_lib contains sources that can be unit-tested without linking the
# rest of xsystem35. They may reference sys_* and other small hooks;
# tests provide stub definitions for those.
add_library(src_lib STATIC
ald_manager.c
alpha_plane.c
audio_meta.c
bmp.c
cache.c
cali.c
dri.c
filecheck.c
font.c
gameresource.c
hankaku.c
mmap.c
msgqueue.c
pms.c
profile.c
qnt.c
scenario.c
utfsjis.c
variable.c
vsp.c
)
target_compile_options(src_lib PRIVATE -Wno-pointer-sign -Wall)
target_include_directories(src_lib PRIVATE .)
@@ -41,6 +22,12 @@ target_link_libraries(xsystem35 PRIVATE src_lib)
target_sources(xsystem35 PRIVATE
ags.c
ald_manager.c
alpha_plane.c
bgi.c
bmp.c
cache.c
cali.c
cdrom.bgm.c
cg.c
cmd2F.c
@@ -70,22 +57,22 @@ target_sources(xsystem35 PRIVATE
cmdy.c
cmdz.c
cursor.c
dri.c
ecopy.c
effect.c
event.c
filecheck.c
font.c
hacks.c
gfx_draw.c
gfx_image.c
gfx_video.c
image.c
input.c
input_modal.c
jpeg.c
menu.c
message.c
microui/microui.c
midi.c
modal.c
mmap.c
msgskip.c
mt19937-1.c
music.c
@@ -93,13 +80,19 @@ target_sources(xsystem35 PRIVATE
music_midi.c
nact.c
network.c
pms.c
profile.c
qnt.c
s39ain.c
savedata.c
scenario.c
scheduler.c
selection.c
system.c
texthook.c
volume.c
variable.c
virtual_pointer.c
vsp.c
xsystem35.c
)
@@ -126,6 +119,16 @@ else()
endif()
endif()
if (EMSCRIPTEN)
target_sources(xsystem35 PRIVATE menu_emscripten.c)
elseif (ANDROID)
target_sources(xsystem35 PRIVATE menu_android.c)
elseif (ENABLE_GTK)
target_sources(xsystem35 PRIVATE menu.c menu_callback.c menu_gui.c s39init.c)
else ()
target_sources(xsystem35 PRIVATE menu_sdl.c editor.c)
endif()
if (HAVE_WEBP)
target_sources(xsystem35 PRIVATE webp.c)
target_link_libraries(xsystem35 PRIVATE WebP)
@@ -137,17 +140,12 @@ if (ENABLE_DEBUGGER)
endif()
if (WIN32)
target_sources(src_lib PRIVATE win/resources.c)
target_sources(xsystem35 PRIVATE
win/dialog.c win/resources.rc win/menubar.c win/console.c)
win/dialog.c win/resources.rc win/resources.c win/menubar.c win/console.c)
endif()
if (NOT HAVE_LIBINTL)
target_sources(xsystem35 PRIVATE nls.c)
endif()
target_link_libraries(src_lib PRIVATE sdl2 freetype2 zlib)
target_link_libraries(xsystem35 PRIVATE m zlib sdl2)
target_link_libraries(src_lib PRIVATE sdl2 zlib)
target_link_libraries(xsystem35 PRIVATE m zlib sdl2 sdl2_ttf)
if (TARGET sdl2_mixer)
target_link_libraries(xsystem35 PRIVATE sdl2_mixer)
endif()
@@ -158,28 +156,18 @@ if (EMSCRIPTEN)
# Without optimizations, Asyncify generates very large code.
list(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG "-O1")
if (JSPI)
target_link_options(xsystem35 PRIVATE
-sJSPI
-sJSPI_IMPORTS=wait_vsync
)
else()
target_link_options(xsystem35 PRIVATE
-sASYNCIFY=1
-sASYNCIFY_IGNORE_INDIRECT=1
-sASYNCIFY_REMOVE=SDL_Delay
-sASYNCIFY_IMPORTS=wait_vsync
# Functions that call an asynchronous function via a function pointer must
# be added to ASYNCIFY_ADD, even if it had a direct call to another
# asynchronous function, so that the indirect call itself is instrumented.
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
)
endif()
target_link_options(xsystem35 PRIVATE
-sENVIRONMENT=web
-sMODULARIZE=1
-sEXPORT_ES6=1
-sASYNCIFY=1
-sASYNCIFY_IGNORE_INDIRECT=1
-sASYNCIFY_REMOVE=SDL_Delay
-sASYNCIFY_IMPORTS=wait_vsync
# Functions that call an asynchronous function via a function pointer must
# be added to ASYNCIFY_ADD, even if it had a direct call to another
# asynchronous function, so that the indirect call itself is instrumented.
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite
-sALLOW_MEMORY_GROWTH=1
-sNO_EXIT_RUNTIME=1
-sEXPORTED_FUNCTIONS=_main,_malloc
@@ -194,6 +182,9 @@ else() # non-emscripten, non-android
if (TARGET cJSON)
target_link_libraries(xsystem35 PRIVATE cJSON)
endif()
if (GTK3_FOUND)
target_link_libraries(xsystem35 PRIVATE PkgConfig::GTK3)
endif()
if (ENABLE_MIDI_PORTMIDI)
target_link_libraries(xsystem35 PRIVATE ${PORTMIDI})
endif()
@@ -202,17 +193,11 @@ else() # non-emscripten, non-android
add_executable(src_tests
src_tests.c
cache_test.c
font_test.c
gameresource_test.c
hankaku_test.c
qnt_test.c
variable_test.c
)
target_compile_options(src_tests PRIVATE -Wno-pointer-sign -Wall)
target_compile_definitions(src_tests PRIVATE
TEST_FONT_DIR="${PROJECT_SOURCE_DIR}/fonts")
target_link_libraries(src_tests PRIVATE src_lib sdl2)
target_link_libraries(src_tests PRIVATE src_lib)
add_test(NAME src_tests COMMAND src_tests)
file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
configure_file(testdata/test.gr ${CMAKE_CURRENT_BINARY_DIR}/testdata/test.gr COPYONLY)
endif()
+25 -37
View File
@@ -125,7 +125,7 @@ bool ags_check_param_xy(int *x, int *y) {
return true;
}
void ags_init(const char *render_driver) {
void ags_init(const char *render_driver, bool enable_zb) {
nact->ags.mouse_warp_enabled = true;
nact->ags.world_width = SYS35_DEFAULT_WIDTH;
nact->ags.world_height = SYS35_DEFAULT_HEIGHT;
@@ -138,7 +138,8 @@ void ags_init(const char *render_driver) {
nact->ags.font_type = FONT_GOTHIC;
nact->ags.text_decoration_type = 0;
nact->ags.text_decoration_color = 0;
nact->ags.font_weight = FONT_WEIGHT_BOLD;
nact->ags.enable_zb = enable_zb;
nact->ags.font_weight = enable_zb ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
gfx_Initialize(render_driver);
event_init();
@@ -192,7 +193,7 @@ void ags_setViewArea(int x, int y, int width, int height) {
nact->ags.view_area.y = y;
nact->ags.view_area.w = width;
nact->ags.view_area.h = height;
gfx_setViewSize(width, height);
gfx_setWindowSize(width, height);
}
void ags_setWindowTitle(const char *title_utf8) {
@@ -201,33 +202,20 @@ void ags_setWindowTitle(const char *title_utf8) {
gfx_setWindowTitle(buf);
}
void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth) {
SDL_DisplayMode dm;
switch (display) {
case AGS_DISPLAY_CURRENT:
SDL_GetCurrentDisplayMode(0, &dm);
*width = dm.w;
*height = dm.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
case AGS_DISPLAY_DIB:
*width = nact->ags.world_width;
*height = nact->ags.world_height;
*depth = nact->ags.world_depth;
break;
case AGS_DISPLAY_VIEW_AREA:
SDL_GetCurrentDisplayMode(0, &dm);
*width = nact->ags.view_area.w;
*height = nact->ags.view_area.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
case AGS_DISPLAY_DESKTOP:
SDL_GetDesktopDisplayMode(0, &dm);
*width = dm.w;
*height = dm.h;
*depth = SDL_BITSPERPIXEL(dm.format);
break;
}
void ags_getDIBInfo(DispInfo *info) {
info->width = nact->ags.world_width;
info->height = nact->ags.world_height;
info->depth = nact->ags.world_depth;
}
void ags_getViewAreaInfo(DispInfo *info) {
gfx_getWindowInfo(NULL, NULL, &info->depth);
info->width = nact->ags.view_area.w;
info->height = nact->ags.view_area.h;
}
void ags_getWindowInfo(DispInfo *info) {
gfx_getWindowInfo(&info->width, &info->height, &info->depth);
}
void ags_setExposeSwitch(bool expose) {
@@ -478,7 +466,7 @@ int ags_drawString(int x, int y, const char *src, int col, int size, SDL_Rect *r
SDL_Surface *ags_drawStringToSurface(const char *str, int r, int g, int b, FontSpec font) {
char *utf8 = toUTF8(str);
SDL_Color color = {r, g, b, 255};
SDL_Surface *sf = font_render_text(font, utf8, color, true);
SDL_Surface *sf = font_render_text(font, utf8, color);
free(utf8);
return sf;
}
@@ -591,7 +579,7 @@ void ags_alpha_copyArea(int sx, int sy, int w, int h, int dx, int dy) {
alpha_copy_area(nact->ags.dib, sx, sy, w, h, dx, dy);
}
void ags_alpha_getPixel(int x, int y, vmvar_t *pic) {
void ags_alpha_getPixel(int x, int y, int *pic) {
if (nact->ags.world_depth == 8) return;
if (!ags_check_param_xy(&x, &y)) {
@@ -728,10 +716,10 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
}
#ifdef __EMSCRIPTEN__
if (!sl_is_s380 && !for_selection) {
if (!for_selection) {
// We can't move the actual cursor in the browser, but can change the
// internal mouse coordinates. This can help with keyboard/gamepad
// navigation in Toushin Toshi 2.
// navigation.
event_set_mouse_internal_location(x, y);
EM_ASM({ xsystem35.shell.showMouseMoveEffect($0, $1); }, x, y);
sys_sleep(cursor_move_time);
@@ -745,11 +733,11 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
for (int i = 1; i < 8; i++) {
int xi = ((dx*i*i*i) >> 9) - ((3*dx*i*i)>> 6) + ((3*dx*i) >> 3) + p.x;
int yi = ((dy*i*i*i) >> 9) - ((3*dy*i*i)>> 6) + ((3*dy*i) >> 3) + p.y;
gfx_warpMouse(xi, yi);
event_set_mouse_location(xi, yi);
sys_sleep(cursor_move_time / 7);
}
gfx_warpMouse(x, y);
} else if (!sl_is_s380 && !for_selection) {
event_set_mouse_location(x, y);
} else if (!for_selection) {
event_set_mouse_internal_location(x, y);
sys_sleep(cursor_move_time);
}
+12 -9
View File
@@ -102,12 +102,11 @@ typedef struct {
uint32_t index;
} PixelColor;
enum ags_display {
AGS_DISPLAY_CURRENT,
AGS_DISPLAY_DIB,
AGS_DISPLAY_VIEW_AREA,
AGS_DISPLAY_DESKTOP,
};
typedef struct {
int width;
int height;
int depth;
} DispInfo;
struct _ags {
SDL_Color pal[256]; /* system palette */
@@ -129,15 +128,17 @@ struct _ags {
int text_decoration_color;
bool mouse_warp_enabled;
bool enable_zb;
bool noantialias; /* antialias を使用しない */
bool noimagecursor; /* リソースファイルのカーソルを読みこまない */
bool virtualpointer; /* enable the virtual mouse pointer (trackpad-style touch) */
};
typedef struct _ags ags_t;
extern SDL_Surface *main_surface;
/* 初期化関係 */
void ags_init(const char *render_driver);
void ags_init(const char *render_driver, bool enable_zb);
void ags_remove(void);
void ags_reset(void);
@@ -145,7 +146,9 @@ void ags_reset(void);
extern void ags_setWorldSize(int width, int height, int depth);
extern void ags_setViewArea(int x, int y, int width, int height);
extern void ags_setWindowTitle(const char *title_utf8);
extern void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth);
extern void ags_getDIBInfo(DispInfo *info);
extern void ags_getWindowInfo(DispInfo *info);
extern void ags_getViewAreaInfo(DispInfo *info);
extern bool ags_check_param(int *x, int *y, int *w, int *h);
extern bool ags_check_param_xy(int *x, int *y);
extern surface_t *ags_getDIB();
@@ -197,7 +200,7 @@ extern void ags_alpha_uppercut(int sx, int sy, int w, int h, int s, int d);
extern void ags_alpha_lowercut(int sx, int sy, int w, int h, int s, int d);
extern void ags_alpha_setLevel(int x, int y, int w, int h, int lv);
extern void ags_alpha_copyArea(int sx, int sy, int w, int h, int dx, int dy);
extern void ags_alpha_getPixel(int x, int y, vmvar_t *pic);
extern void ags_alpha_getPixel(int x, int y, int *pic);
extern void ags_alpha_setPixel(int x, int y, int w, int h, uint8_t *b);
/* fader */
+7 -37
View File
@@ -33,34 +33,18 @@
/* drifiles object */
static drifiles *dri[DRIFILETYPEMAX];
static Cache *dri_cache;
#ifndef ALD_CACHE_SIZE
#define ALD_CACHE_SIZE (10 << 20)
#endif
static uint32_t int_hash(const void *key) {
return (uint32_t)*(const int *)key;
}
static bool int_equal(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
/* cache handler for dri file */
static cacher *cacheid;
/*
* free dridata
* dfile: dridata to be free
*/
static void ald_free(void *data) {
dridata *dfile = data;
static void ald_free(dridata *dfile) {
free(dfile->data_raw);
free(dfile);
}
static bool ald_is_pinned(const void *data) {
return ((const dridata *)data)->refcnt != 0;
}
bool ald_is_linked(DRIFILETYPE type, int no) {
if (type >= DRIFILETYPEMAX || !dri[type])
return false;
@@ -96,12 +80,11 @@ dridata *ald_getdata(DRIFILETYPE type, int no) {
if (dri[type]->mmapped) return dri_getdata(dri[type], no);
/* not mmapped */
int key = (type << 16) + no;
if (NULL == (ddata = cache_get(dri_cache, &key))) {
if (NULL == (ddata = (dridata *)cache_foreach(cacheid, (type << 16) + no))) {
ddata = dri_getdata(dri[type], no);
if (ddata != NULL) {
ddata->refcnt = 0;
ddata->cached = cache_insert(dri_cache, &key, ddata, ddata->size) == CACHE_INSERT_OK;
cache_insert(cacheid, (type << 16) + no, (void *)ddata, ddata->size, &(ddata->refcnt));
}
}
if (ddata != NULL)
@@ -122,8 +105,6 @@ void ald_freedata(dridata *data) {
free(data);
} else {
data->refcnt--;
if (!data->cached && data->refcnt == 0)
ald_free(data);
}
}
@@ -131,15 +112,8 @@ void ald_init(int type, const char **file, int cnt, bool use_mmap) {
if (type >= DRIFILETYPEMAX || cnt <= 0)
return;
dri[type] = dri_init(file, cnt, use_mmap);
if (!dri[type]->mmapped && !dri_cache) {
CacheOps ops = {
.key_size = sizeof(int),
.hash = int_hash,
.equal = int_equal,
.destroy = ald_free,
.is_pinned = ald_is_pinned,
};
dri_cache = cache_new((size_t)ALD_CACHE_SIZE, &ops);
if (!dri[type]->mmapped) {
cacheid = cache_new(ald_free);
}
}
@@ -148,7 +122,3 @@ int ald_get_maxno(DRIFILETYPE type) {
return 0;
return dri[type]->maxno;
}
CacheStats ald_get_cache_stats(void) {
return cache_get_stats(dri_cache);
}
+1 -2
View File
@@ -25,7 +25,6 @@
#define __ALD_MANAGER__
#include "portab.h"
#include "cache.h"
#include "dri.h"
#define DRIFILETYPEMAX 7
@@ -45,6 +44,6 @@ bool ald_exists(DRIFILETYPE type, int no);
dridata *ald_getdata(DRIFILETYPE type, int no);
void ald_freedata(dridata *data);
int ald_get_maxno(DRIFILETYPE type);
CacheStats ald_get_cache_stats(void);
#endif /* !__ALD_MANAGER__ */
+2 -78
View File
@@ -1,5 +1,5 @@
/*
* audio_meta.c: BGI (BGM information) / WAI (wave information) parser
* bgi.c: BGI (BGM information) parser
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
@@ -23,12 +23,10 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include "portab.h"
#include "system.h"
#include "audio_meta.h"
#include "LittleEndian.h"
#include "bgi.h"
#define BGI_MAX 100
@@ -84,77 +82,3 @@ bgi_t *bgi_find(int no) {
}
return NULL;
}
static int *wai_channels;
static int wai_count;
static void wai_unload(void) {
free(wai_channels);
wai_channels = NULL;
wai_count = 0;
}
bool wai_load(const char *path) {
wai_unload();
if (!path)
return false;
FILE *fp = fopen(path, "rb");
if (!fp)
return false;
uint8_t header[24];
if (fread(header, 1, sizeof(header), fp) != sizeof(header) ||
header[0] != 'X' || header[1] != 'I' || header[2] != '2' || header[3] != '\0')
{
WARNING("not WAI file");
fclose(fp);
return false;
}
int count = LittleEndian_getDW(header, 8);
if (count <= 0) {
WARNING("invalid WAI record count: %d", count);
fclose(fp);
return false;
}
int version = LittleEndian_getDW(header, 12);
if (version != 3) {
WARNING("unsupported WAI version: %d", version);
fclose(fp);
return false;
}
int *channels = calloc(count, sizeof(*channels));
if (!channels) {
fclose(fp);
return false;
}
uint8_t record[12];
for (int i = 0; i < count; i++) {
if (fread(record, 1, sizeof(record), fp) != sizeof(record)) {
WARNING("truncated WAI file");
free(channels);
fclose(fp);
return false;
}
channels[i] = LittleEndian_getDW(record, 8);
}
fclose(fp);
wai_channels = channels;
wai_count = count;
return true;
}
bool wai_loaded(void) {
return wai_channels != NULL;
}
int wai_mixch(int no) {
if (!wai_channels || no < 0 || no >= wai_count)
return -1;
return wai_channels[no];
}
+1 -5
View File
@@ -1,5 +1,5 @@
/*
* audio_meta.h: BGI (BGM information) / WAI (wave information) parser
* bgi.c: BGI (BGM information) parser
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
@@ -35,8 +35,4 @@ typedef struct {
extern bool bgi_read(const char *path);
extern bgi_t *bgi_find(int no);
bool wai_load(const char *path);
bool wai_loaded(void);
int wai_mixch(int no);
#endif /* _BGI_H__ */
+1 -6
View File
@@ -23,9 +23,8 @@
#include "portab.h"
#include "nact.h"
#include "bgm.h"
#include "audio_meta.h"
#include "bgi.h"
#include "system.h"
#include "music_private.h"
bool musbgm_init(DRIFILETYPE type, int base_no) {
if (type == DRIFILE_BGM)
@@ -88,7 +87,3 @@ void musbgm_wait(int no, int timeout) {
sys_wait_vsync();
}
}
void musbgm_reapply_valance(void) {
EM_ASM({ xsystem35.cdPlayer.setValance($0); }, prv.volval[BGM_VOLVAL_CH]);
}
-1
View File
@@ -33,6 +33,5 @@ int musbgm_getlen(int no);
bool musbgm_isplaying(int no);
void musbgm_stopall(int time);
void musbgm_wait(int no, int timeout);
void musbgm_reapply_valance(void);
#endif /* __BGM_H__ */
+6 -19
View File
@@ -32,27 +32,17 @@
#include "system.h"
#include "nact.h"
#include "bgm.h"
#include "audio_meta.h"
#include "bgi.h"
#include "music_private.h"
#include "ald_manager.h"
static DRIFILETYPE dri_type;
static int base_no;
static int current_no;
static int current_vol = 100; // game-requested volume (0-100)
static Mix_Music *mix_music;
static dridata* dfile;
static Uint32 start_time;
static void apply_music_volume(int vol) {
current_vol = vol;
Mix_VolumeMusic(current_vol * prv.volval[BGM_VOLVAL_CH] * MIX_MAX_VOLUME / (100 * 100));
}
void musbgm_reapply_valance(void) {
apply_music_volume(current_vol);
}
static void free_music() {
current_no = 0;
if (mix_music) {
@@ -111,14 +101,11 @@ bool musbgm_play(int no, int time, int vol, int loop_count) {
if (!bgm_load(no))
return false;
// We don't use the loop positions in the BGI file; SDL_mixer instead
// understands loop positions in the WAVE's "smpl" chunk.
// We don't use the loop information in the BGI file, but SDL_mixer
// understands loop info in the WAVE's "smpl" chunk.
apply_music_volume(vol);
// SDL_mixer counts repeats after the first play, while System 3.x counts
// the total number of plays. In both APIs, an infinite loop is special.
int loops = loop_count == 0 ? -1 : loop_count - 1;
if (Mix_FadeInMusic(mix_music, loops, time * 10) != 0) {
Mix_VolumeMusic(vol * MIX_MAX_VOLUME / 100);
if (Mix_FadeInMusic(mix_music, loop_count == 0 ? -1 : loop_count, time * 10) != 0) {
free_music();
return false;
}
@@ -137,7 +124,7 @@ void musbgm_fade(int no, int time, int vol) {
return;
// SDL_mixer doesn't provide arbitrary fading, so just set the volume immediately.
apply_music_volume(vol);
Mix_VolumeMusic(vol * MIX_MAX_VOLUME / 100);
}
int musbgm_getpos(int no) {
+29
View File
@@ -0,0 +1,29 @@
/* XPM */
static const char *virtual_cursor[] = {
/* width height num_colors chars_per_pixel */
"16 19 3 1",
/* colors */
"X c #000000",
". c #ffffff",
" c None",
/* pixels */
"X ",
"XX ",
"X.X ",
"X..X ",
"X...X ",
"X....X ",
"X.....X ",
"X......X ",
"X.......X ",
"X........X ",
"X.....XXXXX ",
"X..X..X ",
"X.X X..X ",
"XX X..X ",
"X X..X ",
" X..X ",
" X..X ",
" X..X ",
" XX "
};
+113 -160
View File
@@ -1,5 +1,8 @@
/*
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
* cache.c general cache manager
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* 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
@@ -16,180 +19,130 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* $Id: cache.c,v 1.5 2003/07/21 23:06:47 chikama Exp $ */
#include "config.h"
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include "portab.h"
#include "cache.h"
#include <stdlib.h>
#include <string.h>
/* maximum cache size (in MB) */
#ifndef CACHE_TOTALSIZE
#define CACHE_TOTALSIZE 20
#endif
#define CACHE_BUCKETS 1024
static int totalsize; /* total size in cache */
static int dummyfalse = 0; /* dummy in_use flag */
static int dummytrue = 1; /* dummy in_use flag */
typedef struct CacheEntry {
struct CacheEntry *hash_next;
struct CacheEntry *lru_prev;
struct CacheEntry *lru_next;
uint32_t hash;
size_t cost;
void *data;
unsigned char key[];
} CacheEntry;
/*
* static methods
*/
static void remove_in_cache(cacher *id);
struct Cache {
CacheOps ops;
CacheEntry *buckets[CACHE_BUCKETS];
CacheEntry *lru_head;
CacheEntry *lru_tail;
size_t capacity;
size_t size;
size_t count;
size_t hits;
size_t misses;
};
static bool is_pinned(const Cache *cache, const CacheEntry *entry) {
return cache->ops.is_pinned && cache->ops.is_pinned(entry->data);
/*
* Remove data in cache
* id: cache handler
*/
static void remove_in_cache(cacher *id) {
cacheinfo *ip = id->top;
cacheinfo *ic = ip->next;
if (!ic)
return;
while(ic->next != NULL) {
if (!*ic->in_use) {
totalsize -= ic->size;
ip->next = ic->next;
id->free_(ic->data);
free(ic);
} else {
ip = ic;
}
ic = ip->next;
}
return;
}
static void lru_remove(Cache *cache, CacheEntry *entry) {
if (entry->lru_prev)
entry->lru_prev->lru_next = entry->lru_next;
else
cache->lru_head = entry->lru_next;
if (entry->lru_next)
entry->lru_next->lru_prev = entry->lru_prev;
else
cache->lru_tail = entry->lru_prev;
/*
* Create new cache object
* delcallback: callback function for delete cache data object
* return: new cache handler
*/
cacher *cache_new(void *delcallback) {
cacher *c = calloc(1, sizeof(cacher));
c->top = calloc(1, sizeof(cacheinfo));
c->top->next = NULL;
c->top->in_use = &dummytrue;
c->free_ = delcallback;
return c;
}
static void lru_prepend(Cache *cache, CacheEntry *entry) {
entry->lru_prev = NULL;
entry->lru_next = cache->lru_head;
if (cache->lru_head)
cache->lru_head->lru_prev = entry;
else
cache->lru_tail = entry;
cache->lru_head = entry;
/*
* Insert data to cache
* id : cache handler
* key : data key
* data : data to be cached
* size : data size
* in_use: in_use mark pointer, if in_use is nonzero, dont remove from cache
*/
void cache_insert(cacher *id, int key, void *data, int size, int *in_use) {
cacheinfo *i = id->top;
if (CACHE_TOTALSIZE <= (totalsize >> 20)) {
remove_in_cache(id);
}
while(i->next != NULL) {
i = i->next;
}
i->key = key;
i->data = data;
i->size = size;
i->next = calloc(1, sizeof(cacheinfo));
i->next->next = NULL;
if (in_use) {
i->in_use = in_use;
} else {
i->in_use = &dummyfalse;
}
totalsize += size;
}
static void remove_entry(Cache *cache, CacheEntry *entry) {
CacheEntry **link = &cache->buckets[entry->hash % CACHE_BUCKETS];
while (*link != entry)
link = &(*link)->hash_next;
*link = entry->hash_next;
lru_remove(cache, entry);
cache->size -= entry->cost;
cache->count--;
cache->ops.destroy(entry->data);
free(entry);
}
static CacheEntry *find_entry(Cache *cache, const void *key, uint32_t hash) {
for (CacheEntry *entry = cache->buckets[hash % CACHE_BUCKETS]; entry;
entry = entry->hash_next) {
if (entry->hash == hash && cache->ops.equal(entry->key, key))
return entry;
/*
* Search data in cache
* id : cache handler
* key: data search key
* return: pointer to cached data
*/
void *cache_foreach(cacher *id, int key) {
cacheinfo *i = id->top;
while(i != NULL) {
if (i->key == key) {
return i->data;
}
i = i->next;
}
return NULL;
}
Cache *cache_new(size_t capacity, const CacheOps *ops) {
if (!ops || !ops->key_size || !ops->hash || !ops->equal || !ops->destroy)
return NULL;
Cache *cache = calloc(1, sizeof(Cache));
if (!cache)
return NULL;
cache->ops = *ops;
cache->capacity = capacity;
return cache;
}
void cache_clear(cacher *id) {
cacheinfo *ic = id->top->next;
cacheinfo *next;
void cache_destroy(Cache *cache) {
if (!cache)
return;
while (cache->lru_tail)
remove_entry(cache, cache->lru_tail);
free(cache);
}
void *cache_get(Cache *cache, const void *key) {
if (!cache || !key)
return NULL;
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
if (!entry) {
cache->misses++;
return NULL;
while (ic != NULL) {
next = ic->next;
if (ic->data) {
totalsize -= ic->size;
id->free_(ic->data);
}
free(ic);
ic = next;
}
cache->hits++;
lru_remove(cache, entry);
lru_prepend(cache, entry);
return entry->data;
}
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost) {
if (!cache || !key || !data)
return CACHE_INSERT_NOMEM;
uint32_t hash = cache->ops.hash(key);
if (find_entry(cache, key, hash))
return CACHE_INSERT_EXISTS;
if (cost > cache->capacity)
return CACHE_INSERT_FULL;
CacheEntry *entry = malloc(sizeof(CacheEntry) + cache->ops.key_size);
if (!entry)
return CACHE_INSERT_NOMEM;
for (CacheEntry *old = cache->lru_tail, *prev;
old && cache->size > cache->capacity - cost; old = prev) {
prev = old->lru_prev;
if (!is_pinned(cache, old))
remove_entry(cache, old);
}
if (cache->size > cache->capacity - cost) {
free(entry);
return CACHE_INSERT_FULL;
}
entry->hash = hash;
entry->cost = cost;
entry->data = data;
memcpy(entry->key, key, cache->ops.key_size);
unsigned bucket = hash % CACHE_BUCKETS;
entry->hash_next = cache->buckets[bucket];
cache->buckets[bucket] = entry;
lru_prepend(cache, entry);
cache->size += cost;
cache->count++;
return CACHE_INSERT_OK;
}
bool cache_remove(Cache *cache, const void *key) {
if (!cache || !key)
return false;
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
if (!entry || is_pinned(cache, entry))
return false;
remove_entry(cache, entry);
return true;
}
size_t cache_clear(Cache *cache) {
if (!cache)
return 0;
for (CacheEntry *entry = cache->lru_tail, *prev; entry; entry = prev) {
prev = entry->lru_prev;
if (!is_pinned(cache, entry))
remove_entry(cache, entry);
}
return cache->count;
}
CacheStats cache_get_stats(const Cache *cache) {
if (!cache)
return (CacheStats){0};
return (CacheStats){
.count = cache->count,
.size = cache->size,
.capacity = cache->capacity,
.hits = cache->hits,
.misses = cache->misses,
};
id->top->next = NULL;
}
+28 -46
View File
@@ -1,5 +1,8 @@
/*
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
* cache.h general cache manager
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* 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
@@ -16,54 +19,33 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef XSYSTEM35_CACHE_H
#define XSYSTEM35_CACHE_H
/* $Id: cache.h,v 1.2 2003/07/21 23:06:47 chikama Exp $ */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifndef __CASHE__
#define __CASHE__
typedef struct Cache Cache;
#include "portab.h"
typedef struct {
size_t key_size;
uint32_t (*hash)(const void *key);
bool (*equal)(const void *a, const void *b);
void (*destroy)(void *data);
bool (*is_pinned)(const void *data);
} CacheOps;
/* cache controlr infomartion */
struct _cacheinfo {
int key; /* key of data */
int size; /* data size */
struct _cacheinfo *next; /* next data */
int *in_use; /* if *in_use is nonzero, dont remove from cache */
void *data; /* real data */
};
typedef struct _cacheinfo cacheinfo;
typedef enum {
CACHE_INSERT_OK,
CACHE_INSERT_EXISTS,
CACHE_INSERT_FULL,
CACHE_INSERT_NOMEM,
} CacheInsertResult;
/* cache handler */
struct _cacher {
void (*free_)(void *); /* free data callback */
struct _cacheinfo *top; /* pointer to data */
};
typedef struct _cacher cacher;
typedef struct {
size_t count;
size_t size;
size_t capacity;
size_t hits;
size_t misses;
} CacheStats;
extern cacher *cache_new(void *delcallback);
extern void cache_insert(cacher *id, int key, void *data, int size, int *in_use);
extern void *cache_foreach(cacher *id, int key);
extern void cache_clear(cacher *id);
Cache *cache_new(size_t capacity, const CacheOps *ops);
void cache_destroy(Cache *cache);
/* The returned pointer is owned by the cache and is invalidated by removal. */
void *cache_get(Cache *cache, const void *key);
/* Ownership of data is transferred only when CACHE_INSERT_OK is returned. */
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost);
/* Pinned entries cannot be removed. */
bool cache_remove(Cache *cache, const void *key);
/* Removes all unpinned entries and returns the number of entries left. */
size_t cache_clear(Cache *cache);
/* Returns zero-filled statistics when cache is NULL. */
CacheStats cache_get_stats(const Cache *cache);
#endif /* XSYSTEM35_CACHE_H */
#endif /* !__CASHE__ */
-117
View File
@@ -1,117 +0,0 @@
/*
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "cache.h"
#include "unittest.h"
#include <stdlib.h>
typedef struct {
int value;
bool pinned;
} TestValue;
static int destroyed;
static uint32_t int_hash(const void *key) {
return (uint32_t)*(const int *)key;
}
static bool int_equal(const void *a, const void *b) {
return *(const int *)a == *(const int *)b;
}
static void destroy_value(void *data) {
destroyed++;
free(data);
}
static bool value_is_pinned(const void *data) {
return ((const TestValue *)data)->pinned;
}
static TestValue *new_value(int value) {
TestValue *data = malloc(sizeof(TestValue));
ASSERT_TRUE(data);
data->value = value;
data->pinned = false;
return data;
}
void cache_test(void) {
CacheOps ops = {
.key_size = sizeof(int),
.hash = int_hash,
.equal = int_equal,
.destroy = destroy_value,
.is_pinned = value_is_pinned,
};
Cache *cache = cache_new(2, &ops);
ASSERT_TRUE(cache);
destroyed = 0;
CacheStats stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.count, 0);
ASSERT_EQUAL(stats.size, 0);
ASSERT_EQUAL(stats.capacity, 2);
ASSERT_EQUAL(stats.hits, 0);
ASSERT_EQUAL(stats.misses, 0);
// A lookup makes k1 most-recently used, so inserting k3 evicts k2.
int k1 = 1, k2 = 2, k3 = 3, k4 = 4, k5 = 5;
ASSERT_EQUAL(cache_insert(cache, &k1, new_value(1), 1), CACHE_INSERT_OK);
ASSERT_EQUAL(cache_insert(cache, &k2, new_value(2), 1), CACHE_INSERT_OK);
stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.count, 2);
ASSERT_EQUAL(stats.size, 2);
ASSERT_EQUAL(stats.capacity, 2);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
ASSERT_EQUAL(cache_insert(cache, &k3, new_value(3), 1), CACHE_INSERT_OK);
ASSERT_NULL(cache_get(cache, &k2));
stats = cache_get_stats(cache);
ASSERT_EQUAL(stats.hits, 1);
ASSERT_EQUAL(stats.misses, 1);
// Pinned entries survive both capacity eviction and cache_clear().
((TestValue *)cache_get(cache, &k1))->pinned = true;
ASSERT_EQUAL(cache_insert(cache, &k4, new_value(4), 1), CACHE_INSERT_OK);
ASSERT_NULL(cache_get(cache, &k3));
((TestValue *)cache_get(cache, &k4))->pinned = true;
// Insertion fails when every remaining entry is pinned.
TestValue *rejected = new_value(5);
ASSERT_EQUAL(cache_insert(cache, &k5, rejected, 1), CACHE_INSERT_FULL);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k4))->value, 4);
ASSERT_EQUAL(cache_clear(cache), 2);
ASSERT_EQUAL(destroyed, 2);
free(rejected);
// Once unpinned, cache_clear() destroys the remaining entries.
((TestValue *)cache_get(cache, &k1))->pinned = false;
((TestValue *)cache_get(cache, &k4))->pinned = false;
ASSERT_EQUAL(cache_clear(cache), 0);
ASSERT_EQUAL(destroyed, 4);
cache_destroy(cache);
stats = cache_get_stats(NULL);
ASSERT_EQUAL(stats.count, 0);
ASSERT_EQUAL(stats.size, 0);
ASSERT_EQUAL(stats.capacity, 0);
ASSERT_EQUAL(stats.hits, 0);
ASSERT_EQUAL(stats.misses, 0);
}
+9 -9
View File
@@ -24,10 +24,10 @@
#include <stdio.h>
#include <stdlib.h>
#include "portab.h"
#include "system.h"
#include "cali.h"
#include "variable.h"
#include "scenario.h"
#include "nact.h"
#include "xsystem35.h"
#define OP_AND 0x74
#define OP_OR 0x75
@@ -49,7 +49,7 @@
#define CALI_DEPTH_MAX 256
static vmvar_t *getVar(int c0, struct VarRef *ref) {
static int *getVar(int c0, struct VarRef *ref) {
int addr = sl_getIndex();
int var;
if ((c0 & 0x40) == 0) {
@@ -63,7 +63,7 @@ static vmvar_t *getVar(int c0, struct VarRef *ref) {
c1 = sl_getc();
var = c0 << 8 | c1;
int index = getCaliValue();
vmvar_t *store = v_ref_indexed(var, index, ref);
int *store = v_ref_indexed(var, index, ref);
if (!store)
WARNING("%03d:%05x: Out of bounds index access: %s[%d]", sl_getPage(), addr, v_name(var), index);
return store;
@@ -74,15 +74,15 @@ static vmvar_t *getVar(int c0, struct VarRef *ref) {
return NULL;
}
}
vmvar_t *store = v_ref(var, ref);
int *store = v_ref(var, ref);
if (!store)
WARNING("%03d:%05x: Out of bounds array access: %s", sl_getPage(), addr, v_name(var));
return store;
}
// Returns a pointer to the variable
vmvar_t *getCaliVariable(void) {
vmvar_t *c0 = getVar(sl_getc(), NULL);
int *getCaliVariable(void) {
int *c0 = getVar(sl_getc(), NULL);
if (sl_getc() != OP_END) {
SYSERROR("Invalid variable expression at %03d:%05x", sl_getPage(), sl_getIndex());
}
@@ -98,7 +98,7 @@ bool getCaliArray(struct VarRef *ref) {
}
// For variable assignment commands
vmvar_t *getVariable(void) {
int *getVariable(void) {
return getVar(sl_getc(), NULL);
}
@@ -112,7 +112,7 @@ int getCaliValue(void) {
while ((c0 = sl_getc()) != OP_END) {
if (c0 & 0x80) { // variable
int c1 = sl_getcAt(sl_getIndex());
vmvar_t *t;
int *t;
if (c0 == 0xc0) {
if (c1 == OP_C0_INDEX || c1 >= 0x34) goto l_var;
c1 = sl_getc();

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