493 Commits
Author SHA1 Message Date
Nunuhara Cabbage d3cb6b4001 Fix segfault in dd1_save_walk_data
Fix null pointer dereference when input array is not allocated and walk
data isn't found.
2025-09-28 18:52:05 -07:00
Nunuhara Cabbage 2198bf5970 Implement support for screenshots on Windows 2025-09-28 15:28:33 -07:00
Nunuhara Cabbage 0a152f0e34 Fix MonsterInfo parsing bug on Windows
For reasons which I can't explain, the string "タイプ = ループ" doesn't
work correctly as a format for sscanf on mingw.

Instead, we match the exact string including whitespace.
2025-09-28 14:30:17 -07:00
Nunuhara Cabbage 2a3b636bb0 Fix windows build instructions
make, nasm and diffutils are FFmpeg build dependencies.
2025-09-28 11:52:02 -07:00
Nunuhara Cabbage 1506d085db Merge pull request #284 from kichikuou/debugger
debugger: Display values of reference variables
2025-09-28 08:06:39 -07:00
kichikuou 729957b842 debugger: Display values of reference variables
Previously, the debugger displayed `ref int` variables like this:

    ref_int: <unsupported-data-type>
    <void>: <unsupported-data-type>

This commit introduces `dbg_variable_to_string()` to dereference these
variables and display their underlying value. Both the command-line and
DAP debugger interfaces have been updated to use this new functionality.
2025-09-28 11:11:24 +09:00
Nunuhara Cabbage 6f661fb370 Add Alt+S keybinding to save screenshot
Not implemented on android or windows (yet). Zenity must be installed
for file picker dialog.
2025-09-27 11:18:55 -07:00
Nunuhara Cabbage 57a73cd924 Add F11 keybinding to toggle fullscreen 2025-09-26 17:50:21 -07:00
Nunuhara Cabbage 6424efa6ab Merge pull request #283 from kichikuou/pascha2
Various fixes for Pastel Chime Continue
2025-09-26 17:44:22 -07:00
kichikuou dc4a6f1dfa game_compatibility.md: Mark Pascha C++ as supported 2025-09-27 09:02:51 +09:00
kichikuou b53c511b4a game_compatibility.md: Mark Pastel Chime Continue as supported 2025-09-26 10:24:15 +09:00
kichikuou 0c75f21985 Add stubs for some music related SACT2 functions
This makes Pastel Chime Continue's music mode work, except for the
spectrum analyzer.
2025-09-26 10:24:15 +09:00
kichikuou b4c3818d6b Validate dungeon coordinates in some HLL functions
During dungeon initialization, these functions may be called with
uninitialized variables that hold the coordinates from the previous
dungeon exploration.
2025-09-26 10:24:15 +09:00
kichikuou fe61704b90 PastelChime2.DungeonDataLoad: Remove hardcoded dungeon dimensions
Replaced the hardcoded 40x3x40 dungeon size with dimensions read
dynamically from the `m_dsd` data structure. This allows for loading
dungeons of various sizes.
2025-09-26 10:24:15 +09:00
kichikuou 199c91dd6c DrawField: Do not set lock status on non-existent doors
This change adds a check to ensure that a door is present before
attempting to set its lock status.

This is necessary because the trap that locks doors has a bug and
attempts to lock doors in the wrong direction.
2025-09-26 10:22:58 +09:00
kichikuou 828971ce19 PastelChime2: Do no prune corridor cells with doors
PastelChime2.DLL does not have this logic, but without it it may
generate maps that are impossible to complete.
2025-09-23 14:56:17 +09:00
kichikuou e1b1208438 PastelChime2.DungeonDataLoad: Initialize map after loading 2025-09-23 14:56:17 +09:00
kichikuou b42e59c494 PastelChime2: Do not place dark floor tile directly above exit 2025-09-23 14:56:17 +09:00
Nunuhara Cabbage 0d689ac1c2 Merge pull request #282 from kichikuou/HTTPDownloader
Add stubs for HTTPDownloader
2025-09-22 18:36:12 -07:00
kichikuou aba2a53afe Add stubs for HTTPDownloader
This fixes https://github.com/kichikuou/xsystem4-android/issues/22.

Once the user grants network access permission, Rance Quest will call
HTTPDownloader.Post upon startup, making it impossible to start the
game without this change.
2025-09-23 07:25:18 +09:00
Nunuhara Cabbage 30d599f161 Merge pull request #280 from kichikuou/fix-277
Fix leak of dungeon_renderer
2025-09-21 10:34:45 -07:00
Nunuhara Cabbage 7817592728 Merge pull request #279 from kichikuou/fix-278
Fix flag index calculation in PastelChime2_Field_UpdateDoors
2025-09-21 10:34:00 -07:00
kichikuou 85244a304a Fix leak of dungeon_renderer
This fixes #277.

Now dungeon_load_dungeon() and dungeon_load_texture() can be called
multiple times without causing memory leaks.

This change also simplifies the loading process by removing
dungeon_finalize_loading() helper function, as the map only depends on
ctx->dgn, while the renderer only depends on ctx->dtx.
2025-09-21 11:02:18 +09:00
kichikuou f2d26c2a46 Fix flag index calculation in PastelChime2_Field_UpdateDoors
This fixes #278.
2025-09-20 14:48:22 +09:00
Nunuhara Cabbage d48f637744 Fix 'é' rendering in EN Rance Quest
`str[i]` returns 165 for '・' in English version of Rance Quest Magnum.
(Fixes rendering of 'Café Artful' and 'Tomato Purée'.)
2025-09-18 18:38:43 -07:00
Nunuhara Cabbage 008d43be1b Implement EFFECT_ROTATE_OUT
Used in Rance Quest.
2025-09-15 07:05:13 -07:00
Nunuhara Cabbage 05b78e6dc6 Fix text rendering issue in Rance Quest
Use ceil(threshold) as cutoff distance in dilate shader, and tone down
the strength of the fractional part of the threshold.

Take bold width into account during layout.

This fixes the text rendering of name plates in Rance Quest.
2025-09-13 21:46:00 -07:00
Nunuhara Cabbage b28624203a Merge pull request #276 from kichikuou/generate
Implement PastelChime2.AutoDungeonE_Create
2025-09-13 09:38:33 -07:00
Nunuhara Cabbage e943ef8ea9 Merge pull request #275 from kichikuou/brightness
Implement SACT2.SP_SetBrightness
2025-09-13 09:38:14 -07:00
Nunuhara Cabbage d547919713 Merge pull request #274 from kichikuou/dap-evaluate
debugger_dap: Respect frameId in evaluate requests
2025-09-13 09:37:49 -07:00
kichikuou ddf6ac5088 Implement PastelChime2.AutoDungeonE_Create
This function is responsible for generating random dungeons for Pastel
Chime Continue.

The dgn_generate_drawfield() function implements the main logic. This
algorithm constructs the dungeon through a multi-step process involving
room creation, recursive expansion, wall placement, treasure room
generation, and corridor pruning/extension.

AutoDungeonE_Create() calls this function to create a layout based on
specified parameters, and then populates arrays with the proposed
coordinates of items, enemies, and traps.

This implementation strives to faithfully reproduce the behavior of
DrawField.dll, including its bugs, although it has not been strictly
verified.
2025-09-11 12:53:09 +09:00
kichikuou 037616f38d DrawDungeon2: Move dungeon generation function to generator.c
The dungeon generation function for DrawField will also be added to this
new file.
2025-09-11 12:51:23 +09:00
kichikuou af70ee09de debugger_dap: Respect frameId in evaluate requests
The DAP 'evaluate' request handler now correctly uses the 'frameId'
argument to set the evaluation context.

This also fixes incorrect evaluation of expressions containing non-ASCII
characters.
2025-09-11 12:29:32 +09:00
kichikuou 8d67c87f6f Implement SACT2.SP_SetBrightness
Used in Pastel Chime Continue.
2025-09-09 09:19:38 +09:00
Nunuhara Cabbage 94c8329d7a Update nix version in flake
To pull in newer version of cglm
2025-09-08 07:59:08 -07:00
Nunuhara Cabbage 2c771acfe6 Merge pull request #273 from kichikuou/rsm-version
Pastel Chime Continue: Make sure resume save v7 is used
2025-09-06 17:42:00 -07:00
kichikuou eef99c0910 Pastel Chime Continue: Make sure resume save v7 is used
Because make_rsave() determines the version of resume save based on the
AIN version, it returns RSM v6 for Pastel Chime Continue. But this game
requires the comment field added in RSM v7.

This solves the issue by upgrading to RSM v7 when comments are written
to the save file.
2025-09-07 09:21:37 +09:00
Nunuhara Cabbage 44c73f644f Read .exe and set window icon
Read the game's executable file, extract icons from it, and set the
window icon.

(Ported from ai5-sdl2.)
2025-09-06 16:30:18 -07:00
Nunuhara Cabbage 6cada80d34 Merge pull request #272 from kichikuou/ffi
ffi: Make HLL calls safe against heap reallocation
2025-09-06 12:30:21 -07:00
Nunuhara Cabbage 96a560f6a2 Merge pull request #271 from kichikuou/PastelChime2
Pastel Chime Continue: Add PastelChime2 HLL
2025-09-06 12:29:59 -07:00
Nunuhara Cabbage e13583a75e Merge pull request #270 from brombrom-hani/surface-area-reanchor
fix: anchor sprite surface_area at sprite origin
2025-09-06 12:29:42 -07:00
kichikuou f0e25e53d7 ffi: Make HLL calls safe against heap reallocation
In hll_call(), pointers to heap data passed by reference could become
invalid if the heap was reallocated during the call.

The previous implementation attempted to prevent this by calling
heap_guarantee() to pre-allocate heap space. However, this approach
does not work for certain functions in PastelChime2 HLL, which can
perform an unbounded number of heap allocations.

This commit removes heap_guarantee() and replaces it with a more robust
copy-in/copy-out strategy:

- Before the call (Copy-in): For reference arguments, the pointer value
  is copied from the heap to a local variable on the stack. A pointer to
  this local variable is then passed to the HLL function.
- After the call (Copy-out): The pointer value, which may have been
  modified by the HLL function, is written back from the local variable
  to its original slot in the heap.
2025-09-06 07:34:29 +09:00
BromBrom eb0c642ab1 fix: anchor sprite surface_area at sprite origin 2025-09-04 16:44:01 +02:00
kichikuou acb4d92705 Pastel Chime Continue: Add PastelChime2 HLL
This HLL implements the core gameplay logic for the 3D dungeon
exploration mode.

Key features:

- Collision and Movement:
  - Implements 2D collision detection for player movement against walls.
  - Field_CalcMove() function calculates the desired position and
    adjusts it to slide along walls upon collision.

- Party and AI Mechanics:
  - Field_UpdatePlayersUnit() manages party movement, creating a smooth,
    snake-like following behavior for up to two companions.
  - Field_UpdateEnemyPos() provides basic enemy AI, enabling them to
    chase the player when in line-of-sight and within range, or return
    to a base position otherwise.

- Interactive Environment:
  - Field_UpdateDoors() implements automatic doors that open and close
    based on player proximity.
  - Field_CheckObjectHit() detects when the player enters the hit-range
    of an object, triggering events.

- 3D Space UI:
  - Adds dungeon_project_world_to_screen() to translate 3D world
    coordinates into 2D screen positions.
  - Field_UpdateObjectNamePlate() and Field_UpdateDoorNamePlate() use
    this projection to display and manage interactive nameplates for
    objects and doors.

- Data Persistence:
  - DungeonDataSave() and DungeonDataLoad() serializes and deserializes
    the complete dungeon state to .DSD files.
  - Integrates dungeon data with the main save system by copying .DSD
    files to/from slot-specific .DSA files.

Note: The most complex function in this HLL, the dungeon generator
AutoDungeonE_Create(), is not implemented in this change.
2025-09-02 08:14:58 +09:00
Nunuhara Cabbage b6c523f4f3 Merge pull request #269 from brombrom-hani/revert-267-surface-area-fix
Revert "fix: Y origin in prepare_chipmunk_shader"
2025-09-01 07:05:21 -07:00
brombrom-hani 6b9edcdd69 Revert "fix: Y origin in prepare_chipmunk_shader" 2025-09-01 14:35:35 +02:00
Nunuhara Cabbage 538480b348 Merge pull request #268 from kichikuou/drawfield
Pastel Chime Continue: Add DrawField HLL
2025-08-30 10:05:32 -07:00
Nunuhara Cabbage d9be9a7ad3 Merge pull request #267 from brombrom-hani/surface-area-fix
fix: Y origin in prepare_chipmunk_shader
2025-08-30 10:05:14 -07:00
kichikuou 554f742a00 Pastel Chime Continue: Add DrawField HLL
DrawField is a variant of DrawDungeon. Unlike the first-person
perspective of DrawDungeon, DrawField uses a third-person, bird's-eye
view camera.

Key features include:

- Character Rendering: A new system to render up to 256 billboarded
  sprites (characters, enemies, etc.) in the 3D world. The renderer
  supports controlling position, sprite animation, and visibility for
  each character.

- DrawField HLL API: Functions for loading assets, managing characters,
  and manipulating dungeon properties like door locks.

- Renderer: The core renderer is updated to support the DrawField mode.
  This includes disabling fog, handling sprite sheets via UV
  manipulation, and rendering ceilings as billboards.

- Map: The 2D map has been updated to reflect the DrawField style,
  including colored door lock indicators.
2025-08-30 14:30:16 +09:00
BromBrom eda8ad35f3 fix: Y origin in prepare_chipmunk_shader
Convert surface_area from top-left pixels to bottom-left UVs so `bot_left`/`top_right` align with `tex_coord` and the clip box is correct.
2025-08-28 22:32:53 +02:00
Nunuhara Cabbage 59a66a8a5a Merge pull request #266 from kichikuou/pascha2-hlls
Add HLLs for Pastel Chime Continue
2025-08-24 13:31:03 -07:00
Nunuhara Cabbage 02eab7149c Merge pull request #265 from kichikuou/Key_ClearFlagNoCtrl
Implement SACT2.Key_ClearFlagNoCtrl
2025-08-24 13:30:50 -07:00
kichikuou 1042b328a1 Pastel Chime Continue: Add DrawNumeral HLL 2025-08-23 07:12:54 +09:00
kichikuou f3484643a6 Pastel Chime Continue: Add ACXLoaderP2 HLL 2025-08-23 07:12:53 +09:00
kichikuou 46469e8732 Implement SACT2.Key_ClearFlagNoCtrl
Used in Pastel Chime Continue.

It clears key status except for VK_CONTROL.
2025-08-23 07:08:40 +09:00
kichikuou 62ec0be41a Pastel Chime Continue: Add MusicSystem HLL 2025-08-16 06:45:39 +09:00
Nunuhara Cabbage 0972c60558 Merge pull request #264 from kichikuou/s_mod
S_MOD: Consider padding for FMT_STRING
2025-08-15 13:51:56 -07:00
Nunuhara Cabbage ba2805b09f Merge pull request #263 from kichikuou/lightmap
Dungeons & Dolls: Enable light mapping
2025-08-15 13:49:56 -07:00
kichikuou 20711ffa57 S_MOD: Consider padding for FMT_STRING 2025-08-12 08:39:32 +09:00
kichikuou 7adbf9d6e6 Update libsys4 2025-08-11 16:22:23 +09:00
kichikuou 2dcc9b2aa9 Dungeons & Dolls: Enable light mapping
* dtx_load_from_dtl() loads lightmap textures.
* dgn_calc_lightmap() calculates lightmap texture index for each cell
  based on the state of the surrounding walls.
2025-08-11 16:22:20 +09:00
Nunuhara Cabbage 441419d0f9 Merge pull request #262 from kichikuou/lightmap
DrawDungeon: Implement light mapping
2025-08-10 10:42:53 -07:00
Nunuhara Cabbage a32e3b9401 Merge pull request #261 from kichikuou/walkdata-fix
DrawDungeon: bugfixes
2025-08-10 10:40:21 -07:00
kichikuou d579374a9c DrawDungeon: Implement light mapping 2025-08-09 17:04:27 +09:00
kichikuou f0a54b337d DrawDungeon14: Fix LoadWalkData and SaveWalkData
They were broken by commit 5ed05ba.
2025-08-09 14:28:41 +09:00
kichikuou 80f4246095 DrawDungeon.LoadWalkData should return true when data is not found
Returning false causes system.Error() in Rance 6.
2025-08-09 14:28:41 +09:00
Nunuhara Cabbage b71b61c1d6 Improve detection of MG version of Sengoku Rance
Fixes #241
2025-07-30 17:47:01 -07:00
Nunuhara Cabbage 8c791db5d4 Fix text overlap bug in Haruka
Fixes #252
2025-07-30 17:41:32 -07:00
Nunuhara Cabbage d404324e7b Merge pull request #260 from kichikuou/dolls
game_compatibility.md: Mark DUNGEONS & DOLLS as supported
2025-07-21 21:39:57 -07:00
Nunuhara Cabbage aeb5dbb08c Merge pull request #259 from kichikuou/global-dtors
Dungeons & Dolls: Call global variable destructors on exit
2025-07-21 21:39:38 -07:00
Nunuhara Cabbage 9db75ab870 Merge pull request #258 from kichikuou/circular-scene-buffer
AnteaterADVEngine: Change `scenes` to a circular buffer
2025-07-21 21:38:15 -07:00
kichikuou 67387e88d0 game_compatibility.md: Mark DUNGEONS & DOLLS as supported 2025-07-21 13:39:56 +09:00
kichikuou d57c8cc9f4 Dungeons & Dolls: Call global variable destructors on exit
Early versions of System4 (Sys42VM.dll < 3.0) execute destructors for
global variables on exit.

Dungeons & Dolls saves its game state in the destructors of global
variables (~CDataDoll(), ~CDataItem(), ~CDataMain()), so the game was
not saved in xsystem4 before this.

This behavior has some quirks:
 * Destructors for variables on the call stack are not called on exit.
 * Destructors are called in the reverse order of the global variable
   declarations.
 * Inside a destructor, it is possible to access another global variable
   whose destructor has already been called (!).

Since this is a problematic behavior that was removed in later versions
of Sys42VM, let's add minimal support for it as a game-specific hack.
2025-07-21 13:32:54 +09:00
Nunuhara Cabbage 677f20628c Merge pull request #257 from kichikuou/DrawDungeon2
Dungeons & Dolls: Implement DrawDungeon2 HLL
2025-07-20 21:22:08 -07:00
kichikuou e68c41a0b9 AnteaterADVEngine: Change scenes to a circular buffer
Following the implementation of `logs` (#220), `scenes` is now also
implemented as a circular buffer. This ensures that when the number of
saved scenes reaches a certain limit (500), the oldest scenes are
automatically discarded.
2025-07-21 12:35:47 +09:00
kichikuou fbd105b697 DrawDungeon2: Implement pathfinding functions 2025-07-14 20:51:44 +09:00
kichikuou 3c7634522a DrawDungeon2: Use special event marker for boss floors 2025-07-14 16:51:59 +09:00
kichikuou 5ed05ba685 DrawDungeon2: Fix 2D map 2025-07-14 16:51:59 +09:00
kichikuou 11fe6f6dda DrawDungeon2: Do not use skybox 2025-07-14 16:51:59 +09:00
kichikuou 3057c2a96b DrawDungeon2: Load textures from Texture.dtl file
Texture.dtl contains multiple sets of wall, floor, and ceiling texture
images. dtx_load_from_dtl() randomly selects a texture set using the
floor number as the random seed and returns it as a dtx structure.
2025-07-14 16:51:59 +09:00
kichikuou 5041d817d1 Dungeons & Dolls: Implement DrawDungeon2 HLL
In DrawDungeon2, dungeons are generated randomly using a dedicated
random number generator (mtrand43.c), which is a Mersenne Twister with
N=4, M=3.

The entry point for map generation is dgn_generate_drawdungeon2(),
which deterministically generates a floor map using the floor number as
the random seed.

I verified that generated floor layout, player starting position,
exit, and treasure chest locations match the original DrawDungeon2.dll
up to the 1000th floor.
2025-07-14 16:51:32 +09:00
Nunuhara Cabbage cb4e44a551 Merge pull request #256 from kichikuou/dtor
Do not call destructors for structs passed by value to HLL functions
2025-07-13 08:27:33 -07:00
Nunuhara Cabbage 74ae7223c3 Merge pull request #255 from kichikuou/DnD-hlls
Implement HLLs for Dungeons & Dolls
2025-07-13 08:26:10 -07:00
kichikuou 7dc6760928 Do not call destructors for structs passed by value to HLL functions
Some classes in Dungeons & Dolls serialize themselves using
`File.Write(this)` in their destructors. Because `File.Write` takes the
argument by value, a copy of `this` is created. If the destructor is
called on the copied `this`, it falls into an an infinite loop.
2025-07-13 13:48:30 +09:00
kichikuou e1ca00a27b Implement Array HLL
This HLL defines numerous functions, but only two are used in
Dungeons & Dolls: NV_eneq and NV_sceq.
2025-07-13 12:54:56 +09:00
kichikuou 7989ffc542 Implement CalcTable HLL
It provides 64-bit calculation functions with virtual registers.

While the original CalcTable.dll accumulates instructions and executes
them with CalcTable.Run(), this implementation performs calculations on
the fly. This simplification is possible because Run() is only called
with loop=1.
2025-07-13 12:49:34 +09:00
Nunuhara Cabbage 6e14285038 Merge pull request #254 from kichikuou/system_error
Add message box for system.Error()
2025-07-12 07:47:05 -07:00
kichikuou 55a214553a Add message box for system.Error()
`system.Error()` is used for critical errors, so its message should be
presented to the user.

This makes system.Error() show a message box, allowing users to choose
between stopping execution or continuing.
2025-07-11 11:39:12 +09:00
Nunuhara Cabbage bf9900d8aa Merge pull request #251 from kichikuou/powervr
3d: Fix skeletal animation on PowerVR GPUs
2025-06-25 17:37:31 -07:00
kichikuou e00ba69546 3d: Fix skeletal animation on PowerVR GPUs
This fixes https://github.com/kichikuou/xsystem4-android/issues/14.

There appears to be a bug in the OpenGL ES implementation for PowerVR
that prevents row_major mat4x3 uniforms from loading correctly.

This changes bone_matrices from row_major mat4x3 to column_major mat3x4
and transposes them manually in the vertex shaders. (Column major mat4x3
cannot be used due to the UBO size limitation)
2025-06-25 12:55:52 +09:00
Nunuhara Cabbage b88abe375a Merge pull request #250 from kichikuou/shader-compile-error
3d: Ensure shader fragment ends with a newline
2025-06-24 06:11:42 -07:00
kichikuou c86977ad1b 3d: Ensure shader fragment ends with a newline
The `defines` string in load_shader() is prepended to the glsl file, but
it was not terminated with a newline, so the resulting shader source
looked like this:

    #define REIGN_ENGINE 0
    #define TAPIR_ENGINE 1
    #define ENGINE 1/* Copyright (C) ...
     *
     * This program is free software; ...
    ...

This confused some OpenGL ES implementations, causing a syntax error.
https://github.com/kichikuou/xsystem4-android/issues/15
2025-06-24 18:34:46 +09:00
Nunuhara Cabbage ffb9fbae31 Silence spurious warnings
None of these are actual bugs as far as I can tell.
2025-06-23 19:04:52 -07:00
Nunuhara Cabbage 3ef7ce926b Merge pull request #249 from brombrom-hani/master
SystemService: Implement playing manual
2025-06-23 18:27:18 -07:00
Nunuhara Cabbage bf6dcef2a8 Merge pull request #248 from kichikuou/rq
Update game compatibility table
2025-06-23 18:26:58 -07:00
Nunuhara Cabbage 8a62ec6dc5 Merge pull request #247 from kichikuou/3d
Rance Quest: Fix 3D rendering issues
2025-06-23 18:26:36 -07:00
Brombrom Hani 152af8685f Remove percent_encode from the compilation on windows 2025-06-23 13:09:20 +02:00
Brombrom Hani c5d6afdd08 Fix manual opening on Windows by conditionally applying percent encoding
- Only apply percent encoding on non-Windows platforms in OpenPlayingManual
- Windows uses ShellExecuteW which doesn't work with percent-encoded paths
- Unix platforms still use percent encoding for proper URL handling
- Fixes manual opening functionality on Windows systems
2025-06-23 13:03:50 +02:00
Brombrom Hani 9d6861b10b Add percent encoding for manual file paths in OpenPlayingManual
- Add percent_encode() function to properly encode URLs for SDL_OpenURL
- Removed not needed null check on filename
- Update SystemService_OpenPlayingManual to use percent encoding on file paths
2025-06-23 11:54:31 +02:00
Brombrom Hani c7ba379173 Fix file:// URL formatting for cross-platform compatibility
Use file:/// prefix on Windows and file:// on Unix-like systems.
Windows paths need the extra slash since they don't start with /.
2025-06-23 00:13:37 +02:00
Brombrom Hani f4d4e8f90a Implement open playing manual 2025-06-22 16:37:35 +02:00
kichikuou 379bbfeb07 Update game compatibility table
Marking Rance Quest and Rance Quest Magnum as supported.

I was able to clear Rance Quest Magnum Integrated Edition (TADA patch
v3.86E) without any issues.

I have only played the first few quests of the original Rance Quest, but
I think it is safe to say that it works.

Fixes #209.
2025-06-22 08:24:33 +09:00
kichikuou 117c72cf09 3d: Implement texture animation
Skinned models can have multiple texture images for animation. When a
material colormap in the .pol file is `image.png`, additional images are
stored with names like `image[1].png`, `image[2].png`, etc.

Animation frame information is stored in `<motion_name>.txa` file. This
is a text file containing newline-separated integers, specifying the
texture index for each motion frame.
2025-06-21 16:55:24 +09:00
kichikuou 3577079a04 3d: Do not draw shadow on transparent or sprite objects
This fixes mapT_0a.POL in Rance Quest.
2025-06-21 06:40:15 +09:00
kichikuou 8d120b8a4d 3d: Avoid zero division in update_motion() 2025-06-16 11:43:26 +09:00
kichikuou e2440a5a44 Implement TapirEngine.SetInstanceMeshShow 2025-06-16 11:43:26 +09:00
kichikuou bba3f96a1b opr: All meshes are selected by default 2025-06-16 11:43:26 +09:00
kichikuou f4f7adb618 3d: Change alpha testing thresholds
- 0.1 for ALPHA_MAP_TEST, which fixes a rendering issue of cyouchin.POL
- 0.75 for ALPHA_TEST. Judging from the rendering of mapT_0a.POL, this
  seems to be the correct value
2025-06-16 11:43:26 +09:00
Nunuhara Cabbage 8730475f1c Merge pull request #246 from kichikuou/parts
PE_SetPartsCG_by_string_index: Accept `<save>` path
2025-06-15 18:43:49 -07:00
Nunuhara Cabbage a84d78ac5d Merge pull request #245 from kichikuou/ffi-fix
Fix bool return value of HLL functions
2025-06-15 18:43:14 -07:00
kichikuou 92322c27f0 PE_SetPartsCG_by_string_index: Accept <save> path
The custom characters feature in Rance Quest calls GoatGUIEngine.
SetPartsCG() with a name like "<save>SaveData\\顔CG\\filename.png",
which must be resolved with the save directory.
2025-06-15 09:37:28 +09:00
kichikuou 50cb0f8063 Fix bool return value of HLL functions
Since bool functions return a 1-byte value, the value of the upper 24
bits of r.i after ffi_call is undefined.

This actually led to a bug where PassRegister.ExistText returned
incorrect values on Windows x64.
2025-06-15 08:30:40 +09:00
Nunuhara Cabbage 13e76f67b3 Merge pull request #244 from kichikuou/parts
Implement GoatGUIEngine.AddDrawRectToPartsConstructionProcess
2025-06-14 13:04:14 -07:00
Nunuhara Cabbage 37f1bc2723 Merge pull request #243 from kichikuou/InputString
Implement InputString.Inputs and InputString.Converts
2025-06-14 13:03:34 -07:00
Nunuhara Cabbage 0d7830f24c Merge pull request #242 from kichikuou/vibration
parts: Implement PE_AddWholeMotionVibrationSize
2025-06-14 13:03:17 -07:00
kichikuou c70e5e028e Implement GoatGUIEngine.AddDrawRectToPartsConstructionProcess
Used in Rance Quest.
2025-06-14 15:37:35 +09:00
kichikuou a4b4e7f5bb Implement InputString.Inputs and InputString.Converts
Used in Rance Quest.

Presumably, Inputs() returns true when there is unconverted text in IME,
and Converts() returns whether the user is in the conversion phase of
IME input.

SDL text input API cannot distinguish them, so in this implementation
both return true when there is uncommitted text.
2025-06-14 08:41:24 +09:00
kichikuou 9ccc4ab0c7 parts: Implement PE_AddWholeMotionVibrationSize
It vibrates the whole screen. To implement this, this introduces a
global motion list and parts_set_global_pos(), which sets the position
of the root of the parts tree.
2025-06-10 09:35:11 +09:00
Nunuhara Cabbage 13a00c131d Merge pull request #240 from kichikuou/3d
Rance Quest: Fix 3D rendering issues
2025-06-07 06:45:10 -07:00
kichikuou 929a2dd3e7 3d: Draw transparent objects before outlines
Otherwise, the background of transparent objects with outlines (e.g.
hitotsurei01.POL) are filled with the outline color.
2025-06-04 10:23:03 +09:00
kichikuou b5b92bc367 3d: Add ALPHA_MAP_TEST mode
It does alpha testing using alpha map texture.
2025-06-02 07:25:38 +09:00
kichikuou e5fadf2b73 TapirEngine: Correctly implement MeshPart in .opr parser
`MeshPart = "str"` sets the attribute to all meshes whose name contains
`str`.
2025-06-02 07:25:38 +09:00
kichikuou 2713290ac9 TapirEngine: Add support for BlendMode = Add opr attribute 2025-06-02 07:25:37 +09:00
Nunuhara Cabbage 3bfb92e1fb Merge pull request #239 from kichikuou/pathfinding
TapirEngine: Implement pathfinding functions
2025-06-01 09:01:05 -07:00
kichikuou 378450c625 Implement TapirEngine.OptimizeInstancePathLine
It simplifies the path calculated by TapirEngine.FindInstancePath() by
removing unnecessary points.
2025-05-30 07:43:15 +09:00
kichikuou 919b86916c TapirEngine: Implement pathfinding functions
TapirEngine.FindInstancePath() takes a starting point and an end point
and executes the A* algorithm on the collision mesh. The resulting path
can be obtained with TapirEngine.GetInstancePathLine().
2025-05-30 07:43:11 +09:00
kichikuou 6fbd147c54 Implement TapirEngine.CalcPathFinderIntersectEyeVec
It returns the position on the collision mesh corresponding to the mouse
coordinates.
2025-05-26 10:06:13 +09:00
Nunuhara Cabbage bcae91003a Merge pull request #238 from kichikuou/mg-rance7-text
Fix overlapping text issue in MangaGamer Sengoku Rance
2025-05-24 10:50:27 -07:00
kichikuou 7e584c5fd2 Fix overlapping text issue in MangaGamer Sengoku Rance
This fixes https://github.com/kichikuou/xsystem4-android/issues/12.

This game uses DrawGraph.FillAMap() to erase text drawn on the sprite,
but our SengokuRanceFont implementation draws text on sp->text.texture.

For a detailed analysis of this issue and other possible solutions, see
https://github.com/nunuhara/xsystem4/issues/237.
2025-05-24 09:10:25 +09:00
Nunuhara Cabbage b0c7aad6c3 Merge pull request #236 from kichikuou/world_transform
gfx: Remove world_transform from struct texture
2025-05-21 06:07:34 -07:00
Nunuhara Cabbage 5d0819a115 Merge pull request #235 from kichikuou/rq
Small changes for Rance Quest
2025-05-21 06:06:24 -07:00
kichikuou 724bbcdfdd 3d: Disable light mapping for meshes without light_uv 2025-05-21 07:44:15 +09:00
kichikuou dc0daf3329 gfx: Remove world_transform from struct texture
Pure refactoring, no behavioral changes.
2025-05-18 21:15:03 +09:00
kichikuou 5d271deec0 ChipmunkSpriteEngine: Remove duplicated HLL_EXPORT entries 2025-05-18 11:34:08 +09:00
kichikuou 2ede7e6bb1 StoatSpriteEngine: Clear cached font_size struct in SP_SetTextSpriteType
This fixes a bug where the wrong font was used on the Rance Quest status
screen.

Commit 6c2a55c reset `font_size` on size changes, but the same is needed
for face changes.
2025-05-18 11:23:28 +09:00
kichikuou e6d069b757 SystemService.GetGameFolderPath should return SJIS string 2025-05-18 11:23:27 +09:00
kichikuou d6abcc2f92 Plumb GoatGUIEngine.AddWholeMotionVibrationSize 2025-05-18 11:23:27 +09:00
kichikuou de0646e430 Implement ChipmunkSpriteEngine.SP_{Suspend,Resume,IsSuspend}
Their exact meaning is unknown; currently it is just a read/write
boolean flag.
2025-05-18 11:23:27 +09:00
Nunuhara Cabbage c04fd2280d Merge pull request #233 from kichikuou/sprite-transform
Rance Quest: Implement ChipmunkSpriteEngine.SP_SetSpriteTransform*
2025-05-17 18:13:35 -07:00
kichikuou 71c58e85ae LIST_REMOVE regardless of whether free() is defined 2025-05-18 07:27:32 +09:00
kichikuou 61650308b7 Implement ChipmunkSpriteEngine.SP_SetSpriteTransform*
Used for the treasure chest opening effect in Rance Quest.

Implemented using draw_plugin. The render() method of draw_plugin is no
longer dedicated to custom sprites, but can be used for any sprites to
override the default sprite rendering function.
2025-05-17 14:28:45 +09:00
kichikuou bb26326929 draw_plugin: Add free() method
It is called when the plugin is unbound from a sprite, or when the
sprite to which the plugin is bound is released.

This prevents memory leaks when sprites are destroyed without the plugin
being released, and simplifies lifetime management of the DrawDungeon
plugin.
2025-05-16 09:13:10 +09:00
kichikuou f50c9f6d77 gfx: Add gfx_render_quadrilateral()
It draws a quadrilateral with arbitrary vertex and texture coordinates.

This is used to properly implement DrawGraph.DrawDeformedSpriteBilinear,
and will be used to implement ChipmunkSpriteEngine.
SP_SetSpriteTransformPos in a subsequent patch.
2025-05-16 09:13:06 +09:00
Nunuhara Cabbage ea081e0eb7 Merge pull request #231 from kichikuou/debugger-dap
debugger_dap: Source-level debugging
2025-05-12 17:50:08 -07:00
kichikuou e8ecad598a debugger_dap: Support SteppingGranularity
Debuggers that operate at the instruction level can execute instruction-
level step even when xsystem4 has loaded debug information, by adding
`granularity: "instruction"` to the step request.
2025-05-10 16:17:54 +09:00
kichikuou cb551e8f06 debugger_dap: Source-level debugging
- Add support for setBreakpoints request
- Add source locations to stackTrace response
- Source-level stepIn/stepOut/next executions

All of these only work if debug information is available. Otherwise they
work at the instruction level as before.
2025-05-10 15:31:04 +09:00
Nunuhara Cabbage 002bca4e24 Merge pull request #230 from kichikuou/a_sort
A_SORT, A_SORT_MEM: Use stable sorting
2025-05-09 22:56:33 -07:00
kichikuou 0bbd9171b5 A_SORT, A_SORT_MEM: Use stable sorting
This fixes the weird ordering of the quest list in Rance Quest.
2025-05-04 12:44:27 +09:00
Nunuhara Cabbage 7dfc9926be Merge pull request #229 from kichikuou/z-tie-break
SACTDX/Stoat/Chipmunk: render sprites in correct order for same Z
2025-05-03 13:04:09 -07:00
kichikuou 1dd10ecc45 SACTDX/Stoat/Chipmunk: render sprites in correct order for same Z
In SACT2, if two sprites have the same Z value, they are rendered in
increasing order of their sprite numbers. This behavior is documented in
the SDK manual.

However, in later versions of the sprite engine (SACTDX,
StoatSpriteEngine, ChipmunkSpriteEngine), sprites are drawn in the order
they were created.

This fixes an issue in Rance Quest Magnum which relies on this behavior.
2025-05-03 11:13:03 +09:00
Nunuhara Cabbage ba11900350 Merge pull request #228 from kichikuou/assets
Use case-insensitive match for asset file names
2025-04-29 17:33:53 -07:00
kichikuou 508839ed4c Use case-insensitive match for asset file names
This fixes #227.

Rance 1 Digest Edition includes `Rance1.ain` and `RANCE1BA.ald`.
2025-04-28 16:47:47 +09:00
Nunuhara Cabbage 55f826db36 Merge pull request #225 from kichikuou/trial
SystemService: Add two functions
2025-04-13 07:11:09 -07:00
kichikuou 882354c692 SystemService: Add two functions
Used in the trial versions of Rance 01 and Dorapeko.
2025-04-12 13:25:13 +09:00
Nunuhara Cabbage 071d7d2137 Merge pull request #224 from kichikuou/cmake
cmake: Use find_package() for zlib, SDL and freetype
2025-04-08 19:16:01 -07:00
kichikuou eddac54fa0 cmake: Use find_package() for zlib, SDL and freetype
It is the user's responsibility to ensure that these are found.
2025-04-07 13:20:42 +09:00
Nunuhara Cabbage 2e1059cbac Merge pull request #222 from kichikuou/daiteikoku
Daiteikoku support
2025-04-01 18:12:57 -07:00
kichikuou f9177b89b1 game_compatibility.md: Mark Daiteikoku as supported 2025-04-01 14:18:44 +09:00
kichikuou 7bae2cdf8a DrawGraph.CopyRotZoom2Bilinear should copy src alpha 2025-04-01 11:53:25 +09:00
kichikuou 769bdca725 Implement DrawGraph.CopyReverseLRWithAMap
Used in Daiteikoku.
2025-04-01 11:47:21 +09:00
kichikuou 6517303e2a Implement FileOperation.{ExistFile,GetFileSize}
This unblocks the "User Admiral" feature of Daiteikoku.
2025-04-01 10:41:56 +09:00
Nunuhara Cabbage 0e388c74c0 Merge pull request #221 from kichikuou/effect60
Implement effect number 60
2025-03-22 23:46:02 -07:00
kichikuou 7ff5ec89e7 Implement effect number 60
Used in Rance Quest.
2025-03-23 08:22:16 +09:00
Nunuhara Cabbage 33403cbd3d Merge pull request #220 from kichikuou/circular-log-buffer
AnteaterADVEngine: Limit log size to 1000 pages
2025-03-17 20:43:50 -07:00
kichikuou 40fe2068e7 AnteaterADVEngine: Limit log size to 1000 pages
If the log exceeds 1000 pages, the oldest pages are discarded. This
behavior is in line with AnteaterADVEngine.dll of Daiteikoku.

This prevents the log buffer from growing indefinitely, slowing down
saves and loads.
2025-03-17 21:06:10 +09:00
Nunuhara Cabbage 40914a7cef Merge pull request #219 from kichikuou/hoken
StoatSpriteEngine: multisprite_update() should not use sprite_set_show()
2025-03-15 07:18:04 -07:00
kichikuou 0946900e02 game_compatibility: Mark "Boku Dake no Hokenshitsu" as supported 2025-03-15 08:19:48 +09:00
kichikuou 56d9b607b2 StoatSpriteEngine: multisprite_update() should not use sprite_set_show()
sprite_set_show() is also used by MultiSprite_SetAllShowMessageFrame,
causing interference.

Now multisprite_update() clears the sprite's texture when there's no CG,
rather than hiding it.

Fixes #218.
2025-03-15 08:15:02 +09:00
Nunuhara Cabbage 155c801804 Update libsys4 2025-03-09 17:31:16 -07:00
Nunuhara Cabbage 4942e41fb8 Merge pull request #217 from kichikuou/drawgraph
Daiteikoku: Implement DrawGraph.DrawDeformedSpriteBilinear
2025-03-03 07:53:39 -08:00
kichikuou ac8a37f67b Daiteikoku: Implement DrawGraph.DrawDeformedSpriteBilinear
This function draws a quad with arbitrary vertex and texture
coordinates, but since it is actually only used to copy a rectangular
area, this can be implemented using gfx_copy_stretch().

This also adds HLL_TODO_EXPORTs for the DrawGraph functions defined but
unused in Daiteikoku.
2025-02-26 13:28:10 +09:00
Nunuhara Cabbage 331563a165 Merge pull request #216 from kichikuou/daiteikoku-text
Fix text rendering issues in Daiteikoku
2025-02-24 15:43:46 -08:00
Nunuhara Cabbage c30510e688 Merge pull request #215 from kichikuou/vsync
SystemService: Persist "wait vsync" setting to disk
2025-02-24 15:43:23 -08:00
kichikuou 81c0c494b0 parts: Draw per-character glyphs directly to the screen
Before this, character glyphs were copied to an intermediate texture
(text->common.texture) using gfx_copy_with_alpha_map(). In Daiteikoku,
some text sprites have a character spacing of -4, causing the copy to
cut off the right edge of the previously drawn character.

This fixes the problem by having parts_render_text() blend per-glyph
textures directly onto the target without using an intermediate texture.
2025-02-24 10:07:48 +09:00
kichikuou e9bec3c2fe Use text_style_width() to calculate CharSprite width
So that edge width is taken into account.
2025-02-24 10:07:47 +09:00
kichikuou 298e9bddf5 Fix text rendering issue on the save screen of Daiteikoku
This fixes the same bug that commit c4e17c6 fixed, for sprites created
by ChipmunkSpriteEngine.SP_SetTextSprite. This uses text_style_width()
instead of gfx_size_char() so that edge width is taken into account.
2025-02-24 10:07:47 +09:00
kichikuou bf3c4feb9d SystemService: Persist "wait vsync" setting to disk
This setting has a significant impact on system load, so users may want
xsystem4 to remember their preference.

This implementation uses a JSON format, while the AliceSoft's
implementation saves it to a binary file (WindowSetting.sav).

This only records the wait_vsync setting, since the other fields in
window_settings are currently no-op.
2025-02-23 11:02:27 +09:00
Nunuhara Cabbage 6a36fc8f53 Merge pull request #214 from kichikuou/DrawEffect
Implement DrawEffect HLL
2024-12-29 20:36:06 -08:00
Nunuhara Cabbage 45876a9921 Merge pull request #213 from kichikuou/rsm
Use RSM save format by default
2024-12-29 20:35:30 -08:00
kichikuou 2461c1e52d Implement DrawEffect HLL
Used in Ultra Mahou Shoujo Manana vol.5.

Although the HLL interface allows partial screen effects, it is actually
used only for full screen effects.
2024-12-29 14:06:42 +09:00
kichikuou 41c7830558 Use RSM save format by default
Before this, the resume save format defaulted to JSON, which could not
be loaded by System40.exe and save/load was sometimes too slow even on
desktop environments.

In xsystem4-android the RSM format has been the default for 9 months and
no problems have been reported. Let's make this the default on all
platforms.
2024-12-28 18:23:14 +09:00
Nunuhara Cabbage 009bafae40 Merge pull request #212 from kichikuou/debugger_dap
debugger_dap: minor fixes
2024-11-18 20:47:42 -08:00
Nunuhara Cabbage e891e74bcf Merge pull request #211 from kichikuou/android-fix
Android: Update SDL2 to 2.30.9
2024-11-18 20:47:19 -08:00
kichikuou 5576f14a0f debugger_dap: minor fixes
* Add `allThreadsStopped` to Stopped event to indicate that stacktrace
  can be accessed
* StackTraceResponse should return most recent call first
* Fix instruction pointers of calling frames

The first two will improve compatibility with DAP clients other than
xsys4dbg.
2024-11-17 09:42:25 +09:00
kichikuou 90075f2e25 Android: Update SDL2 to 2.30.9
This fixes an audio issue on Android 15.
https://github.com/libsdl-org/SDL/issues/11376
2024-11-17 08:34:28 +09:00
Nunuhara Cabbage c0cc3f729b Merge pull request #210 from kichikuou/debugger
debugger_cmd: Add support for source-level debugging
2024-11-10 07:32:29 -08:00
kichikuou 4d303b291d debugger_cmd: Add support for source-level debugging
This adds source-level debugging capabilities to the command-line
debugger, utilizing debug information generated by sys4dc. The debug
information format is documented at:
https://github.com/kichikuou/sys4dc/blob/master/docs/debug_info.md

The location of the debug information file can be specified with the
`--debug-info` command-line flag. If not specified, xsystem4 will
attempt to load it from `gamedir_path("src/debug_info.json")`.

When debug information is available, a breakpoint can be set at a
specific line in a file using `breakpoint <file> <line>`. Also, `step`
and `next` commands advance execution to the next source line (this is
naively implemented by repeating instruction-level stepping).
2024-11-09 13:10:13 +09:00
Nunuhara Cabbage e73abc3cd6 Merge pull request #208 from kichikuou/draw-plugins
Implement DrawSnow, DrawRain, and DrawRipple HLLs
2024-11-02 15:28:02 -07:00
kichikuou 579c158477 Implement DrawRipple HLL
Used in Majo no Shokuzai.
2024-11-02 07:55:30 +09:00
kichikuou 842bac92f3 Implement DrawRain HLL
Used in Tsuma Shibori.
2024-11-01 10:59:06 +09:00
kichikuou dc8a9379d0 Implement DrawSnow HLL
Used in Tsuma Shibori, and some indie System 4 games.

This HLL is a draw plugin that uses SACT2's "custom sprite" mechanism.
Custom sprites do not have a texture. Instead, the plugin's render()
callback draws them directly to the main surface.
2024-11-01 08:53:58 +09:00
Nunuhara Cabbage f8f02a1c99 Merge pull request #207 from kichikuou/framebuffer
Fix "incomplete framebuffer" error in gfx_init_texture_rgb(a)
2024-10-27 20:38:55 -07:00
kichikuou d7d9bb70f2 Fix "incomplete framebuffer" error in gfx_init_texture_rgb(a)
This fixes "incomplete framebuffer" error in Haru Urare right after the
game start, due to gfx_set_framebuffer() being called with a zero width
texture. It was a regression introduced by commit 04d5237, which started
using gfx_set_framebuffer() in gfx_init_texture_rgba().

Ideally, we should avoid creating zero-area textures in upper layers
(commit 3f18b18 was an attempt to do so), but this is difficult because
there are many places that create textures. Let's just make sure that we
don't generate unrecoverable errors.
2024-10-28 11:28:08 +09:00
Nunuhara Cabbage dcdb82e2a8 Merge pull request #205 from kichikuou/alpha
TapirEngine: Fix alpha test / alpha blend conditions
2024-10-25 19:00:27 -07:00
kichikuou f1a2904262 TapirEngine: Fix alpha test / alpha blend conditions
In ReignEngine, alpha blending is the default. Alpha testing is used if
the mesh/material has the (sprite) attribute.

In TapirEngine, alpha testing is the default. Alpha blending is used if
the mesh/material has the (alpha) attribute.
2024-10-22 10:17:07 +09:00
kichikuou ecd71896b3 3d: Store 3D engine version in a global variable
So that we don't have to pass it around everywhere.
2024-10-22 10:16:51 +09:00
Nunuhara Cabbage d7b7879747 Merge pull request #204 from kichikuou/3d
3d: Various improvements for Rance Quest
2024-10-20 09:08:45 -07:00
kichikuou 1cbf6125ce 3d: Implement vertex alpha
POL v2 models can have an alpha mod per vertex.
2024-10-20 08:25:42 +09:00
kichikuou 9b03741288 3d: Pack bone transform matrices
Before this, the BoneTransforms uniform block consumed
(4 * 4 * sizeof(float) * MAX_BONES) = 19712 bytes, which exceeds 16384,
the minimum possible value of GL_MAX_UNIFORM_BLOCK_SIZE in OpenGL ES
3.2.

Since the bone matrices are affine, we can drop the 4th rows and pack
them into a mat4x3 array. Now the BoneTransforms block is 14784 bytes
and it should work with all OpenGL ES 3.2 implementations.
2024-10-20 08:25:39 +09:00
kichikuou 4e04db0045 3d: Implement MESH_BOTH attribute
It disables back-face culling.
2024-10-20 08:16:27 +09:00
kichikuou abd3a276a3 TapirEngine: Implement UVScroll mesh attribute 2024-10-20 08:16:24 +09:00
Nunuhara Cabbage 9ca3d3e9c6 Merge pull request #203 from kichikuou/lighting
TapirEngine: Simplify lighting
2024-10-18 20:15:02 -07:00
kichikuou d8ad22b638 TapirEngine: Simplify lighting
In TapirEngine, the following lighting features are not used:

- global ambient
- directional lights
- specular lights
- rim lights
- fog

(The setters / getters for these parameters are still there, but they
don't affect rendering.)

This implementation disables those features in GLSL using #ifdefs. The C
code that sets up the lighting remains almost unchanged --
glGetUniformLocation() will return -1 if the specified uniform variable
name is not found, and glUniform*(-1, ...) will be no-op.
2024-10-19 08:43:31 +09:00
Nunuhara Cabbage 3f02a22a8d Merge pull request #202 from kichikuou/fps
gfx: Add frame rate counter
2024-10-17 06:30:07 -07:00
kichikuou bff491bbe9 gfx: Add frame rate counter
It is exposed through StoatSpriteEngine functions, and is normally only
visible when the game's debug mode is enabled. It's very useful for
checking graphics performance.
2024-10-17 09:20:26 +09:00
Nunuhara Cabbage f566539e4f Merge pull request #201 from kichikuou/outline
TapirEngine: Outline rendering
2024-10-13 11:28:53 -07:00
kichikuou c04aa8a844 TapirEngine: Outline rendering
This implements outline (called "edge" in the game config) rendering of
3D objects, using the "inverted hull" method.
2024-10-13 07:18:49 +09:00
kichikuou 87b1e2abe2 Make variable names consistent between reign.v.glsl and reign_shadow.v.glsl
Pure refactoring, no behavior changes.
2024-10-13 07:18:49 +09:00
kichikuou a0a68a022c TapirEngine: Load .opr file
A .pol file may have a corresponding .opr file, which is a text file
containing parameters for the meshes.
2024-10-13 07:18:49 +09:00
Nunuhara Cabbage 3fb2dc3650 Merge pull request #200 from kichikuou/bones
3d: Increase MAX_BONES to 308
2024-10-11 21:00:49 -07:00
kichikuou c1ce2c88c6 3d: Increase MAX_BONES to 308
This is the maximum number of bones in a model for Rance Quest
(pasuteru_full.POL).

Now bone transform matrices are stored in a uniform buffer object,
because its size exceeds GL_MAX_VERTEX_UNIFORM_COMPONENTS in iOS Safari.
2024-10-12 07:35:51 +09:00
Nunuhara Cabbage 222d2700a9 Merge pull request #199 from kichikuou/CGManager
CGManager.LoadArchive() should open archive in the game directory
2024-10-09 06:24:33 -07:00
kichikuou dd18d68be5 CGManager.LoadArchive() should open archive in the game directory
This fixes a boot problem in Rance Quest Magnum.
2024-10-09 12:51:08 +09:00
Nunuhara Cabbage db7c3a608c Merge pull request #198 from kichikuou/shadow
3d: Fix shadow rendering in Rance Quest
2024-10-06 19:57:03 -07:00
kichikuou 899c6c02a1 3d: Fix shadow rendering in Rance Quest
- Use glm_look_anyup() to avoid singularities when the shadow light
  direction is parallel to the up vector
- Fix bounding sphere calculation when there are multiple shadow casters
2024-10-07 10:53:41 +09:00
Nunuhara Cabbage 8ff5ffe30b Merge pull request #197 from kichikuou/alphamap
3d: Ignore alpha map if same as color map
2024-10-05 21:22:58 -07:00
Nunuhara Cabbage ff9175c1fd Merge pull request #196 from kichikuou/wheel
IbisInputEngine: Mouse wheel support
2024-10-05 21:22:41 -07:00
kichikuou 74666c9b90 3d: Ignore alpha map if same as color map
In Rance Quest (e.g. mapF_ew01), some materials use the same image for
both color map and alpha map. In that case, the alpha channel of the
color map can be used, so ignore the alpha map.
2024-10-06 07:53:15 +09:00
kichikuou 68bd33e099 IbisInputEngine: Mouse wheel support
This enables wheel operations in Rance Quest.
2024-10-06 07:38:24 +09:00
Nunuhara Cabbage 84ab9a41da Merge pull request #195 from kichikuou/vert-color
3d: Implement vertex colors
2024-10-04 20:39:59 -07:00
kichikuou 22a06b1da9 3d: Implement vertex colors
3D objects can have a color per vertex, which is multiplied with the
texture color.
2024-10-05 07:41:17 +09:00
Nunuhara Cabbage 128b2fd6d5 Merge pull request #194 from kichikuou/collision
TapirEngine: Proper collision detection
2024-09-30 10:53:47 -07:00
kichikuou 09dc9470ae TapirEngine: Proper collision detection
Collision detection is based on a mesh named "collision" in the map's 3D
model. The "collision" mesh specifies the area in which the character
can move in the xz plane, and the y coordinate represents the height of
the map at that point.

Now cglm 0.9.2 is required, for 2D AABB operations.
2024-09-30 09:11:54 +09:00
Nunuhara Cabbage 0f5a39c292 Merge pull request #193 from kichikuou/FileOperation
Implement FileOperation HLL functions for Rance Quest
2024-09-28 10:37:52 -07:00
kichikuou 73f7acde40 Implement FileOperation HLL functions for Rance Quest 2024-09-28 11:07:43 +09:00
Nunuhara Cabbage 3afe4e4453 Merge pull request #192 from kichikuou/A_SORT
Fix A_SORT
2024-09-25 17:32:28 -07:00
kichikuou 8f69f1911a Fix A_SORT
* Fix sorting of strings and floats
* Avoid overflow in integer comparison
2024-09-25 13:20:03 +09:00
Nunuhara Cabbage e466b3f06b Merge pull request #191 from kichikuou/game_compatibility
Update game_compatibility.md
2024-09-22 07:42:33 -07:00
kichikuou a2ae28c1c1 Update game_compatibility.md 2024-09-22 21:12:42 +09:00
Nunuhara Cabbage 41626b5018 Merge pull request #190 from kichikuou/dash
Implement StoatSpriteEngine.SP_SetDashTextSprite
2024-09-21 22:38:47 -07:00
kichikuou 9a76d472db Implement StoatSpriteEngine.SP_SetDashTextSprite
This implementation creates a temporary "glyph" texture and draws it to
the sprite using gfx_draw_glyph().
2024-09-21 10:35:42 +09:00
Nunuhara Cabbage 964bba1823 Merge pull request #189 from kichikuou/vanish
StoatSpriteEngine: Make SP_GetMaxZ consider multisprites
2024-09-20 18:12:09 -07:00
kichikuou 49b85f885d StoatSpriteEngine: Make SP_GetMaxZ consider multisprites
This fixes a problem in Vanish! where the opening movie was hidden
behind other sprites.
2024-09-20 20:39:20 +09:00
Nunuhara Cabbage 93e9357f84 Merge pull request #188 from kichikuou/glsl
Fix compile error of dungeon_raster.f.glsl on WebGL
2024-09-19 06:09:19 -07:00
kichikuou 2a7f58c187 Fix compile error of dungeon_raster.f.glsl on WebGL
`sample` is a reserved word in GLSL ES 3.0.
2024-09-19 12:55:21 +09:00
Nunuhara Cabbage 08e47b8d04 Merge pull request #187 from kichikuou/vmDrawGauge
Add guard against division by zero in vmDrawGauge.Draw()
2024-09-18 18:58:51 -07:00
kichikuou 217c4d5d65 Add guard against division by zero in vmDrawGauge.Draw()
When `max` is 0, the gauge is drawn as empty.
2024-09-19 07:32:30 +09:00
Nunuhara Cabbage ea762de937 Merge pull request #186 from kichikuou/DrawDungeon
Implement DrawDungeon.SetWalkedAll
2024-09-17 20:05:31 -07:00
Nunuhara Cabbage e6105ca654 Merge pull request #185 from kichikuou/collision
Initial implementation of TapirEngine.CalcInstance2DDetection
2024-09-17 20:05:16 -07:00
kichikuou 7a87ffc20a Implement DrawDungeon.SetWalkedAll
In GALZOO Island, there are non-debugging code paths that use this.
2024-09-17 15:43:07 +09:00
kichikuou f53ad7d557 Initial implementation of TapirEngine.CalcInstance2DDetection
This is used for collision detection between a map object and the player
character. The current implementation is very simple, only performing
AABB intersection tests, but it is enough to allow the player to walk
around the first dungeon in Rance Quest.
2024-09-16 15:27:11 +09:00
Nunuhara Cabbage ffe4d0d4ae Merge pull request #184 from kichikuou/gsave7
Add support for group save format v7
2024-09-15 21:02:14 -07:00
kichikuou 9e2f03d8be Add support for group save format v7
V5 is used until Daiteikoku, and v7 since Rance Quest. Since both are
Ain v6, the save version is determined by the presence of the "MainVM"
field in AliceStart.ini.
2024-09-16 09:01:49 +09:00
Nunuhara Cabbage 489cfe5a96 Merge pull request #183 from kichikuou/RanceQuest
Fix two graphical bugs of Rance quest
2024-09-15 13:06:18 -07:00
kichikuou 71debfbb65 Rance Quest: Do not render suspended TapirEngine sprites 2024-09-15 08:04:14 +09:00
kichikuou 34258511b9 Rance Quest: GetPartsCGName() returns the name specified by SetPartsCG()
This fixes a graphical bug mentioned in
https://github.com/nunuhara/xsystem4/issues/180#issuecomment-2351079908 .
2024-09-15 07:57:40 +09:00
Nunuhara Cabbage 0f121860fe Merge pull request #182 from kichikuou/msgskip
Android: Call msgskip_save() when app goes to the background
2024-09-14 10:39:50 -07:00
Nunuhara Cabbage 70a6f547cf parts: use separate sprite for each parts
Each parts object is now its own entity in the scene so that entities
are rendered in the correct order when mixing sprites and parts.

Fixes #180
2024-09-14 10:22:51 -07:00
kichikuou 886f15aa9f Android: Call msgskip_save() when app goes to the background
This ensures that msgskip file is saved even if the app is killed.
2024-09-13 14:43:46 +09:00
Nunuhara Cabbage d895301116 Merge pull request #181 from kichikuou/ndk27
Android: Update SDL2 to 2.30.7
2024-09-01 22:45:41 -07:00
kichikuou fdaa2d1dba Android: Update SDL2 to 2.30.7
This fixes build with Android NDK 27.
https://github.com/libsdl-org/SDL/issues/9792
2024-09-02 11:25:56 +09:00
Nunuhara Cabbage da52b312ca Use git describe for version number
Use `git describe` instead of `git rev-parse` for the version number,
since this command will report the latest tagged version in addition to
the current commit.
2024-08-25 10:26:55 -07:00
Nunuhara Cabbage 8cc2334692 windows: use 'windows' subsystem for release build
Only show the console on debug builds.

Fixes item 2 in #177.
2024-08-25 10:10:46 -07:00
Nunuhara Cabbage 486d89669f Reduce window size on small displays
Reduce the window size if the display resolution is smaller than the
game resolution.

Fixes item 1 of #177.
2024-08-25 10:03:10 -07:00
Nunuhara Cabbage c3614c9280 Merge pull request #179 from kichikuou/rsave-comments
Update rsave_read() parameters to specify read mode
2024-08-24 17:43:05 -07:00
kichikuou 00aa0c2ae6 Fix build without debugger 2024-08-25 08:08:45 +09:00
kichikuou 240c2c9395 Update rsave_read() parameters to specify read mode 2024-08-25 08:00:16 +09:00
Nunuhara Cabbage 88236b5a0b Merge branch 'master' of github.com:nunuhara/xsystem4 2024-08-18 13:00:21 -07:00
Nunuhara Cabbage 041cbafef1 Merge pull request #176 from kichikuou/zerodiv
Add guard against division by zero in sact_QuakeScreen()
2024-08-13 18:03:18 -07:00
kichikuou c5b0916279 Add guard against division by zero in sact_QuakeScreen()
GALZOO calls this with amp_x = 0, to shake the screen vertically.
2024-08-13 18:27:48 +09:00
Nunuhara Cabbage 3c61f7cebf dap: draw origin/bounds of parts 2024-08-10 18:54:13 -07:00
Nunuhara Cabbage a13bd155dc dap: send correct reason for stopped event 2024-08-10 18:52:08 -07:00
Nunuhara Cabbage 09bae3d423 Merge pull request #175 from kichikuou/SoundFilePlayer
Implement SoundFilePlayer HLL
2024-08-10 18:40:22 -07:00
kichikuou 1f0fc09294 Implement SoundFilePlayer HLL
Using this, Widenyo plays user-provided BGM files in the
SaveData/MusicData directory.
2024-08-11 07:18:19 +09:00
Nunuhara Cabbage acc869086f Merge pull request #174 from kichikuou/FileOperation
Fix FileOperation.Get{File,Folder}List
2024-08-09 19:18:25 -07:00
kichikuou 0cdf793b42 Fix FileOperation.Get{File,Folder}List
They should return base names, not full paths.
2024-08-10 09:20:13 +09:00
Nunuhara Cabbage d1ba60b673 Merge pull request #173 from kichikuou/hll-sigs
Fix signature mismatch in HLL_EXPORT functions
2024-08-06 19:12:34 -07:00
kichikuou ade7ea9235 Fix signature mismatch in HLL_EXPORT functions
Notable changes:

* The numerator / denominator parameters of AddMotion[HV]GaugeRate are
  int in GoatGUIEngine but float in GUIEngine and PartsEngine.
* AnteaterADVEngine.ADVLogList_AddText has second parameter
  (int WindowNo) since Oyako Rankan.

This also avoids using non-void functions where a void function is
expected. It is fine in most ABIs, but raises a runtime error in wasm32.
2024-08-07 09:14:38 +09:00
Nunuhara Cabbage 7365e8a7ca Merge pull request #172 from kichikuou/RSMv9
resume: Support RSM v9 format
2024-08-05 21:33:52 -07:00
kichikuou c4207e4226 Store struct_ptr in local page
It is currently only used to populate a field in RSM v9 save file.

As far as I can tell from examining Rance 01's resume save, this
reference does not seem to increment the target's refcount.
2024-08-06 07:15:54 +09:00
kichikuou ddaef07fb5 resume: Support RSM v9 format
This enables using --save-format=rsm for games that use delegates.
2024-08-06 07:02:21 +09:00
Nunuhara Cabbage f8174231cb parts: implement flash blend modes
Implement the add, multiply and screen blend modes.
2024-08-05 14:52:13 -07:00
Nunuhara Cabbage 0c8e52f18c parts: ignore invalid state in PE_SetInputState
Fixes bug with disappearing frame on choices in Drapeko.
2024-08-05 13:56:11 -07:00
Nunuhara Cabbage 559ccae1ad Fix text issue in Daibanchou EN
Fix issue where black boxes would appear in the action list text.
Glyph textures are now *at least* as wide as the duospace block width
(e.g. 16/8 on a 16px font), but can be larger to accomodate proportional
fonts with larger glyphs.
2024-08-05 12:37:28 -07:00
Nunuhara Cabbage 0597bfa513 Change status of STB (EN) to supported 2024-08-05 11:15:09 -07:00
Nunuhara Cabbage 921c0b699c Fix glitch in EFFECT_TURN_PAGE shader
The first frame of this effect was not displayed correctly.
2024-08-05 10:57:41 -07:00
Nunuhara Cabbage 3f36bf29b1 Gpx2Plus: use shaders for fullscreen effects
Use the normal effect system for fullscreen effects. This allows effects
that are implemented via shaders to be used, such as the blind effects
which are used frequently in Daibanchou.
2024-08-05 10:48:22 -07:00
Nunuhara Cabbage c6e1f8f155 Fix NewFont layout issues
Ensure that our FT_Face has the correct size before calling
FT_Get_Advance. This fixes the backscene issue mentioned in #154 as well
as some other spacing issues.
2024-08-05 09:12:21 -07:00
Nunuhara Cabbage 65c178c04c Sleep less in ChipmunkSpriteEngine.Sleep
Take delta from previous frame into account when deciding how long to
sleep. And leave a few ms headroom.

This is not how this function works in AliceSoft's implementation. On a
(nearly) empty scene, they start by sleeping 1ms and gradually ramp up.
The largest delay I observed was 11ms.
2024-08-05 08:44:12 -07:00
Nunuhara Cabbage 69ab413676 Remove random print at movie end
Oops.
2024-08-05 08:10:00 -07:00
Nunuhara Cabbage 71a4f5079e Merge pull request #171 from kichikuou/cmake
Add new files to CMakeLists.txt
2024-08-05 08:04:40 -07:00
Nunuhara Cabbage caeba1536a Merge pull request #170 from kichikuou/delegate
Rework delegate implementation
2024-08-05 08:04:15 -07:00
kichikuou fd16bab763 Add new files to CMakeLists.txt 2024-08-05 16:47:16 +09:00
kichikuou 2046ad4d06 Remove delegate field from struct function_call
Now dg_index in the stack is incremented by delegate_call().
2024-08-05 16:25:08 +09:00
kichikuou 12ddb585fb Rework delegate implementation
Delegates "weakly" reference objects, meaning that referenced objects
can be deleted. Delegate invocations must not invoke on deleted objects.
This is achieved as follows:

* Each heap object has a sequential number.
* Each delegate object is backed by a page storing (object, function,
  seq) triples.
* Deleted objects can be detected by comparing the sequential number
  stored in the delegate with the sequential number of the object
  currently on the heap.

This is consistent with the AliceSoft's implementation (presumed from
the contents of resume save).

This implementation removes dead objects from the delegate in DG_CALL
and DG_NUMOF instructions. (We could do that more often, e.g. when an
object is added to a delegate.)

This breaks existing resume saves that contain delegates. For games that
predate delegate support, this does not affect the save format.
2024-08-05 16:25:08 +09:00
kichikuou 0a78489993 Revert delegate memory management changes
This reverts the following commits:

* 4b1257f "Fix delegate memory management"
* 9bb4665 "Add changes to vm.h" (partial revert)
* 75d6386 "Fix ResumeSave/ResumeLoad bugs with delegates" (partial
  revert)

We are going to take a different approach to implementing weak
references in delegates.
2024-08-05 16:25:08 +09:00
Nunuhara Cabbage 21abe6ff9a movie: load case-insensitive filename 2024-08-04 22:25:17 -07:00
Nunuhara Cabbage ab810e8f57 Implement functions for EN Haruka
Implement the NewFont HLL. This is a basic text rendering interface
which supports proportional fonts and kerning

Fixes #154
2024-08-04 21:17:10 -07:00
Nunuhara Cabbage ac298baaec Fix fnl font selection
type 0 -> gothic (ttf)
type 1 -> mincho (ttf)
type >= 256 -> fnl[type - 256]
2024-08-04 09:27:57 -07:00
Nunuhara Cabbage cf3fe0e852 dbg: allow middle-click break in DAP debugger 2024-08-04 09:10:01 -07:00
Nunuhara Cabbage 79ebe443f5 dbg: handle window events while waiting for input
Keep the screen updated while in the debugger.
2024-08-03 09:34:59 -07:00
Nunuhara Cabbage f886f0a64c parts: fix origin mode 3
Mode 3 is top-right.
2024-08-02 19:26:33 -07:00
Nunuhara Cabbage 6b78f53bd7 Implement various functions for Drapeko
The game now runs until after the opening movie, although with some GUI
layout bugs.
2024-08-02 15:08:44 -07:00
Nunuhara Cabbage b21e28ce82 Rance 01 support
As far as I'm aware, the only unimplemented functionality is Y-rotations
of parts objects, which is only used for some screen transition
animations. I was able to clear the game without issue.
2024-08-01 09:52:36 -07:00
Nunuhara Cabbage 35bd6292bb Fix segfault when no mixer channels given in ini 2024-08-01 09:17:51 -07:00
Nunuhara Cabbage c4e17c672d Fix text rendering issue in Daiteikoku
Use gfx_size_char to calculate CharSprite width.

Daiteikoku uses ascii numerals in the .ain file, but the .fnl font has
full-width glyphs.
2024-08-01 08:32:33 -07:00
Nunuhara Cabbage d06d0ff8b5 GoatGUIEngine: fix rotate
Angles are given in radians in GoatGUIEngine.
2024-07-31 20:55:00 -07:00
Nunuhara Cabbage 07290cc8df Rework effect system
Rework transition effect system so that TRANS_Update uses the current
contents of the main surface rather than a stale copy.
2024-07-31 18:51:10 -07:00
Nunuhara Cabbage bc65a40eb4 parts: only render on PE_Update/PE_UpdateParts
Also, fix an issue in Rance 01 where a bad frame would be displayed when
loading a save (this is a bug present in the original game).
2024-07-31 16:28:48 -07:00
Nunuhara Cabbage bc04af131a parts: implement miscellaneous functions 2024-07-31 07:35:10 -07:00
Nunuhara Cabbage d48cd2545d parts: fix load issues, use numeral fonts
When loading parts, we need to mark them as dirty and recalculate
hitboxes. Also fix loading of numerals, which was completely broken
previously on games that use combined numeral CGs (e.g. Rance 01).

Numerals now use a `parts_numeral_font` structure, so that textures can
be shared between numeral objects.
2024-07-28 15:40:56 -07:00
Nunuhara Cabbage 133ff5421f debugger: use current stack frame for 'this'
Interpret 'this' expression in current call stack frame.
2024-07-28 15:37:41 -07:00
Nunuhara Cabbage 69deb9ee91 Add --msgskip-delay option
Add an option to insert a delay when skipping messages with CTRL.

This is accomplished by overriding the 'A' function. Since the function
to check the state of a key (CTRL in this case) varies by game, an
xsystem4-specific system call is added for this purpose.

Fixes #119
2024-07-28 10:24:01 -07:00
Nunuhara Cabbage f2700398dd Merge pull request #169 from kichikuou/dalk-gaiden
Support DALK Gaiden
2024-07-28 08:17:27 -07:00
kichikuou 8f7d9886c2 Update game compatibility table
DALK Gaiden is now supported.
2024-07-28 09:48:02 +09:00
kichikuou a2c225bf64 vmSprite changes for DALK Gaiden
* SetFind(2) lets the sprite to participate in hit testing even if it is
  not visible.
* SetAnime() respects frame_s and frame_e parameters. Sprites animate
  only when `current` is within the range of [frame_s, frame_e].
* SetCurrent() resets animation state.
2024-07-28 09:24:07 +09:00
kichikuou dd77de027b Implement vmSound functions for DALK Gaiden 2024-07-28 09:09:21 +09:00
kichikuou bcf81af71d Fix vmMsgSkip.Query
It should return true only if message skip is turned on.
2024-07-28 09:00:40 +09:00
kichikuou c3c241adf5 vmDrawNumber: Respect space parameter 2024-07-28 09:00:40 +09:00
kichikuou cb4a04ce14 Implement vmString functions for DALK Gaiden 2024-07-28 09:00:40 +09:00
kichikuou 877ca1f74e Implement vmSystem functions for DALK Gaiden 2024-07-28 09:00:40 +09:00
kichikuou d3bd28a0c1 Implement vmFile functions for DALK Gaiden 2024-07-28 08:52:20 +09:00
kichikuou 4e4f0b8683 vmDrawMsg: Implement tags used in DALK Gaiden 2024-07-28 07:58:59 +09:00
kichikuou dfad543f6e Implement vmDalkGaiden HLL 2024-07-28 07:53:12 +09:00
Nunuhara Cabbage 9a1f9e763a Merge pull request #168 from kichikuou/DALKDemo
Add DALKDemo and DALKEDemo HLLs
2024-07-27 07:15:16 -07:00
kichikuou 1a9021e1ed Add DALKDemo and DALKEDemo HLLs
DALKDemo is responsible not only for the opening demo but also for the
UI of the title menu. This change fully implements DALKDemo. DALKEDemo
is stubbed out.
2024-07-27 20:36:35 +09:00
Nunuhara Cabbage 5cca12b0ce Merge branch 'master' of github.com:nunuhara/xsystem4 2024-07-26 21:01:09 -07:00
Nunuhara Cabbage 0aed62f448 Merge pull request #167 from kichikuou/vmGraph
vmGraph changes for DALK Gaiden
2024-07-26 20:49:31 -07:00
Nunuhara Cabbage 37a5f977c3 Merge pull request #166 from kichikuou/vmData
Implement vmData HLL
2024-07-26 20:47:09 -07:00
Nunuhara Cabbage d7c550ea62 parts: fix GetPartsUpperLeftPos{X,Y}
Hitbox is stored in local coordinates. These functions return global
coordinates.
2024-07-26 20:28:55 -07:00
kichikuou da86fd4875 vmGraph changes for DALK Gaiden
The effect API is slightly changed so that it can be used from pre-SACT
graphics engines.
2024-07-27 07:55:32 +09:00
kichikuou aba32d7fab Implement vmData HLL
Used in DALK Gaiden to read DA.ALD.
2024-07-27 07:25:54 +09:00
Nunuhara Cabbage 103976b2b0 parts: use floats to calculate text width
gfx_render_text uses floats for layout, so the parts_text layout should
use floats to match.

This fixes an issue with off-center text in the English version of Rance
01.
2024-07-26 14:41:28 -07:00
Nunuhara Cabbage 0330006b89 parts: implement SetPartsMessageWindowShowLink
The interface to this feature is strange. The message window visibility
state is passed to Update/UpdateParts every frame rather than being a
part of the global parts state. I've implemented it using global state,
which might be subtly wrong in some way, but it seems to work as
expected in Rance 01.
2024-07-25 20:49:30 -07:00
Nunuhara Cabbage 12f3b5d241 Implement ChipmunkSpriteEngine.VIEW_SetOffsetPos
Used for screen quake effect in Rance 01.
2024-07-25 18:29:36 -07:00
Nunuhara Cabbage 673827cf70 parts: implement vibration motion 2024-07-24 20:18:04 -07:00
Nunuhara Cabbage 5212d5b7f9 dap: render parts with children
When rendering parts, render the whole hierarchy instead of just the
individual node.

Also, output parts entities in the format expected by xsys4dbg.
2024-07-24 19:18:13 -07:00
Nunuhara Cabbage bfa2deeffb Fix transition issues in Rance 01
PE_UpdateComponent needs to be called before rendering the starting
frame of a transition effect.

This fixes a glitch in the ●立ち絵 function of Rance 01 when
transitioning to a variant CG.
2024-07-23 19:51:22 -07:00
Nunuhara Cabbage 635cce877f Merge pull request #165 from kichikuou/typo
Fix typo
2024-07-23 06:30:45 -07:00
kichikuou 0bab7544cd Fix typo 2024-07-23 18:10:34 +09:00
Nunuhara Cabbage 48b6e24135 Fix A_SORT_MEM with string members
Fixes various bugs in Rance 01.
2024-07-22 11:50:33 -07:00
Nunuhara Cabbage 423ceb5abc Merge pull request #164 from kichikuou/gpx2plus
Gpx2Plus.Create() should fail if width <= 0 or height <= 0
2024-07-22 06:36:24 -07:00
Nunuhara Cabbage 9b6a032d24 Merge pull request #163 from kichikuou/vmMsgSkip
Implement vmMsgSkip HLL functions
2024-07-22 06:35:45 -07:00
Nunuhara Cabbage 525006d716 Merge pull request #162 from kichikuou/vmArray
Implement vmArray functions for DALK Gaiden
2024-07-22 06:34:41 -07:00
Nunuhara Cabbage 083fd23503 Merge pull request #161 from kichikuou/sys40vm
VM changes for DALK Gaiden (AIN v0)
2024-07-22 06:30:24 -07:00
kichikuou 3f18b181a3 Gpx2Plus.Create() should fail if width <= 0 or height <= 0
This fixes "Incomplete framebuffer" error in Daibanchou.
2024-07-21 14:26:01 +09:00
kichikuou 50ec61ba37 Implement vmMsgSkip HLL functions
Reusing the functions for MsgSkip.
2024-07-21 10:45:36 +09:00
kichikuou f1ad3ddf25 Implement vmArray functions for DALK Gaiden
This also fixes a bug of vmArray.AroundRect (although it does not affect
Mamanyonyo).
2024-07-21 09:14:17 +09:00
kichikuou 936167ee9c VM changes for DALK Gaiden (AIN v0)
- Opcode 0x62 (EOF in later games) is used as SH_STRUCTREF
- The STR0 table does not exist, S_PUSH refers to the MSG0 table instead
2024-07-21 08:21:58 +09:00
Nunuhara Cabbage 52a624009d Merge pull request #160 from kichikuou/ffmpeg
movie_ffmpeg: Use new FIFO API and channel layout
2024-07-03 21:00:12 -07:00
kichikuou 71ead2bf85 movie_ffmpeg: Use new FIFO API and channel layout
This fixes https://github.com/nunuhara/xsystem4/issues/159.

The minimum required ffmpeg version is now 5.1.
2024-07-04 12:48:56 +09:00
Nunuhara Cabbage d56f075ee3 Merge pull request #158 from kichikuou/text
Add a parameter to gfx_draw_glyph() for controlling blending behavior
2024-06-18 17:45:35 -07:00
kichikuou 04d5237020 Use glClear() to initialize texture in gfx_init_texture_rgba()
This is faster when initializing large textures.
2024-06-19 07:45:55 +09:00
kichikuou 6379dbad4b SengokuRanceFont: Draw text on sact_sprite.text.texture
To get consistent rendering results regardless of the background image
on the sprite.
2024-06-15 14:35:01 +09:00
kichikuou b155b4b79f Add a parameter to gfx_draw_glyph() for controlling blending behavior
This adds a new parameter `blend` to `gfx_draw_glyph()`. When `blend` is
true, the function performs normal alpha blending (same as
`gfx_draw_glyph_to_pmap()`).

If `blend` is false, the resulting pixel color will be the text color
and the alpha value will be copied from the glyph texture. However, if
the alpha value is less than 0.01, the pixel will not be written.

If you are drawing on a transparent texture (which will later be blended
into another texture), `blend` should be false. If you are drawing on top
of an image, `blend` should be true.

`gfx_draw_glyph()` used to fill transparent pixels with text color, but
this step is no longer needed and has been removed.
2024-06-15 14:34:58 +09:00
Nunuhara Cabbage d7fd6d4e1e Merge pull request #157 from kichikuou/mamanyonyo
Mamanyonyo support
2024-06-02 14:46:45 -07:00
kichikuou 64fd2a6858 Add HLL libraries for Mamanyonyo
== Handles

As a general pattern, these libraries have an interface based on integer
handles allocated by Open() and released by Close(); in the AliceSoft's
implementation, handles are pointers to internal objects. Note that:

* 0 is used to represent an invalid handle, and
* Small numbers cannot be handles. For example, Mamanyonyo has code that
  treats an integer as a CG number if it is less than 10000, and as a
  surface handle otherwise.

In this patch, handles are generated using id_pool.h functions. In order
to achieve the above, the interface is extended so that the minimum ID
value can be specified (the `base` parameter of id_pool_init()).

== Graphics System

Surface objects (implemented in vmSurface.[ch]) plays a central role in
Mamanyonyo's graphics system, and the sprite system (vmSprite.c) is
implemented on top of it. vmSprite.c uses scene.h functions.

== vmChrLoader and vmMapLoader

Since Mamanyonyo's character data format is the same as Widenyo's,
the ChrLoader code has been refactored and used from vmChrLoader.c.
On the other hand, MapLoader and vmMapLoader do not share code because
the map file format is different from Widenyo.
2024-05-31 13:36:29 +09:00
Nunuhara Cabbage 1ec06a4b80 Merge pull request #156 from kichikuou/sys41vm
VM changes for Mamanyonyo
2024-05-30 18:03:37 -07:00
kichikuou 2840b21946 Sys41VM: Do not unref arrays passed by value to HLL calls 2024-05-29 13:14:48 +09:00
kichikuou 69b1826d63 SR_ASSIGN does not take a struct type in ain v1 2024-05-29 13:14:48 +09:00
kichikuou 69cd1cb630 Add support for S_PLUSA instruction
I'm not sure how it is different from S_PLUSA2.
2024-05-29 13:14:48 +09:00
kichikuou 012f8f8af4 Implement scenario jump for ain v1
In ain v1, destination addresses of scenario jumps are stored in the
SLBL section of the ain.
2024-05-29 13:14:48 +09:00
kichikuou b85b426872 Local struct initialization in Sys41VM (ain v1)
Sys41VM doesn't have the SH_LOCALCREATE instruction, so struct pages
must be allocated when function_call() initializes local variables.
2024-05-29 13:14:48 +09:00
Nunuhara Cabbage 10169951c0 Merge pull request #155 from kichikuou/fix-incomplete-framebuffer
gfx: Clamp texture size larger than GL_MAX_TEXTURE_SIZE
2024-05-25 07:16:01 -07:00
kichikuou 8eefbcec95 gfx: Clamp texture size larger than GL_MAX_TEXTURE_SIZE
This prevents glTexImage2D() from failing and resulting in an invalid
texture if a width or height specified in sact2.SP_Create() is greater
than GL_MAX_TEXTURE_SIZE.

It is very rare and is probably a programming error in the game script.
If the resulting texture is used for rendering, it may mess up the
screen, but at least it won't crash.

Fixes https://github.com/kichikuou/xsystem4-android/issues/4.
2024-05-25 08:11:15 +09:00
Nunuhara Cabbage f73dd38dab Merge pull request #153 from kichikuou/remove_utf8
Fix system.DeleteSaveFile and vmFile.Delete on Windows
2024-05-19 06:46:43 -07:00
kichikuou 9e821a754c Fix system.DeleteSaveFile and vmFile.Delete on Windows 2024-05-19 16:03:48 +09:00
Nunuhara Cabbage f2caed46a8 Merge pull request #152 from kichikuou/little_endian
Use little_endian.h in libsys4
2024-05-18 22:08:14 -07:00
kichikuou 7ef9719cc7 Use little_endian.h in libsys4 2024-05-19 10:59:50 +09:00
Nunuhara Cabbage f47e5e5a82 Merge pull request #151 from kichikuou/PixelRestore
Add stubs for PixelRestore hll
2024-05-03 20:37:55 -07:00
kichikuou f0fc0b6699 Add stubs for PixelRestore hll
It is used (only?) in Beat Blades Haruka, when two enemies are found by
enemy seek.

This is just a screen effect and can be ignored without affecting
gameplay.
2024-05-04 07:31:35 +09:00
Nunuhara Cabbage 8cfc20999f Merge pull request #150 from kichikuou/PassRegister
PassRegister: Use the same format as AliceSoft's implementation
2024-04-26 21:12:47 -07:00
kichikuou bb72ad60b2 PassRegister: Improve query performance
Keep the arrays sorted and use binary search.
2024-04-27 11:45:35 +09:00
kichikuou 2b3e100cba PassRegister: Use the same format as AliceSoft's implementation
The old JSON format can still be loaded for backwards compatibility.
2024-04-27 11:45:29 +09:00
Nunuhara Cabbage 5f432ce672 Merge pull request #149 from kichikuou/file
Fix compilation error with clang
2024-04-15 06:09:24 -07:00
kichikuou c3e4cd5e9f Fix compilation error with clang 2024-04-15 15:27:29 +09:00
Nunuhara Cabbage 0f4529e1a6 Merge pull request #148 from kichikuou/file
File.Read/Write: Use the same format as AliceSoft's implementation
2024-04-14 15:46:57 -07:00
kichikuou 58366e199e File.Read/Write: Use the same format as AliceSoft's implementation
This improves interoperability of save files in Rance6, GALZOO, etc.
The old JSON format can still be loaded for backwards compatibility.
2024-04-14 09:23:23 +09:00
Nunuhara Cabbage d512275f99 Merge pull request #147 from kichikuou/system-gettime
system.GetTime(): Use SDL_GetTicks() instead of clock_gettime()
2024-04-02 17:30:09 -07:00
kichikuou 7e54e159df system.GetTime(): Use SDL_GetTicks() instead of clock_gettime()
Some games (e.g. Toushin Toshi 3) malfunction when system.GetTime()
returns a negative number.

On many systems clock_gettime(CLOCK_MONOTONIC) returns the time since
OS startup, but this wraps to a negative number after about 24.8 days.

As a mitigation, use SDL_GetTicks() instead, which returns the elapsed
time since game startup.
2024-04-02 13:26:53 +09:00
Nunuhara Cabbage a92e26c49c Merge pull request #146 from kichikuou/fix-feedback-loop
SACT2: Do not put sprite -1 (main surface) in the scene list
2024-03-30 07:37:18 -07:00
kichikuou e65a506dac SACT2: Do not put sprite -1 (main surface) in the scene list
Since this is the surface to render to, it should not be added to the
list of surfaces to draw from.

This is done by marking sprite -1 as hidden. Now SACT2.SP_GetShow(-1)
returns 0 and SACT2.SP_SetShow(-1, true) raises a runtime error. This is
consistent with the AliceSoft's implementation.
2024-03-28 09:38:50 +09:00
Nunuhara Cabbage 6e78f05d75 Merge pull request #145 from kichikuou/android-upstream
Android support
2024-03-16 09:10:40 -07:00
kichikuou f0f84a05ce Android: Fix text input issue
On Android, when the user enters "ら", "ん", "す" with the virtual
keyboard and converts it to "ランス", xsystem4 receives the following
events:

    SDL_TEXTINPUT("ら")
    SDL_TEXTINPUT("ん")
    SDL_TEXTINPUT("す")
    SDL_KEYDOWN(backspace)
    SDL_KEYUP(backspace)
    SDL_KEYDOWN(backspace)
    SDL_KEYUP(backspace)
    SDL_KEYDOWN(backspace)
    SDL_KEYUP(backspace)
    SDL_TEXTINPUT("ランス")

The backspace key events are generated by SDL to delete the unconverted
text. However, System4 games process the backspace key by polling, so
if the SDL_KEYDOWN and SDL_KEYUP events occur in close succession, the
game cannot detect that the backspace key has been pressed.

To solve this problem, this patch queues these consecutive input events
and processes them with an interval of at least 10ms.

Fixes https://github.com/kichikuou/xsystem4-android/issues/2.
2024-03-16 13:36:57 +09:00
kichikuou 18a71960e8 Add touch gestures
This adds gestures that allow touch device users to emulate the
following mouse/keyboard actions:

- Right-click: touch outside the game's viewport
- Ctrl key: one-finger touch and hold (for one second)
- Mouse wheel: Swipe up / down with two fingers
2024-03-16 13:35:12 +09:00
kichikuou 5e80c96587 Defer mouse button events synthesized from touch events
A typical event loop in System4 game looks like this:

    while (true) {
        SACT2.Mouse_GetPos(x, y);

        // Update the screen according to the mouse position (x, y)
        ...
        SACT2.Update();

        if (SACT2.Key_IsDown(VK_LBUTTON)) {
            // A button was pressed at (x, y)
            ...
        }
    }

This works poorly with touch devices. Assume that a touch event occured
during SACT2.Update(). It updates internal mouse position and button
status. After SACT2.Update(), the game calls SACT2.Key_IsDown(VK_LBUTTON).
It returns true, but the game has not yet called SACT2.Mouse_GetPos()
after the touch event, so it assumes that the button was pressed where
the mouse pointer was before the touch.

In order to avoid this situation, this defers mouse button events
synthesized (by SDL) from touch. Such deferred events are processed when
SACT2.Mouse_GetPos() is called or after 50ms.
2024-03-16 13:26:29 +09:00
kichikuou 9ec354c198 Android: Add CMakeLists.txt 2024-03-16 12:54:54 +09:00
kichikuou b7304f747b Android: Load fonts and shaders from apk assets 2024-03-16 12:53:56 +09:00
kichikuou b7ab1ed476 Android: Enforce landscape fullscreen mode 2024-03-16 12:49:52 +09:00
kichikuou bc522b27e0 Android: Report ERROR() with a message box 2024-03-16 12:45:07 +09:00
kichikuou 3e9f975213 Update libsys4 2024-03-16 12:43:55 +09:00
Nunuhara Cabbage ba9e42fad6 Merge pull request #144 from kichikuou/resume-save
Add support for reading / writing System4 resume save files
2024-02-15 20:34:45 -08:00
kichikuou 05226453d9 Add support for reading / writing System4 resume save files
Writing save files in RSM format is only enabled if `--save-format=rsm`
command line flag is specified, because this implementation does not
create save files that are fully compatible with the AliceSoft's
implementation:

* There is no simple way to determine the RSM format version used by the
  game.
* There are several unknown fields in the RSM format.
* Empty arrays are represented as NULL pages in xsystem4, so type
  information of such objects cannot be saved.

These limitations are not a problem for the following use cases:

* Use the created save files only in xsystem4
* Load save files created by System40.exe

Also, RSM save files are much smaller and faster to read and write than
JSON.
2024-02-14 12:45:37 +09:00
Nunuhara Cabbage 4e2c050ffc Merge pull request #143 from kichikuou/savedir
Create savedir just before running VM
2024-02-09 06:52:02 -08:00
kichikuou 39701c7657 Create savedir just before running VM
Before this change, directory described in System40.ini was created even
if --save-folder was specified.
2024-02-07 16:36:57 +09:00
Nunuhara Cabbage 6bdd22f875 Merge pull request #142 from kichikuou/game_compatibility
Update game_compatibility.md
2024-01-31 06:12:09 -08:00
kichikuou 20411f3518 Update game_compatibility.md 2024-01-31 18:09:28 +09:00
Nunuhara Cabbage 27e733758d Merge pull request #141 from kichikuou/banmisc
BanMisc: Use the same save format as AliceSoft's implementation
2023-12-16 17:01:35 -08:00
kichikuou 288381d8f6 BanMisc: Use the same save format as AliceSoft's implementation
BanMisc.{Save,Load}Struct now serializes structs in the same format as
AliceSoft's implementation, rather than in JSON format. The old JSON
format can also be loaded for backwards compatibility.
2023-12-17 09:01:36 +09:00
kichikuou c3f39aeb96 Use mt19937_xorcode helper function 2023-12-17 09:01:28 +09:00
Nunuhara Cabbage 05d2ad8600 Merge pull request #139 from kichikuou/shimaima
Update libsys4
2023-11-17 23:41:48 -08:00
kichikuou 0911103949 Update libsys4
And add tests for https://github.com/nunuhara/libsys4/pull/26.
2023-11-18 16:35:58 +09:00
Nunuhara Cabbage 9c8c290d83 Merge pull request #138 from kichikuou/movie-fix
Fix a bug that bgm did not work after movie load failed
2023-10-15 07:02:08 -07:00
kichikuou bf3700ea8e Fix a bug that bgm did not work after movie load failed
When movie_free() was called in movie_load(), mixer_stream_stop(0) was
mistakenly called and the music mixer was stopped.
2023-10-15 15:00:07 +09:00
Nunuhara Cabbage c521f1faf7 Merge pull request #137 from kichikuou/confirm3
Add Confirm3.Initialize
2023-09-25 20:10:23 -07:00
kichikuou ef7b789810 Add Confirm3.Initialize
For some reason, this function is renamed from GetHDDVolumeNumber only
in Momoiro Guardian.

This also makes all Confirm3 functions HLL_QUIET_UNIMPLEMENTED.
2023-09-17 21:22:43 +09:00
Nunuhara Cabbage 530f606657 Merge pull request #136 from kichikuou/sactdx
Add SACTDX.SP_DeleteAll
2023-09-05 06:06:03 -07:00
kichikuou 120b9888f1 Add SACTDX.SP_DeleteAll
Used in Edo Ranze.
2023-09-05 20:31:27 +09:00
Nunuhara Cabbage 87884d4230 Merge pull request #135 from kichikuou/s_mod
Fix S_MOD for bool and long int
2023-09-01 20:38:21 -07:00
kichikuou 0b4eb92e95 Fix S_MOD for bool and long int
* The "type" argument of S_MOD is 48 for bool, 56 for long int
* %b, %c, %d can consume any integer types (int, bool, or long int)
* If the value cannot be consumed, S_MOD should return the format
  string instead of an empty string
2023-09-02 11:36:54 +09:00
kichikuou 51e55644f5 DrawDungeon14: Fix incorrect return types in SetRaster{Scroll,Amp} 2023-09-02 11:11:01 +09:00
Nunuhara Cabbage 296496e895 Merge pull request #134 from kichikuou/raster
DrawDungeon14: Implement raster effect
2023-09-01 13:42:49 -07:00
kichikuou afae3fc9a6 DrawDungeon14: Implement raster effect
It is implemented as a post-processing effect. The hack for the
projection transform matrix has been removed, because now the image is
flipped upside down in the post-processing stage.
2023-09-01 21:45:46 +09:00
kichikuou a301ca1311 DrawGraph, Gpx2Plus: Implement CopyReverseUD() 2023-08-31 14:16:52 +09:00
Nunuhara Cabbage eb97693e3f Merge pull request #133 from kichikuou/msgskip
Fix path resolution in MsgSkip.Init
2023-08-25 06:48:35 -07:00
kichikuou a6f01f7f82 Fix path resolution in MsgSkip.Init
Some games (Ojou-sama o Iinari ni Suru Game and Double Sensei Life, as
far as I know) pass "SaveData\\MsgSkip.asd" to MsgSkip.Init. So this
change:

* uses savedir_path() to resolve the relative path, and
* removes the "SaveData\\" prefix, otherwise "SaveData/" will be
  duplicated in the resolved path.
2023-08-24 21:31:14 +09:00
Nunuhara Cabbage a27996def8 Merge pull request #132 from kichikuou/window-settings
SystemService: Implement "Wait Vsync" window setting
2023-08-22 18:26:42 -07:00
kichikuou fa96987eca SystemService: Implement "Wait Vsync" window setting
This also rewrites the SetWindowSetting and GetWindowSetting functions
based on the understanding gained from examining games after Haru Urare:

* Setting IDs are consistent across games.
* Settings 5 and 6 are added in Pastel Chime 3, both default true
2023-08-22 21:27:46 +09:00
Nunuhara Cabbage 192e65a827 Merge pull request #131 from kichikuou/mixer
SystemService can only access mixers specified in ini
2023-07-13 16:58:47 -07:00
kichikuou a536912b38 SystemService can only access mixers specified in ini
This ensures that SystemService.hll mixer functions can only access
mixers specified in System40.ini, even if xsystem4 added more mixers.

Beat Blades Haruka checks the number and names of mixers, and refuses to
open the config screen if they are not as expected.
2023-07-13 09:13:26 +09:00
Nunuhara Cabbage fcf26a9ca7 Merge pull request #130 from kichikuou/flash
Flash: Add support for sounds and sprite objects
2023-07-09 16:43:49 -07:00
kichikuou 4cb374dd5d Flash: add support for sprite objects
This implementation supports only single-frame sprites with a single
child object. As far as I know, all Flash effects in AliceSoft games
meet this requirement.
2023-07-09 17:05:25 +09:00
kichikuou 9543bf1761 Flash: sound support
The DefineSound tag in SWF has raw PCM data, so we create a WAV file in-
memory and pass it to audio_play_archive_data(). This is not strictly
necessary since libsndfile can handle raw PCM data, but keeps the audio
and mixer interfaces simple.
2023-07-09 17:05:21 +09:00
Nunuhara Cabbage 1527a1cb85 Merge pull request #129 from kichikuou/tapir
Initial implementation of TapirEngine HLL
2023-07-08 07:27:32 -07:00
kichikuou 588d4f1024 Initial implementation of TapirEngine HLL
TapirEngine is the 3D rendering engine used in RanceQuest.

This implements the minimum functionality that is needed to render the
first battle scene. The lighting is still incorrect.
2023-07-08 08:05:07 +09:00
kichikuou c815d48274 Add stubs for SpriteEngine functions
- ChipmunkSpriteEngine.SP_Suspend
- StoatSpriteEngine.FPS_SetShow
- StoatSpriteEngine.FPS_GetShow
2023-07-07 09:29:21 +09:00
kichikuou 1730ddebae ChipmunkSpriteEngine: Add DX_{Get,Set}UsePower2Texture 2023-07-03 08:39:45 +09:00
kichikuou 1694f5cb89 Add Math.tan 2023-07-03 08:39:42 +09:00
Nunuhara Cabbage 1855cd4167 Merge pull request #128 from kichikuou/drawline
DrawGraph: Implement DrawLine and DrawLineToAMap
2023-07-02 07:30:10 -07:00
kichikuou a4ff641366 DrawGraph: Implement DrawLine and DrawLineToAMap 2023-07-02 14:04:31 +09:00
Nunuhara Cabbage 2085c28bd1 Merge pull request #127 from kichikuou/text
StoatSpriteEngine: Clear cached font_size struct in SP_SetTextSpriteSize
2023-07-01 20:54:12 -07:00
kichikuou 6c2a55c67a StoatSpriteEngine: Clear cached font_size struct in SP_SetTextSpriteSize
This fixes text layout issue in Daiteikoku and Rance Quest.
2023-07-02 10:11:44 +09:00
Nunuhara Cabbage 2ffb4a4ab0 Merge pull request #126 from kichikuou/DrawGraph
Add two DrawGraph functions used in Daiteikoku
2023-06-18 07:16:08 -07:00
kichikuou 0e5d41a728 Implement DrawGraph.CopyGrayscale
Used in Daiteikoku.
2023-06-18 20:57:00 +09:00
kichikuou e1d5eba1f2 Implement DrawGraph.CopyRotZoom2Bilinear
Used in Daiteikoku.

It's similar to DrawGraph.CopyRotZoom, but specifies source and
destination center points.
2023-06-18 20:57:00 +09:00
Nunuhara Cabbage c99e9842ad Merge pull request #125 from kichikuou/file2
Implement File2.OpenArchiveForRead
2023-06-17 22:05:22 -07:00
kichikuou 20fabf40fa Implement File2.OpenArchiveForRead
Rance Quest uses yet another ad hoc archive format (.rea) for the map
data. This function opens an entry in the rea archive for reading.
2023-06-17 13:47:51 +09:00
Nunuhara Cabbage d8bba6e391 Merge pull request #124 from kichikuou/flash
Partial implementation of Flash parts type
2023-06-16 19:54:40 -07:00
kichikuou 01429fe2a8 Update libsys4 2023-06-17 11:32:47 +09:00
kichikuou 3496f45e39 parts: add basic implementation of Flash type
Sprite objects and sounds are not implemented yet.
2023-06-17 11:32:14 +09:00
kichikuou 88c37a9165 Add AFF/SWF parser
This SWF parser is far from complete, in particular it only supports
simple bitmap-filled rectangular shapes, but it covers all the features
used in Daiteikoku, Rance Quest, Pastel Chime 3 and Dorapeko.

Flash files are stored in archives in AFF format, which is an encrypted
SWF wrapper. The decryption code was adapted from GARbro.
2023-06-17 11:32:11 +09:00
kichikuou 6d3abdaa02 Add ASSET_FLASH asset type 2023-06-11 15:06:44 +09:00
Nunuhara Cabbage eaa08b6991 Merge pull request #123 from kichikuou/sprite-newline
Fix sact_SP_TextNewLine when nTextSize is 0
2023-06-04 15:30:35 -07:00
kichikuou 1887ceff7f Fix sact_SP_TextNewLine when nTextSize is 0
Text sprites hold the height of the text drawn on them, and
sact_SP_TextNewLine increases the Y coordinate by this value if
nTextSize is 0.

The nTextSize argument of sact_SP_TextHome affects this value.

Beat Blades Haruka depends on this behavior.
2023-06-04 12:04:28 +09:00
Nunuhara Cabbage ff26a7f646 Merge pull request #122 from kichikuou/majo
Fix graphics issues found in Majo no Shokuzai
2023-06-03 13:07:02 -07:00
kichikuou 7dd85e445e Fix DrawGraph.CopyRotateY*
The back side sprite should not appear as a mirror image.
2023-06-03 13:18:13 +09:00
kichikuou 4f22907004 Fix DrawGraph.CopyRotZoom*
The `w` and `h` parameters are clamped to the source texture size.
2023-06-03 13:18:13 +09:00
Nunuhara Cabbage ddc9f1ae12 DAP: add scene/parts requests
xsystem4.scene: returns a list of scene entities
  * all entities have an entityId that can be used in further requests
  * SACT2 sprites have a 'sprite' member with a spriteId that can be
    used in further requests

xsystem4.renderEntity: renders a scene entity and returns the texture
xsystem4.spriteTexture: returns the stored SACT2 sprite texture
xsystem4.renderParts: renders a parts object and returns the texture
xsystem4.partsTexture: returns the stored parts object texture

Image data in texture objects is RGB8888 encoded in base 64.
2023-05-26 08:44:36 -07:00
Nunuhara Cabbage 7392d74efe Replace debug_print with to_json
to_json and cJSON_Print are now used when displaying debug information.

The main reason for this change it to support sending objects over DAP.
2023-05-14 20:12:34 -07:00
Nunuhara Cabbage 535a1f1804 Merge branch 'master' of github.com:nunuhara/xsystem4 2023-05-13 12:14:12 -07:00
Nunuhara Cabbage d387ee2816 Merge pull request #121 from kichikuou/dungeon
DrawDungeon: Fix cell drawing order
2023-05-13 12:13:13 -07:00
kichikuou dc174fa91c DrawDungeon: Fix cell drawing order
Transparent objects should be drawn in order from furthest to nearest.
In DrawDungeon, the actual viewpoint is slightly behind the camera, but
this was not taken into account when sorting cells by distance.

Fixes #120
2023-05-13 11:27:45 +09:00
kichikuou cf318bb47c Add debug print function for DrawDungeon plugin
For now it just prints the camera position.
2023-05-13 10:56:36 +09:00
Nunuhara Cabbage d0a2c5ec40 Fix vm_exit when called before vm_execute_ain
The can happen when launched in the debugger and then exited before
starting execution.
2023-05-07 15:32:29 -07:00
Nunuhara Cabbage cff6272c18 Always use absolute path for config.home_dir
And by extension config.save_dir. This fixes an issue where paths
retrieved by system.GetSaveFolderName and then passed to gamedir_path
would become invalid.

Fixes #115
2023-05-06 08:29:12 -07:00
Nunuhara Cabbage e808d770a9 Merge pull request #118 from kichikuou/fade
Fix channel volume after fade completion
2023-05-06 07:49:23 -07:00
Nunuhara Cabbage 63067d32d3 Merge pull request #116 from kichikuou/SystemService
Add two SystemService functions
2023-05-06 07:48:57 -07:00
kichikuou f9e9f20c05 Fix channel volume after fade completion
This fixes https://github.com/nunuhara/xsystem4/issues/117.
2023-05-05 10:01:58 +09:00
kichikuou 3ee2f703a4 Add two SystemService functions
- Implement SystemService.GetGameVersion
- Add stub for SystemService.AddURLMenu
2023-05-04 21:06:34 +09:00
Nunuhara Cabbage 7b512485e6 DAP: implement disconnect command
Also, fix bug in setInstructionBreakpopints response.
2023-05-03 17:19:03 -07:00
Nunuhara Cabbage d4e06db8e2 Initial DAP implementation
So far only basic debugging functionality is implemented (breakpoints,
stepping, stack traces/variables).

This is not tested against an established DAP client. My intention is to
create a custom GUI frontend and extend the protocol to support
xsystem4-specific features, such as inspecting the scene. (This work is
underway.)

Instruction references are hex-encoded address strings. Clients do not
need to obtain them from DAP requests. This is outside the spec, but it
is needed for a binary-only debugger.

Functions that write directly to stdout (e.g. printf) should generally
not be used any more.  Instead, use either sys_message (which respects
sys_silent) or log_message (which will send the message to the debugger
if DAP is enabled). stderr can be used as normal.
2023-04-29 10:32:50 -07:00
Nunuhara Cabbage 4ad893c147 Merge pull request #114 from kichikuou/windows-fix
Fix character encoding issue in vm_stack_trace()
2023-04-23 11:18:57 -07:00
kichikuou 381e644a42 Fix character encoding issue in vm_stack_trace() 2023-04-23 21:34:35 +09:00
Nunuhara Cabbage ba81077c2e Fix performance issue with PartsEngine text
Don't allocate massive textures for each text object. Instead, allocate
a texture for each character and then compose them onto an appropriately
sized texture.

Fixes a performance issue in Rance 01.
2023-04-16 15:44:58 -07:00
Nunuhara Cabbage f7cf2264fa Fix multiple issues with resetting HLLs
Clear PartsEngine/SACT2/CharSpriteManager/audio state when resetting the
VM.

Fixes #113
2023-04-14 16:00:40 -07:00
Nunuhara Cabbage 9bb4665fa9 Add changes to vm.h
Missed this file in the last commit.
2023-04-14 15:57:22 -07:00
Nunuhara Cabbage 4c9c23f620 Merge branch 'master' of github.com:nunuhara/xsystem4 2023-04-14 15:16:10 -07:00
Nunuhara Cabbage 75d6386e25 Fix ResumeSave/ResumeLoad bugs with delegates
When loading a delegate, the stored objects must be registered to it.

Also, delegate_call cannot use vm_execute because the VM cannot be
resumed from such a state.

Incidentally, this means that ResumeSave cannot be called from within a
constructor/destructor (since they also use vm_execute), but this is
probably not a problem.
2023-04-14 15:08:45 -07:00
Nunuhara Cabbage 983944a776 Implement PartsEngine Save/Load
The format should not be considered stable at this point, as PartsEngine
support is still experimental and subject to change. But it's useful to
have these functions for testing purposes.
2023-04-14 10:30:36 -07:00
Nunuhara Cabbage 373903cdb5 Implement ChipmunkSpriteEngine.SP_RenderView
Used to create save thumbnails in Shaman's Sanctuary.
2023-04-14 07:55:57 -07:00
Nunuhara Cabbage 4b1257fc13 Fix delegate memory management
Although delegate pages store references to objects on the heap,
adding/removing objects from a delegate does not affect their reference
counts. When an object that is a member of a delegate is deleted, it is
automatically removed from the delegate.

In practice, this means that every object needs to keep a list of
delegates that it belongs to. This increases the size of the page header
on 64 bit systems, but not by much since the additional metadata can be
stored in a union with the array-specific metadata.
2023-04-13 16:38:13 -07:00
Nunuhara Cabbage 0416d4f1f7 Merge pull request #112 from kichikuou/msgbox
Fix garbled characters in message box on Windows
2023-04-13 16:35:55 -07:00
kichikuou 911f82cda0 Fix garbled characters in message box on Windows
SDL MessageBox functions expect UTF-8 strings even in Japanese-locale
Windows.
2023-04-13 17:55:02 +09:00
Nunuhara Cabbage e4ed459376 Implement miscellaneous PartsEngine functions 2023-04-10 21:48:09 -07:00
Nunuhara Cabbage 46f25755c5 Add PartsEngine HLL (Oyako Rankan version)
The game currently runs up until the first unimplemented function
(SeekEndMotion) during the intro events, using the existing PE_
functions with a few minor fixes.
2023-04-10 15:16:14 -07:00
Nunuhara Cabbage 249e447920 asset_manager: use built-in afa index
The afa implementation from libsys4 now handles basename indexing, so we
use that instead of creating a separate index. Also, afa v1 is now
properly supported in libsys4, so there is no need for the
id_indexed_afa hack.
2023-04-09 10:02:07 -07:00
Nunuhara Cabbage cc199dcf4e Add notes directory 2023-04-05 19:28:30 -07:00
Nunuhara Cabbage baae8f5237 Document Windows FFmpeg build, etc.
Statically link FFmpeg on Windows. This requires building a slimmed-down
version of FFmpeg from source. The build process is documented in the
README.

Also add ffmpeg dependency to the nix flake.
2023-04-02 19:34:45 -07:00
Nunuhara Cabbage 8437a87b54 Merge pull request #111 from kichikuou/ffmpeg
Add FFmpeg movie backend
2023-04-02 12:31:29 -07:00
kichikuou b11a173b67 Add FFmpeg movie backend
This allows playback of videos in encodings other than MPEG-1, e.g. VC-1
used in the English version of Rance VI.

The FFmpeg dependency is optional. If it's not available, the PL_MPEG
backend will be used.
2023-04-01 13:59:55 +09:00
Nunuhara Cabbage 0e18f69cbf build: require meson >= 0.59 and cglm >= 0.8.3
get_option('<option>').allowed() requires meson >= 0.59.

glm_quat_nlerp requres cglm >= 0.8.3.
2023-03-28 22:41:49 -07:00
Nunuhara Cabbage c3f9d93917 Link statically on Windows
Try to link statically as much as possible on Windows, to reduce the
number of DLLs that have to be shipped with Windows builds.

Also document the build process on Windows.
2023-03-29 03:09:20 -07:00
Nunuhara Cabbage ac411754a0 Merge pull request #109 from kichikuou/sact
Change the second parameter of `sact_SP_SetShow` from bool to int
2023-03-27 06:26:08 -07:00
kichikuou 45ebaa2c7f Set debug printing function for sprites initialized with CGs 2023-03-27 20:13:27 +09:00
kichikuou a923b0ce23 Change the second parameter of sact_SP_SetShow from bool to int
In SACT2 it's declared as int, so any integer can be passed. A Sengoku
Rance mod actually passes 2, but some compilers generate code that
interprets it as false.
2023-03-27 20:13:27 +09:00
Nunuhara Cabbage 1be2cffd22 Merge pull request #108 from kichikuou/msgskip
Use System4-compatible MsgSkip file format
2023-03-25 23:20:02 -07:00
kichikuou 50af8bd3ff Use System4-compatible MsgSkip file format 2023-03-24 20:05:17 +09:00
Nunuhara Cabbage 32ef29ee20 Merge pull request #107 from kichikuou/savefile
Use global/group save file format compatible with System4
2023-03-20 22:24:59 -07:00
kichikuou 8620be452e Ignore non-existent structure fields when loading from gsave
This matches System4's behavior.
2023-03-19 22:18:28 +09:00
kichikuou cc79f9caee Use global/group save file format compatible with System4
After this, xsystem4 uses the global/group save format used by the
original System4, instead of the JSON format. Existing JSON save files
can still be loaded.

This makes save files interoperable between System4 and xsystem4 in some
games, including Sengoku Rance and Toushin Toshi III.
2023-03-19 11:42:20 +09:00
Nunuhara Cabbage 6a2eae7274 Merge pull request #106 from kichikuou/sact
sact_SP_TextDraw: Early return if sprite size is zero
2023-03-18 16:47:02 -07:00
kichikuou 66002f7ceb sact_SP_TextDraw: Early return if sprite size is zero
Otherwise it ERROR()s in gfx_set_framebuffer() due to an incomplete
framebuffer.

This happens when trying to display text without specifying a message
window in SACT2. AliceSoft's implementation ignores this.
2023-03-17 22:11:41 +09:00
Nunuhara Cabbage d1b18f39dd Add flake.nix 2023-03-09 19:37:42 -08:00
Nunuhara Cabbage dbd1e54c46 Merge branch 'master' of github.com:nunuhara/xsystem4 2023-03-07 18:21:21 -08:00
Nunuhara Cabbage 3c0ee51714 Merge pull request #105 from kichikuou/drawdungeon
Add stub for DrawDungeon14.SetRasterScroll
2023-03-06 17:00:06 -08:00
kichikuou d4f7ab06cf Add stub for DrawDungeon14.SetRasterScroll
This function is used for the underwater effect in GALZOO. It should be
implemented as a post-processing effect, but in the meantime let's stub
it out to avoid runtime error.
2023-03-06 20:03:03 +09:00
Nunuhara Cabbage 66d8def3f8 Merge pull request #104 from kichikuou/sleep
Add vm_sleep()
2023-02-28 16:29:05 -08:00
Nunuhara Cabbage 8f29146c28 Merge pull request #103 from kichikuou/win
Use UTF8-aware dirname/basename functions
2023-02-28 16:28:08 -08:00
kichikuou e53dc6ad0e Add vm_sleep()
The nanosleep() function in mingw-w64 often sleeps for more than the
specified time. This causes a problem in Rance 6 combat scene, where
the damage popup is noticeably slow.

This adds vm_sleep() that calls SDL_Delay() which uses high-resolution
timer on Windows.
2023-02-25 21:57:34 +09:00
kichikuou 90a96eb8bb Use UTF8-aware dirname/basename functions
Command line paths containing non-ASCII characters now work on Windows.
2023-02-25 21:05:53 +09:00
Nunuhara Cabbage 3ff2c33d3c Fix crash due to gap in mixer list
Fix a crash that occurs during startup of Oyako Rankan due to a gap in
the "VolumeValancer" list in AliceStart.ini
2023-02-12 19:49:28 -08:00
Nunuhara Cabbage 2237bb814c Implement various HLL functions for Rance Quest
Most of the functionality of gfx_render_texture (draw method, blend
rate) is moved into sprite_render, since this was the only place using
it.
The draw method and blend rate are now stored in sact_sprite objects
instead of textures.
sact_sprite now has two renderers: the old SACT2 renderer (with blend
rate as the only shader parameter) and the new ChipmunkSpriteEngine
renderer, which supports multiply/add colors and the "surface area"
feature from PartsEngine.

Rance Quest now runs up to the first Flash-related function call
(Flash?!?!).
2023-02-12 15:59:36 -08:00
Nunuhara Cabbage 6b62e1db23 Fix CG loading functions for Rance Quest
Rance Quest uses strings for CG names, but the strings contain numbers
which should be converted to integers for lookup.
2023-02-11 16:36:05 -08:00
216 changed files with 31882 additions and 3644 deletions
+222
View File
@@ -0,0 +1,222 @@
# CMakeLists.txt for Android build
cmake_minimum_required(VERSION 3.14)
project(xsystem4 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
cglm
URL https://github.com/recp/cglm/archive/refs/tags/v0.9.2.tar.gz
URL_HASH SHA1=cb8472aa8c2ab67b66378dbaf10c2c7368d4e4c3
)
FetchContent_MakeAvailable(cglm)
add_library(ffi STATIC IMPORTED)
set_target_properties(ffi PROPERTIES
IMPORTED_LOCATION ${CMAKE_STAGING_PREFIX}/lib/libffi.a
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_STAGING_PREFIX}/include)
find_package(Freetype REQUIRED)
find_package(SDL2 REQUIRED)
find_package(SndFile REQUIRED)
find_package(ZLIB REQUIRED)
add_subdirectory(subprojects/libsys4)
add_library(xsystem4 SHARED)
target_compile_definitions(xsystem4 PRIVATE
_DEFAULT_SOURCE
USE_GLES
)
target_include_directories(xsystem4 PRIVATE include)
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VCS_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE)
configure_file(src/version.h.in version.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_sources(xsystem4 PRIVATE
src/audio.c
src/audio_meta.c
src/audio_mixer.c
src/asset_manager.c
src/base64.c
src/cJSON.c
src/draw.c
src/effect.c
src/ffi.c
src/font_freetype.c
src/font_fnl.c
src/format.c
src/hacks.c
src/heap.c
src/icon.c
src/id_pool.c
src/input.c
src/json.c
src/movie_plmpeg.c
src/msgqueue.c
src/page.c
src/resume.c
src/savedata.c
src/scene.c
src/screenshot.c
src/sprite.c
src/swf.c
src/system4.c
src/text.c
src/util.c
src/video.c
src/vm.c
src/3d/collision.c
src/3d/debug.c
src/3d/model.c
src/3d/parser.c
src/3d/particle.c
src/3d/reign.c
src/3d/renderer.c
src/dungeon/dgn.c
src/dungeon/dtx.c
src/dungeon/dungeon.c
src/dungeon/generator.c
src/dungeon/map.c
src/dungeon/mtrand43.c
src/dungeon/polyobj.c
src/dungeon/renderer.c
src/dungeon/skybox.c
src/dungeon/tes.c
src/parts/construction.c
src/parts/debug.c
src/parts/flash.c
src/parts/input.c
src/parts/motion.c
src/parts/parts.c
src/parts/render.c
src/parts/text.c
src/parts/save.c
src/hll/ACXLoader.c
src/hll/ADVSYS.c
src/hll/AliceLogo.c
src/hll/AliceLogo2.c
src/hll/AliceLogo3.c
src/hll/AliceLogo4.c
src/hll/AliceLogo5.c
src/hll/AnteaterADVEngine.c
src/hll/Array.c
src/hll/BanMisc.c
src/hll/Bitarray.c
src/hll/CalcTable.c
src/hll/CGManager.c
src/hll/CharSpriteManager.c
src/hll/ChipmunkSpriteEngine.c
src/hll/ChrLoader.c
src/hll/CommonSystemData.c
src/hll/Confirm.c
src/hll/Confirm2.c
src/hll/Confirm3.c
src/hll/CrayfishLogViewer.c
src/hll/Cursor.c
src/hll/DALKDemo.c
src/hll/DALKEDemo.c
src/hll/Data.c
src/hll/DataFile.c
src/hll/DrawDungeon.c
src/hll/DrawEffect.c
src/hll/DrawGraph.c
src/hll/DrawMovie.c
src/hll/DrawNumeral.c
src/hll/DrawPluginManager.c
src/hll/DrawRain.c
src/hll/DrawRipple.c
src/hll/DrawSimpleText.c
src/hll/DrawSnow.c
src/hll/File.c
src/hll/File2.c
src/hll/FileOperation.c
src/hll/FillAngle.c
src/hll/GoatGUIEngine.c
src/hll/Gpx2Plus.c
src/hll/GUIEngine.c
src/hll/HTTPDownloader.c
src/hll/iarray.c
src/hll/IbisInputEngine.c
src/hll/InputDevice.c
src/hll/InputString.c
src/hll/KiwiSoundEngine.c
src/hll/LoadCG.c
src/hll/MainEXFile.c
src/hll/MainSurface.c
src/hll/MamanyoDemo.c
src/hll/MamanyoSDemo.c
src/hll/MarmotModelEngine.c
src/hll/Math.c
src/hll/MapLoader.c
src/hll/MenuMsg.c
src/hll/MonsterInfo.c
src/hll/MsgLogManager.c
src/hll/MsgLogViewer.c
src/hll/MsgSkip.c
src/hll/MusicSystem.c
src/hll/NewFont.c
src/hll/OutputLog.c
src/hll/PassRegister.c
src/hll/PastelChime2.c
src/hll/PartsEngine.c
src/hll/PixelRestore.c
src/hll/PlayDemo.c
src/hll/PlayMovie.c
src/hll/ReignEngine.c
src/hll/SACT2.c
src/hll/SengokuRanceFont.c
src/hll/SoundFilePlayer.c
src/hll/Sound2ex.c
src/hll/StoatSpriteEngine.c
src/hll/StretchHelper.c
src/hll/SystemService.c
src/hll/SystemServiceEx.c
src/hll/Timer.c
src/hll/Toushin3Loader.c
src/hll/vmAnime.c
src/hll/vmArray.c
src/hll/vmCG.c
src/hll/vmChrLoader.c
src/hll/vmCursor.c
src/hll/vmDalkGaiden.c
src/hll/vmData.c
src/hll/vmDialog.c
src/hll/vmDrawGauge.c
src/hll/vmDrawMsg.c
src/hll/vmDrawNumber.c
src/hll/vmFile.c
src/hll/vmGraph.c
src/hll/vmGraphQuake.c
src/hll/vmKey.c
src/hll/vmMapLoader.c
src/hll/vmMsgLog.c
src/hll/vmMusic.c
src/hll/vmSound.c
src/hll/vmSprite.c
src/hll/vmString.c
src/hll/vmSurface.c
src/hll/vmSystem.c
src/hll/vmTimer.c
src/hll/VSFile.c
)
target_link_libraries(xsystem4 PRIVATE
m ZLIB::ZLIB SDL2::SDL2 Freetype::Freetype ffi GLESv3 cglm SndFile::sndfile sys4)
target_compile_options(xsystem4 PRIVATE -Wno-unused-parameter)
install(TARGETS xsystem4)
+66
View File
@@ -47,6 +47,72 @@ Finally install it to your system (optional),
ninja -C build install
### Windows
xsystem4 can be built on Windows using MSYS2.
First install MSYS2, and then open the MINGW64 shell and run the following command,
pacman -S make flex bison \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-meson \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-freetype \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libwebp \
mingw-w64-x86_64-libsndfile \
mingw-w64-x86_64-glew \
mingw-w64-x86_64-nasm \
mingw-w64-x86_64-diffutils
To build with FFmpeg support, you must compile FFmpeg as a static library:
git clone https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg
git checkout n7.1.2
./configure --disable-everything \
--enable-decoder=mpegvideo \
--enable-decoder=mpeg1video \
--enable-decoder=mpeg2video \
--enable-decoder=mp2 \
--enable-parser=mpegaudio \
--enable-parser=mpegvideo \
--enable-demuxer=mpegps \
--enable-demuxer=mpegts \
--enable-demuxer=mpegtsraw \
--enable-demuxer=mpegvideo \
--enable-decoder=vc1 \
--enable-decoder=wmapro \
--enable-parser=vc1 \
--enable-hwaccel=vc1_d3d11va \
--enable-hwaccel=vc1_d3d11va2 \
--enable-hwaccel=vc1_dxva2 \
--enable-demuxer=asf \
--enable-protocol=file \
--enable-filter=scale \
--enable-static \
--disable-shared \
--extra-libs=-static \
--extra-cflags=--static
make
make install
cd ..
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH"
Then build the xsystem4 executable with meson,
mkdir build
meson build
ninja -C build
To create a portable executable, it is neccessary to copy some DLLs into the same directory as xsystem4.exe.
You can determine the required DLLs with the following command,
ldd build/src/xsystem4.exe | grep mingw64
The `fonts` and `shaders` directories must also be shipped together with xsystem4.exe.
Installing
----------
Generated
+26
View File
@@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1757244434,
"narHash": "sha256-AeqTqY0Y95K1Fgs6wuT1LafBNcmKxcOkWnm4alD9pqM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "092c565d333be1e17b4779ac22104338941d913f",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-25.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+75
View File
@@ -0,0 +1,75 @@
{
description = "Open source implementation of AliceSoft's System 4 game engine for unix-like operating systems";
# Nixpkgs / NixOS version to use.
inputs.nixpkgs.url = "nixpkgs/nixos-25.05";
outputs = { self, nixpkgs }:
let
# System types to support.
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in {
# Set up xsystem4 development environment:
# nix develop # create shell environment with dependencies available
devShell = forAllSystems (system:
let pkgs = nixpkgsFor.${system}; in with pkgs; pkgs.mkShell {
nativeBuildInputs = [ meson ninja pkg-config ];
buildInputs = [
cglm
SDL2
freetype
libffi
libjpeg
libwebp
libsndfile
glew
readline
chibi
flex
bison
ffmpeg
];
}
);
# Build xsystem4:
# nix build .?submodules=1 # build xsystem4 (outputs to ./result/)
# nix shell .?submodules=1 # create shell environment with xsystem4 available
# Install xsystem4 to user profile:
# nix profile install .?submodules=1
packages = forAllSystems (system:
let pkgs = nixpkgsFor.${system}; in {
default = with pkgs; stdenv.mkDerivation rec {
name = "xsystem4";
src = ./.;
mesonAutoFeatures = "auto";
nativeBuildInputs = [ meson ninja pkg-config ];
buildInputs = [
cglm
SDL2
freetype
libffi
libjpeg
libwebp
libsndfile
glew
readline
chibi
flex
bison
ffmpeg
];
};
}
);
};
}
+40 -31
View File
@@ -14,52 +14,52 @@ issues, please let me know or open a pull request to update this table.
| Game | Status | Notes |
| ------------------------------------------------------- | ----------- | ----- |
| DALK外伝 | Unsupported | |
| ままにょにょ | Unsupported | |
| DALK外伝 | Supported | |
| ままにょにょ | Supported | |
| 大番長 -Big Bang Age- | Supported | |
| Daibanchou -Big Bang Age- (EN) | Supported | |
| 魔女の贖罪 | Unknown | |
| Rance VI -ゼス崩壊- | Supported | |
| Rance VI -Collapse of Zeth- (EN) | Supported | |
| ウルトラ魔法少女まなな Vol.1~Vol.5 | Unknown | |
| 鈴宮刑務娼館 | Unknown | From ALICEの館7 |
| しまいま | Unknown | From ALICEの館7 |
| DUNGEONS & DOLLS | Unsupported | From ALICEの館7 |
| こすぷれれもん | Unknown | From ALICEの館7 |
| エルメイフラワー | Unknown | From ALICEの館7 |
| CGは17年ゼミ | Unknown | From ALICEの館7 |
| でーぴーえすVer_N | Unknown | From ALICEの館7 |
| 大番長外伝 | Unknown | From ALICEの館7 |
| ZERO | Unknown | From ALICEの館7 |
| ぷろGRX | Unknown | From ALICEの館7 |
| Girls | Unknown | From ALICEの館7 |
| 神武 -カムナガラ- | Unknown | From ALICEの館7 |
| オニむス | Unknown | From ALICEの館7 |
| 透色 | Unknown | From ALICEの館7 |
| DEBUT | Unknown | From ALICEの館7 |
| ランス6その後 | Unknown | From ALICEの館7 |
| ほいっぷハニー | Unknown | From ALICEの館7 |
| Text Jam Session | Unknown | From ALICEの館7 |
| 没ロゴの哀しみ | Unknown | From ALICEの館7 |
| スタッフコーナー | Unknown | From ALICEの館7 |
| ぱすてるチャイムContinue | Unsupported | |
| ウルトラ魔法少女まなな Vol.1~Vol.5 | Supported | |
| 鈴宮刑務娼館 | Supported | From ALICEの館7 |
| しまいま | Supported | From ALICEの館7 |
| DUNGEONS & DOLLS | Supported | From ALICEの館7 |
| こすぷれれもん | Supported | From ALICEの館7 |
| エルメイフラワー | Supported | From ALICEの館7 |
| CGは17年ゼミ | Supported | From ALICEの館7 |
| でーぴーえすVer_N | Supported | From ALICEの館7 |
| 大番長外伝 | Supported | From ALICEの館7 |
| ZERO | Unsupported | From ALICEの館7 |
| ぷろGRX | Supported | From ALICEの館7 |
| Girls | Supported | From ALICEの館7 |
| 神武 -カムナガラ- | Supported | From ALICEの館7 |
| オニむス | Supported | From ALICEの館7 |
| 透色 | Supported | From ALICEの館7 |
| DEBUT | Supported | From ALICEの館7 |
| ランス6その後 | Supported | From ALICEの館7 |
| ほいっぷハニー | Supported | From ALICEの館7 |
| Text Jam Session | Supported | From ALICEの館7 |
| 没ロゴの哀しみ | Supported | From ALICEの館7 |
| スタッフコーナー | Supported | From ALICEの館7 |
| ぱすてるチャイムContinue | Supported | |
| GALZOOアイランド | Supported | |
| ぱすちゃC++ | Unknown | |
| ぱすちゃC++ | Supported | |
| よくばりサボテン | Supported | |
| 妻しぼり | Unknown | |
| 戦国ランス | Supported | |
| Sengoku Rance (EN FanTL) | Supported | |
| Sengoku Rance (MangaGamer) | Supported | |
| だぶる先生らいふっ | Unknown | |
| お嬢様をいいなりにするゲーム | Unknown | |
| お嬢様をいいなりにするゲーム | Supported | |
| 超昂閃忍ハルカ | Unknown | |
| 戦国ランス~三匹が斬ったり突いたり燃やしたり | Unknown | |
| Sengoku Rance -Three for the Slash! Thrust! Burn!- (EN) | Unknown | |
| 戦国ランス~三匹が斬ったり突いたり燃やしたり | Supported | |
| Sengoku Rance -Three for the Slash! Thrust! Burn!- (EN) | Supported | |
| AliveZ | Unknown | |
| エドランゼ ~Come on!水戸姫様~ | Unknown | |
| エドランゼ ~Come on!水戸姫様~ | Supported | |
| 闘神都市III | Supported | |
| ばにしゅ!~おっぱいの消えた王国~ | Unknown | |
| 僕だけの保健室 | Unknown | |
| 僕だけの保健室 | Supported | |
| ももいろガーディアン | Unknown | |
| 超昂閃忍ハルカ ハルカVSエスカレイヤー | Unknown | From Alice 2010 |
| 超昂閃忍ハルカ ―疾風!?忍者大作戦― | Unknown | From Alice 2010 |
@@ -72,4 +72,13 @@ issues, please let me know or open a pull request to update this table.
| ランス02 -反逆の少女たち- | Supported | From Alice 2010 |
| Rance 02 -The Rebellious Maidens- (Fan TL) | Supported | |
| Rance 02 -The Rebellious Maidens- (MangaGamer) | Supported | |
| All games released after Alice 2010 | Unsupported | |
| しゃーまんず・さんくちゅあり-巫女の聖域- | Unknown | |
| 大帝国 | Supported | |
| ランス・クエスト | Supported | |
| ランス・クエスト マグナム | Supported | |
| 母娘乱館 | Unknown | |
| パステルチャイム3 バインドシーカー | Unsupported | |
| どらぺこ! ~おねだりドラゴンとおっぱい勇者~ | Unsupported | |
| ランス01 光をもとめて | Supported | |
| Rance 01 - Quest for Hikari (EN) | Supported | |
| All games released after Rance 01 | Unsupported | |
+3 -2
View File
@@ -30,9 +30,10 @@ enum asset_type {
ASSET_CG,
ASSET_FLAT,
ASSET_PACT,
ASSET_DATA
ASSET_DATA,
ASSET_FLASH
};
#define ASSET_TYPE_MAX (ASSET_DATA+1)
#define ASSET_TYPE_MAX (ASSET_FLASH+1)
const char *asset_strtype(enum asset_type type);
void asset_manager_init(void);
+8 -6
View File
@@ -19,10 +19,14 @@
#include <stdbool.h>
struct archive_data;
void audio_init(void);
void audio_fini(void);
void audio_reset(void);
void audio_update(void);
bool audio_play_sound(int sound_no);
// Takes ownership of dfile.
bool audio_play_archive_data(struct archive_data *dfile);
bool wav_exists(int no);
bool bgm_exists(int no);
@@ -74,15 +78,13 @@ int wav_get_volume(int id);
int bgm_get_volume(int id);
int wav_get_time_length(int id);
int bgm_get_time_length(int id);
//int wav_get_group_num(int id);
int wav_get_group_num(int id);
//int bgm_get_group_num(int id);
//int wav_prepare_from_file(int id, char *filename);
//int bgm_prepare_from_file(int id, char *filename);
int wav_prepare_from_file(int id, char *filename);
int bgm_prepare_from_file(int id, char *filename);
int wav_get_group_num_from_data_num(int no);
struct archive_data;
// Takes ownership of dfile.
int wav_prepare_from_archive_data(int id, struct archive_data *dfile);
+25
View File
@@ -0,0 +1,25 @@
/* Copyright (C) 2023 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_BASE64_H
#define SYSTEM4_BASE64_H
#include <stddef.h>
unsigned char *base64_encode(const unsigned char *src, size_t len,
size_t *out_len);
#endif /* SYSTEM4_BASE64_H */
+45 -2
View File
@@ -18,6 +18,7 @@
#define SYSTEM4_DEBUGGER_H
#ifdef DEBUGGER_ENABLED
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include "system4/instructions.h"
@@ -28,6 +29,18 @@
struct ain_variable;
enum dbg_stop_type {
DBG_STOP_PAUSE,
DBG_STOP_ERROR,
DBG_STOP_BREAKPOINT,
DBG_STOP_STEP,
};
struct dbg_stop {
enum dbg_stop_type type;
const char *message;
};
struct breakpoint {
enum opcode restore_op;
void *data;
@@ -46,13 +59,19 @@ struct dbg_cmd {
void (*run)(unsigned nr_args, char **args);
};
struct dbg_info;
extern bool dbg_dap;
extern bool dbg_enabled;
extern bool dbg_start_in_debugger;
extern unsigned dbg_current_frame;
extern struct dbg_info *dbg_info;
void dbg_init(void);
void dbg_init(const char *debug_info_path);
void dbg_fini(void);
void dbg_repl(void);
void dbg_repl(enum dbg_stop_type, const char *msg);
void dbg_log(const char *log, const char *fmt, va_list ap);
void dbg_continue(void);
void dbg_quit(void);
@@ -61,6 +80,8 @@ void dbg_cmd_init(void);
void dbg_cmd_repl(void);
void dbg_cmd_add_module(const char *name, unsigned nr_commands, struct dbg_cmd *commands);
void dbg_handle_breakpoint(void);
bool dbg_clear_breakpoint(uint32_t addr, void(*free_data)(void*));
void dbg_foreach_breakpoint(void (*fun)(int addr, struct breakpoint*, void *data), void *data);
bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data);
bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*), void *data);
bool dbg_set_step_over_breakpoint(void);
@@ -70,13 +91,35 @@ void dbg_print_frame(unsigned no);
void dbg_print_stack_trace(void);
void dbg_print_stack(void);
void dbg_print_vm_state(void);
void dbg_print_current_line(void);
union vm_value dbg_eval_string(const char *str, struct ain_type *type_out);
struct string *dbg_value_to_string(struct ain_type *type, union vm_value value, int recursive);
struct string *dbg_variable_to_string(struct ain_type *type, struct page *page, int slot, int recursive);
void dbg_dap_init(void);
void dbg_dap_quit(void);
void dbg_dap_repl(struct dbg_stop *stop);
void dbg_dap_handle_messages(void);
void dbg_dap_log(const char *log, const char *fmt, va_list ap);
struct dbg_info *dbg_info_load(const char *path);
const char *dbg_info_source_name(const struct dbg_info *info, int file);
char *dbg_info_source_path(const struct dbg_info *info, int file);
const char *dbg_info_source_line(const struct dbg_info *info, int file, int line);
int dbg_info_find_file(const struct dbg_info *info, const char *filename);
bool dbg_info_addr2line(const struct dbg_info *info, int addr, int *file, int *line);
int dbg_info_line2addr(const struct dbg_info *info, int file, int line);
#ifdef HAVE_SCHEME
void dbg_scm_init(void);
void dbg_scm_fini(void);
void dbg_scm_repl(void);
#endif /* HAVE_SCHEME */
#else /* DEBUGGER ENABLED */
#define dbg_init(debug_info_path)
#define dbg_repl(type, msg)
#define dbg_dap 0
#define dbg_dap_handle_messages()
#define dbg_dap_log(log, fmt, ap)
#endif /* DEBUGGER_ENABLED */
#endif /* SYSTEM4_DEBUGGER_H */
+28 -3
View File
@@ -20,6 +20,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <cglm/cglm.h>
/*
* Dungeon data is a collection of cells indexed by three integers (x, y, z).
@@ -45,7 +46,13 @@ struct dgn_cell {
int32_t west_door;
int32_t stairs_texture;
int32_t stairs_orientation;
// int32_t unknown[13];
int32_t lightmap_floor;
int32_t lightmap_ceiling;
int32_t lightmap_north;
int32_t lightmap_south;
int32_t lightmap_east;
int32_t lightmap_west;
// int32_t unknown[7];
int32_t enterable;
int32_t enterable_north;
int32_t enterable_south;
@@ -86,11 +93,16 @@ struct dgn_cell {
float south_door_angle;
float east_door_angle;
float west_door_angle;
int north_door_lock;
int south_door_lock;
int east_door_lock;
int west_door_lock;
int32_t floor_event2;
int32_t north_event2;
int32_t south_event2;
int32_t east_event2;
int32_t west_event2;
int pathfinding_cost;
struct dgn_cell **visible_cells;
};
@@ -106,16 +118,28 @@ struct packed_pvs {
};
struct dgn {
uint32_t version; // 10: Rance VI, 13: GALZOO Island
uint32_t size_x;
uint32_t size_y;
uint32_t size_z;
// uint32_t unknown[10];
struct dgn_cell *cells;
struct packed_pvs *pvs;
vec3 sphere_theta;
float sphere_color_top;
float sphere_color_bottom;
// for generated dungeons
int start_x, start_y;
int exit_x, exit_y;
// DrawField
int32_t back_color_r;
int32_t back_color_g;
int32_t back_color_b;
};
struct dgn *dgn_parse(uint8_t *data, size_t size);
struct dgn *dgn_new(uint32_t size_x, uint32_t size_y, uint32_t size_z);
struct dgn *dgn_parse(uint8_t *data, size_t size, bool for_draw_field);
void dgn_free(struct dgn *dgn);
int dgn_cell_index(struct dgn *dgn, uint32_t x, uint32_t y, uint32_t z);
@@ -134,5 +158,6 @@ static inline bool dgn_is_in_map(struct dgn *dgn, uint32_t x, uint32_t y, uint32
// Returns a list of cells visible from (x, y, z), sorted by distance from (x, y, z).
struct dgn_cell **dgn_get_visible_cells(struct dgn *dgn, int x, int y, int z, int *nr_cells_out);
void dgn_calc_lightmap(struct dgn *dgn);
#endif /* SYSTEM4_DGN_H */
+13 -7
View File
@@ -23,14 +23,19 @@
struct cg;
enum dtx_texture_type {
DTX_WALL = 0,
DTX_FLOOR = 1,
DTX_CEILING = 2,
DTX_STAIRS = 3,
DTX_DOOR = 4,
DTX_SKYBOX = 5,
// cell textures
DTX_WALL,
DTX_FLOOR,
DTX_CEILING,
DTX_STAIRS,
DTX_DOOR,
DTX_LIGHTMAP,
// skybox textures
DTX_SKYBOX,
};
#define DTX_NR_CELL_TEXTURE_TYPES (DTX_LIGHTMAP + 1)
struct dtx_entry {
uint32_t size;
uint8_t *data;
@@ -45,6 +50,7 @@ struct dtx {
struct dtx *dtx_parse(uint8_t *data, size_t size);
void dtx_free(struct dtx *dtx);
struct cg *dtx_create_cg(struct dtx *dtx, int type, int index);
struct cg *dtx_create_cg(struct dtx *dtx, enum dtx_texture_type type, int index);
struct dtx *dtx_load_from_dtl(const char *path, uint32_t seed);
#endif /* SYSTEM4_DTX_H */
+25 -5
View File
@@ -20,20 +20,25 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "gfx/gl.h"
#include <cglm/cglm.h>
#include "gfx/gfx.h"
#include "plugin.h"
#define DRAWFIELD_NR_CHARACTERS 256
struct page;
struct dgn;
struct dtx;
struct tes;
struct dungeon_renderer;
struct dungeon_map;
struct drawfield_character;
enum draw_dungeon_version {
DRAW_DUNGEON_1, // Rance VI
// DRAW_DUNGEON_2, // Dungeons & Dolls
DRAW_DUNGEON_14 // GALZOO Island
DRAW_DUNGEON_2, // Dungeons & Dolls
DRAW_DUNGEON_14, // GALZOO Island
DRAW_FIELD // Pastel Chime Continue
};
struct camera {
@@ -62,19 +67,34 @@ struct dungeon_context {
struct dgn *dgn;
struct dtx *dtx;
struct tes *tes;
ivec3 player_pos;
struct drawfield_character *characters;
mat4 proj_transform;
struct dungeon_renderer *renderer;
struct texture texture;
GLuint depth_buffer;
};
struct dungeon_context *dungeon_context_create(enum draw_dungeon_version version, int surface);
void dungeon_context_free(struct dungeon_context *ctx);
struct dungeon_context *dungeon_context_create(enum draw_dungeon_version version, int width, int height);
bool dungeon_load(struct dungeon_context *ctx, int num);
bool dungeon_load_dungeon(struct dungeon_context *ctx, const char *filename, int num);
bool dungeon_load_texture(struct dungeon_context *ctx, const char *filename);
void dungeon_set_camera(int surface, float x, float y, float z, float angle, float angle_p);
void dungeon_set_perspective(int surface, int width, int height, float near, float far, float deg);
void dungeon_set_player_pos(int surface, int x, int y, int z);
void dungeon_set_walked(int surface, int x, int y, int z, int flag);
int dungeon_get_walked(int surface, int x, int y, int z);
void dungeon_set_walked_all(int surface);
int dungeon_calc_conquer(int surface);
bool dungeon_load_walk_data(int surface, int map, struct page **page);
bool dungeon_save_walk_data(int surface, int map, struct page **page);
void dungeon_paint_step(int surface, int x, int y, int z);
void dungeon_set_chara_sprite(int surface, int num, int sprite);
void dungeon_set_chara_pos(int surface, int num, float x, float y, float z);
void dungeon_set_chara_cg(int surface, int num, int cg);
void dungeon_set_chara_cg_info(int surface, int num, int num_chara_x, int num_chara_y);
void dungeon_set_chara_show(int surface, int num, bool show);
bool dungeon_project_world_to_screen(struct dungeon_context *ctx, vec3 world_pos, Point *screen_pos);
struct dungeon_context *dungeon_get_context(int surface);
+39
View File
@@ -0,0 +1,39 @@
/* Copyright (C) 2025 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_DUNGEON_GENERATOR_H
#define SYSTEM4_DUNGEON_GENERATOR_H
struct dgn;
enum drawfield_cell_flag {
DF_FLAG_ITEM = 1 << 0,
DF_FLAG_ENEMY = 1 << 1,
DF_FLAG_TRAP = 1 << 2,
DF_FLAG_NO_TREASURE = 1 << 3,
DF_FLAG_NO_MONSTER = 1 << 4,
DF_FLAG_NO_TRAP = 1 << 5,
DF_FLAG_TREASURE_ROOM = 1 << 6
};
struct dgn *dgn_generate_drawdungeon2(int level);
void dgn_paint_step(struct dgn *dgn, int x, int y);
struct dgn *dgn_generate_drawfield(
int floor, int complexity, int wall_arrange_method, int floor_arrange_method,
int field_size_x, int field_size_y, int door_lock_percent, uint32_t seed,
uint8_t *cell_flags);
#endif /* SYSTEM4_DUNGEON_GENERATOR_H */
+2 -1
View File
@@ -32,6 +32,7 @@ void dungeon_map_set_all_view(int surface, int flag);
void dungeon_map_set_cg(int surface, int index, int sprite);
void dungeon_map_set_small_map_floor(int surface, int floor);
void dungeon_map_set_large_map_floor(int surface, int floor);
void dungeon_map_reveal(struct dungeon_context *ctx, int x, int y, int z, bool transparent);
void dungeon_map_reveal(struct dungeon_context *ctx, int x, int y, int z, bool from_walk_data);
void dungeon_map_hide(struct dungeon_context *ctx, int x, int y, int z);
#endif /* SYSTEM4_DUNGEON_MAP_H */
+35
View File
@@ -0,0 +1,35 @@
/* Copyright (C) 2021 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_MTRAND43_H
#define SYSTEM4_MTRAND43_H
#include <stddef.h>
#include <stdint.h>
#define MTRAND43_STATE_SIZE 4
// Mersenne Twister engine with N=4, M=3
struct mtrand43 {
uint32_t st[MTRAND43_STATE_SIZE];
int i;
};
void mtrand43_init(struct mtrand43 *mt, uint32_t seed);
uint32_t mtrand43_genrand(struct mtrand43 *mt);
float mtrand43_genfloat(struct mtrand43 *mt);
#endif /* SYSTEM4_MTRAND43_H */
+18 -2
View File
@@ -20,16 +20,32 @@
#include <cglm/cglm.h>
#include "gfx/gl.h"
struct texture;
struct dgn_cell;
struct dtx;
struct polyobj;
struct dungeon_renderer;
struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version version, struct dtx *dtx, GLuint *event_textures, int nr_event_textures, struct polyobj *po);
struct drawfield_character {
vec3 pos;
int sprite;
int rows;
int cols;
int cg_index;
bool show;
};
struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version version, int num, struct dtx *dtx, GLuint *event_textures, int nr_event_textures, struct polyobj *po);
void dungeon_renderer_free(struct dungeon_renderer *r);
void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells, int nr_cells, mat4 view_transform, mat4 proj_transform);
void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells, int nr_cells, struct drawfield_character *characters, mat4 view_transform, mat4 proj_transform);
void dungeon_renderer_enable_event_markers(struct dungeon_renderer *r, bool enable);
bool dungeon_renderer_event_markers_enabled(struct dungeon_renderer *r);
bool dungeon_renderer_is_floor_opaque(struct dungeon_renderer *r, struct dgn_cell *cell);
void dungeon_renderer_run_post_processing(struct dungeon_renderer *r, struct texture *src, struct texture *dst);
void dungeon_renderer_set_raster_scroll(struct dungeon_renderer *r, int type);
void dungeon_renderer_set_raster_amp(struct dungeon_renderer *r, float amp);
void dungeon_renderer_enable_lightmap(struct dungeon_renderer *r, bool enable);
bool dungeon_renderer_get_draw_obj_flag(struct dungeon_renderer *r, int type);
void dungeon_renderer_set_draw_obj_flag(struct dungeon_renderer *r, int type, bool flag);
#endif /* SYSTEM4_DUNGEON_RENDERER_H */
+2 -1
View File
@@ -19,10 +19,11 @@
#include <cglm/types.h>
enum draw_dungeon_version;
struct dtx;
struct skybox;
struct skybox *skybox_create(struct dtx *dtx);
struct skybox *skybox_create(enum draw_dungeon_version version, struct dtx *dtx);
void skybox_free(struct skybox *s);
void skybox_render(struct skybox *s, const mat4 view_transform, const mat4 proj_transform);
+8
View File
@@ -17,6 +17,8 @@
#ifndef SYSTEM4_EFFECT_H
#define SYSTEM4_EFFECT_H
struct texture;
enum effect {
EFFECT_CROSSFADE = 1,
EFFECT_FADEOUT = 2,
@@ -70,9 +72,15 @@ enum effect {
EFFECT_BLUR_FADEOUT = 50,
EFFECT_BLUR_CROSSFADE = 51,
EFFECT_2ROT_ZOOM_BLEND_BLUR = 52,
EFFECT_VWAVE_CROSSFADE = 60,
NR_EFFECTS
};
extern const char *effect_names[NR_EFFECTS];
int effect_init(enum effect type);
void effect_update_texture(int type, struct texture *dst, struct texture *old, struct texture *new, float rate);
int effect_update(float rate);
int effect_fini(void);
#endif /* SYSTEM4_EFFECT_H */
+40 -2
View File
@@ -68,6 +68,7 @@ struct font {
float (*get_actual_size_round_down)(struct font *font, float size);
bool (*get_glyph)(struct font_size *size, struct glyph *dst, uint32_t code, enum font_weight weight);
float (*size_char)(struct font_size *size, uint32_t code);
float (*size_char_kerning)(struct font_size *size, uint32_t code, uint32_t code_next);
};
struct text_style {
@@ -89,6 +90,27 @@ struct text_style {
struct font_size *font_size;
};
enum text_render_mode {
RENDER_COPY,
RENDER_BLENDED,
RENDER_PMAP,
RENDER_AMAP,
};
struct text_render_metrics {
float x;
int y;
SDL_Color color;
enum font_weight weight;
float edge_width;
float scale_x;
float space_scale_x;
float font_spacing;
float edge_spacing;
enum text_render_mode mode;
struct font_size *font_size;
};
static inline float text_style_edge_width(struct text_style *ts)
{
return max(max(ts->edge_up, ts->edge_down), max(ts->edge_left, ts->edge_right));
@@ -132,14 +154,30 @@ int gfx_get_font_space(void);
SDL_Color gfx_get_font_color(void);
void gfx_set_font_name(const char *name);
int gfx_render_text(Texture *dst, float x, int y, char *msg, struct text_style *ts);
struct font_size *gfx_font_get_size(unsigned face, float size);
enum font_weight gfx_int_to_font_weight(int weight);
float _gfx_render_text(Texture *dst, char *msg, struct text_render_metrics *tm);
int gfx_render_text(Texture *dst, float x, int y, char *msg, struct text_style *ts, bool blend);
float gfx_render_textf(Texture *dst, float x, int y, char *msg, struct text_style *ts, bool blend);
void gfx_render_dash_text(Texture *dst, struct text_style *ts);
void gfx_draw_text_to_amap(Texture *dst, int x, int y, char *text);
void gfx_draw_text_to_pmap(Texture *dst, int x, int y, char *text);
float gfx_size_char(struct text_style *ts, const char *ch);
float gfx_size_char_kerning(struct text_style *ts, uint32_t code, uint32_t code_next);
float gfx_size_text(struct text_style *ts, const char *text);
float gfx_get_actual_font_size(unsigned face, float size);
float gfx_get_actual_font_size_round_down(unsigned face, float size);
void gfx_print_text_style(struct text_style *style, int indent);
static inline float text_style_width(struct text_style *ts, const char *ch)
{
return (gfx_size_char(ts, ch) + (ts->bold_width * 2) + ts->edge_left + ts->edge_right)
* ts->scale_x;
}
static inline float text_style_height(struct text_style *ts)
{
return ts->size + (ts->bold_width * 2) + ts->edge_up + ts->edge_down;
}
#endif /* SYSTEM4_FONT_H */
+31 -14
View File
@@ -36,11 +36,7 @@ enum draw_method {
typedef struct texture {
GLuint handle;
mat4 world_transform;
int w, h;
bool has_alpha;
int alpha_mod;
enum draw_method draw_method;
} Texture;
struct gfx_render_job;
@@ -51,19 +47,31 @@ typedef struct shader {
GLuint world_transform;
GLuint view_transform;
GLuint texture;
GLuint vertex;
GLuint alpha_mod;
GLuint vertex_pos;
GLuint vertex_uv;
void (*prepare)(struct gfx_render_job *, void *);
} Shader;
enum gfx_shape {
GFX_RECTANGLE,
GFX_LINE,
GFX_QUADRILATERAL,
};
struct gfx_render_job {
struct shader *shader;
enum gfx_shape shape;
GLuint texture;
GLfloat *world_transform;
GLfloat *view_transform;
void *data;
};
struct gfx_vertex {
GLfloat x, y, z, w;
GLfloat u, v;
};
int gfx_init(void);
void gfx_fini(void);
@@ -71,18 +79,24 @@ Texture *gfx_main_surface(void);
void gfx_set_window_logical_size(int w, int h);
void gfx_update_screen_scale(void);
void gfx_set_wait_vsync(bool wait);
float gfx_get_frame_rate(void);
void gfx_load_shader(struct shader *dst, const char *vertex_shader_path, const char *fragment_shader_path);
GLuint gfx_load_shader_file(const char *path, GLenum type);
GLuint gfx_load_shader_file(const char *path, GLenum type, const char *defines);
// rendering
void gfx_set_clear_color(int r, int g, int b, int a);
void gfx_set_view_offset(int x, int y);
void gfx_clear(void);
void gfx_swap(void);
void gfx_set_view(struct texture *t);
void gfx_reset_view(void);
void gfx_prepare_job(struct gfx_render_job *job);
void gfx_run_job(struct gfx_render_job *job);
void gfx_render(struct gfx_render_job *job);
void _gfx_render_texture(struct shader *s, struct texture *t, Rectangle *r, void *data);
void gfx_render_texture(struct texture *t, Rectangle *r);
void gfx_render_quadrilateral(struct texture *t, struct gfx_vertex vertices[4]);
// texture management
void gfx_init_texture_blank(struct texture *t, int w, int h);
@@ -92,6 +106,7 @@ void gfx_init_texture_rgb(struct texture *t, int w, int h, SDL_Color color);
void gfx_init_texture_with_pixels(struct texture *t, int w, int h, void *pixels);
void gfx_init_texture_amap(struct texture *t, int w, int h, uint8_t *amap, SDL_Color color);
void gfx_init_texture_rmap(struct texture *t, int w, int h, uint8_t *rmap);
void gfx_update_texture_with_pixels(struct texture *t, void *pixels);
void gfx_copy_main_surface(struct texture *dst);
void gfx_delete_texture(struct texture *t);
GLuint gfx_set_framebuffer(GLenum target, Texture *t, int x, int y, int w, int h);
@@ -143,13 +158,17 @@ void gfx_bright_dest_only(Texture *dst, int x, int y, int w, int h, int rate);
void gfx_copy_stretch(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh);
void gfx_copy_stretch_amap(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh);
void gfx_copy_stretch_blend(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh, int a);
void gfx_copy_stretch_blend_screen(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh);
void gfx_copy_stretch_blend_amap(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh);
void gfx_copy_stretch_blend_amap_alpha(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh, int a);
void gfx_copy_rot_zoom(Texture *dst, Texture *src, int sx, int sy, int w, int h, float rotate, float mag);
void gfx_copy_rot_zoom_amap(Texture *dst, Texture *src, int sx, int sy, int w, int h, float rotate, float mag);
void gfx_copy_rot_zoom_use_amap(Texture *dst, Texture *src, int sx, int sy, int w, int h, float rotate, float mag);
void gfx_copy_rot_zoom2(Texture *dst, float cx, float cy, Texture *src, float scx, float scy, float rot, float mag);
void gfx_copy_reverse_LR(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_reverse_UD(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_reverse_amap_LR(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_reverse_LR_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_rotate_y(Texture *dst, Texture *front, Texture *back, int sx, int sy, int w, int h, float rot, float mag);
void gfx_copy_rotate_y_use_amap(Texture *dst, Texture *front, Texture *back, int sx, int sy, int w, int h, float rot, float mag);
void gfx_copy_rotate_x(Texture *dst, Texture *front, Texture *back, int sx, int sy, int w, int h, float rot, float mag);
@@ -161,14 +180,12 @@ void gfx_copy_amap_height_blur(Texture *dst, int dx, int dy, Texture *src, int s
void gfx_copy_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_fill_with_alpha(Texture *dst, int x, int y, int w, int h, int r, int g, int b, int a);
void gfx_copy_stretch_with_alpha_map(Texture *dst, int dx, int dy, int dw, int dh, Texture *src, int sx, int sy, int sw, int sh);
void gfx_draw_glyph(Texture *dst, float dx, int dy, Texture *glyph, SDL_Color color, float scale_x, float bold_width);
void gfx_copy_grayscale(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_draw_line(Texture *dst, int x0, int y0, int x1, int y1, int r, int g, int b);
void gfx_draw_line_to_amap(Texture *dst, int x0, int y0, int x1, int y1, int a);
void gfx_draw_glyph(Texture *dst, float dx, int dy, Texture *glyph, SDL_Color color, float scale_x, float bold_width, bool blend);
void gfx_draw_glyph_to_pmap(Texture *dst, float dx, int dy, Texture *glyph, Rectangle glyph_pos, SDL_Color color, float scale_x);
void gfx_draw_glyph_to_amap(Texture *dst, float dx, int dy, Texture *glyph, Rectangle glyph_pos, float scale_x);
// debug printing
void gfx_print_color(SDL_Color *c);
void gfx_print_rectangle(Rectangle *r);
void gfx_print_point(Point *p);
void gfx_print_texture(struct texture *t, int indent);
void gfx_draw_quadrilateral(Texture *dst, Texture *src, struct gfx_vertex vertices[4]);
#endif /* SYSTEM4_SDL_CORE_H */
+3 -1
View File
@@ -31,7 +31,9 @@ struct sdl_private {
SDL_GLContext context;
GLuint vao;
GLuint vbo;
GLuint ibo;
GLuint quad_vbo;
GLuint rect_ibo;
GLuint line_ibo;
} gl;
int w, h;
SDL_Rect viewport;
+6
View File
@@ -56,4 +56,10 @@ typedef SDL_Rect Rectangle;
0, 0, 1, -1, \
0, 0, 0, 1)
#define WORLD_TRANSFORM(w, h, x, y) \
MAT4(w, 0, 0, x, \
0, h, 0, y, \
0, 0, 1, 0, \
0, 0, 0, 1)
#endif /* SYSTEM4_GRAPHICS_H */
+26
View File
@@ -0,0 +1,26 @@
/* Copyright (C) 2025 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_ICON_H
#define SYSTEM4_ICON_H
#ifdef __ANDROID__
#define icon_init()
#else
void icon_init(void);
#endif
#endif /* SYSTEM4_ICON_H */
+10 -2
View File
@@ -17,14 +17,22 @@
#ifndef SYSTEM4_ID_POOL_H
#define SYSTEM4_ID_POOL_H
// A reasonable base value for pointer-based handles.
#define ID_POOL_HANDLE_BASE 0x100000
struct id_pool {
void **slots;
int nr_slots;
int base;
};
void id_pool_init(struct id_pool *pool);
void id_pool_release(struct id_pool *pool, int id);
void id_pool_init(struct id_pool *pool, int base);
void id_pool_delete(struct id_pool *pool);
void *id_pool_release(struct id_pool *pool, int id);
int id_pool_count(struct id_pool *pool);
int id_pool_get_unused(struct id_pool *pool);
int id_pool_get_first(struct id_pool *pool);
int id_pool_get_next(struct id_pool *pool, int id);
void *id_pool_get(struct id_pool *pool, int id);
void *id_pool_set(struct id_pool *pool, int id, void *data);
+2 -1
View File
@@ -137,9 +137,10 @@ enum sact_keycode {
extern bool mouse_focus;
extern bool keyboard_focus;
void handle_window_events(void);
void handle_events(void);
bool key_is_down(enum sact_keycode code);
void key_clear_flag(void);
void key_clear_flag(bool no_ctrl);
bool joy_key_is_down(uint8_t code);
void joy_clear_flag(void);
bool joy_get_stick_status(int controller, int type, float *deg, float *pow);
+90
View File
@@ -0,0 +1,90 @@
/* Copyright (C) 2023 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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, see <http://gnu.org/licenses/>.
*/
#ifndef XSYSTEM4_JSON_H
#define XSYSTEM4_JSON_H
#include <stdbool.h>
#include "gfx/types.h"
typedef struct cJSON cJSON;
struct texture;
struct text_style;
struct sact_sprite;
struct sprite;
cJSON *num2_to_json(double a, double b);
cJSON *num2_to_json_object(const char *a_name, double a, const char *b_name, double b);
cJSON *num3_to_json(double a, double b, double c);
cJSON *num3_to_json_object(const char *a_name, double a, const char *b_name, double b,
const char *c_name, double c);
cJSON *num4_to_json(double a, double b, double c, double d);
cJSON *num4_to_json_object(const char *a_name, double a, const char *b_name, double b,
const char *c_name, double c, const char *d_name, double d);
cJSON *texture_to_json(struct texture *t, bool verbose);
cJSON *texture_to_json_with_pixels(struct texture *t);
cJSON *text_style_to_json(struct text_style *ts, bool verbose);
cJSON *scene_sprite_to_json(struct sprite *sp, bool verbose);
cJSON *sprite_to_json(struct sact_sprite *sp, bool verbose);
static inline cJSON *xy_to_json(double x, double y, bool verbose)
{
if (!verbose)
return num2_to_json(x, y);
return num2_to_json_object("x", x, "y", y);
}
static inline cJSON *wh_to_json(double w, double h, bool verbose)
{
if (!verbose)
return num2_to_json(w, h);
return num2_to_json_object("w", w, "h", h);
}
static inline cJSON *point_to_json(Point *p, bool verbose)
{
return xy_to_json(p->x, p->y, verbose);
}
static inline cJSON *rectangle_to_json(Rectangle *r, bool verbose)
{
if (!verbose)
return num4_to_json(r->x, r->y, r->w, r->h);
return num4_to_json_object("x", r->x, "y", r->y, "w", r->w, "h", r->h);
}
static inline cJSON *color_to_json(Color *c, bool verbose)
{
if (!verbose)
return num4_to_json(c->r, c->g, c->b, c->a);
return num4_to_json_object("r", c->r, "g", c->g, "b", c->b, "a", c->a);
}
static inline cJSON *vec3_point_to_json(vec3 v, bool verbose)
{
if (!verbose)
return num3_to_json(v[0], v[1], v[2]);
return num3_to_json_object("x", v[0], "y", v[1], "z", v[2]);
}
static inline cJSON *vec3_color_to_json(vec3 v, bool verbose)
{
if (!verbose)
return num3_to_json(v[0], v[1], v[2]);
return num3_to_json_object("r", v[0], "g", v[1], "b", v[2]);
}
#endif
-76
View File
@@ -1,76 +0,0 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* Based on code from xsystem35
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#ifndef LITTLE_ENDIAN_H
#define LITTLE_ENDIAN_H
#include <stdint.h>
// no need to decode on byte-addressable, little-endian arch
#if defined(__i386__) || defined(__x86_64__)
#define LittleEndian_getDW(b, i) (*((int32_t*)((uint8_t*)(b)+(i))))
#define LittleEndian_getW(b, i) (*((int16_t*)((uint8_t*)(b)+(i))))
#else
static inline int32_t LittleEndian_getDW(const uint8_t *b, int i)
{
int c0, c1, c2, c3;
int d0, d1;
c0 = *(b + i + 0);
c1 = *(b + i + 1);
c2 = *(b + i + 2);
c3 = *(b + i + 3);
d0 = c0 + (c1 << 8);
d1 = c2 + (c3 << 8);
return d0 + (d1 << 16);
}
static inline int16_t LittleEndian_getW(const uint8_t *b, int i)
{
int c0, c1;
c0 = *(b + i + 0);
c1 = *(b + i + 1);
return c0 + (c1 << 8);
}
#endif /* defined(__i386__) || defined(__x86_64__) */
static inline int32_t LittleEndian_get3B(const uint8_t *b, int i)
{
int c0, c1, c2;
c0 = *(b + i + 0);
c1 = *(b + i + 1);
c2 = *(b + i + 2);
return c0 + (c1 << 8) + (c2 << 16);
}
static inline void LittleEndian_putDW(uint8_t *dst, int i, uint32_t dword)
{
dst[i] = (uint8_t)(dword);
dst[i+1] = (uint8_t)(dword >> 8);
dst[i+2] = (uint8_t)(dword >> 16);
dst[i+3] = (uint8_t)(dword >> 24);
}
static inline void LittleEndian_putW(uint8_t *dst, int i, uint16_t word)
{
dst[i] = word & 0xFF;
dst[i+1] = word >> 8;
}
#endif /* LITTLE_ENDIAN_H */
+2
View File
@@ -61,6 +61,7 @@ enum asset_type;
struct channel *channel_open(enum asset_type type, int no);
// Takes ownership of dfile.
struct channel *channel_open_archive_data(struct archive_data *dfile);
struct channel *channel_open_file(const char *path);
void channel_close(struct channel *ch);
int channel_play(struct channel *ch);
int channel_stop(struct channel *ch);
@@ -83,5 +84,6 @@ int channel_seek(struct channel *ch, int pos);
int channel_reverse_LR(struct channel *ch);
int channel_get_volume(struct channel *ch);
int channel_get_time_length(struct channel *ch);
int channel_get_data_no(struct channel *ch);
#endif /* SYSTEM4_MIXER_H */
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 kichikuou <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
*
*/
#ifndef __MSGQUEUE_H__
#define __MSGQUEUE_H__
#include <stdbool.h>
#include <SDL_mutex.h>
struct msgq_elem;
struct msgq {
SDL_mutex *mutex;
SDL_cond *cond_nonempty;
struct msgq_elem *head;
struct msgq_elem *last;
};
static inline bool msgq_isempty(struct msgq *q) {
return !q->head;
}
struct msgq *msgq_new(void);
void msgq_free(struct msgq *q);
void msgq_enqueue(struct msgq *q, void *msg);
void *msgq_dequeue(struct msgq *q);
void *msgq_dequeue_timeout(struct msgq *q, uint32_t timeout_ms);
#endif // __MSGQUEUE_H__
+75 -28
View File
@@ -24,6 +24,7 @@ struct string;
// parts.c
bool PE_Init(void);
void PE_Reset(void);
void PE_Update(int passed_time, bool message_window_show);
void PE_UpdateComponent(int passed_time);
void PE_UpdateParts(int passed_time, bool is_skip, bool message_window_show);
@@ -31,6 +32,8 @@ void PE_SetDelegateIndex(int parts_no, int delegate_index);
int PE_GetDelegateIndex(int parts_no);
bool PE_SetPartsCG(int parts_no, struct string *cg_name, int sprite_deform, int state);
bool PE_SetPartsCG_by_index(int parts_no, int cg_no, int sprite_deform, int state);
bool PE_SetPartsCG_by_string_index(int parts_no, struct string *cg_no,
int sprite_deform, int state);
void PE_GetPartsCGName(int parts_no, struct string **cg_name, int state);
bool PE_SetPartsCGSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
int PE_GetPartsCGNumber(int parts_no, int state);
@@ -40,12 +43,15 @@ bool PE_SetLoopCG(int parts_no, struct string *cg_name, int start_no, int nr_fra
bool PE_SetLoopCGSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetHGaugeCG(int parts_no, struct string *cg_name, int state);
bool PE_SetHGaugeCG_by_index(int parts_no, int cg_no, int state);
bool PE_SetHGaugeRate(int parts_no, int numerator, int denominator, int state);
bool PE_SetHGaugeRate(int parts_no, float numerator, float denominator, int state);
bool PE_SetHGaugeRate_int(int parts_no, int numerator, int denominator, int state);
bool PE_SetHGaugeSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetVGaugeCG(int parts_no, struct string *cg_name, int state);
bool PE_SetVGaugeCG_by_index(int parts_no, int cg_no, int state);
bool PE_SetVGaugeRate(int parts_no, int numerator, int denominator, int state);
bool PE_SetVGaugeRate(int parts_no, float numerator, float denominator, int state);
bool PE_SetVGaugeRate_int(int parts_no, int numerator, int denominator, int state);
bool PE_SetVGaugeSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetNumeralCG(int parts_no, struct string *cg_name, int state);
bool PE_SetNumeralCG_by_index(int parts_no, int cg_no, int state);
bool PE_SetNumeralLinkedCGNumberWidthWidthList_by_index(int parts_no, int cg_no,
int w0, int w1, int w2, int w3, int w4, int w5, int w6, int w7, int w8,
@@ -58,46 +64,51 @@ bool PE_SetNumeralShowComma(int parts_no, bool show_comma, int state);
bool PE_SetNumeralSpace(int parts_no, int space, int state);
bool PE_SetNumeralLength(int parts_no, int length, int state);
bool PE_SetNumeralSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetPartsRectangleDetectionSize(int PartsNumber, int Width, int Height, int State);
bool PE_SetPartsFlash(int PartsNumber, struct string *pIFlashFileName, int State);
bool PE_IsPartsFlashEnd(int PartsNumber, int State);
int PE_GetPartsFlashCurrentFrameNumber(int PartsNumber, int State);
bool PE_BackPartsFlashBeginFrame(int PartsNumber, int State);
bool PE_StepPartsFlashFinalFrame(int PartsNumber, int State);
void PE_ReleaseParts(int parts_no);
void PE_ReleaseAllParts(void);
void PE_ReleaseAllPartsWithoutSystem(void);
void PE_SetPos(int parts_no, int x, int y);
void PE_SetZ(int parts_no, int z);
void PE_SetShow(int parts_no, bool show);
void PE_SetAlpha(int parts_no, int alpha);
void PE_SetPartsDrawFilter(int PartsNumber, int DrawFilter);
void PE_SetAddColor(int parts_no, int r, int g, int b);
void PE_SetMultiplyColor(int parts_no, int r, int g, int b);
int PE_GetPartsX(int parts_no);
int PE_GetPartsY(int parts_no);
void PE_SetZ(int parts_no, int z);
int PE_GetPartsZ(int parts_no);
void PE_SetShow(int parts_no, bool show);
bool PE_GetPartsShow(int parts_no);
void PE_SetAlpha(int parts_no, int alpha);
int PE_GetPartsAlpha(int parts_no);
void PE_SetPartsDrawFilter(int parts_no, int draw_filter);
int PE_GetPartsDrawFilter(int parts_no);
void PE_SetAddColor(int parts_no, int r, int g, int b);
void PE_GetAddColor(int parts_no, int *r, int *g, int *b);
void PE_SetMultiplyColor(int parts_no, int r, int g, int b);
void PE_GetMultiplyColor(int parts_no, int *r, int *g, int *b);
int PE_GetPartsWidth(int parts_no, int state);
int PE_GetPartsHeight(int parts_no, int state);
int PE_GetPartsUpperLeftPosX(int parts_no, int state);
int PE_GetPartsUpperLeftPosY(int parts_no, int state);
int PE_GetPartsHeight(int parts_no, int state);
bool PE_GetPartsShow(int parts_no);
int PE_GetPartsAlpha(int parts_no);
void PE_SetPartsOriginPosMode(int parts_no, int origin_pos_mode);
int PE_GetPartsOriginPosMode(int parts_no);
void PE_SetParentPartsNumber(int parts_no, int parent_parts_no);
bool PE_SetPartsGroupNumber(int PartsNumber, int GroupNumber);
int PE_GetParentPartsNumber(int parts_no);
bool PE_SetPartsGroupNumber(int parts_no, int group_no);
void PE_SetPartsMessageWindowShowLink(int parts_no, bool message_window_show_link);
bool PE_GetPartsMessageWindowShowLink(int PartsNumber);
bool PE_GetPartsMessageWindowShowLink(int parts_no);
void PE_SetSpeedupRateByMessageSkip(int parts_no, int rate);
void PE_SetPartsMagX(int parts_no, float scale_x);
float PE_GetPartsMagX(int parts_no);
void PE_SetPartsMagY(int parts_no, float scale_y);
float PE_GetPartsMagY(int parts_no);
void PE_SetPartsRotateX(int parts_no, float rot_x);
void PE_SetPartsRotateY(int parts_no, float rot_y);
void PE_SetPartsRotateZ(int parts_no, float rot_z);
void PE_SetPartsAlphaClipperPartsNumber(int PartsNumber, int AlphaClipperPartsNumber);
void PE_SetPartsPixelDecide(int PartsNumber, bool PixelDecide);
bool PE_SetThumbnailReductionSize(int ReductionSize);
float PE_GetPartsRotateZ(int parts_no);
void PE_SetPartsPixelDecide(int PartsNumber, bool pixel_decide);
bool PE_SetThumbnailReductionSize(int reduction_size);
bool PE_SetThumbnailMode(bool Mode);
void PE_SetInputState(int parts_no, int state);
int PE_GetInputState(int parts_no);
bool PE_SetPartsRectangleDetectionSize(int parts_no, int w, int h, int state);
bool PE_SetPartsCGDetectionSize(int parts_no, struct string *cg_name, int state);
bool PE_Save(struct page **buffer);
bool PE_SaveWithoutHideParts(struct page **buffer);
bool PE_Load(struct page **buffer);
@@ -114,6 +125,8 @@ bool PE_AddFillToPartsConstructionProcess(int parts_no, int x, int y, int w, int
bool PE_AddFillAlphaColorToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int a, int state);
bool PE_AddFillAMapToPartsConstructionProcess(int parts_no, int x, int y, int w, int h, int a, int state);
bool PE_AddDrawRectToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int state);
bool PE_AddDrawCutCGToPartsConstructionProcess(int parts_no, struct string *cg_name,
int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, int interp_type, int state);
bool PE_AddCopyCutCGToPartsConstructionProcess(int parts_no, struct string *cg_name,
@@ -127,15 +140,17 @@ bool PE_AddDrawTextToPartsConstructionProcess(int parts_no, int x, int y, struct
int edge_r, int edge_g, int edge_b, float edge_weight,
int char_space, int line_space, int state);
bool PE_BuildPartsConstructionProcess(int parts_no, int state);
bool PE_ClearPartsConstructionProcess(int parts_no, int state);
bool PE_SetPartsConstructionSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
// input.c
void PE_UpdateInputState(int passed_time);
void PE_SetClickable(int parts_no, bool clickable);
bool PE_GetPartsClickable(int parts_no);
void PE_SetPartsGroupDecideOnCursor(int GroupNumber, bool DecideOnCursor);
void PE_SetPartsGroupDecideClick(int GroupNumber, bool DecideClick);
void PE_SetPartsGroupDecideOnCursor(int group_no, bool decide_on_cursor);
void PE_SetPartsGroupDecideClick(int group_no, bool decide_click);
void PE_SetOnCursorShowLinkPartsNumber(int parts_no, int link_parts_no);
int PE_GetOnCursorShowLinkPartsNumber(int parts_no);
bool PE_SetPartsOnCursorSoundNumber(int parts_no, int sound_no);
bool PE_SetPartsClickSoundNumber(int parts_no, int sound_no);
bool PE_SetClickMissSoundNumber(int sound_no);
@@ -152,21 +167,41 @@ void PE_AddMotionAlpha(int parts_no, int begin_a, int end_a, int begin_t, int en
void PE_AddMotionAlpha_curve(int parts_no, int begin_a, int end_a, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionCG_by_index(int parts_no, int begin_cg_no, int nr_cg, int begin_t, int end_t);
void PE_AddMotionHGaugeRate(int parts_no, int begin_numerator, int begin_denominator,
int end_numerator, int end_denominator, int begin_t, int end_t);
void PE_AddMotionVGaugeRate(int parts_no, int begin_numerator, int begin_denominator,
int end_numerator, int end_denominator, int begin_t, int end_t);
void PE_AddMotionHGaugeRate(int parts_no, float begin_numerator, float begin_denominator,
float end_numerator, float end_denominator, int begin_t, int end_t);
void PE_AddMotionHGaugeRate_curve(int parts_no, float begin_numerator, float begin_denominator,
float end_numerator, float end_denominator, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionVGaugeRate(int parts_no, float begin_numerator, float begin_denominator,
float end_numerator, float end_denominator, int begin_t, int end_t);
void PE_AddMotionVGaugeRate_curve(int parts_no, float begin_numerator, float begin_denominator,
float end_numerator, float end_denominator, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionNumeralNumber(int parts_no, int begin_n, int end_n, int begin_t, int end_t);
void PE_AddMotionNumeralNumber_curve(int parts_no, int begin_n, int end_n, int begin_t,
int end_t, struct string *curve_name);
void PE_AddMotionMagX(int parts_no, float begin, float end, int begin_t, int end_t);
void PE_AddMotionMagX_curve(int parts_no, float begin, float end, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionMagY(int parts_no, float begin, float end, int begin_t, int end_t);
void PE_AddMotionMagY_curve(int parts_no, float begin, float end, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionRotateX(int parts_no, float begin, float end, int begin_t, int end_t);
void PE_AddMotionRotateX_curve(int parts_no, float begin, float end, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionRotateY(int parts_no, float begin, float end, int begin_t, int end_t);
void PE_AddMotionRotateY_curve(int parts_no, float begin, float end, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionRotateZ(int parts_no, float begin, float end, int begin_t, int end_t);
void PE_AddMotionRotateZ_curve(int parts_no, float begin, float end, int begin_t, int end_t,
struct string *curve_name);
void PE_AddMotionVibrationSize(int parts_no, int begin_w, int begin_h, int begin_t, int end_t);
void PE_AddMotionSound(int sound_no, int begin_t);
void PE_AddWholeMotionVibrationSize(int begin_w, int begin_h, int begin_t, int end_t);
void PE_BeginMotion(void);
void PE_EndMotion(void);
void PE_SetMotionTime(int t);
void PE_SeekEndMotion(void);
void PE_UpdateMotionTime(int time, bool skip);
bool PE_IsMotion(void);
int PE_GetMotionEndTime(void);
@@ -185,4 +220,16 @@ bool PE_SetPartsFontEdgeWeight(int parts_no, float edge_weight, int state);
bool PE_SetTextCharSpace(int parts_no, int char_space, int state);
bool PE_SetTextLineSpace(int parts_no, int line_space, int state);
// flash.c
bool PE_SetPartsFlash(int parts_no, struct string *flash_filename, int state);
bool PE_IsPartsFlashEnd(int parts_no, int state);
int PE_GetPartsFlashCurrentFrameNumber(int parts_no, int state);
bool PE_BackPartsFlashBeginFrame(int parts_no, int state);
bool PE_StepPartsFlashFinalFrame(int parts_no, int state);
bool PE_SetPartsFlashAndStop(int parts_no, struct string *flash_filename, int state);
bool PE_StopPartsFlash(int parts_no, int state);
bool PE_StartPartsFlash(int parts_no, int state);
bool PE_GoFramePartsFlash(int parts_no, int frame_no, int state);
int PE_GetPartsFlashEndFrame(int parts_no, int state);
#endif /* SYSTEM4_PARTS_H */
+6 -1
View File
@@ -17,12 +17,17 @@
#ifndef SYSTEM4_PLUGIN_H
#define SYSTEM4_PLUGIN_H
#include <stdbool.h>
typedef struct cJSON cJSON;
struct sact_sprite;
struct draw_plugin {
const char *name;
void (*free)(struct draw_plugin *);
void (*update)(struct sact_sprite *);
void (*debug_print)(struct sact_sprite *, int indent);
void (*render)(struct sact_sprite *);
cJSON *(*to_json)(struct sact_sprite *, bool);
};
#endif /* SYSTEM4_PLUGIN_H */
+37 -3
View File
@@ -25,8 +25,14 @@
#include "gfx/gfx.h"
#include "plugin.h"
typedef struct cJSON cJSON;
struct hash_table;
enum RE_plugin_version {
RE_REIGN_PLUGIN, // Toushin Toshi 3
RE_TAPIR_PLUGIN, // Rance Quest
};
enum RE_instance_type {
RE_ITYPE_UNINITIALIZED = 0,
RE_ITYPE_STATIC = 1,
@@ -35,6 +41,7 @@ enum RE_instance_type {
RE_ITYPE_DIRECTIONAL_LIGHT = 4,
RE_ITYPE_SPECULAR_LIGHT = 6,
RE_ITYPE_PARTICLE_EFFECT = 8,
RE_ITYPE_PATH_LINE = 9,
};
enum RE_motion_state {
@@ -55,6 +62,17 @@ enum RE_draw_type {
RE_DRAW_TYPE_MAX = RE_DRAW_TYPE_ADDITIVE
};
enum RE_draw_options {
RE_DRAW_OPTION_EDGE = 0,
RE_DRAW_OPTION_MAX
};
enum RE_draw_edge_mode {
RE_DRAW_EDGE_NONE = 0,
RE_DRAW_EDGE_CHARACTERS_ONLY = 1,
RE_DRAW_EDGE_ALL = 2,
};
struct RE_options {
int anti_aliasing;
int wait_vsync;
@@ -126,6 +144,10 @@ struct RE_plugin {
float post_effect_filter_y;
float post_effect_filter_cb;
float post_effect_filter_cr;
int draw_options[RE_DRAW_OPTION_MAX];
// TapirEngine
bool suspended;
};
struct RE_instance {
@@ -168,17 +190,22 @@ struct RE_instance {
bool local_transform_needs_update;
mat4 local_transform;
mat3 normal_transform;
mat4 *bone_transforms; // model->nr_bones elements
mat3x4 *bone_transforms; // row-major, model->nr_bones elements
GLuint bone_transforms_ubo;
vec4 bounding_sphere;
struct RE_instance *shadow_volume_instance;
float z_from_camera;
int texture_animation_index;
};
struct RE_plugin *RE_plugin_new(void);
struct RE_plugin *RE_plugin_new(enum RE_plugin_version version);
void RE_plugin_free(struct RE_plugin *plugin);
bool RE_plugin_bind(struct RE_plugin *plugin, int sprite);
bool RE_plugin_unbind(struct RE_plugin *plugin);
bool RE_plugin_suspend(struct RE_plugin *plugin);
bool RE_plugin_resume(struct RE_plugin *plugin);
bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms);
bool RE_set_viewport(struct RE_plugin *plugin, int x, int y, int width, int height);
bool RE_set_projection(struct RE_plugin *plugin, float width, float height, float near, float far, float deg);
int RE_create_instance(struct RE_plugin *plugin);
bool RE_release_instance(struct RE_plugin *plugin, int instance);
@@ -188,11 +215,18 @@ bool RE_instance_load(struct RE_instance *instance, const char *name);
bool RE_instance_load_motion(struct RE_instance *instance, const char *name);
bool RE_instance_load_next_motion(struct RE_instance *instance, const char *name);
bool RE_instance_free_next_motion(struct RE_instance *instance);
bool RE_instance_set_mesh_show(struct RE_instance *instance, const char *mesh_name, bool show);
bool RE_instance_set_vertex_pos(struct RE_instance *instance, int index, float x, float y, float z);
int RE_instance_get_bone_index(struct RE_instance *instance, const char *name);
bool RE_instance_trans_local_pos_to_world_pos_by_bone(struct RE_instance *instance, int bone, vec3 offset, vec3 out);
float RE_instance_calc_height(struct RE_instance *instance, float x, float z);
float RE_instance_calc_2d_detection_height(struct RE_instance *instance, float x, float z);
bool RE_instance_calc_2d_detection(struct RE_instance *instance, float x0, float y0, float z0, float x1, float y1, float z1, float *x2, float *y2, float *z2, float radius);
bool RE_instance_set_debug_draw_shadow_volume(struct RE_instance *instance, bool draw);
bool RE_instance_find_path(struct RE_instance *instance, vec3 start, vec3 goal);
const vec3 *RE_instance_get_path_line(struct RE_instance *instance, int *nr_path_points);
bool RE_instance_optimize_path_line(struct RE_instance *instance);
bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *instance, int mouse_x, int mouse_y, vec3 out);
int RE_motion_get_state(struct motion *motion);
bool RE_motion_set_state(struct motion *motion, int state);
@@ -270,6 +304,6 @@ bool RE_back_cg_set(struct RE_back_cg *bcg, int no);
bool RE_back_cg_set_name(struct RE_back_cg *bcg, struct string *name, struct archive *aar);
void RE_render(struct sact_sprite *sp);
void RE_debug_print(struct sact_sprite *sp, int indent);
cJSON *RE_to_json(struct sact_sprite *sp, bool verbose);
#endif /* SYSTEM4_REIGN_H */
+21 -2
View File
@@ -20,11 +20,19 @@
#include <stdbool.h>
#include "sprite.h"
enum sprite_engine_type {
UNINITIALIZED_SPRITE_ENGINE,
SACT2_SPRITE_ENGINE,
SACTDX_SPRITE_ENGINE,
STOAT_SPRITE_ENGINE,
CHIPMUNK_SPRITE_ENGINE,
};
struct sact_sprite *sact_get_sprite(int sp);
struct sact_sprite *sact_try_get_sprite(int sp);
struct sact_sprite *sact_create_sprite(int sp_no, int width, int height, int r, int g, int b, int a);
void sact_ModuleFini(void);
int sact_Init(void *_, int cg_cache_size);
int sact_init(int cg_cache_size, enum sprite_engine_type engine);
#define sact_SetWP scene_set_wp
#define sact_SetWP_Color scene_set_wp_color
int sact_GetScreenWidth(void);
@@ -42,6 +50,7 @@ int sact_SP_SetCGFromFile(int sp, struct string *filename);
int sact_SP_SaveCG(int sp, struct string *filename);
int sact_SP_Create(int sp, int width, int height, int r, int g, int b, int a);
int sact_SP_CreatePixelOnly(int sp, int width, int height);
int sact_SP_CreateCustom(int sp);
int sact_SP_Delete(int sp);
int sact_SP_DeleteAll(void);
int sact_SP_SetPos(int sp_no, int x, int y);
@@ -50,7 +59,7 @@ int sact_SP_SetY(int sp_no, int y);
int sact_SP_SetZ(int sp, int z);
int sact_SP_GetBlendRate(int sp_no);
int sact_SP_SetBlendRate(int sp_no, int rate);
int sact_SP_SetShow(int sp_no, bool show);
int sact_SP_SetShow(int sp_no, int show);
int sact_SP_SetDrawMethod(int sp_no, int method);
int sact_SP_GetDrawMethod(int sp_no);
int sact_SP_IsUsing(int sp_no);
@@ -82,6 +91,8 @@ int sact_SP_IsPtInRect(int sp_no, int x, int y);
int sact_CG_GetMetrics(int cg_no, struct page **page);
int sact_SP_GetAMapValue(int sp_no, int x, int y);
int sact_SP_GetPixelValue(int sp_no, int x, int y, int *r, int *g, int *b);
int sact_SP_SetBrightness(int sp_no, int brightness);
int sact_SP_GetBrightness(int sp_no);
int sact_GAME_MSG_GetNumOf(void);
void sact_IntToZenkaku(struct string **s, int value, int figures, int zero_pad);
void sact_IntToHankaku(struct string **s, int value, int figures, int zero_pad);
@@ -92,6 +103,7 @@ int sact_Joypad_IsKeyDown(int num, int key);
bool sact_Joypad_GetAnalogStickStatus(int num, int type, float *degree, float *power);
bool sact_Joypad_GetDigitalStickStatus(int num, int type, bool *left, bool *right, bool *up, bool *down);
int sact_Key_ClearFlag(void);
void sact_Key_ClearFlagNoCtrl(void);
int sact_Key_IsDown(int keycode);
int sact_CG_IsExist(int cg_no);
void sact_System_GetDate(int *year, int *month, int *mday, int *wday);
@@ -100,6 +112,10 @@ void sact_CG_BlendAMapBin(int dst, int dx, int dy, int src, int sx, int sy, int
int sact_TRANS_Begin(int type);
int sact_TRANS_Update(float rate);
int sact_TRANS_End(void);
bool sact_VIEW_SetMode(int mode);
int sact_VIEW_GetMode(void);
bool sact_DX_GetUsePower2Texture(void);
void sact_DX_SetUsePower2Texture(bool use);
extern struct text_style text_sprite_ts;
bool StoatSpriteEngine_SP_SetTextSprite(int sp_no, struct string *text);
@@ -110,6 +126,9 @@ void StoatSpriteEngine_SP_SetTextSpriteBoldWeight(float weight);
void StoatSpriteEngine_SP_SetTextSpriteEdgeWeight(float weight);
void StoatSpriteEngine_SP_SetTextSpriteEdgeColor(int r, int g, int b);
bool StoatSpriteEngine_SP_SetDashTextSprite(int sp_no, int width, int height);
void StoatSpriteEngine_FPS_SetShow(bool show);
bool StoatSpriteEngine_FPS_GetShow(void);
float StoatSpriteEngine_FPS_Get(void);
//int StoatSpriteEngine_SP_SetBrightness(int sp_no, int brightness);
//int StoatSpriteEngine_SP_GetBrightness(int sp_no);
int StoatSpriteEngine_SYSTEM_IsResetOnce(void);
+2 -5
View File
@@ -20,14 +20,11 @@
#include "cJSON.h"
#include "vm.h"
cJSON *page_to_json(struct page *page);
cJSON *vm_value_to_json(enum ain_data_type type, union vm_value val);
void json_load_page(struct page *page, cJSON *vars, bool call_dtors);
union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type struct_type, int array_rank, cJSON *json);
int save_json(const char *filename, cJSON *json);
int save_globals(const char *keyname, const char *filename);
int save_group(const char *keyname, const char *filename, const char *group_name, int *n);
cJSON *load_json(const char *filename);
int save_globals(const char *keyname, const char *filename, const char *group_name, int *n);
int load_globals(const char *keyname, const char *filename, const char *group_name, int *n);
int delete_save_file(const char *filename);
+6 -1
View File
@@ -20,6 +20,7 @@
#include <stdbool.h>
#include "queue.h"
typedef struct cJSON cJSON;
struct texture;
struct sprite {
@@ -37,10 +38,12 @@ struct sprite {
// the current scene. A sprite should be in the scene if there is pixel
// or text data attached to it and it is not hidden.
bool in_scene;
// Unique ID
int id;
// The rendering function.
void (*render)(struct sprite*);
// Debug printing function
void (*debug_print)(struct sprite*);
cJSON *(*to_json)(struct sprite*, bool);
};
extern bool scene_is_dirty;
@@ -55,6 +58,8 @@ void scene_set_sprite_z(struct sprite *sp, int z);
void scene_set_sprite_z2(struct sprite *sp, int z, int z2);
void scene_print(void);
cJSON *scene_to_json(bool verbose);
struct sprite *scene_get(int id);
static inline void scene_sprite_dirty(struct sprite *sp)
{
+29 -4
View File
@@ -35,8 +35,16 @@ struct sact_sprite {
struct texture texture;
// If no CG is attached to the sprite, the solid color to fill with.
SDL_Color color;
// Shader params
int blend_rate;
SDL_Color multiply_color;
SDL_Color add_color;
// Draw method (blend mode)
enum draw_method draw_method;
// The position and dimensions of the sprite.
Rectangle rect;
// The visible area of the sprite
Rectangle surface_area;
// Arbitrary text can be attached to the sprite. This is rendered on a
// separate texture and overlayed.
struct {
@@ -46,15 +54,29 @@ struct sact_sprite {
Point pos;
int char_space;
int line_space;
int current_line_height;
} text;
// The sprite handle
int no;
// The CG number attached to the sprite.
int cg_no;
// The sprite is suspended. The exact meaning of this is unknown.
bool suspended;
// (optional) Draw plugin bound to this sprite.
struct draw_plugin *plugin;
};
/*
* XXX: There are two different sprite renderers:
* * SACT2 - blend rate and brightness (stored in multiply_color field)
* * ChipmunkSpriteEngine - blend rate, add color, multiply color, surface area
* (uses parts shader)
*
* The initialization function determines which renderer is used.
*/
void sprite_init_sact(void);
void sprite_init_chipmunk(void);
static inline void sprite_dirty(struct sact_sprite *sp)
{
scene_sprite_dirty(&sp->sp);
@@ -69,14 +91,19 @@ int sprite_set_cg_2x_from_asset(struct sact_sprite *sp, int cg_no);
int sprite_set_cg_by_name(struct sact_sprite *sp, const char *name);
int sprite_set_cg_from_file(struct sact_sprite *sp, const char *path);
int sprite_save_cg(struct sact_sprite *sp, const char *path);
void sprite_init(struct sact_sprite *sp, int w, int h, int r, int g, int b, int a);
void sprite_init(struct sact_sprite *sp);
void sprite_init_color(struct sact_sprite *sp, int w, int h, int r, int g, int b, int a);
void sprite_init_custom(struct sact_sprite *sp);
void sprite_set_pos(struct sact_sprite *sp, int x, int y);
void sprite_set_x(struct sact_sprite *sp, int x);
void sprite_set_y(struct sact_sprite *sp, int y);
static inline void sprite_set_z(struct sact_sprite *sp, int z) { scene_set_sprite_z(&sp->sp, z); }
static inline void sprite_set_z2(struct sact_sprite *sp, int z, int z2) { scene_set_sprite_z2(&sp->sp, z, z2); }
int sprite_get_blend_rate(struct sact_sprite *sp);
static inline int sprite_get_blend_rate(struct sact_sprite *sp) { return sp->blend_rate; }
void sprite_set_blend_rate(struct sact_sprite *sp, int rate);
void sprite_set_multiply_color(struct sact_sprite *sp, int r, int g, int b);
void sprite_set_add_color(struct sact_sprite *sp, int r, int g, int b);
void sprite_set_surface_area(struct sact_sprite *sp, int x, int y, int w, int h);
static inline void sprite_set_show(struct sact_sprite *sp, bool show) { scene_set_sprite_show(&sp->sp, show); }
int sprite_set_draw_method(struct sact_sprite *sp, enum draw_method method);
enum draw_method sprite_get_draw_method(struct sact_sprite *sp);
@@ -110,6 +137,4 @@ void sprite_get_pixel_value(struct sact_sprite *sp, int x, int y, int *r, int *g
void sprite_bind_plugin(struct sact_sprite *sp, struct draw_plugin *plugin);
void sprite_call_plugins(void);
void sprite_print(struct sact_sprite *sp);
#endif /* SYSTEM4_SPRITE_H */
+274
View File
@@ -0,0 +1,274 @@
/* Copyright (C) 2023 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#ifndef SYSTEM4_SWF_H
#define SYSTEM4_SWF_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
typedef int32_t fixed32; // 32-bit 16.16 fixed-point number
typedef int16_t fixed16; // 16-bit 8.8 fixed-point number
typedef uint16_t fixedu16;
typedef uint32_t rgb_t; // 0x00BBGGRR
typedef uint32_t rgba_t; // 0xAABBGGRR
static inline float twips_to_float(int32_t twips) {
return twips / 20.0f;
}
static inline float fixed32_to_float(fixed32 f32) {
return f32 / 65536.0f;
}
static inline float fixed16_to_float(fixed16 f16) {
return f16 / 256.0f;
}
static inline fixed16 fixed16_mul(fixed16 a, fixed16 b) {
return (int32_t)a * (int32_t)b / 256;
}
struct swf_rect {
// in twips (1/20 pixel)
int32_t x_min;
int32_t x_max;
int32_t y_min;
int32_t y_max;
};
struct swf {
uint8_t version;
uint32_t file_length;
struct swf_rect frame_size;
fixedu16 frame_rate;
uint16_t frame_count;
struct swf_tag *tags;
};
struct swf_matrix {
fixed32 scale_x;
fixed32 scale_y;
fixed32 rotate_skew_0;
fixed32 rotate_skew_1;
int32_t translate_x;
int32_t translate_y;
};
struct swf_cxform_with_alpha {
int16_t add_terms[4]; // red, green, blue, alpha
fixed16 mult_terms[4]; // red, green, blue, alpha
};
// We only support clipped bitmap fill.
struct swf_fill_style {
uint16_t bitmap_id;
bool smoothed;
struct swf_matrix matrix;
};
struct swf_sound_spec {
uint16_t rate;
uint8_t sample_size; // 8 or 16 (bits)
uint8_t channels;
};
// Tags
enum swf_tag_type {
TAG_END = 0,
TAG_SHOW_FRAME = 1,
TAG_DEFINE_SHAPE = 2,
TAG_SET_BACKGROUND_COLOR = 9,
TAG_DO_ACTION = 12,
TAG_DEFINE_SOUND = 14,
TAG_START_SOUND = 15,
TAG_DEFINE_BITS_LOSSLESS = 20,
TAG_PLACE_OBJECT2 = 26,
TAG_REMOVE_OBJECT2 = 28,
TAG_DEFINE_BITS_LOSSLESS2 = 36,
TAG_DEFINE_SPRITE = 39,
TAG_SOUND_STREAM_HEAD2 = 45,
TAG_FILE_ATTRIBUTES = 69,
TAG_PLACE_OBJECT3 = 70,
};
struct swf_tag {
int type;
struct swf_tag *next;
};
struct swf_tag_define_bits_lossless {
struct swf_tag t; // TAG_DEFINE_BITS_LOSSLESS or TAG_DEFINE_BITS_LOSSLESS2
uint16_t character_id;
uint8_t format;
uint16_t width;
uint16_t height;
uint8_t *data;
};
// We only support simple rectangle shapes with a fill style.
struct swf_tag_define_shape {
struct swf_tag t;
uint16_t shape_id;
struct swf_rect bounds;
struct swf_fill_style fill_style;
};
enum swf_sound_format {
SWF_SOUND_UNCOMPRESSED_LE = 3,
};
struct swf_tag_define_sound {
struct swf_tag t;
uint16_t sound_id;
uint8_t format;
struct swf_sound_spec spec;
uint32_t sample_count;
uint8_t *data;
uint32_t data_len;
};
struct swf_tag_define_sprite {
struct swf_tag t;
uint16_t sprite_id;
uint16_t frame_count;
struct swf_tag *tags;
};
struct swf_tag_do_action {
struct swf_tag t;
struct swf_action *actions;
};
enum place_object_flags {
PLACE_FLAG_RESERVED_BITS = 0xffff - ((1 << 11) - 1),
PLACE_FLAG_HAS_CACHE_AS_BITMAP = 1 << 10,
PLACE_FLAG_HAS_BLEND_MODE = 1 << 9,
PLACE_FLAG_HAS_FILTER_LIST = 1 << 8,
PLACE_FLAG_HAS_CLIP_ACTIONS = 1 << 7,
PLACE_FLAG_HAS_CLIP_DEPTH = 1 << 6,
PLACE_FLAG_HAS_NAME = 1 << 5,
PLACE_FLAG_HAS_RATIO = 1 << 4,
PLACE_FLAG_HAS_COLOR_TRANSFORM = 1 << 3,
PLACE_FLAG_HAS_MATRIX = 1 << 2,
PLACE_FLAG_HAS_CHARACTER = 1 << 1,
PLACE_FLAG_MOVE = 1 << 0,
};
struct swf_tag_place_object {
struct swf_tag t; // TAG_PLACE_OBJECT2 or TAG_PLACE_OBJECT3
uint16_t flags;
uint16_t depth;
uint16_t character_id;
struct swf_matrix matrix;
struct swf_cxform_with_alpha color_transform;
uint16_t ratio;
char *name;
uint8_t blend_mode;
uint8_t bitmap_cache;
};
struct swf_tag_remove_object2 {
struct swf_tag t;
uint16_t depth;
};
struct swf_tag_set_background_color {
struct swf_tag t;
rgb_t background_color;
};
struct swf_tag_sound_stream_head {
struct swf_tag t;
struct swf_sound_spec playback_spec;
uint8_t compression;
struct swf_sound_spec stream_spec;
uint16_t sample_count;
int16_t latency_seek;
};
enum start_sound_flags {
SOUND_INFO_RESERVED_BITS = 0xff - ((1 << 6) - 1),
SOUND_INFO_SYNC_STOP = 1 << 5,
SOUND_INFO_SYNC_NO_MULTIPLE = 1 << 4,
SOUND_INFO_HAS_ENVELOPE = 1 << 3,
SOUND_INFO_HAS_LOOPS = 1 << 2,
SOUND_INFO_HAS_OUT_POINT = 1 << 1,
SOUND_INFO_HAS_IN_POINT = 1 << 0,
};
struct swf_tag_start_sound {
struct swf_tag t;
uint16_t sound_id;
uint8_t flags;
uint32_t in_point;
uint32_t out_point;
uint16_t loop_count;
};
// Actions
enum swf_action_type {
// Actions without payload.
ACTION_END = 0x00,
ACTION_PLAY = 0x06,
ACTION_STOP = 0x07,
ACTION_POP = 0x17,
ACTION_CALL_FUNCTION = 0x3d,
// Actions with payloads.
ACTION_GOTO_FRAME = 0x81,
ACTION_CONSTANT_POOL = 0x88,
ACTION_PUSH = 0x96,
};
struct swf_action {
uint8_t code;
struct swf_action *next;
};
struct swf_action_constant_pool {
struct swf_action a;
uint16_t count;
char *constant_pool[];
};
struct swf_action_goto_frame {
struct swf_action a;
uint16_t frame;
};
enum swf_action_push_type {
ACTION_PUSH_INTEGER = 7,
ACTION_PUSH_CONSTANT8 = 8,
};
struct swf_action_push {
struct swf_action a;
uint16_t count;
struct {
enum swf_action_push_type type;
uint32_t value;
} values[];
};
struct swf *swf_load(uint8_t *data, size_t size);
void swf_free(struct swf *swf);
struct swf *aff_load(uint8_t *data, size_t size);
#endif /* SYSTEM4_SWF_H */
+25 -2
View File
@@ -26,6 +26,11 @@ enum ain_data_type;
struct page;
struct string;
// xsystem4-specific system calls (used for hacks)
enum vm_extra_syscall {
VM_XSYS_KEY_IS_DOWN = 0x80000000,
};
// Non-heap values. Stored in pages and on the stack.
union vm_value {
int32_t i;
@@ -106,6 +111,7 @@ union vm_value vm_copy(union vm_value v, enum ain_data_type type);
int vm_execute_ain(struct ain *program);
void vm_call(int fno, int struct_page);
int vm_time(void);
void vm_sleep(int ms);
void hll_call(int libno, int fno);
bool library_exists(int libno);
@@ -113,17 +119,34 @@ bool library_function_exists(int libno, int fno);
void init_libraries(void);
void exit_libraries(void);
struct string *string_format(struct string *fmt, union vm_value arg, enum ain_data_type type);
// NOTE: This can probably be merged with ain_data_type, since the values are
// disjoint.
enum string_format_type {
STRFMT_INT = 2,
STRFMT_FLOAT = 3,
STRFMT_STRING = 4,
STRFMT_BOOL = 48,
STRFMT_LONG_INT = 56,
};
struct string *string_format(struct string *fmt, union vm_value arg, enum string_format_type type);
void vm_stack_trace(void);
_Noreturn void _vm_error(const char *fmt, ...);
_Noreturn void vm_exit(int code);
_Noreturn void vm_reset(void);
extern bool vm_reset_once;
#if defined(__ANDROID__) || defined(RELEASE)
// Report the error with a message box and exit.
#define VM_ERROR ERROR
#else
#define VM_ERROR(fmt, ...) \
_vm_error("*ERROR*(%s:%s:%d): " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#endif
#ifdef VM_PRIVATE
#include "little_endian.h"
#include "system4/little_endian.h"
#include "system4/ain.h"
struct function_call {
+4 -9
View File
@@ -33,6 +33,7 @@ enum vm_pointer_type {
// Heap-backed objects. Reference counted.
struct vm_pointer {
int ref;
uint32_t seq;
enum vm_pointer_type type;
union {
struct string *s;
@@ -60,6 +61,8 @@ void heap_ref(int slot);
void heap_unref(int slot);
void exit_unref(int slot);
uint32_t heap_get_seq(int slot);
bool heap_index_valid(int index);
bool page_index_valid(int index);
bool string_index_valid(int index);
@@ -75,17 +78,9 @@ int32_t heap_alloc_string(struct string *s);
void heap_describe_slot(int slot);
/*
* Guarantee `headroom` free slots are available on the heap.
*
* XXX: this is a hack to fix an issue with HLL calls where a pointer into the
* heap can be made invalid by a heap reallocation triggered within the
* body of the HLL function.
*/
void heap_guarantee(unsigned headroom);
#ifdef VM_PRIVATE
extern uint32_t heap_next_seq;
extern int32_t *heap_free_stack;
extern size_t heap_free_ptr;
+15 -9
View File
@@ -50,8 +50,8 @@ enum page_type {
* Multi-dimensional arrays are implemented as a tree of pages (meaning the
* whole array is NOT contiguous in memory).
*
* Delegates: each delegate object is backed by a page storing object/function
* pairs.
* Delegates: each delegate object is backed by a page storing (object,
* function, seq) triples.
*/
struct page {
enum page_type type;
@@ -66,11 +66,17 @@ struct page {
int index;
enum ain_data_type a_type;
};
// array-specific metadata
struct {
int struct_type;
int rank;
} array;
union {
// local-specific metadata
struct {
int struct_ptr;
} local;
// array-specific metadata
struct {
int struct_type;
int rank;
} array;
};
int nr_vars;
union vm_value values[];
};
@@ -97,7 +103,7 @@ static inline void _page_set_var(struct page *page, int i, union vm_value v)
// variables
union vm_value variable_initval(enum ain_data_type type);
void variable_fini(union vm_value v, enum ain_data_type type);
void variable_fini(union vm_value v, enum ain_data_type type, bool call_dtor);
enum ain_data_type variable_type(struct page *page, int varno, int *struct_type, int *array_rank);
void variable_set(struct page *page, int varno, enum ain_data_type type, union vm_value val);
@@ -139,6 +145,6 @@ struct page *delegate_append(struct page *dst, int obj, int fun);
struct page *delegate_plusa(struct page *dst, struct page *add);
struct page *delegate_minusa(struct page *dst, struct page *minus);
struct page *delegate_clear(struct page *page);
void delegate_get(struct page *page, int i, int *obj_out, int *fun_out);
bool delegate_get(struct page *page, int i, int *obj_out, int *fun_out);
#endif /* SYSTEM4_PAGE_H */
+22 -3
View File
@@ -20,9 +20,16 @@
#include <stddef.h>
#include <stdbool.h>
enum resume_save_format {
SAVE_FORMAT_JSON,
SAVE_FORMAT_RSM,
};
struct config {
char *game_name;
char *boot_name;
char *ain_filename;
char *vm_name;
char *game_dir;
char *save_dir;
char *home_dir;
@@ -43,6 +50,8 @@ struct config {
bool echo;
float text_x_scale;
bool manual_text_x_scale;
enum resume_save_format save_format;
int msgskip_delay;
};
extern struct config config;
@@ -56,15 +65,24 @@ const char *display_utf0(const char *utf);
const char *display_utf1(const char *utf);
const char *display_utf2(const char *utf);
void indent_printf(int indent, const char *fmt, ...);
void indent_message(int indent, const char *fmt, ...);
void log_message(const char *log, const char *fmt, ...);
#define UNIMPLEMENTED(fmt, ...) \
sys_warning("unimplemented: %s" fmt "\n", __func__, ##__VA_ARGS__)
bool is_absolute_path(const char *path);
char *unix_path(const char *path);
char *gamedir_path(const char *path);
char *gamedir_path_icase(const char *path);
char *savedir_path(const char *path);
void get_date(int *year, int *month, int *mday, int *wday);
void get_time(int *hour, int *min, int *sec, int *ms);
void screenshot_save(void);
#ifndef XSYS4_DATA_DIR
#define XSYS4_DATA_DIR "/usr/local/share/xsystem4"
#endif
@@ -75,7 +93,8 @@ extern bool game_daibanchou_en;
extern bool game_rance02_mg;
extern bool game_rance6_mg;
extern bool game_rance7_mg;
extern bool id_indexed_afa;
extern bool game_rance8;
extern bool game_rance8_mg;
extern bool game_dungeons_and_dolls;
#endif /* XSYSTEM4_H */
+26 -11
View File
@@ -1,18 +1,33 @@
project('system4', 'c',
meson_version: '>= 0.59',
default_options : ['c_std=c11'])
add_project_arguments('-D_DEFAULT_SOURCE', language : 'c')
add_project_arguments('-DXSYS4_DATA_DIR="' + get_option('prefix') / get_option('datadir') / 'xsystem4"', language : 'c')
zlib = dependency('zlib')
if get_option('buildtype').startswith('release')
add_project_arguments('-DRELEASE', language : 'c')
endif
static_libs = false
if host_machine.system() == 'windows'
static_libs = true
endif
zlib = dependency('zlib', static : static_libs)
libm = meson.get_compiler('c').find_library('m', required: false)
sdl2 = dependency('sdl2')
ft2 = dependency('freetype2')
ffi = dependency('libffi')
tj = dependency('libturbojpeg')
webp = dependency('libwebp')
png = dependency('libpng')
cglm = dependency('cglm', fallback : ['cglm', 'cglm_dep'])
sndfile = dependency('sndfile')
sdl2 = dependency('sdl2', static : static_libs)
ft2 = dependency('freetype2', static : static_libs)
ffi = dependency('libffi', static : static_libs)
tj = dependency('libturbojpeg', static : static_libs)
webp = dependency('libwebp', static : static_libs)
png = dependency('libpng', static : static_libs)
cglm = dependency('cglm', version : '>=0.9.2', fallback : ['cglm', 'cglm_dep'])
sndfile = dependency('sndfile', static : static_libs)
avcodec = dependency('libavcodec', version : '>=59.37.100', required: false, static : static_libs)
avformat = dependency('libavformat', required: false, static : static_libs)
avutil = dependency('libavutil', version : '>=57.28.100', required: false, static : static_libs)
swscale = dependency('libswscale', required: false, static : static_libs)
gles = dependency('glesv2', version : '>=3', required: get_option('opengles'))
if gles.found()
@@ -24,8 +39,8 @@ else
gl_deps = [gl, glew]
endif
chibi = dependency('chibi-scheme', required : false)
readline = dependency('readline', required : false)
chibi = dependency('chibi-scheme', required : false, static : static_libs)
readline = dependency('readline', required : false, static : static_libs)
flex = find_program('flex')
bison = find_program('bison')
+140
View File
@@ -0,0 +1,140 @@
Updates to the System 4 language since Evenicle
===============================================
SLC's System 4 decompiler/compiler was last updated to work with Evenicle. There have been
significant changes to the System 4 language since. What follows is a list of features which
have been added.
Namespaces
----------
Added in Tsumamigui 3. This is purely a compile time feature. If you look at the global
variable and function names, you will notice lots of `::`, e.g. `time::detail::g_PrevPlayTime`.
Lambdas
-------
Added in Heartful Maman. I'm not sure how these work, but if you look at the disassembled
code there are now functions embedded within functions with names like `<lambda ...>`.
Generic Arrays
--------------
Added in Ixseal. The old array types have been replaced with a new generic array type, with
the syntax `array<type>`. The newly added types 79 and 80 were added to support this feature
(type 79 is the regular array type, and type 80 is the reference-to-array type).
There also appears to be a `foreach` loop construct added which operates on the new arrays.
I believe the newly added type 82 is some kind of iterator type which is used in these loops.
The syntax is likely something like:
foreach (var : array) {
// do somthing with var
}
where `var` is a type 82.
Array methods are now implemented via the `Array` library rather than as dedicated bytecode
instructions.
Properties (Getters/Setters)
----------------------------
Added in Ixseal. Some struct/class members now have their names surrounded in angle brackets,
e.g. `<PropertyName>`. These members have get/set methods defined for them, with names like
`ClassName@PropertyName::get`/`ClassName@PropertyName::set`. Most likely these methods are
automatically generated.
Interfaces & vtables
--------------------
Added in Ixseal. If you look at the struct declarations, you will find empty structs with
names like `IParts`. Embedded in the struct data in the ain file there is a list of interfaces
for each struct, listing the implemented interfaces, along with indices into the struct's
vtable corresponding to the location of the methods for that interface.
All structures which implement an interface have an `array<int>` as their first member with
the name `<vtable>`. This is a table listing the virtual (interface) methods of the struct.
`CALLMETHOD` now takes the function index as a stack-passed argument, rather than a static
instruction argument. The instruction argument to `CALLMETHOD` is changed to be the number of
arguments that are passed to the method. The calling convention is as follows:
PUSH <struct page>
PUSH <function index>
PUSH <argument 0>
...
PUSH <argument n>
CALLMETHOD <n>
This new calling convention enables calling compile-time-indeterminate methods through the
vtable.
To support variables which represent an abstract *instance of an interface*, the new type 89
was added. This is a 2-valued type in which the first value is a struct page, and the second
value is an index into the struct's vtable corresponding to the location of the interface's
methods. Thus, methods can be called on interface objects as follows:
PUSH <page> ; [A] (A is page of type-89 variable)
PUSH <var> ; [A,B] (B is index of type-89 variable)
REFREF ; [C,D] (C is page of object; D is index into object's vtable)
DUP_U2 ; [C,D,C]
PUSH 0 ; [C,D,C,0]
REF ; [C,D,E] (E is the vtable of object C)
SWAP ; [C,E,D]
PUSH <n> ; [C,E,D,n]
ADD ; [C,E,D+n]
REF ; [C,F] (F is global index of method)
PUSH <arg 0> ; [C,F,a0] (a0 is the first argument)
...
PUSH <arg n> ; [C,F,a0,...,an] (an is the last argument)
CALLMETHOD <n> ; [stack now contains return value of method]
Generic Structures
------------------
Added in Rance X. This is purely a compile time feature. If you look at the struct names you
will find names like `MapPair<int, QuestMapPosition>`, where the type names inside the angled
brackets appear again in the struct members.
Function Overloading
--------------------
Added in Rance X. This is purely a compile time feature. If you look at the function
signatures you will notice duplicate names with differing argument lists. In fact, there are
even duplicate function names with *exactly the same argument list*. I'm not sure why.
Enums
-----
Added in Rance X. Enum types have the following automatically generated static methods:
* `string EnumType@String(EnumType value)`
* `EnumType EnumType::Parse(string value)`
* `EnumType EnumType::Parse(int value)`
* `array<EnumType> EnumType::GetList(void)`
* `bool EnumType::IsExist(int value)`
* `int EnumType::NumOf(void)`
There are 3 new types associated with enums. Types 92 and 93 are the regular enum and
reference-to-enum types, respectively. They store an index into the enum table in the struct
type field of their type descriptor.
Type 91 is only used in the return value from the EnumType::Parse methods. It's wrapped in an option
type (new type 86).
Option Types
------------
Added in Rance X. The new type 86 serves this function. Type 86 is a 2-valued type where the first
value is the wrapped value, and the second value indicates sucess/failure.
In Rance X, a value of -1 as the second value indicates failure, and a value of 0 indicates success.
In Evenicle 2 and later games, a value of 1 as the second value indicates failure.
Unclear
-------
Variables in the ain file now have two names. Usually these names are the same string, but
occasionally there are minor differences between them, and the differences don't reveal much
about what the purpose of the second name is.
File diff suppressed because it is too large Load Diff
View File
+5 -3
View File
@@ -15,12 +15,14 @@
*/
uniform sampler2D tex;
uniform vec4 color;
uniform vec4 color; // .a is the discard threshold
in vec2 tex_coord;
out vec4 frag_color;
void main() {
vec4 texel = texture(tex, tex_coord);
frag_color = vec4(color.rgb, texel.r * color.a);
vec4 texel = texture(tex, tex_coord);
if (texel.r < color.a)
discard;
frag_color = vec4(color.rgb, texel.r);
}
+25
View File
@@ -0,0 +1,25 @@
/* Copyright (C) 2023 kichikuou <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, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex;
in vec2 tex_coord;
out vec4 frag_color;
void main() {
float gray = dot(texture(tex, tex_coord).rgb, vec3(0.299, 0.587, 0.114));
frag_color = vec4(vec3(gray), 1);
}
+6 -3
View File
@@ -16,7 +16,7 @@
uniform sampler2D tex;
uniform float threshold;
uniform vec4 color;
uniform vec4 color; // .a is the discard threshold
in vec2 tex_coord;
out vec4 frag_color;
@@ -26,15 +26,16 @@ void main() {
// edge pixels
int size = int(floor(threshold));
int cutoff = size + 1;
float edge_weight = fract(threshold);
float edge_weight = fract(threshold) / 4.0;
vec2 tex_size = vec2(textureSize(tex, 0).xy);
float t = ceil(threshold);
float a_out = 0.0;
for (int x = -cutoff; x <= cutoff; x++) {
for (int y = -cutoff; y <= cutoff; y++) {
float d = distance(vec2(x, y), vec2(0, 0));
if (d > threshold)
if (d > t)
continue;
float a = texture(tex, tex_coord + vec2(x, y) / tex_size).r;
if (d > float(size))
@@ -43,5 +44,7 @@ void main() {
}
}
if (a_out < color.a)
discard;
frag_color = vec4(color.rgb, min(a_out, 1.0));
}
+17 -3
View File
@@ -15,7 +15,10 @@
*/
uniform sampler2D tex;
uniform sampler2D light_texture;
uniform bool use_lightmap;
uniform float alpha_mod;
uniform bool use_fog;
in float dist;
in vec2 tex_coord;
@@ -26,7 +29,18 @@ const vec3 FOG_COLOR = vec3(0.0, 0.0, 0.0);
void main() {
vec4 texel = texture(tex, tex_coord);
float fog_factor = (FOG_MAX_DIST - dist) / FOG_MAX_DIST;
fog_factor = clamp(fog_factor, 0.3, 1.0);
frag_color = vec4(mix(FOG_COLOR, vec3(texel), fog_factor), texel.a * alpha_mod);
float light_factor = 1.0;
if (use_lightmap) {
light_factor = texture(light_texture, tex_coord).a;
}
if (use_fog) {
float fog_factor = (FOG_MAX_DIST - dist) / FOG_MAX_DIST;
fog_factor = clamp(fog_factor, 0.3, 1.0);
frag_color = vec4(mix(FOG_COLOR, texel.rgb * light_factor, fog_factor), texel.a * alpha_mod);
} else {
if (texel.a < 0.01) {
discard;
}
frag_color = vec4(texel.rgb * light_factor, texel.a * alpha_mod);
}
}
+3 -1
View File
@@ -17,6 +17,8 @@
uniform mat4 local_transform;
uniform mat4 view_transform;
uniform mat4 proj_transform;
uniform vec2 uv_offset;
uniform vec2 uv_scale;
in vec3 vertex_pos;
in vec2 vertex_uv;
@@ -27,5 +29,5 @@ void main() {
vec4 pos = view_transform * local_transform * vec4(vertex_pos, 1.0);
dist = abs(pos.z);
gl_Position = proj_transform * pos;
tex_coord = vertex_uv;
tex_coord = vertex_uv * uv_scale + uv_offset;
}
+39
View File
@@ -0,0 +1,39 @@
/* Copyright (C) 2023 kichikuou <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, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex;
uniform float amp;
uniform float t;
const float PI = 3.14159265;
in vec2 tex_coord;
out vec4 frag_color;
vec4 sample_texture(float offset_x) {
return texture(tex, vec2(tex_coord.x + offset_x, tex_coord.y));
}
void main() {
float a = amp * cos((tex_coord.y + fract(t / 8.0)) * 16.0 * PI);
float p1 = 6.0 * tex_coord.y + 1.5;
float p2 = 4.0 * tex_coord.y + 2.0;
float offset1 = cos(p1 * 2.0 * PI) * a;
float offset2 = cos(p2 * 2.0 * PI) * a;
frag_color = mix(sample_texture(offset1), sample_texture(offset2), 0.5);
}
+1 -1
View File
@@ -50,7 +50,7 @@ void main() {
vec2 p = tex_coord * resolution;
vec2 p1 = resolution;
vec2 p2 = vec2((1.0 - progress*2.0) * p1.x, (1.0 - progress*2.0) * p1.y);
vec2 p2 = vec2((1.0 - max(progress, 0.01)*2.0) * p1.x, (1.0 - max(progress, 0.01)*2.0) * p1.y);
vec2 p3 = vec2(p2.x, resolution.y);
vec2 p4 = vec2(resolution.x, p2.y);
+39
View File
@@ -0,0 +1,39 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex; // the new scene
uniform sampler2D old; // the old scene
uniform vec2 resolution; // the screen resolution
uniform float progress; // effect progress (0..1)
in vec2 tex_coord;
out vec4 frag_color;
vec3 get_pixel(vec2 xy) {
return mix(texture(old, xy).rgb, texture(tex, xy).rgb, progress);
}
float triangle(float f) {
return 1.0 - abs(f * 2.0 - 1.0);
}
void main() {
vec2 p = tex_coord * resolution;
float scale = triangle(progress) * 80.0; // (0..0.5..1) -> (0..80..0);
float wobble = progress * 24.0;
float offset = sin((p.y / 100.0) - wobble) * scale;
frag_color = vec4(get_pixel(fract(vec2(p.x + offset, p.y) / resolution)), 1.0);
}
+2 -2
View File
@@ -15,7 +15,7 @@
*/
uniform sampler2D tex;
uniform float alpha_mod;
uniform float blend_rate;
uniform vec2 bot_left;
uniform vec2 top_right;
uniform vec3 add_color;
@@ -36,5 +36,5 @@ void main() {
vec4 tex_color = texture(tex, tex_coord);
vec3 mod_color = (tex_color.rgb + add_color) * multiply_color;
frag_color = vec4(mod_color, tex_color.a * alpha_mod) * point_in_rect(tex_coord, bl, tr);
frag_color = vec4(mod_color, tex_color.a * blend_rate) * point_in_rect(tex_coord, bl, tr);
}
+58 -30
View File
@@ -14,19 +14,21 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#define NR_DIR_LIGHTS 3
#define DIFFUSE_EMISSIVE 0
#define DIFFUSE_NORMAL 1
#define DIFFUSE_LIGHT_MAP 2
#define DIFFUSE_ENV_MAP 3
#define FOG_LINEAR 1
#define FOG_LIGHT_SCATTERING 2
#define ALPHA_BLEND 0
#define ALPHA_TEST 1
#define ALPHA_MAP_BLEND 2
#define ALPHA_MAP_TEST 3
#if ENGINE == REIGN_ENGINE
#define NR_DIR_LIGHTS 3
#define FOG_LINEAR 1
#define FOG_LIGHT_SCATTERING 2
struct dir_light {
vec3 dir;
@@ -34,8 +36,46 @@ struct dir_light {
vec3 globe_diffuse;
};
uniform sampler2D tex;
uniform sampler2D specular_texture;
uniform dir_light dir_lights[NR_DIR_LIGHTS];
uniform float specular_strength;
uniform float specular_shininess;
uniform bool use_specular_map;
uniform float rim_exponent;
uniform vec3 rim_color;
uniform int fog_type;
uniform float fog_near;
uniform float fog_far;
uniform vec3 fog_color;
in vec3 light_dir[NR_DIR_LIGHTS];
in vec3 specular_dir;
in vec3 ls_ex;
in vec3 ls_in;
uniform vec3 global_ambient;
vec3 dir_lights_diffuse(vec3 norm)
{
vec3 diffuse = vec3(0.0);
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5;
diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert);
}
return diffuse;
}
#else // ENGINE == REIGN_ENGINE
const vec3 global_ambient = vec3(0.0);
vec3 dir_lights_diffuse(vec3 norm)
{
return vec3(1.0);
}
#endif // ENGINE == REIGN_ENGINE
uniform sampler2D tex;
uniform sampler2D alpha_texture;
uniform sampler2D light_texture;
uniform sampler2D shadow_texture;
@@ -44,33 +84,20 @@ uniform float alpha_mod;
uniform bool use_normal_map;
uniform sampler2D normal_texture;
uniform vec3 ambient;
uniform dir_light dir_lights[NR_DIR_LIGHTS];
uniform float specular_strength;
uniform float specular_shininess;
uniform bool use_specular_map;
uniform float rim_exponent;
uniform vec3 rim_color;
uniform vec3 instance_ambient;
uniform int diffuse_type;
uniform float shadow_darkness;
uniform float shadow_bias;
uniform int fog_type;
uniform float fog_near;
uniform float fog_far;
uniform vec3 fog_color;
uniform int alpha_mode;
in vec2 tex_coord;
in vec2 light_tex_coord;
in vec4 color_mod;
in vec3 frag_pos;
in vec4 shadow_frag_pos;
in float dist;
in vec3 eye;
in vec3 normal;
in vec3 light_dir[NR_DIR_LIGHTS];
in vec3 specular_dir;
in vec3 ls_ex;
in vec3 ls_in;
out vec4 frag_color;
void main() {
@@ -83,7 +110,7 @@ void main() {
}
vec3 view_dir = normalize(eye - frag_pos);
vec3 frag_rgb = ambient;
vec3 frag_rgb = global_ambient + instance_ambient;
// Diffuse lighting
vec4 texel;
@@ -95,18 +122,15 @@ void main() {
if (diffuse_type == DIFFUSE_EMISSIVE) {
frag_rgb = texel.rgb;
} else {
vec3 diffuse = vec3(0.0);
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5;
diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert);
}
vec3 diffuse = dir_lights_diffuse(norm);
if (diffuse_type == DIFFUSE_LIGHT_MAP) {
diffuse *= texture(light_texture, light_tex_coord).rgb;
}
frag_rgb += texel.rgb * diffuse;
frag_rgb += texel.rgb * diffuse * color_mod.rgb;
}
}
#if ENGINE == REIGN_ENGINE
// Specular lighting
if (specular_strength > 0.0) {
vec3 reflect_dir = reflect(specular_dir, norm);
@@ -131,6 +155,7 @@ void main() {
} else if (fog_type == FOG_LIGHT_SCATTERING) {
frag_rgb = frag_rgb * ls_ex + ls_in;
}
#endif
// Shadow mapping
if (shadow_darkness > 0.0) {
@@ -150,12 +175,15 @@ void main() {
}
// Alpha mapping
float alpha = texel.a * alpha_mod;
float alpha = texel.a * color_mod.a * alpha_mod;
if (alpha_mode == ALPHA_TEST) {
if (alpha < 0.8)
if (alpha < 0.75)
discard;
} else if (alpha_mode == ALPHA_MAP_BLEND) {
alpha *= texture(alpha_texture, tex_coord).r;
} else if (alpha_mode == ALPHA_MAP_TEST) {
if (texture(alpha_texture, tex_coord).r < 0.1)
discard;
}
frag_color = vec4(frag_rgb, alpha);
+47 -24
View File
@@ -14,70 +14,89 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
const float PI = 3.14159265358979323846;
#if ENGINE == REIGN_ENGINE
#define NR_DIR_LIGHTS 3
#define FOG_LIGHT_SCATTERING 2
const float PI = 3.14159265358979323846;
struct dir_light {
vec3 dir;
vec3 diffuse;
vec3 globe_diffuse;
};
uniform mat4 local_transform;
uniform mat4 view_transform;
uniform mat4 proj_transform;
uniform mat3 normal_transform;
const int MAX_BONES = 211; // see 3d_internal.h
const int NR_WEIGHTS = 4;
uniform bool has_bones;
uniform mat4 bone_matrices[MAX_BONES];
uniform bool use_normal_map;
uniform vec3 camera_pos;
uniform dir_light dir_lights[NR_DIR_LIGHTS];
uniform vec3 specular_light_dir;
uniform mat4 shadow_transform;
uniform int fog_type;
uniform vec4 ls_params; // (beta_r, beta_m, g, distance)
uniform vec3 ls_light_dir;
uniform vec3 ls_light_color;
uniform vec3 ls_sun_color;
out vec3 light_dir[NR_DIR_LIGHTS];
out vec3 specular_dir;
out vec3 ls_ex;
out vec3 ls_in;
const vec2 uv_scroll = vec2(0.0);
#else // ENGINE == REIGN_ENGINE
uniform vec2 uv_scroll;
#endif // ENGINE == REIGN_ENGINE
uniform mat4 local_transform;
uniform mat4 view_transform;
uniform mat4 proj_transform;
uniform mat3 normal_transform;
const int MAX_BONES = 308; // see 3d_internal.h
const int NR_WEIGHTS = 4;
uniform bool has_bones;
layout(std140) uniform BoneTransforms {
mat3x4 bone_matrices[MAX_BONES]; // transposed
};
uniform bool use_normal_map;
uniform vec3 camera_pos;
uniform mat4 shadow_transform;
in vec3 vertex_pos;
in vec3 vertex_normal;
in vec2 vertex_uv;
in vec2 vertex_light_uv;
in vec4 vertex_color;
in vec4 vertex_tangent;
in ivec4 vertex_bone_index;
in vec4 vertex_bone_weight;
out vec2 tex_coord;
out vec2 light_tex_coord;
out vec4 color_mod;
out vec3 frag_pos;
out vec4 shadow_frag_pos;
out float dist;
out vec3 eye;
out vec3 normal;
out vec3 light_dir[NR_DIR_LIGHTS];
out vec3 specular_dir;
out vec3 ls_ex;
out vec3 ls_in;
void main() {
mat4 local_bone_transform = local_transform;
mat3 normal_bone_transform = normal_transform;
if (has_bones) {
mat4 bone_transform = mat4(0.f);
mat3x4 bone_transform = mat3x4(0.0);
for (int i = 0; i < NR_WEIGHTS; i++) {
if (vertex_bone_index[i] >= 0) {
bone_transform += bone_matrices[vertex_bone_index[i]] * vertex_bone_weight[i];
}
}
local_bone_transform *= bone_transform;
local_bone_transform *= mat4(
vec4(bone_transform[0].x, bone_transform[1].x, bone_transform[2].x, 0.0),
vec4(bone_transform[0].y, bone_transform[1].y, bone_transform[2].y, 0.0),
vec4(bone_transform[0].z, bone_transform[1].z, bone_transform[2].z, 0.0),
vec4(bone_transform[0].w, bone_transform[1].w, bone_transform[2].w, 1.0));
normal_bone_transform *= mat3(bone_transform);
}
@@ -95,8 +114,9 @@ void main() {
vec4 view_pos = view_transform * world_pos;
gl_Position = proj_transform * view_pos;
tex_coord = vertex_uv;
light_tex_coord = vertex_light_uv;
tex_coord = vertex_uv + uv_scroll;
light_tex_coord = vertex_light_uv + uv_scroll;
color_mod = vertex_color;
dist = -view_pos.z;
shadow_frag_pos = shadow_transform * world_pos;
@@ -104,6 +124,8 @@ void main() {
// otherwise.
frag_pos = TBN * vec3(world_pos);
eye = TBN * camera_pos;
#if ENGINE == REIGN_ENGINE
light_dir[0] = TBN * dir_lights[0].dir;
light_dir[1] = TBN * dir_lights[1].dir;
light_dir[2] = TBN * dir_lights[2].dir;
@@ -127,4 +149,5 @@ void main() {
ls_in = (phase_r * beta_r + phase_m * beta_m) / (beta_r + beta_m) * (1.0 - f_ex) * ls_sun_color;
ls_ex = ls_light_color * f_ex;
}
#endif
}
+22
View File
@@ -0,0 +1,22 @@
/* Copyright (C) 2024 kichikuou <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, see <http://gnu.org/licenses/>.
*/
uniform vec3 outline_color;
out vec4 frag_color;
void main() {
frag_color = vec4(outline_color, 1.0);
}
+57
View File
@@ -0,0 +1,57 @@
/* Copyright (C) 2024 kichikuou <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, see <http://gnu.org/licenses/>.
*/
uniform mat4 local_transform;
uniform mat4 view_transform;
uniform mat4 proj_transform;
uniform mat3 normal_transform;
const int MAX_BONES = 308; // see 3d_internal.h
const int NR_WEIGHTS = 4;
uniform bool has_bones;
layout(std140) uniform BoneTransforms {
mat3x4 bone_matrices[MAX_BONES]; // transposed
};
uniform float outline_thickness;
in vec3 vertex_pos;
in vec3 vertex_normal;
in ivec4 vertex_bone_index;
in vec4 vertex_bone_weight;
void main() {
mat4 local_bone_transform = local_transform;
mat3 normal_bone_transform = normal_transform;
if (has_bones) {
mat3x4 bone_transform = mat3x4(0.0);
for (int i = 0; i < NR_WEIGHTS; i++) {
if (vertex_bone_index[i] >= 0) {
bone_transform += bone_matrices[vertex_bone_index[i]] * vertex_bone_weight[i];
}
}
local_bone_transform *= mat4(
vec4(bone_transform[0].x, bone_transform[1].x, bone_transform[2].x, 0.0),
vec4(bone_transform[0].y, bone_transform[1].y, bone_transform[2].y, 0.0),
vec4(bone_transform[0].z, bone_transform[1].z, bone_transform[2].z, 0.0),
vec4(bone_transform[0].w, bone_transform[1].w, bone_transform[2].w, 1.0));
normal_bone_transform *= mat3(bone_transform);
}
vec4 pos = local_bone_transform * vec4(vertex_pos, 1.0);
vec3 normal = normalize(normal_bone_transform * vertex_normal);
gl_Position = proj_transform * view_transform * (pos + vec4(normal, 0.0) * outline_thickness);
}
+13 -7
View File
@@ -14,29 +14,35 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
uniform mat4 world_transform;
uniform mat4 local_transform;
uniform mat4 view_transform;
const int MAX_BONES = 211; // see 3d_internal.h
const int MAX_BONES = 308; // see 3d_internal.h
const int NR_WEIGHTS = 4;
uniform bool has_bones;
uniform mat4 bone_matrices[MAX_BONES];
layout(std140) uniform BoneTransforms {
mat3x4 bone_matrices[MAX_BONES]; // transposed
};
in vec3 vertex_pos;
in ivec4 vertex_bone_index;
in vec4 vertex_bone_weight;
void main() {
mat4 world_bone_transform = world_transform;
mat4 local_bone_transform = local_transform;
if (has_bones) {
mat4 bone_transform = mat4(0.f);
mat3x4 bone_transform = mat3x4(0.0);
for (int i = 0; i < NR_WEIGHTS; i++) {
if (vertex_bone_index[i] >= 0) {
bone_transform += bone_matrices[vertex_bone_index[i]] * vertex_bone_weight[i];
}
}
world_bone_transform *= bone_transform;
local_bone_transform *= mat4(
vec4(bone_transform[0].x, bone_transform[1].x, bone_transform[2].x, 0.0),
vec4(bone_transform[0].y, bone_transform[1].y, bone_transform[2].y, 0.0),
vec4(bone_transform[0].z, bone_transform[1].z, bone_transform[2].z, 0.0),
vec4(bone_transform[0].w, bone_transform[1].w, bone_transform[2].w, 1.0));
}
gl_Position = view_transform * world_bone_transform * vec4(vertex_pos, 1.0);
gl_Position = view_transform * local_bone_transform * vec4(vertex_pos, 1.0);
}
-2
View File
@@ -15,12 +15,10 @@
*/
uniform sampler2D tex;
uniform float alpha_mod;
in vec2 tex_coord;
out vec4 frag_color;
void main() {
frag_color = texture(tex, tex_coord);
frag_color.a *= alpha_mod;
}
+2 -1
View File
@@ -18,9 +18,10 @@ uniform mat4 world_transform;
uniform mat4 view_transform;
in vec4 vertex_pos;
in vec2 vertex_uv;
out vec2 tex_coord;
void main() {
gl_Position = view_transform * world_transform * vertex_pos;
tex_coord = vertex_pos.xy;
tex_coord = vertex_uv;
}
+28
View File
@@ -0,0 +1,28 @@
/* Copyright (C) 2023 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* 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, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex;
uniform float blend_rate;
uniform vec3 multiply_color;
in vec2 tex_coord;
out vec4 frag_color;
void main() {
frag_color = texture(tex, tex_coord);
frag_color.rgb *= multiply_color;
frag_color.a *= blend_rate;
}
+87 -14
View File
@@ -26,7 +26,7 @@
#include "reign.h"
#define NR_DIR_LIGHTS 3
#define MAX_BONES 211 // Maximum in TT3
#define MAX_BONES 308 // Maximum in Rance Quest
typedef struct vec3_range {
vec3 begin;
@@ -44,24 +44,32 @@ struct model {
struct hash_table *bone_map; // bone id in POL/MOT -> struct bone *
struct hash_table *bone_name_map; // bone name -> (struct bone * | NULL)
struct hash_table *mot_cache; // name -> struct mot *
struct collider *collider;
vec3 aabb[2]; // axis-aligned bounding box
bool has_transparent_material;
bool has_transparent_mesh;
};
struct mesh {
char *name;
uint32_t flags;
bool is_transparent;
bool hidden;
GLuint vao;
GLuint attr_buffer;
GLuint index_buffer;
int nr_vertices;
int nr_indices;
int material;
vec3 outline_color;
float outline_thickness;
vec2 uv_scroll;
};
struct material {
uint32_t flags;
bool is_transparent;
GLuint color_map;
int nr_color_maps;
GLuint *color_maps;
GLuint specular_map;
GLuint alpha_map;
GLuint light_map;
@@ -87,6 +95,7 @@ struct motion {
float current_frame;
float frame_begin, frame_end;
float loop_frame_begin, loop_frame_end;
int texture_index;
};
// Attribute variable locations
@@ -97,6 +106,7 @@ enum RE_attribute_location {
VATTR_BONE_INDEX,
VATTR_BONE_WEIGHT,
VATTR_LIGHT_UV,
VATTR_COLOR,
VATTR_TANGENT,
};
@@ -106,19 +116,33 @@ struct shadow_renderer {
GLuint texture;
// Uniform variable locations
GLint world_transform;
GLint local_transform;
GLint view_transform;
GLint has_bones;
GLint bone_matrices;
};
struct outline_renderer {
GLuint program;
// Uniform variable locations
GLint local_transform;
GLint view_transform;
GLint proj_transform;
GLint normal_transform;
GLint has_bones;
GLint outline_color;
GLint outline_thickness;
};
struct RE_renderer {
int viewport_width;
int viewport_height;
GLuint program;
GLuint depth_buffer;
struct shadow_renderer shadow;
struct outline_renderer outline;
// Uniform variable locations
GLint world_transform;
GLint view_transform;
GLint texture;
GLint local_transform;
@@ -126,8 +150,8 @@ struct RE_renderer {
GLint normal_transform;
GLint alpha_mod;
GLint has_bones;
GLint bone_matrices;
GLint ambient;
GLint global_ambient;
GLint instance_ambient;
struct {
GLint dir;
GLint diffuse;
@@ -160,10 +184,13 @@ struct RE_renderer {
GLint ls_sun_color;
GLint alpha_mode;
GLint alpha_texture;
GLint uv_scroll;
GLuint billboard_vao;
GLuint billboard_attr_buffer;
struct hash_table *billboard_textures; // cg_no -> struct billboard_texture*
uint32_t last_frame_timestamp;
};
struct billboard_texture {
@@ -173,6 +200,8 @@ struct billboard_texture {
// reign.c
extern enum RE_plugin_version re_plugin_version;
void RE_instance_update_local_transform(struct RE_instance *inst);
// model.c
@@ -188,8 +217,9 @@ struct archive_data *RE_get_aar_entry(struct archive *aar, const char *dir, cons
// renderer.c
struct RE_renderer *RE_renderer_new(struct texture *texture);
struct RE_renderer *RE_renderer_new(void);
void RE_renderer_free(struct RE_renderer *r);
void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height);
bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no);
struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r, struct model *model);
void RE_renderer_free_height_detector(struct height_detector *hd);
@@ -389,6 +419,7 @@ enum pol_texture_type {
enum material_flags {
MATERIAL_SPRITE = 1 << 0,
MATERIAL_ALPHA = 1 << 1,
};
struct pol_material {
@@ -404,11 +435,16 @@ struct pol_material_group {
};
enum mesh_flags {
MESH_NOLIGHTING = 1 << 0,
MESH_NOMAKESHADOW = 1 << 1,
MESH_ENVMAP = 1 << 2,
MESH_BOTH = 1 << 3,
MESH_SPRITE = 1 << 4,
MESH_NOLIGHTING = 1 << 0,
MESH_NOMAKESHADOW = 1 << 1,
MESH_ENVMAP = 1 << 2,
MESH_BOTH = 1 << 3,
MESH_SPRITE = 1 << 4,
MESH_BLEND_ADDITIVE = 1 << 5,
MESH_NO_EDGE = 1 << 6,
MESH_NO_HEIGHT_DETECTION = 1 << 7,
MESH_ALPHA = 1 << 8,
MESH_HAS_LIGHT_UV = 1 << 9,
};
struct pol_mesh {
@@ -421,8 +457,16 @@ struct pol_mesh {
vec2 *uvs;
uint32_t nr_light_uvs;
vec2 *light_uvs;
uint32_t nr_colors;
vec3 *colors;
uint32_t nr_alphas;
float *alphas;
uint32_t nr_triangles;
struct pol_triangle *triangles;
// Parameters in .opr file.
Color edge_color;
float edge_size;
vec2 uv_scroll;
};
struct pol_vertex {
@@ -440,6 +484,8 @@ struct pol_triangle {
uint32_t vert_index[3];
uint32_t uv_index[3];
uint32_t light_uv_index[3];
uint32_t color_index[3];
uint32_t alpha_index[3];
vec3 normals[3];
uint32_t material_group_index;
};
@@ -456,6 +502,8 @@ struct mot {
char *name;
uint32_t nr_frames;
uint32_t nr_bones;
uint32_t nr_texture_indices;
uint32_t *texture_indices; // texture index for each frame
struct mot_bone *motions[];
};
@@ -506,4 +554,29 @@ struct amt *amt_parse(uint8_t *data, size_t size);
void amt_free(struct amt *amt);
struct amt_material *amt_find_material(struct amt *amt, const char *name);
void opr_load(uint8_t *data, size_t size, struct pol *pol);
void txa_load(uint8_t *data, size_t size, struct mot *mot);
// collision.c
struct collider_triangle;
struct collider_edge;
struct collider {
struct collider_triangle *triangles;
uint32_t nr_triangles;
struct collider_edge *edges; // boundary edges
uint32_t nr_edges;
vec3 *path_points;
uint32_t nr_path_points;
};
struct collider *collider_create(struct pol_mesh *mesh);
void collider_free(struct collider *collider);
bool collider_height(struct collider *collider, vec2 xz, float *h_out);
bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius, vec2 out);
bool collider_raycast(struct collider *collider, vec3 origin, vec3 direction, vec3 out);
bool collider_find_path(struct collider *collider, vec3 start, vec3 goal, mat4 vp_transform);
bool collider_optimize_path(struct collider *collider);
#endif /* SYSTEM4_3D_3D_INTERNAL_H */
+509
View File
@@ -0,0 +1,509 @@
/* Copyright (C) 2024 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdlib.h>
#include <cglm/cglm.h>
#include "system4.h"
#include "3d_internal.h"
struct collider_triangle {
vec3 vertices[3];
vec3 center;
vec2 aabb[2]; // xz coordinates
vec2 slope;
float intercept;
int neighbors[3];
};
struct collider_edge {
vec2 vertices[2]; // xz coordinates
vec2 aabb[2];
};
static void init_triangles(struct collider *collider, struct pol_mesh *mesh)
{
collider->nr_triangles = mesh->nr_triangles;
collider->triangles = xcalloc(mesh->nr_triangles, sizeof(struct collider_triangle));
struct collider_triangle *t = collider->triangles;
for (int tri_i = 0; tri_i < mesh->nr_triangles; tri_i++, t++) {
glm_aabb2d_invalidate(t->aabb);
for (int i = 0; i < 3; i++) {
glm_vec3_copy(mesh->vertices[mesh->triangles[tri_i].vert_index[i]].pos, t->vertices[i]);
glm_vec3_add(t->center, t->vertices[i], t->center);
vec2 xz = { t->vertices[i][0], t->vertices[i][2] };
glm_vec2_minv(xz, t->aabb[0], t->aabb[0]);
glm_vec2_maxv(xz, t->aabb[1], t->aabb[1]);
}
glm_vec3_divs(t->center, 3.f, t->center);
// Do some precomputation so that we can calculate height(x, z) quickly.
vec3 v1, v2, normal;
glm_vec3_sub(t->vertices[1], t->vertices[0], v1);
glm_vec3_sub(t->vertices[2], t->vertices[0], v2);
glm_vec3_cross(v1, v2, normal);
t->slope[0] = -normal[0] / normal[1];
t->slope[1] = -normal[2] / normal[1];
t->intercept = glm_vec3_dot(normal, t->vertices[0]) / normal[1];
for (int i = 0; i < 3; i++) {
t->neighbors[i] = -1;
}
}
}
static void link_neighbors(struct collider *collider, int t1_index, int t2_index)
{
struct collider_triangle *t1 = &collider->triangles[t1_index];
struct collider_triangle *t2 = &collider->triangles[t2_index];
for (int i = 0; i < 3; i++) {
if (t1->neighbors[i] == -1) {
t1->neighbors[i] = t2_index;
break;
}
}
for (int i = 0; i < 3; i++) {
if (t2->neighbors[i] == -1) {
t2->neighbors[i] = t1_index;
break;
}
}
}
static void init_edges(struct collider *collider, struct pol_mesh *mesh)
{
// Link neighboring triangles.
if (mesh->nr_triangles >= 65535)
ERROR("Too many triangles in collision mesh: %d", mesh->nr_triangles);
const int nv = mesh->nr_vertices;
const int table_size = nv * nv;
uint16_t *table = xcalloc(table_size, sizeof(uint16_t));
int nr_edges = 0;
for (int i = 0; i < mesh->nr_triangles; i++) {
for (int j = 0; j < 3; j++) {
int v1 = mesh->triangles[i].vert_index[j];
int v2 = mesh->triangles[i].vert_index[(j + 1) % 3];
int k = v2 * nv + v1;
if (table[k]) {
link_neighbors(collider, table[k] - 1, i);
table[k] = 0;
nr_edges--;
} else {
table[v1 * nv + v2] = i + 1;
nr_edges++;
}
}
}
// Collect boundary edges, i.e., edges that belong to only one triangle.
collider->nr_edges = nr_edges;
collider->edges = xcalloc(nr_edges, sizeof(struct collider_edge));
struct collider_edge* edge = collider->edges;
for (int i = 0; i < table_size; i++) {
if (!table[i]) continue;
int v1 = i / nv;
int v2 = i % nv;
edge->vertices[0][0] = mesh->vertices[v1].pos[0];
edge->vertices[0][1] = mesh->vertices[v1].pos[2];
edge->vertices[1][0] = mesh->vertices[v2].pos[0];
edge->vertices[1][1] = mesh->vertices[v2].pos[2];
glm_vec2_minv(edge->vertices[0], edge->vertices[1], edge->aabb[0]);
glm_vec2_maxv(edge->vertices[0], edge->vertices[1], edge->aabb[1]);
edge++;
}
assert(edge == collider->edges + nr_edges);
free(table);
}
struct collider *collider_create(struct pol_mesh *mesh)
{
struct collider *collider = xcalloc(1, sizeof(struct collider));
init_triangles(collider, mesh);
init_edges(collider, mesh);
return collider;
}
void collider_free(struct collider *collider)
{
free(collider->triangles);
free(collider->edges);
free(collider->path_points);
free(collider);
}
static bool in_triangle(struct collider_triangle* t, vec2 xz)
{
vec2 v[3];
for (int i = 0; i < 3; i++) {
v[i][0] = t->vertices[i][0];
v[i][1] = t->vertices[i][2];
}
for (int i = 0; i < 3; i++) {
vec2 a, b;
glm_vec2_sub(v[(i + 1) % 3], v[i], a);
glm_vec2_sub(xz, v[i], b);
if (glm_vec2_cross(a, b) > 0.f)
return false;
}
return true;
}
static struct collider_triangle* find_triangle(struct collider *collider, vec2 xz)
{
struct collider_triangle* t = collider->triangles;
for (; t < collider->triangles + collider->nr_triangles; t++) {
if (!glm_aabb2d_point(t->aabb, xz))
continue;
if (in_triangle(t, xz))
return t;
}
return NULL;
}
bool collider_height(struct collider *collider, vec2 xz, float *h_out)
{
struct collider_triangle* t = find_triangle(collider, xz);
if (!t)
return false;
*h_out = glm_vec2_dot(t->slope, xz) + t->intercept;
return true;
}
// Calculates the squared distance from a point to a line segment in 2D space.
static float distance2_point_to_segment(vec2 p, vec2 a, vec2 b, vec2 closest_point_out)
{
vec2 v, w;
glm_vec2_sub(b, a, v);
glm_vec2_sub(p, a, w);
float c1 = glm_vec2_dot(w, v);
if (c1 < 0.f) {
glm_vec2_copy(a, closest_point_out);
return glm_vec2_distance2(p, a);
}
float c2 = glm_vec2_norm2(v);
if (c2 <= c1) {
glm_vec2_copy(b, closest_point_out);
return glm_vec2_distance2(p, b);
}
float t = c1 / c2;
glm_vec2_copy(a, closest_point_out);
glm_vec2_muladds(v, t, closest_point_out);
return glm_vec2_distance2(p, closest_point_out);
}
static bool segments_intersect(vec2 p0, vec2 p1, vec2 q0, vec2 q1)
{
vec2 r, s;
glm_vec2_sub(p1, p0, r);
glm_vec2_sub(q1, q0, s);
float r_cross_s = glm_vec2_cross(r, s);
if (r_cross_s == 0.f) {
// The segments are collinear.
return false;
}
vec2 q0_p0;
glm_vec2_sub(q0, p0, q0_p0);
float t = glm_vec2_cross(q0_p0, s) / r_cross_s;
float u = glm_vec2_cross(q0_p0, r) / r_cross_s;
return (t >= 0.f && t <= 1.f && u >= 0.f && u <= 1.f);
}
bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius, vec2 out)
{
if (find_triangle(collider, p1)) {
glm_vec2_copy(p1, out);
} else {
if (!find_triangle(collider, p0))
return false;
// Find a point on the "collision" mesh, using binary search.
vec2 good, bad;
glm_vec2_copy(p0, good);
glm_vec2_copy(p1, bad);
float w = glm_vec2_distance(good, bad);
while (w > radius) {
vec2 mid;
glm_vec2_center(good, bad, mid);
if (find_triangle(collider, mid)) {
glm_vec2_copy(mid, good);
} else {
glm_vec2_copy(mid, bad);
}
w /= 2.f;
}
glm_vec2_copy(good, out);
}
// Ensure that `out` is at least `radius` away from any boundary edge.
vec2 aabb[2];
glm_vec2_subs(out, radius, aabb[0]);
glm_vec2_adds(out, radius, aabb[1]);
for (int i = 0; i < collider->nr_edges; i++) {
struct collider_edge *e = &collider->edges[i];
if (!glm_aabb2d_aabb(e->aabb, aabb))
continue;
vec2 q;
float sq_distance = distance2_point_to_segment(out, e->vertices[0], e->vertices[1], q);
if (sq_distance < radius * radius) {
// out = q + normalize(out - q) * radius
vec2 v;
glm_vec2_sub(out, q, v);
glm_vec2_normalize(v);
glm_vec2_copy(q, out);
glm_vec2_muladds(v, radius, out);
// update AABB
glm_vec2_subs(out, radius, aabb[0]);
glm_vec2_adds(out, radius, aabb[1]);
}
}
return true;
}
bool collider_raycast(struct collider *collider, vec3 origin, vec3 direction, vec3 out)
{
struct collider_triangle* t = collider->triangles;
for (; t < collider->triangles + collider->nr_triangles; t++) {
float d;
if (glm_ray_triangle(origin, direction, t->vertices[0], t->vertices[1], t->vertices[2], &d)) {
glm_vec3_scale(direction, d, out);
glm_vec3_add(origin, out, out);
return true;
}
}
return false;
}
struct pathfinder_node {
int pred;
float g_score;
enum { UNDISCOVERED, IN_FRONTIER, VISITED, NOT_VISIBLE } state;
};
struct frontier {
int triangle_index;
float f_score;
};
struct pathfinder {
struct pathfinder_node *nodes; // indexed by triangle index
struct frontier *frontiers; // min-heap
int nr_frontiers;
};
static int frontier_pop(struct pathfinder *pf)
{
if (pf->nr_frontiers == 0) {
return -1;
}
int index = pf->frontiers[0].triangle_index;
if (--pf->nr_frontiers > 0) {
pf->frontiers[0] = pf->frontiers[pf->nr_frontiers];
int i = 0;
while (i < pf->nr_frontiers) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int smallest = i;
if (left < pf->nr_frontiers && pf->frontiers[left].f_score < pf->frontiers[smallest].f_score) {
smallest = left;
}
if (right < pf->nr_frontiers && pf->frontiers[right].f_score < pf->frontiers[smallest].f_score) {
smallest = right;
}
if (smallest == i) {
break;
}
struct frontier tmp = pf->frontiers[i];
pf->frontiers[i] = pf->frontiers[smallest];
pf->frontiers[smallest] = tmp;
i = smallest;
}
}
return index;
}
static void frontier_push(struct pathfinder *pf, int index, float f_score)
{
pf->frontiers[pf->nr_frontiers++] = (struct frontier){ index, f_score };
int i = pf->nr_frontiers - 1;
while (i > 0) {
int parent = (i - 1) / 2;
if (pf->frontiers[i].f_score >= pf->frontiers[parent].f_score) {
break;
}
struct frontier tmp = pf->frontiers[i];
pf->frontiers[i] = pf->frontiers[parent];
pf->frontiers[parent] = tmp;
i = parent;
}
}
static bool is_point_visible(vec3 point, mat4 vp_transform)
{
vec4 p = { point[0], point[1], point[2], 1.f };
glm_mat4_mulv(vp_transform, p, p);
// Check if the point is in the view frustum.
return p[0] >= -p[3] && p[0] <= p[3] &&
p[1] >= -p[3] && p[1] <= p[3] &&
p[2] >= -p[3] && p[2] <= p[3];
}
bool collider_find_path(struct collider *collider, vec3 start, vec3 goal, mat4 vp_transform)
{
if (collider->path_points) {
collider->nr_path_points = 0;
free(collider->path_points);
collider->path_points = NULL;
}
// A* search for a graph with triangle centers as nodes and neighbors as edges.
struct collider_triangle* start_t = find_triangle(collider, (vec2){ start[0], start[2] });
if (!start_t || !is_point_visible(start_t->center, vp_transform))
return false;
struct collider_triangle* goal_t = find_triangle(collider, (vec2){ goal[0], goal[2] });
if (!goal_t || !is_point_visible(goal_t->center, vp_transform))
return false;
int start_i = start_t - collider->triangles;
int goal_i = goal_t - collider->triangles;
struct pathfinder pf;
pf.nodes = xcalloc(collider->nr_triangles, sizeof(struct pathfinder_node));
pf.nodes[goal_i].pred = -2;
pf.nodes[start_i].pred = -1;
pf.nodes[start_i].g_score = 0.f;
pf.nodes[start_i].state = IN_FRONTIER;
pf.frontiers = xcalloc(collider->nr_triangles, sizeof(struct frontier));
pf.nr_frontiers = 0;
frontier_push(&pf, start_i, glm_vec3_distance(start_t->center, goal_t->center));
while (pf.nr_frontiers > 0) {
int current_i = frontier_pop(&pf);
if (pf.nodes[current_i].state == VISITED)
continue;
struct collider_triangle *current_t = &collider->triangles[current_i];
if (current_i == goal_i)
break;
pf.nodes[current_i].state = VISITED;
for (int j = 0; j < 3; j++) {
int neighbor_i = current_t->neighbors[j];
if (neighbor_i == -1)
continue;
struct collider_triangle *neighbor_t = &collider->triangles[neighbor_i];
struct pathfinder_node *neighbor_n = &pf.nodes[neighbor_i];
if (neighbor_n->state == NOT_VISIBLE)
continue;
if (neighbor_n->state == UNDISCOVERED && !is_point_visible(neighbor_t->center, vp_transform)) {
neighbor_n->state = NOT_VISIBLE;
continue;
}
float g = pf.nodes[current_i].g_score + glm_vec3_distance(current_t->center, neighbor_t->center);
if (neighbor_n->state == UNDISCOVERED || g < neighbor_n->g_score) {
neighbor_n->pred = current_i;
neighbor_n->g_score = g;
neighbor_n->state = IN_FRONTIER;
float f_score = g + glm_vec3_distance(neighbor_t->center, goal_t->center);
frontier_push(&pf, neighbor_i, f_score);
}
}
}
if (pf.nodes[goal_i].pred != -2) {
// Reconstruct the path.
int path_length = 0;
int current_i = goal_i;
while (current_i != -1) {
path_length++;
current_i = pf.nodes[current_i].pred;
}
collider->nr_path_points = path_length + 2;
collider->path_points = xcalloc(path_length + 2, sizeof(vec3));
glm_vec3_copy(start, collider->path_points[0]);
glm_vec3_copy(goal, collider->path_points[path_length + 1]);
current_i = goal_i;
for (int i = path_length - 1; i >= 0; i--) {
glm_vec3_copy(collider->triangles[current_i].center, collider->path_points[i + 1]);
current_i = pf.nodes[current_i].pred;
}
}
free(pf.nodes);
free(pf.frontiers);
return !!collider->path_points;
}
// determines if a segment intersects with any collider edges, or is close enough to them.
static bool test_segment(struct collider *collider, vec2 p0, vec2 p1, float threshold)
{
vec2 aabb[2];
glm_vec2_minv(p0, p1, aabb[0]);
glm_vec2_maxv(p0, p1, aabb[1]);
aabb[0][0] -= threshold;
aabb[0][1] -= threshold;
aabb[1][0] += threshold;
aabb[1][1] += threshold;
float threshold2 = threshold * threshold;
for (int i = 0; i < collider->nr_edges; i++) {
struct collider_edge *e = &collider->edges[i];
if (!glm_aabb2d_aabb(e->aabb, aabb))
continue;
if (segments_intersect(p0, p1, e->vertices[0], e->vertices[1]))
return true;
vec2 closest_point;
float distance2 = distance2_point_to_segment(p0, e->vertices[0], e->vertices[1], closest_point);
if (distance2 < threshold2)
return true;
distance2 = distance2_point_to_segment(p1, e->vertices[0], e->vertices[1], closest_point);
if (distance2 < threshold2)
return true;
distance2 = distance2_point_to_segment(p0, p1, e->vertices[0], closest_point);
if (distance2 < threshold2)
return true;
distance2 = distance2_point_to_segment(p0, p1, e->vertices[1], closest_point);
if (distance2 < threshold2)
return true;
}
return false;
}
static void optimize_path_rec(struct collider *collider, vec3 *points, int start, int end)
{
if (end - start >= 2) {
// If the segment (points[start], points[end]) does not intersect any
// collider edges, we can skip all points in between.
vec2 p0 = { points[start][0], points[start][2] };
vec2 p1 = { points[end][0], points[end][2] };
if (test_segment(collider, p0, p1, 0.5f)) {
int mid = (start + end) / 2;
optimize_path_rec(collider, points, start, mid);
optimize_path_rec(collider, points, mid, end);
return;
}
}
glm_vec3_copy(points[start], collider->path_points[collider->nr_path_points++]);
}
bool collider_optimize_path(struct collider *collider)
{
if (collider->nr_path_points < 3)
return collider->nr_path_points > 0; // nothing to optimize
int nr_points = collider->nr_path_points;
vec3 *points = xmalloc(nr_points * sizeof(vec3));
memcpy(points, collider->path_points, nr_points * sizeof(vec3));
collider->nr_path_points = 0;
optimize_path_rec(collider, points, 0, nr_points - 1);
glm_vec3_copy(points[nr_points - 1], collider->path_points[collider->nr_path_points++]);
free(points);
return true;
}
+109 -72
View File
@@ -17,6 +17,8 @@
#include "system4/string.h"
#include "3d_internal.h"
#include "cJSON.h"
#include "json.h"
#include "reign.h"
#include "sprite.h"
#include "xsystem4.h"
@@ -33,6 +35,7 @@ static const char *instance_type_name(enum RE_instance_type type)
case RE_ITYPE_DIRECTIONAL_LIGHT: return "DIRECTIONAL_LIGHT";
case RE_ITYPE_SPECULAR_LIGHT: return "SPECULAR_LIGHT";
case RE_ITYPE_PARTICLE_EFFECT: return "PARTICLE_EFFECT";
case RE_ITYPE_PATH_LINE: return "PATH_LINE";
}
return "UNKNOWN";
}
@@ -57,107 +60,141 @@ static const char *fog_type_name(enum RE_fog_type type)
return "UNKNOWN";
}
static void print_motion(const char *name, struct motion *m, int indent)
static cJSON *motion_to_json(struct motion *m)
{
if (m->instance->type == RE_ITYPE_BILLBOARD) {
indent_printf(indent, "%s = {state=%s, frame=%f, range=(%d,%d), loop_range=(%d,%d)},\n",
name, motion_state_name(m->state), m->current_frame,
(int)m->frame_begin, (int)m->frame_end, (int)m->loop_frame_begin, (int)m->loop_frame_end);
} else if (m->mot) {
indent_printf(indent, "%s = {name=\"%s\", state=%s, frame=%f},\n",
name, m->mot->name, motion_state_name(m->state), m->current_frame);
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "state", motion_state_name(m->state));
cJSON_AddNumberToObject(obj, "frame", m->current_frame);
cJSON_AddItemToObjectCS(obj, "range", num2_to_json(m->frame_begin, m->frame_end));
cJSON_AddItemToObjectCS(obj, "loop_range", num2_to_json(m->loop_frame_begin, m->loop_frame_end));
return obj;
}
if (m->mot) {
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "name", m->mot->name);
cJSON_AddStringToObject(obj, "state", motion_state_name(m->state));
cJSON_AddNumberToObject(obj, "frame", m->current_frame);
return obj;
}
return cJSON_CreateNull();
}
static void print_instance(struct RE_instance *inst, int index, int indent)
static cJSON *instance_to_json(struct RE_instance *inst, int index, bool verbose)
{
if (!inst)
return;
indent_printf(indent, "instance[%d] = {\n", index);
indent++;
indent_printf(indent, "type = %s,\n", instance_type_name(inst->type));
cJSON *obj = cJSON_CreateObject();
cJSON_AddNumberToObject(obj, "index", index);
cJSON_AddStringToObject(obj, "type", instance_type_name(inst->type));
if (inst->type == RE_ITYPE_DIRECTIONAL_LIGHT || inst->type == RE_ITYPE_SPECULAR_LIGHT) {
indent_printf(indent, "vec = {x=%f, y=%f, z=%f},\n", SPREAD_VEC3(inst->vec));
indent_printf(indent, "diffuse = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(inst->diffuse));
indent_printf(indent, "globe_diffuse = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(inst->globe_diffuse));
cJSON_AddItemToObjectCS(obj, "vec", vec3_point_to_json(inst->vec, verbose));
cJSON_AddItemToObjectCS(obj, "diffuse", vec3_color_to_json(inst->diffuse, verbose));
cJSON_AddItemToObjectCS(obj, "globe_diffuse", vec3_color_to_json(inst->globe_diffuse, verbose));
} else {
indent_printf(indent, "draw = %d,\n", inst->draw);
indent_printf(indent, "pos = {x=%f, y=%f, z=%f},\n", SPREAD_VEC3(inst->pos));
indent_printf(indent, "scale = {x=%f, y=%f, z=%f},\n", SPREAD_VEC3(inst->scale));
indent_printf(indent, "ambient = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(inst->ambient));
cJSON_AddNumberToObject(obj, "draw", inst->draw);
cJSON_AddItemToObjectCS(obj, "pos", vec3_point_to_json(inst->pos, verbose));
cJSON_AddItemToObjectCS(obj, "scale", vec3_point_to_json(inst->scale, verbose));
cJSON_AddItemToObjectCS(obj, "ambient", vec3_color_to_json(inst->ambient, verbose));
}
if (inst->model) {
indent_printf(indent, "path = \"%s\",\n", inst->model->path);
indent_printf(indent, "aabb = {min = {x=%f, y=%f, z=%f}, max = {x=%f, y=%f, z=%f}},\n",
SPREAD_VEC3(inst->model->aabb[0]), SPREAD_VEC3(inst->model->aabb[1]));
cJSON *tmp;
cJSON_AddStringToObject(obj, "path", inst->model->path);
cJSON_AddItemToObjectCS(obj, "aabb", tmp = cJSON_CreateObject());
cJSON_AddItemToObjectCS(tmp, "min", vec3_point_to_json(inst->model->aabb[0], verbose));
cJSON_AddItemToObjectCS(tmp, "max", vec3_point_to_json(inst->model->aabb[1], verbose));
}
if (inst->motion) {
indent_printf(indent, "fps = %f,\n", inst->fps);
print_motion("motion", inst->motion, indent);
cJSON_AddNumberToObject(obj, "fps", inst->fps);
cJSON_AddItemToObjectCS(obj, "motion", motion_to_json(inst->motion));
}
if (inst->next_motion) {
cJSON_AddItemToObjectCS(obj, "next_motion", motion_to_json(inst->next_motion));
}
if (inst->motion_blend) {
cJSON_AddNumberToObject(obj, "motion_blend_rate", inst->motion_blend_rate);
}
if (inst->effect) {
cJSON_AddStringToObject(obj, "path", inst->effect->pae->path);
}
if (inst->next_motion)
print_motion("next_motion", inst->next_motion, indent);
if (inst->motion_blend)
indent_printf(indent, "motion_blend_rate = %f,\n", inst->motion_blend_rate);
if (inst->effect)
indent_printf(indent, "path = \"%s\",\n", inst->effect->pae->path);
indent--;
indent_printf(indent, "},\n");
return obj;
}
static void print_back_cg(struct RE_back_cg *bcg, int index, int indent)
static cJSON *back_cg_to_json(struct RE_back_cg *bcg, int index, bool verbose)
{
if (!bcg->name && !bcg->no)
return;
indent_printf(indent, "backCG[%d] = {\n", index);
indent++;
if (bcg->no)
indent_printf(indent, "no = %d,\n", bcg->no);
if (bcg->name)
indent_printf(indent, "name = \"%s\",\n", bcg->name ? bcg->name->text : "");
indent_printf(indent, "pos = {x=%f, y=%f},\n", bcg->x, bcg->y);
indent_printf(indent, "blend_rate = %f,\n", bcg->blend_rate);
indent_printf(indent, "mag = %f,\n", bcg->mag);
indent_printf(indent, "show = %d,\n", bcg->show);
indent--;
indent_printf(indent, "},\n");
cJSON *obj = cJSON_CreateObject();
cJSON_AddNumberToObject(obj, "index", index);
if (bcg->no) {
cJSON_AddNumberToObject(obj, "no", bcg->no);
}
if (bcg->name) {
cJSON_AddStringToObject(obj, "name", bcg->name->text);
}
if (!verbose) {
cJSON_AddItemToObjectCS(obj, "pos", num2_to_json(bcg->x, bcg->y));
} else {
cJSON *pos;
cJSON_AddItemToObjectCS(obj, "pos", pos = cJSON_CreateObject());
cJSON_AddNumberToObject(pos, "x", bcg->x);
cJSON_AddNumberToObject(pos, "y", bcg->y);
}
cJSON_AddNumberToObject(obj, "blend_rate", bcg->blend_rate);
cJSON_AddNumberToObject(obj, "mag", bcg->mag);
cJSON_AddNumberToObject(obj, "show", bcg->show);
return obj;
}
void RE_debug_print(struct sact_sprite *sp, int indent)
cJSON *RE_to_json(struct sact_sprite *sp, bool verbose)
{
struct RE_plugin *p = (struct RE_plugin *)sp->plugin;
struct RE_plugin *p = (struct RE_plugin*)sp->plugin;
cJSON *obj, *cam, *a;
indent_printf(indent, "name = \"%s\",\n", p->plugin.name);
indent_printf(indent, "camera = {x=%f, y=%f, z=%f, pitch=%f, roll=%f, yaw=%f},\n",
SPREAD_VEC3(p->camera.pos), p->camera.pitch, p->camera.roll, p->camera.yaw);
indent_printf(indent, "shadow_map_light_dir = {x=%f, y=%f, z=%f},\n", SPREAD_VEC3(p->shadow_map_light_dir));
indent_printf(indent, "shadow_bias = %f,\n", p->shadow_bias);
indent_printf(indent, "fog_type = %s,\n", fog_type_name(p->fog_type));
obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "name", p->plugin.name);
cJSON_AddItemToObjectCS(obj, "camera", cam = cJSON_CreateObject());
cJSON_AddItemToObjectCS(cam, "pos", vec3_point_to_json(p->camera.pos, verbose));
cJSON_AddNumberToObject(cam, "pitch", p->camera.pitch);
cJSON_AddNumberToObject(cam, "roll", p->camera.roll);
cJSON_AddNumberToObject(cam, "yaw", p->camera.yaw);
cJSON_AddItemToObjectCS(obj, "shadow_map_light_dir",
vec3_point_to_json(p->shadow_map_light_dir, verbose));
cJSON_AddNumberToObject(obj, "shadow_bias", p->shadow_bias);
cJSON_AddStringToObject(obj, "fog_type", fog_type_name(p->fog_type));
switch (p->fog_type) {
case RE_FOG_NONE:
break;
case RE_FOG_LINEAR:
indent_printf(indent, "fog_near = %f, fog_far = %f,\n", p->fog_near, p->fog_far);
indent_printf(indent, "fog_color = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(p->fog_color));
cJSON_AddNumberToObject(obj, "fog_near", p->fog_near);
cJSON_AddNumberToObject(obj, "fog_far", p->fog_far);
break;
case RE_FOG_LIGHT_SCATTERING:
indent_printf(indent, "ls_beta_r = %f, ls_beta_m = %f,\n", p->ls_beta_r, p->ls_beta_m);
indent_printf(indent, "ls_g = %f,\n", p->ls_g);
indent_printf(indent, "ls_distance = %f,\n", p->ls_distance);
indent_printf(indent, "ls_light_dir = {x=%f, y=%f, z=%f},\n", SPREAD_VEC3(p->ls_light_dir));
indent_printf(indent, "ls_light_color = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(p->ls_light_color));
indent_printf(indent, "ls_sun_color = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(p->ls_sun_color));
cJSON_AddNumberToObject(obj, "ls_beta_r", p->ls_beta_r);
cJSON_AddNumberToObject(obj, "ls_beta_m", p->ls_beta_m);
cJSON_AddNumberToObject(obj, "ls_g", p->ls_g);
cJSON_AddNumberToObject(obj, "ls_distance", p->ls_distance);
cJSON_AddItemToObjectCS(obj, "ls_light_dir",
vec3_point_to_json(p->ls_light_dir, verbose));
cJSON_AddItemToObjectCS(obj, "ls_light_color",
vec3_color_to_json(p->ls_light_color, verbose));
cJSON_AddItemToObjectCS(obj, "ls_sun_color",
vec3_color_to_json(p->ls_sun_color, verbose));
break;
}
for (int i = 0; i < p->nr_instances; i++)
print_instance(p->instances[i], i, indent);
cJSON_AddItemToObjectCS(obj, "instances", a = cJSON_CreateArray());
for (int i = 0; i < p->nr_instances; i++) {
if (!p->instances[i])
continue;
cJSON_AddItemToArray(a, instance_to_json(p->instances[i], i, verbose));
}
for (int i = 0; i < RE_NR_BACK_CGS; i++)
print_back_cg(&p->back_cg[i], i, indent);
cJSON_AddItemToObjectCS(obj, "back_cgs", a = cJSON_CreateArray());
for (int i = 0; i < RE_NR_BACK_CGS; i++) {
struct RE_back_cg *bcg = &p->back_cg[i];
if (!bcg->name && !bcg->no)
continue;
cJSON_AddItemToArray(a, back_cg_to_json(bcg, i, verbose));
}
return obj;
}
+131 -23
View File
@@ -29,6 +29,7 @@
#define FP16_MIN 6.103516e-5f
#define NR_WEIGHTS 4
#define DEFAULT_OUTLINE_THICKNESS 0.003
struct vertex_common {
GLfloat pos[3];
@@ -40,6 +41,10 @@ struct vertex_light_uv {
GLfloat uv[2];
};
struct vertex_color {
GLfloat color[4];
};
struct vertex_tangent {
GLfloat tangent[4];
};
@@ -49,6 +54,22 @@ struct vertex_bones {
GLfloat bone_weight[NR_WEIGHTS];
};
static bool is_transparent_mesh(const struct pol_mesh *mesh)
{
if (re_plugin_version == RE_REIGN_PLUGIN)
return !(mesh->flags & MESH_SPRITE);
else
return mesh->flags & MESH_ALPHA;
}
static bool is_transparent_material(const struct pol_material *material)
{
if (re_plugin_version == RE_REIGN_PLUGIN)
return !(material->flags & MATERIAL_SPRITE);
else
return material->flags & MATERIAL_ALPHA;
}
struct archive_data *RE_get_aar_entry(struct archive *aar, const char *dir, const char *name, const char *ext)
{
char *path = xmalloc(strlen(dir) + strlen(name) + strlen(ext) + 2);
@@ -62,7 +83,6 @@ static GLuint load_texture(struct archive *aar, const char *path, const char *na
{
struct archive_data *dfile = RE_get_aar_entry(aar, path, name, "");
if (!dfile) {
WARNING("cannot load texture %s\\%s", path, name);
return 0;
}
struct cg *cg = cg_load_data(dfile);
@@ -89,6 +109,35 @@ static GLuint load_texture(struct archive *aar, const char *path, const char *na
return texture;
}
static GLuint *load_texture_list(struct archive *aar, const char *path, const char *name, int *nr_textures_out, bool *has_alpha_out)
{
// Load the base texture (e.g. face.png)
GLuint tex = load_texture(aar, path, name, has_alpha_out);
if (!tex)
return NULL;
GLuint *textures = xmalloc(10 * sizeof(GLuint));
int nr_textures = 0;
textures[nr_textures++] = tex;
// Try to load additional textures (e.g. face[1].png, face[2].png, etc.)
const char *ext = strrchr(name, '.');
if (ext) {
for (;;) {
char next_name[100];
snprintf(next_name, sizeof(next_name), "%.*s[%d]%s", (int)(ext - name), name, nr_textures, ext);
tex = load_texture(aar, path, next_name, NULL);
if (!tex)
break;
textures[nr_textures++] = tex;
if (nr_textures % 10 == 0) {
textures = xrealloc(textures, (nr_textures + 10) * sizeof(GLuint));
}
}
}
*nr_textures_out = nr_textures;
return textures;
}
static bool init_material(struct material *material, const struct pol_material *m, struct amt *amt, struct archive *aar, const char *path)
{
material->flags = m->flags;
@@ -97,20 +146,25 @@ static bool init_material(struct material *material, const struct pol_material *
return false;
}
bool has_alpha;
material->color_map = load_texture(aar, path, m->textures[COLOR_MAP], &has_alpha);
if (!material->color_map)
material->color_maps = load_texture_list(aar, path, m->textures[COLOR_MAP], &material->nr_color_maps, &has_alpha);
if (!material->color_maps)
return false;
if (m->textures[SPECULAR_MAP])
material->specular_map = load_texture(aar, path, m->textures[SPECULAR_MAP], NULL);
if (m->textures[ALPHA_MAP])
material->alpha_map = load_texture(aar, path, m->textures[ALPHA_MAP], NULL);
if (m->textures[ALPHA_MAP]) {
if (has_alpha && !strcmp(m->textures[COLOR_MAP], m->textures[ALPHA_MAP])) {
// Do nothing; the alpha channel of the color map is used.
} else {
material->alpha_map = load_texture(aar, path, m->textures[ALPHA_MAP], NULL);
}
}
if (m->textures[LIGHT_MAP])
material->light_map = load_texture(aar, path, m->textures[LIGHT_MAP], NULL);
if (m->textures[NORMAL_MAP])
material->normal_map = load_texture(aar, path, m->textures[NORMAL_MAP], NULL);
material->is_transparent = (has_alpha || material->alpha_map) && !(material->flags & MATERIAL_SPRITE);
material->is_transparent = (has_alpha || material->alpha_map) && is_transparent_material(m);
material->shadow_darkness = 1.0f;
struct amt_material *amt_m = amt ? amt_find_material(amt, m->name) : NULL;
@@ -136,8 +190,10 @@ static bool init_material(struct material *material, const struct pol_material *
static void destroy_material(struct material *material)
{
if (material->color_map)
glDeleteTextures(1, &material->color_map);
if (material->color_maps) {
glDeleteTextures(material->nr_color_maps, material->color_maps);
free(material->color_maps);
}
if (material->specular_map)
glDeleteTextures(1, &material->specular_map);
if (material->alpha_map)
@@ -217,12 +273,15 @@ static void *buf_alloc(uint8_t **ptr, int size)
static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_group_index, int material)
{
bool has_light_map = m->light_uvs && model->materials[material].light_map;
bool has_vertex_colors = m->nr_colors > 0 || m->nr_alphas > 0;
bool has_normal_map = model->materials[material].normal_map != 0;
bool has_bones = !!model->bone_map;
GLsizei stride = sizeof(struct vertex_common);
if (has_light_map)
stride += sizeof(struct vertex_light_uv);
if (has_vertex_colors)
stride += sizeof(struct vertex_color);
if (has_normal_map)
stride += sizeof(struct vertex_tangent);
if (has_bones)
@@ -249,6 +308,14 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
struct vertex_light_uv *v_light_uv = buf_alloc(&ptr, sizeof(struct vertex_light_uv));
glm_vec2_copy(m->light_uvs[t->light_uv_index[j]], v_light_uv->uv);
}
if (has_vertex_colors) {
struct vertex_color *v_color = buf_alloc(&ptr, sizeof(struct vertex_color));
if (m->nr_colors > 0)
glm_vec3_copy(m->colors[t->color_index[j]], v_color->color);
else
glm_vec3_one(v_color->color);
v_color->color[3] = m->nr_alphas > 0 ? m->alphas[t->alpha_index[j]] : 1.f;
}
if (has_normal_map) {
struct vertex_tangent *v_tangent = buf_alloc(&ptr, sizeof(struct vertex_tangent));
glm_vec4_ucopy(tangent[j], v_tangent->tangent);
@@ -280,8 +347,20 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
}
model->meshes = xrealloc_array(model->meshes, model->nr_meshes, model->nr_meshes + 1, sizeof(struct mesh));
struct mesh *mesh = &model->meshes[model->nr_meshes++];
mesh->name = xstrdup(m->name);
mesh->flags = m->flags;
mesh->material = material;
if (re_plugin_version == RE_REIGN_PLUGIN)
mesh->is_transparent = model->materials[material].is_transparent && is_transparent_mesh(m);
else
mesh->is_transparent = model->materials[material].is_transparent || is_transparent_mesh(m);
if (mesh->is_transparent)
model->has_transparent_mesh = true;
mesh->outline_color[0] = m->edge_color.r / 255.f;
mesh->outline_color[1] = m->edge_color.g / 255.f;
mesh->outline_color[2] = m->edge_color.b / 255.f;
mesh->outline_thickness = m->edge_size ? m->edge_size : DEFAULT_OUTLINE_THICKNESS;
glm_vec2_copy(m->uv_scroll, mesh->uv_scroll);
mesh->nr_vertices = nr_vertices;
glGenVertexArrays(1, &mesh->vao);
@@ -305,6 +384,14 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
glDisableVertexAttribArray(VATTR_LIGHT_UV);
glVertexAttrib2f(VATTR_LIGHT_UV, 0.0, 0.0);
}
if (has_vertex_colors) {
glEnableVertexAttribArray(VATTR_COLOR);
glVertexAttribPointer(VATTR_COLOR, 4, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_color, color));
base += sizeof(struct vertex_color);
} else {
glDisableVertexAttribArray(VATTR_COLOR);
glVertexAttrib4f(VATTR_COLOR, 1.0, 1.0, 1.0, 1.0);
}
if (has_normal_map) {
glEnableVertexAttribArray(VATTR_TANGENT);
glVertexAttribPointer(VATTR_TANGENT, 4, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_tangent, tangent));
@@ -337,6 +424,7 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
static void destroy_mesh(struct mesh *mesh)
{
free(mesh->name);
glDeleteVertexArrays(1, &mesh->vao);
glDeleteBuffers(1, &mesh->attr_buffer);
if (mesh->index_buffer)
@@ -416,6 +504,13 @@ struct model *model_load(struct archive *aar, const char *path)
struct model *model = xcalloc(1, sizeof(struct model));
model->path = strdup(path);
// Load .opr file, if any
struct archive_data *opr_file = RE_get_aar_entry(aar, path, basename, ".opr");
if (opr_file) {
opr_load(opr_file->data, opr_file->size, pol);
archive_free_data(opr_file);
}
// Bones
if (pol->nr_bones > 0) {
if (pol->nr_bones > MAX_BONES)
@@ -452,17 +547,18 @@ struct model *model_load(struct archive *aar, const char *path)
&pol->materials[i].children[j], amt, aar, path);
}
}
for (int i = 0; i < model->nr_materials; i++) {
if (model->materials[i].is_transparent) {
model->has_transparent_material = true;
break;
}
}
// Meshes
for (uint32_t i = 0; i < pol->nr_meshes; i++) {
if (!pol->meshes[i])
continue;
if (!strcmp(pol->meshes[i]->name, "collision")) {
if (model->collider)
WARNING("multiple collision meshes");
else
model->collider = collider_create(pol->meshes[i]);
continue;
}
struct pol_material_group *mg = &pol->materials[pol->meshes[i]->material];
int m_off = material_offsets[pol->meshes[i]->material];
if (mg->nr_children == 0) {
@@ -485,6 +581,9 @@ struct model *model_load(struct archive *aar, const char *path)
void model_free(struct model *model)
{
if (model->collider)
collider_free(model->collider);
for (int i = 0; i < model->nr_meshes; i++)
destroy_mesh(&model->meshes[i]);
free(model->meshes);
@@ -594,12 +693,14 @@ struct model *model_create_sphere(int r, int g, int b, int a)
model->materials = xcalloc(1, sizeof(struct material));
struct material *material = &model->materials[0];
material->is_transparent = true;
glGenTextures(1, &material->color_map);
glBindTexture(GL_TEXTURE_2D, material->color_map);
material->color_maps = xmalloc(sizeof(GLuint));
material->nr_color_maps = 1;
glGenTextures(1, material->color_maps);
glBindTexture(GL_TEXTURE_2D, material->color_maps[0]);
uint8_t pixel[4] = {r, g, b, a};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
glBindTexture(GL_TEXTURE_2D, 0);
model->has_transparent_material = true;
model->has_transparent_mesh = true;
return model;
}
@@ -611,18 +712,18 @@ static int cmp_motions_by_bone_id(const void *lhs, const void *rhs)
static struct mot *mot_load(const char *name, struct model *model, struct archive *aar)
{
struct archive_data *dfile = RE_get_aar_entry(aar, model->path, name, ".MOT");
if (!dfile) {
struct archive_data *mot_file = RE_get_aar_entry(aar, model->path, name, ".MOT");
if (!mot_file) {
WARNING("%s\\%s.MOT: not found", model->path, name);
return NULL;
}
struct mot *mot = mot_parse(dfile->data, dfile->size, name);
struct mot *mot = mot_parse(mot_file->data, mot_file->size, name);
if (!mot) {
WARNING("%s: parse error", dfile->name);
archive_free_data(dfile);
WARNING("%s: parse error", mot_file->name);
archive_free_data(mot_file);
return NULL;
}
archive_free_data(dfile);
archive_free_data(mot_file);
if (model->nr_bones != (int)mot->nr_bones)
ERROR("%s: wrong number of bones. Expected %d but got %d", name, model->nr_bones, mot->nr_bones);
@@ -641,6 +742,13 @@ static struct mot *mot_load(const char *name, struct model *model, struct archiv
}
qsort(mot->motions, mot->nr_bones, sizeof(struct mot_bone *), cmp_motions_by_bone_id);
// Load optional .txa file.
struct archive_data *txa_file = RE_get_aar_entry(aar, model->path, name, ".txa");
if (txa_file) {
txa_load(txa_file->data, txa_file->size, mot);
archive_free_data(txa_file);
}
return mot;
}
+152 -14
View File
@@ -14,6 +14,7 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <cglm/cglm.h>
@@ -64,6 +65,8 @@ static uint32_t parse_material_attributes(const char *name)
uint32_t flags = 0;
if (strstr(name, "(sprite)"))
flags |= MATERIAL_SPRITE;
if (strstr(name, "(alpha)"))
flags |= MATERIAL_ALPHA;
return flags;
}
@@ -134,7 +137,7 @@ static void destroy_vertex(struct pol_vertex *v)
free(v->weights);
}
static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle_index, int unknowns_length, const struct pol_material_group *mg)
static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle_index, const struct pol_material_group *mg)
{
struct pol_triangle *t = &mesh->triangles[triangle_index];
t->vert_index[0] = buffer_read_int32(r);
@@ -149,7 +152,14 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle
t->light_uv_index[2] = buffer_read_int32(r) - mesh->nr_uvs;
}
buffer_skip(r, unknowns_length);
t->color_index[0] = buffer_read_int32(r);
t->color_index[1] = buffer_read_int32(r);
t->color_index[2] = buffer_read_int32(r);
if (mesh->alphas) {
t->alpha_index[0] = buffer_read_int32(r);
t->alpha_index[1] = buffer_read_int32(r);
t->alpha_index[2] = buffer_read_int32(r);
}
read_direction(r, t->normals[0]);
read_direction(r, t->normals[1]);
@@ -172,6 +182,8 @@ static uint32_t parse_mesh_attributes(const char *name)
flags |= MESH_BOTH;
if (strstr(name, "(sprite)"))
flags |= MESH_SPRITE;
if (strstr(name, "(alpha)"))
flags |= MESH_ALPHA;
return flags;
}
@@ -207,6 +219,7 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol)
mesh->nr_light_uvs = buffer_read_int32(r);
if (mesh->nr_light_uvs > 0) {
mesh->flags |= MESH_HAS_LIGHT_UV;
mesh->light_uvs = xcalloc(mesh->nr_light_uvs, sizeof(vec2));
for (uint32_t i = 0; i < mesh->nr_light_uvs; i++) {
mesh->light_uvs[i][0] = buffer_read_float(r);
@@ -214,24 +227,37 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol)
}
}
int triangle_unknowns_length = 12;
if (pol->version == 1) {
int nr_unknown_vecs = buffer_read_int32(r);
buffer_skip(r, nr_unknown_vecs * 12);
} else {
int nr_unknown_ints = buffer_read_int32(r);
buffer_skip(r, nr_unknown_ints * 4);
int nr_unknown_bytes = buffer_read_int32(r);
if (nr_unknown_bytes) {
buffer_skip(r, nr_unknown_bytes);
triangle_unknowns_length += 12;
mesh->nr_colors = buffer_read_int32(r);
if (mesh->nr_colors > 0) {
mesh->colors = xcalloc(mesh->nr_colors, sizeof(vec3));
for (uint32_t i = 0; i < mesh->nr_colors; i++) {
if (pol->version == 1) {
mesh->colors[i][0] = buffer_read_float(r);
mesh->colors[i][1] = buffer_read_float(r);
mesh->colors[i][2] = buffer_read_float(r);
} else {
mesh->colors[i][0] = buffer_read_u8(r) / 255.f;
mesh->colors[i][1] = buffer_read_u8(r) / 255.f;
mesh->colors[i][2] = buffer_read_u8(r) / 255.f;
buffer_skip(r, 1);
}
}
}
if (pol->version >= 2) {
mesh->nr_alphas = buffer_read_int32(r);
if (mesh->nr_alphas > 0) {
mesh->alphas = xcalloc(mesh->nr_alphas, sizeof(float));
for (uint32_t i = 0; i < mesh->nr_alphas; i++) {
mesh->alphas[i] = buffer_read_u8(r) / 255.f;
}
}
}
mesh->nr_triangles = buffer_read_int32(r);
mesh->triangles = xcalloc(mesh->nr_triangles, sizeof(struct pol_triangle));
for (uint32_t i = 0; i < mesh->nr_triangles; i++) {
parse_triangle(r, mesh, i, triangle_unknowns_length, &pol->materials[mesh->material]);
parse_triangle(r, mesh, i, &pol->materials[mesh->material]);
}
if (pol->version == 1) {
@@ -254,6 +280,8 @@ static void free_mesh(struct pol_mesh *mesh)
free(mesh->vertices);
free(mesh->uvs);
free(mesh->light_uvs);
free(mesh->colors);
free(mesh->alphas);
free(mesh->triangles);
free(mesh);
}
@@ -397,6 +425,9 @@ void mot_free(struct mot *mot)
free(mot->motions[i]->name);
free(mot->motions[i]);
}
if (mot->texture_indices) {
free(mot->texture_indices);
}
free(mot);
}
@@ -457,3 +488,110 @@ struct amt_material *amt_find_material(struct amt *amt, const char *name)
}
return NULL;
}
void opr_load(uint8_t *data, size_t size, struct pol *pol)
{
bool *selected = xmalloc(pol->nr_meshes * sizeof(bool));
for (int i = 0; i < pol->nr_meshes; i++) {
selected[i] = !!pol->meshes[i];
}
while (size > 0) {
char line[200];
uint8_t *nl = memchr(data, '\n', size);
if (nl)
nl++;
else
nl = data + size;
int copylen = min(nl - data, sizeof(line) - 1);
memcpy(line, (char *)data, copylen);
line[copylen] = '\0';
size -= nl - data;
data = nl;
char s[200];
int i1, i2, i3;
float f1, f2;
if (sscanf(line, "Mesh = \"%[^\"]\"", s) == 1) {
for (int i = 0; i < pol->nr_meshes; i++) {
selected[i] = pol->meshes[i] && !strcmp(pol->meshes[i]->name, s);
}
} else if (sscanf(line, "MeshPart = \"%[^\"]\"", s) == 1) {
for (int i = 0; i < pol->nr_meshes; i++) {
selected[i] = pol->meshes[i] && strstr(pol->meshes[i]->name, s);
}
} else if (sscanf(line, "BlendMode = %s", s) == 1) {
if (!strcmp(s, "Add")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_BLEND_ADDITIVE;
}
} else {
WARNING("unknown BlendMode: %s", s);
}
} else if (sscanf(line, "Edge = %d", &i1) == 1) {
if (i1 == 0) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_NO_EDGE;
}
} else {
WARNING("invalid Edge value: %d", i1);
}
} else if (sscanf(line, "EdgeColor = ( %d , %d , %d )", &i1, &i2, &i3) == 3) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->edge_color = COLOR(i1, i2, i3, 255);
}
} else if (sscanf(line, "EdgeSize = %f", &f1) == 1) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->edge_size = f1;
}
} else if (sscanf(line, "HeightDetection = %s", s) == 1) {
if (!strcmp(s, "false")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_NO_HEIGHT_DETECTION;
}
} else {
WARNING("invalid HeightDetection: %s", s);
}
} else if (sscanf(line, "UVScroll = ( %f , %f )", &f1, &f2) == 2) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->uv_scroll[0] = f1;
pol->meshes[i]->uv_scroll[1] = f2;
}
} else if (strchr(line, '=')) {
WARNING("unknown field: %s", line);
}
}
free(selected);
}
void txa_load(uint8_t *data, size_t size, struct mot *mot)
{
uint32_t count = 0;
uint32_t *indices = xmalloc(100 * sizeof(uint32_t));
const char *p = (const char *)data;
while (p < (const char *)data + size) {
if (isdigit(*p)) {
uint32_t n = 0;
while (p < (const char *)data + size && isdigit(*p)) {
n = n * 10 + (*p - '0');
p++;
}
indices[count++] = n;
if (count % 100 == 0) {
indices = xrealloc(indices, (count + 100) * sizeof(uint32_t));
}
} else if (isspace(*p)) {
p++;
} else {
WARNING("invalid character in txa file: '%c'", *p);
p++;
}
}
mot->nr_texture_indices = count;
mot->texture_indices = indices;
}
+1 -1
View File
@@ -943,7 +943,7 @@ void particle_object_calc_local_transform(struct RE_instance *inst, struct parti
struct pae_object *pae_obj = po->pae_obj;
float t = glm_percent(pi->begin_frame, pi->end_frame, frame);
vec3 pos, up_vector;
vec3 pos = {0}, up_vector;
calc_pos_and_up_vector(po, pi, t, pos, up_vector);
float rel_frame = frame - pi->begin_frame;
+167 -10
View File
@@ -34,6 +34,8 @@
#include "vm.h"
#include "xsystem4.h"
enum RE_plugin_version re_plugin_version;
static struct RE_instance *create_instance(struct RE_plugin *plugin)
{
struct RE_instance *instance = xcalloc(1, sizeof(struct RE_instance));
@@ -75,6 +77,7 @@ static void unload_instance(struct RE_instance *instance)
instance->effect = NULL;
}
if (instance->bone_transforms) {
glDeleteBuffers(1, &instance->bone_transforms_ubo);
free(instance->bone_transforms);
instance->bone_transforms = NULL;
}
@@ -128,12 +131,18 @@ static void update_motion(struct motion *m, float delta_frame)
}
break;
case RE_MOTION_STATE_LOOP:
if (m->current_frame > m->loop_frame_end)
if (m->loop_frame_begin == m->loop_frame_end)
m->current_frame = m->loop_frame_begin;
else if (m->current_frame > m->loop_frame_end)
m->current_frame = m->loop_frame_begin + fmodf(m->current_frame - m->loop_frame_begin, m->loop_frame_end - m->loop_frame_begin);
break;
default:
VM_ERROR("Invalid motion state %d", m->state);
}
uint32_t anim_frame = m->current_frame - m->frame_begin;
m->texture_index = (m->mot && anim_frame < m->mot->nr_texture_indices)
? m->mot->texture_indices[anim_frame] : 0;
}
static void calc_motion_frame(struct motion *m, int bone, struct mot_frame *out)
@@ -177,11 +186,16 @@ static void update_bones(struct RE_instance *inst)
}
glm_mat4_copy(bone_transform, parent_transforms[i]);
glm_mat4_mul(bone_transform, inst->model->bones[i].inverse_bind_matrix, inst->bone_transforms[i]);
glm_vec3_minv(aabb[0], bone_transform[3], aabb[0]);
glm_vec3_maxv(aabb[1], bone_transform[3], aabb[1]);
glm_mat4_mul(bone_transform, inst->model->bones[i].inverse_bind_matrix, bone_transform);
glm_mat4_transpose(bone_transform); // column major -> row major
glm_mat3x4_copy(bone_transform, inst->bone_transforms[i]);
}
glBindBuffer(GL_UNIFORM_BUFFER, inst->bone_transforms_ubo);
glBufferSubData(GL_UNIFORM_BUFFER, 0, inst->model->nr_bones * sizeof(mat3x4), inst->bone_transforms);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// Update inst->bounding_sphere.
vec3 center;
@@ -199,8 +213,10 @@ static void update_bones(struct RE_instance *inst)
}
}
struct RE_plugin *RE_plugin_new(void)
struct RE_plugin *RE_plugin_new(enum RE_plugin_version version)
{
re_plugin_version = version;
char *aar_path = gamedir_path("Data/ReignData.red");
int error = ARCHIVE_SUCCESS;
struct archive *aar = (struct archive *)aar_open(aar_path, MMAP_IF_64BIT, &error);
@@ -216,7 +232,7 @@ struct RE_plugin *RE_plugin_new(void)
struct RE_plugin *plugin = xcalloc(1, sizeof(struct RE_plugin));
plugin->plugin.name = "ReignEngine";
plugin->plugin.update = RE_render;
plugin->plugin.debug_print = RE_debug_print;
plugin->plugin.to_json = RE_to_json;
plugin->nr_instances = 16;
plugin->instances = xcalloc(plugin->nr_instances, sizeof(struct RE_instance *));
plugin->aar = aar;
@@ -255,9 +271,11 @@ bool RE_plugin_bind(struct RE_plugin *plugin, int sprite)
struct sact_sprite *sp = sact_try_get_sprite(sprite);
if (!sp)
return false;
plugin->renderer = RE_renderer_new(sprite_get_texture(sp));
plugin->renderer = RE_renderer_new();
plugin->sprite = sprite;
sprite_bind_plugin(sp, &plugin->plugin);
struct texture *texture = sprite_get_texture(sp);
RE_renderer_set_viewport_size(plugin->renderer, texture->w, texture->h);
return true;
}
@@ -274,6 +292,28 @@ bool RE_plugin_unbind(struct RE_plugin *plugin)
return true;
}
bool RE_plugin_suspend(struct RE_plugin *plugin)
{
if (!plugin)
return false;
plugin->suspended = true;
struct sact_sprite *sp = sact_try_get_sprite(plugin->sprite);
if (sp)
sprite_set_show(sp, false);
return true;
}
bool RE_plugin_resume(struct RE_plugin *plugin)
{
if (!plugin)
return false;
plugin->suspended = false;
struct sact_sprite *sp = sact_try_get_sprite(plugin->sprite);
if (sp)
sprite_set_show(sp, true);
return true;
}
bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
{
if (!plugin)
@@ -292,6 +332,7 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
if (inst->type == RE_ITYPE_SKINNED)
update_bones(inst);
inst->texture_animation_index = inst->motion ? inst->motion->texture_index : 0;
}
// Update particle effects in a second pass, because it uses the results of
@@ -304,6 +345,21 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
return true;
}
bool RE_set_viewport(struct RE_plugin *plugin, int x, int y, int width, int height)
{
if (!plugin)
return false;
struct sact_sprite *sp = sact_try_get_sprite(plugin->sprite);
if (!sp || sp->plugin != &plugin->plugin) {
WARNING("No sprite bound to plugin");
return false;
}
sprite_init_color(sp, width, height, 0, 0, 0, 255);
sprite_set_pos(sp, x, y);
RE_renderer_set_viewport_size(plugin->renderer, width, height);
return true;
}
bool RE_set_projection(struct RE_plugin *plugin, float width, float height, float near, float far, float deg)
{
if (!plugin)
@@ -368,8 +424,10 @@ bool RE_instance_set_type(struct RE_instance *instance, int type)
break;
case RE_ITYPE_PARTICLE_EFFECT:
break;
case RE_ITYPE_PATH_LINE:
break;
default:
ERROR("unknown instance type %d", type);
VM_ERROR("unknown instance type %d", type);
}
instance->type = type;
return true;
@@ -393,8 +451,13 @@ bool RE_instance_load(struct RE_instance *instance, const char *name)
if (instance->model)
ht_put(instance->plugin->model_cache, name, instance->model);
}
if (instance->model && instance->model->nr_bones > 0)
instance->bone_transforms = xcalloc(instance->model->nr_bones, sizeof(mat4));
if (instance->model && instance->model->nr_bones > 0) {
instance->bone_transforms = xcalloc(instance->model->nr_bones, sizeof(mat3x4));
glGenBuffers(1, &instance->bone_transforms_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, instance->bone_transforms_ubo);
glBufferData(GL_UNIFORM_BUFFER, MAX_BONES * sizeof(mat3x4), NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
return !!instance->model;
case RE_ITYPE_PARTICLE_EFFECT:
pae = ht_get(instance->plugin->pae_cache, name, NULL);
@@ -446,6 +509,19 @@ bool RE_instance_free_next_motion(struct RE_instance *instance)
return true;
}
bool RE_instance_set_mesh_show(struct RE_instance *instance, const char *mesh_name, bool show)
{
if (!instance || !instance->model)
return false;
for (int i = 0; i < instance->model->nr_meshes; i++) {
if (!strcmp(instance->model->meshes[i].name, mesh_name)) {
instance->model->meshes[i].hidden = !show;
return true;
}
}
return false;
}
bool RE_instance_set_vertex_pos(struct RE_instance *instance, int index, float x, float y, float z)
{
if (!instance)
@@ -480,7 +556,10 @@ bool RE_instance_trans_local_pos_to_world_pos_by_bone(struct RE_instance *instan
if (instance->local_transform_needs_update)
RE_instance_update_local_transform(instance);
glm_mat4_mulv3(instance->bone_transforms[bone], offset, 1.0, out);
mat4 bone_transform = GLM_MAT4_IDENTITY_INIT;
glm_mat3x4_copy(instance->bone_transforms[bone], bone_transform);
glm_mat4_transpose(bone_transform); // row major -> column major
glm_mat4_mulv3(bone_transform, offset, 1.0, out);
glm_mat4_mulv3(instance->local_transform, out, 1.0, out);
return true;
}
@@ -507,6 +586,31 @@ float RE_instance_calc_height(struct RE_instance *instance, float x, float z)
return cnt ? total / cnt : 0.0f;
}
float RE_instance_calc_2d_detection_height(struct RE_instance *instance, float x, float z)
{
if (!instance || !instance->model->collider)
return false;
vec2 xz = { x, -z };
float h;
if (collider_height(instance->model->collider, xz, &h))
return h;
return 0.f;
}
bool RE_instance_calc_2d_detection(struct RE_instance *instance, float x0, float y0, float z0, float x1, float y1, float z1, float *x2, float *y2, float *z2, float radius)
{
if (!instance || !instance->model->collider)
return false;
vec2 p0 = { x0, -z0 };
vec2 p1 = { x1, -z1 };
vec2 p2;
if (!check_collision(instance->model->collider, p0, p1, radius, p2))
return false;
*x2 = p2[0];
*z2 = -p2[1];
return collider_height(instance->model->collider, p2, y2);
}
bool RE_instance_set_debug_draw_shadow_volume(struct RE_instance *inst, bool draw)
{
if (!inst)
@@ -520,6 +624,59 @@ bool RE_instance_set_debug_draw_shadow_volume(struct RE_instance *inst, bool dra
return true;
}
bool RE_instance_find_path(struct RE_instance *inst, vec3 start, vec3 goal)
{
if (!inst || !inst->model->collider)
return false;
mat4 vp_transform;
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, vp_transform);
glm_mat4_mul(inst->plugin->proj_transform, vp_transform, vp_transform);
return collider_find_path(inst->model->collider, start, goal, vp_transform);
}
const vec3 *RE_instance_get_path_line(struct RE_instance *inst, int *nr_path_points)
{
if (!inst || !inst->model->collider)
return NULL;
*nr_path_points = inst->model->collider->nr_path_points;
return inst->model->collider->path_points;
}
bool RE_instance_optimize_path_line(struct RE_instance *inst)
{
if (!inst || !inst->model->collider)
return false;
return collider_optimize_path(inst->model->collider);
}
bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *inst, int mouse_x, int mouse_y, vec3 out)
{
if (!inst || !inst->model->collider)
return false;
float ndc_x = 2.f * mouse_x / inst->plugin->renderer->viewport_width - 1.f;
float ndc_y = 1.f - 2.f * mouse_y / inst->plugin->renderer->viewport_height;
vec4 near = { ndc_x, ndc_y, -1.f, 1.f };
vec4 far = { ndc_x, ndc_y, 1.f, 1.f };
mat4 inv_vp;
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, inv_vp);
glm_mat4_mul(inst->plugin->proj_transform, inv_vp, inv_vp);
glm_mat4_inv(inv_vp, inv_vp);
glm_mat4_mulv(inv_vp, near, near);
glm_mat4_mulv(inv_vp, far, far);
glm_vec3_divs(near, near[3], near);
glm_vec3_divs(far, far[3], far);
vec3 origin, direction;
glm_vec3_copy(near, origin);
glm_vec3_sub(far, near, direction);
glm_vec3_normalize(direction);
return collider_raycast(inst->model->collider, origin, direction, out);
}
void RE_instance_update_local_transform(struct RE_instance *inst)
{
vec3 euler = {
+189 -39
View File
@@ -26,6 +26,8 @@
#include "reign.h"
#include "sact.h"
#define BONE_TRANSFORMS_BINDING 0
// TODO: Respect RE_plugin.shadow_map_resolution_level
#define SHADOW_WIDTH 512
#define SHADOW_HEIGHT 512
@@ -50,6 +52,7 @@ enum {
ALPHA_BLEND = 0,
ALPHA_TEST = 1,
ALPHA_MAP_BLEND = 2,
ALPHA_MAP_TEST = 3,
};
enum draw_phase {
@@ -59,9 +62,17 @@ enum draw_phase {
static GLuint load_shader(const char *vertex_shader_path, const char *fragment_shader_path)
{
char defines[256];
snprintf(defines, sizeof(defines),
"#define REIGN_ENGINE %d\n"
"#define TAPIR_ENGINE %d\n"
"#define ENGINE %d\n",
RE_REIGN_PLUGIN,
RE_TAPIR_PLUGIN,
re_plugin_version);
GLuint program = glCreateProgram();
GLuint vertex_shader = gfx_load_shader_file(vertex_shader_path, GL_VERTEX_SHADER);
GLuint fragment_shader = gfx_load_shader_file(fragment_shader_path, GL_FRAGMENT_SHADER);
GLuint vertex_shader = gfx_load_shader_file(vertex_shader_path, GL_VERTEX_SHADER, defines);
GLuint fragment_shader = gfx_load_shader_file(fragment_shader_path, GL_FRAGMENT_SHADER, defines);
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
@@ -74,6 +85,7 @@ static GLuint load_shader(const char *vertex_shader_path, const char *fragment_s
glBindAttribLocation(program, VATTR_BONE_INDEX, "vertex_bone_index");
glBindAttribLocation(program, VATTR_BONE_WEIGHT, "vertex_bone_weight");
glBindAttribLocation(program, VATTR_LIGHT_UV, "vertex_light_uv");
glBindAttribLocation(program, VATTR_COLOR, "vertex_color");
glBindAttribLocation(program, VATTR_TANGENT, "vertex_tangent");
glLinkProgram(program);
@@ -96,10 +108,11 @@ static void init_shadow_renderer(struct shadow_renderer *sr)
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &orig_fbo);
sr->program = load_shader("shaders/reign_shadow.v.glsl", "shaders/reign_shadow.f.glsl");
sr->world_transform = glGetUniformLocation(sr->program, "world_transform");
sr->local_transform = glGetUniformLocation(sr->program, "local_transform");
sr->view_transform = glGetUniformLocation(sr->program, "view_transform");
sr->has_bones = glGetUniformLocation(sr->program, "has_bones");
sr->bone_matrices = glGetUniformLocation(sr->program, "bone_matrices");
GLuint bone_transforms = glGetUniformBlockIndex(sr->program, "BoneTransforms");
glUniformBlockBinding(sr->program, bone_transforms, BONE_TRANSFORMS_BINDING);
glGenFramebuffers(1, &sr->fbo);
glGenTextures(1, &sr->texture);
@@ -124,6 +137,30 @@ static void destroy_shadow_renderer(struct shadow_renderer *sr)
glDeleteProgram(sr->program);
}
static void init_outline_renderer(struct outline_renderer *or)
{
if (re_plugin_version < RE_TAPIR_PLUGIN) {
or->program = 0;
return;
}
or->program = load_shader("shaders/reign_outline.v.glsl", "shaders/reign_outline.f.glsl");
or->local_transform = glGetUniformLocation(or->program, "local_transform");
or->view_transform = glGetUniformLocation(or->program, "view_transform");
or->proj_transform = glGetUniformLocation(or->program, "proj_transform");
or->normal_transform = glGetUniformLocation(or->program, "normal_transform");
or->has_bones = glGetUniformLocation(or->program, "has_bones");
GLuint bone_transforms = glGetUniformBlockIndex(or->program, "BoneTransforms");
glUniformBlockBinding(or->program, bone_transforms, BONE_TRANSFORMS_BINDING);
or->outline_color = glGetUniformLocation(or->program, "outline_color");
or->outline_thickness = glGetUniformLocation(or->program, "outline_thickness");
}
static void destroy_outline_renderer(struct outline_renderer *or)
{
if (or->program)
glDeleteProgram(or->program);
}
static void init_billboard_mesh(struct RE_renderer *r)
{
static const GLfloat vertices[] = {
@@ -144,6 +181,8 @@ static void init_billboard_mesh(struct RE_renderer *r)
glVertexAttribPointer(VATTR_UV, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12);
glDisableVertexAttribArray(VATTR_LIGHT_UV);
glVertexAttrib2f(VATTR_LIGHT_UV, 0.0, 0.0);
glDisableVertexAttribArray(VATTR_COLOR);
glVertexAttrib4f(VATTR_COLOR, 1.0, 1.0, 1.0, 1.0);
glDisableVertexAttribArray(VATTR_NORMAL);
glVertexAttrib3f(VATTR_NORMAL, 0.0, 0.0, 1.0);
glDisableVertexAttribArray(VATTR_TANGENT);
@@ -165,12 +204,11 @@ static void destroy_billboard_mesh(struct RE_renderer *r)
glDeleteBuffers(1, &r->billboard_attr_buffer);
}
struct RE_renderer *RE_renderer_new(struct texture *texture)
struct RE_renderer *RE_renderer_new(void)
{
struct RE_renderer *r = xcalloc(1, sizeof(struct RE_renderer));
r->program = load_shader("shaders/reign.v.glsl", "shaders/reign.f.glsl");
r->world_transform = glGetUniformLocation(r->program, "world_transform");
r->view_transform = glGetUniformLocation(r->program, "view_transform");
r->texture = glGetUniformLocation(r->program, "tex");
r->local_transform = glGetUniformLocation(r->program, "local_transform");
@@ -178,8 +216,10 @@ struct RE_renderer *RE_renderer_new(struct texture *texture)
r->normal_transform = glGetUniformLocation(r->program, "normal_transform");
r->alpha_mod = glGetUniformLocation(r->program, "alpha_mod");
r->has_bones = glGetUniformLocation(r->program, "has_bones");
r->bone_matrices = glGetUniformLocation(r->program, "bone_matrices");
r->ambient = glGetUniformLocation(r->program, "ambient");
GLuint bone_transforms = glGetUniformBlockIndex(r->program, "BoneTransforms");
glUniformBlockBinding(r->program, bone_transforms, BONE_TRANSFORMS_BINDING);
r->global_ambient = glGetUniformLocation(r->program, "global_ambient");
r->instance_ambient = glGetUniformLocation(r->program, "instance_ambient");
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
char buf[64];
sprintf(buf, "dir_lights[%d].dir", i);
@@ -215,18 +255,27 @@ struct RE_renderer *RE_renderer_new(struct texture *texture)
r->ls_sun_color = glGetUniformLocation(r->program, "ls_sun_color");
r->alpha_mode = glGetUniformLocation(r->program, "alpha_mode");
r->alpha_texture = glGetUniformLocation(r->program, "alpha_texture");
r->uv_scroll = glGetUniformLocation(r->program, "uv_scroll");
glGenRenderbuffers(1, &r->depth_buffer);
glBindRenderbuffer(GL_RENDERBUFFER, r->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, texture->w, texture->h);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
init_shadow_renderer(&r->shadow);
init_outline_renderer(&r->outline);
init_billboard_mesh(r);
r->billboard_textures = ht_create(256);
r->last_frame_timestamp = SDL_GetTicks();
return r;
}
void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height)
{
r->viewport_width = width;
r->viewport_height = height;
glBindRenderbuffer(GL_RENDERBUFFER, r->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no)
{
if (ht_get_int(r->billboard_textures, cg_no, NULL))
@@ -268,6 +317,7 @@ void RE_renderer_free(struct RE_renderer *r)
ht_free_int(r->billboard_textures);
destroy_billboard_mesh(r);
destroy_shadow_renderer(&r->shadow);
destroy_outline_renderer(&r->outline);
free(r);
}
@@ -285,13 +335,19 @@ void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out)
glm_look(camera->pos, front, up, out);
}
static bool should_draw_shadow(struct mesh *mesh, struct material *material)
{
return !(mesh->flags & (MESH_ALPHA | MESH_BOTH | MESH_SPRITE))
&& !(material->flags & (MATERIAL_ALPHA | MATERIAL_SPRITE));
}
static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
struct model *model = inst->model;
if (!inst->model)
return;
bool all_transparent = inst->alpha < 1.0f;
bool all_opaque = !model->has_transparent_material && inst->alpha >= 1.0f;
bool all_opaque = !model->has_transparent_mesh && inst->alpha >= 1.0f;
if ((phase == DRAW_OPAQUE && all_transparent) || (phase == DRAW_TRANSPARENT && all_opaque))
return;
@@ -301,9 +357,7 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, inst->local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, inst->normal_transform[0]);
glUniform1f(r->alpha_mod, inst->alpha);
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
bool draw_shadow = inst->draw_shadow && inst->plugin->shadow_mode;
if (draw_shadow) {
@@ -312,15 +366,21 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
glUniform1i(r->shadow_texture, SHADOW_TEXTURE_UNIT);
}
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
if (mesh->hidden)
continue;
struct material *material = &model->materials[mesh->material];
bool is_transparent = (material->is_transparent && !(mesh->flags & MESH_SPRITE)) || inst->alpha < 1.0f;
bool is_transparent = mesh->is_transparent || inst->alpha < 1.0f;
if (phase != (is_transparent ? DRAW_TRANSPARENT : DRAW_OPAQUE))
continue;
if (mesh->flags & MESH_BLEND_ADDITIVE) {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
} else {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
}
glUniform1i(r->fog_type, (inst->plugin->fog_mode && !(mesh->flags & MESH_NOLIGHTING))
? inst->plugin->fog_type : 0);
@@ -344,10 +404,12 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
glUniform3fv(r->rim_color, 1, material->rim_color);
glActiveTexture(GL_TEXTURE0 + COLOR_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->color_map);
int animation_index = inst->texture_animation_index < material->nr_color_maps
? inst->texture_animation_index : 0;
glBindTexture(GL_TEXTURE_2D, material->color_maps[animation_index]);
glUniform1i(r->texture, COLOR_TEXTURE_UNIT);
if (draw_shadow && !(mesh->flags & MESH_BOTH))
if (draw_shadow && should_draw_shadow(mesh, material))
glUniform1f(r->shadow_darkness, material->shadow_darkness);
else
glUniform1f(r->shadow_darkness, 0.0f);
@@ -356,7 +418,7 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
glUniform1i(r->diffuse_type, DIFFUSE_ENV_MAP);
} else if (mesh->flags & MESH_NOLIGHTING) {
glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE);
} else if (material->light_map && inst->plugin->light_map_mode) {
} else if (material->light_map && mesh->flags & MESH_HAS_LIGHT_UV && inst->plugin->light_map_mode) {
glUniform1i(r->diffuse_type, DIFFUSE_LIGHT_MAP);
glActiveTexture(GL_TEXTURE0 + LIGHT_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->light_map);
@@ -375,23 +437,30 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
}
if (material->alpha_map) {
glUniform1i(r->alpha_mode, ALPHA_MAP_BLEND);
glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_MAP_BLEND : ALPHA_MAP_TEST);
glActiveTexture(GL_TEXTURE0 + ALPHA_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->alpha_map);
glUniform1i(r->alpha_texture, ALPHA_TEXTURE_UNIT);
} else if (material->flags & MATERIAL_SPRITE || mesh->flags & MESH_SPRITE) {
glUniform1i(r->alpha_mode, ALPHA_TEST);
} else {
glUniform1i(r->alpha_mode, ALPHA_BLEND);
glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_BLEND : ALPHA_TEST);
}
vec2 uv_scroll;
glm_vec2_scale(mesh->uv_scroll, r->last_frame_timestamp / 1000.f, uv_scroll);
glUniform2fv(r->uv_scroll, 1, uv_scroll);
glBindVertexArray(mesh->vao);
if (mesh->flags & MESH_BOTH)
glDisable(GL_CULL_FACE);
if (mesh->nr_indices)
glDrawElements(GL_TRIANGLES, mesh->nr_indices, GL_UNSIGNED_SHORT, NULL);
else
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
if (mesh->flags & MESH_BOTH)
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
@@ -415,7 +484,7 @@ static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r
if (model->nr_bones > 0 && inst->motion) {
glUniform1i(r->has_bones, GL_TRUE);
glUniformMatrix4fv(r->bone_matrices, model->nr_bones, GL_FALSE, inst->bone_transforms[0][0]);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo);
} else {
glUniform1i(r->has_bones, GL_FALSE);
}
@@ -449,9 +518,7 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma
// This should be safe because billboards do not have non-uniform scaling.
glm_mat4_pick3(local_transform, normal_transform);
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, normal_transform[0]);
@@ -557,7 +624,7 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *
struct material *material = &model->materials[mesh->material];
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material->color_map);
glBindTexture(GL_TEXTURE_2D, material->color_maps[0]);
glUniform1i(r->texture, 0);
glBindVertexArray(mesh->vao);
@@ -575,9 +642,7 @@ static void render_particle_effect(struct RE_instance *inst, struct RE_renderer
if (!inst->effect || phase == DRAW_OPAQUE)
return;
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
glUniform1i(r->has_bones, GL_FALSE);
glUniform1f(r->specular_strength, 0.0);
@@ -637,6 +702,26 @@ static void render_instance(struct RE_instance *inst, struct RE_renderer *r, mat
}
}
static void merge_spheres(vec4 s1, vec4 s2, vec4 dest)
{
// glm_sphere_merge() doesn't make the smallest possible sphere, so do it manually.
float distance = glm_vec3_distance(s1, s2);
float r1 = s1[3];
float r2 = s2[3];
if (distance + r2 <= r1) {
glm_vec4_copy(s1, dest);
} else if (distance + r1 <= r2) {
glm_vec4_copy(s2, dest);
} else {
float radius = (distance + r1 + r2) / 2.f;
vec3 v12;
glm_vec3_sub(s2, s1, v12);
glm_vec3_copy(s1, dest);
glm_vec3_muladds(v12, (radius - r1) / distance, dest);
dest[3] = radius;
}
}
static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest)
{
// Compute a bounding sphere of shadow casters.
@@ -646,7 +731,7 @@ static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest)
if (!inst || !inst->draw || !inst->make_shadow || !inst->model)
continue;
if (bounding_sphere[3] > 0.0f)
glm_sphere_merge(inst->bounding_sphere, bounding_sphere, inst->bounding_sphere);
merge_spheres(inst->bounding_sphere, bounding_sphere, bounding_sphere);
else
glm_vec4_copy(inst->bounding_sphere, bounding_sphere);
}
@@ -657,7 +742,7 @@ static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest)
// Create a orthographic frustum that contains the bounding sphere.
mat4 view_matrix;
glm_look(bounding_sphere, plugin->shadow_map_light_dir, GLM_YUP, view_matrix);
glm_look_anyup(bounding_sphere, plugin->shadow_map_light_dir, view_matrix);
mat4 proj_matrix;
glm_ortho(-radius, radius, -radius, radius, -radius, radius, proj_matrix);
glm_mat4_mul(proj_matrix, view_matrix, dest);
@@ -693,11 +778,11 @@ static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transfo
struct model *model = inst->model;
if (model->nr_bones > 0) {
glUniform1i(r->shadow.has_bones, GL_TRUE);
glUniformMatrix4fv(r->shadow.bone_matrices, model->nr_bones, GL_FALSE, inst->bone_transforms[0][0]);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo);
} else {
glUniform1i(r->shadow.has_bones, GL_FALSE);
}
glUniformMatrix4fv(r->shadow.world_transform, 1, GL_FALSE, inst->local_transform[0]);
glUniformMatrix4fv(r->shadow.local_transform, 1, GL_FALSE, inst->local_transform[0]);
for (int j = 0; j < model->nr_meshes; j++) {
struct mesh *mesh = &model->meshes[j];
if (mesh->flags & MESH_NOMAKESHADOW)
@@ -713,6 +798,49 @@ static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transfo
glViewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
}
static void render_outlines(struct RE_plugin *plugin, mat4 view_transform)
{
enum RE_draw_edge_mode mode = plugin->draw_options[RE_DRAW_OPTION_EDGE];
if (mode == RE_DRAW_EDGE_NONE)
return;
struct outline_renderer *or = &plugin->renderer->outline;
if (!or->program)
return;
glUseProgram(or->program);
glUniformMatrix4fv(or->view_transform, 1, GL_FALSE, view_transform[0]);
glUniformMatrix4fv(or->proj_transform, 1, GL_FALSE, plugin->proj_transform[0]);
glCullFace(GL_FRONT);
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!inst || !inst->draw)
continue;
if (mode == RE_DRAW_EDGE_CHARACTERS_ONLY && inst->type != RE_ITYPE_SKINNED)
continue;
struct model *model = inst->model;
if (inst->model && model->nr_bones > 0) {
glUniform1i(or->has_bones, GL_TRUE);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo);
} else {
glUniform1i(or->has_bones, GL_FALSE);
}
glUniformMatrix4fv(or->local_transform, 1, GL_FALSE, inst->local_transform[0]);
glUniformMatrix3fv(or->normal_transform, 1, GL_FALSE, inst->normal_transform[0]);
for (int j = 0; j < model->nr_meshes; j++) {
struct mesh *mesh = &model->meshes[j];
if (mesh->flags & MESH_NO_EDGE)
continue;
glUniform3fv(or->outline_color, 1, mesh->outline_color);
glUniform1f(or->outline_thickness, mesh->outline_thickness);
glBindVertexArray(mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
}
glBindVertexArray(0);
}
glCullFace(GL_BACK);
glUseProgram(plugin->renderer->program);
}
static void render_back_cg(struct texture *dst, struct RE_back_cg *bcg, struct RE_renderer *r)
{
if (!bcg->show || !bcg->texture.handle)
@@ -726,6 +854,7 @@ static void setup_lights(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
glUniform3fv(r->camera_pos, 1, plugin->camera.pos);
glUniform3fv(r->global_ambient, 1, plugin->global_ambient);
int light_index = 0;
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
@@ -796,8 +925,15 @@ void RE_render(struct sact_sprite *sp)
{
struct RE_plugin *plugin = (struct RE_plugin *)sp->plugin;
struct RE_renderer *r = plugin->renderer;
if (!r)
if (!r || plugin->suspended)
return;
if (re_plugin_version == RE_TAPIR_PLUGIN) {
uint32_t timestamp = SDL_GetTicks();
RE_build_model(plugin, timestamp - r->last_frame_timestamp);
r->last_frame_timestamp = timestamp;
}
sprite_dirty(sp);
struct texture *texture = sprite_get_texture(sp);
@@ -812,6 +948,7 @@ void RE_render(struct sact_sprite *sp)
if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
ERROR("Incomplete framebuffer");
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw background CGs.
@@ -872,6 +1009,7 @@ void RE_render(struct sact_sprite *sp)
continue;
render_instance(inst, r, view_transform, DRAW_OPAQUE);
}
// Render transparent instances, from farthest to nearest.
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = sorted_instances[i];
@@ -880,6 +1018,9 @@ void RE_render(struct sact_sprite *sp)
render_instance(inst, r, view_transform, DRAW_TRANSPARENT);
}
// Render outlines.
render_outlines(plugin, view_transform);
free(sorted_instances);
plugin->proj_transform[1][1] *= -1;
@@ -935,10 +1076,18 @@ struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glClear(GL_DEPTH_BUFFER_BIT);
glUseProgram(r->shadow.program);
glUniformMatrix4fv(r->shadow.world_transform, 1, GL_FALSE, view_matrix[0]);
glUniformMatrix4fv(r->shadow.local_transform, 1, GL_FALSE, view_matrix[0]);
glUniformMatrix4fv(r->shadow.view_transform, 1, GL_FALSE, proj_matrix[0]);
glUniform1i(r->shadow.has_bones, GL_FALSE);
// Bone transforms are not used, but we still need to bind a UBO.
GLuint bone_transforms_ubo;
glGenBuffers(1, &bone_transforms_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, bone_transforms_ubo);
glBufferData(GL_UNIFORM_BUFFER, MAX_BONES * sizeof(mat3x4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, bone_transforms_ubo);
glEnable(GL_DEPTH_TEST);
// Render the model.
@@ -960,6 +1109,7 @@ struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r
glBindFramebuffer(GL_FRAMEBUFFER, orig_fbo);
glViewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
glDeleteBuffers(1, &bone_transforms_ubo);
glDeleteTextures(1, &color_texture);
return hd;
}
+48 -101
View File
@@ -18,7 +18,6 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <libgen.h>
#include <dirent.h>
#include <limits.h>
#include <assert.h>
@@ -52,6 +51,7 @@ const char *asset_strtype(enum asset_type type)
case ASSET_FLAT: return "Flat";
case ASSET_PACT: return "Pact";
case ASSET_DATA: return "Data";
case ASSET_FLASH: return "Flash";
}
return "Invalid";
}
@@ -75,7 +75,6 @@ struct asset_manager_ald {
struct asset_manager_afa {
struct asset_manager manager;
struct afa_archive *archives[MAX_ARCHIVES];
struct hash_table *index;
};
static struct asset_manager *assets[ASSET_TYPE_MAX] = {0};
@@ -197,111 +196,59 @@ static bool afa_load_archive(struct asset_manager *_manager, const char *name)
manager->archives[i] = manager->archives[i-1];
}
manager->archives[0] = ar;
// index needs to be rebuilt
if (manager->index) {
ht_free(manager->index);
manager->index = NULL;
}
return true;
}
static bool _afa_get_by_id(struct asset_manager *_manager, int id, struct afa_archive **ar_out, int *no_out)
static bool afa_exists_by_id(struct asset_manager *_manager, int id)
{
struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager;
uint32_t no = id - 1;
for (int i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) {
if (no < manager->archives[i]->nr_files) {
*ar_out = manager->archives[i];
*no_out = no;
if (archive_exists(&manager->archives[i]->ar, id - 1))
return true;
}
no -= manager->archives[i]->nr_files;
}
return false;
}
static bool afa_exists_by_id(struct asset_manager *manager, int id)
{
int no;
struct afa_archive *ar;
return _afa_get_by_id(manager, id, &ar, &no);
}
static struct archive_data *afa_get_by_id(struct asset_manager *manager, int id)
{
int no;
struct afa_archive *ar;
if (!_afa_get_by_id(manager, id, &ar, &no))
return NULL;
// XXX: we don't use archive_get here because it will return the wrong thing
// for AFAv1 archives when not using integer indices
struct archive_data *data = afa_entry_to_descriptor(ar, &ar->files[no]);
if (data)
archive_load_file(data);
return data;
}
static void afa_index_archive(struct hash_table *index, struct afa_archive *ar, unsigned base)
{
for (unsigned i = 0; i < ar->nr_files; i++) {
// normalize case and remove file extension from file name
char *name = strdup(ar->files[i].name->text);
char *dot = strrchr(name, '.');
if (dot)
*dot = '\0';
sjis_toupper(name);
// add file to index
ht_put(index, name, (void*)((uintptr_t)base + i + 1));
free(name);
}
}
static bool _afa_get_by_name(struct asset_manager *_manager, const char *_name,
struct afa_archive **ar_out, int *no_out, int *id_out)
static struct archive_data *afa_get_by_id(struct asset_manager *_manager, int id)
{
struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager;
// initialize index lazily
if (!manager->index) {
manager->index = ht_create(4096);
unsigned base = 0;
for (unsigned i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) {
afa_index_archive(manager->index, manager->archives[i], base);
base += manager->archives[i]->nr_files;
for (int i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) {
struct archive_data *data = archive_get(&manager->archives[i]->ar, id - 1);
if (data)
return data;
}
return NULL;
}
static struct archive_data *afa_get_by_name(struct asset_manager *_manager, const char *name,
int *id_out)
{
struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager;
for (unsigned i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) {
struct archive_data *data;
data = archive_get_by_basename(&manager->archives[i]->ar, name);
if (data) {
if (id_out)
*id_out = data->no + 1;
return data;
}
}
// normalize name and get id from index
char *name = strdup(_name);
sjis_toupper(name);
char *dot = strrchr(name, '.');
if (dot)
*dot = '\0';
uintptr_t id = (uintptr_t)ht_get(manager->index, name, NULL);
free(name);
if (!id)
return false;
if (id_out)
*id_out = id;
return _afa_get_by_id(_manager, id, ar_out, no_out);
return NULL;
}
static bool afa_exists_by_name(struct asset_manager *manager, const char *name, int *id_out)
static bool afa_exists_by_name(struct asset_manager *_manager, const char *name, int *id_out)
{
int no;
struct afa_archive *ar;
return _afa_get_by_name(manager, name, &ar, &no, id_out);
}
struct asset_manager_afa *manager = (struct asset_manager_afa*)_manager;
static struct archive_data *afa_get_by_name(struct asset_manager *manager, const char *name, int *id_out)
{
int no;
struct afa_archive *ar;
if (!_afa_get_by_name(manager, name, &ar, &no, id_out))
return NULL;
return archive_get(&ar->ar, no);
for (unsigned i = 0; i < MAX_ARCHIVES && manager->archives[i]; i++) {
if (archive_exists_by_basename(&manager->archives[i]->ar, name, id_out)) {
if (id_out)
*id_out += 1;
return true;
}
}
return false;
}
static void _ald_init(enum asset_type type, struct archive *ar)
@@ -347,18 +294,14 @@ static void afa_init(enum asset_type type, char *file)
if (!ar)
ERROR("Failed to open AFA file: %s", archive_strerror(error));
if (id_indexed_afa) {
_ald_init(type, &ar->ar);
} else {
struct asset_manager_afa *manager = xcalloc(1, sizeof(struct asset_manager_afa));
manager->manager.load_archive = afa_load_archive;
manager->manager.exists_by_id = afa_exists_by_id;
manager->manager.exists_by_name = afa_exists_by_name;
manager->manager.get_by_id = afa_get_by_id;
manager->manager.get_by_name = afa_get_by_name;
manager->archives[0] = ar;
assets[type] = &manager->manager;
}
struct asset_manager_afa *manager = xcalloc(1, sizeof(struct asset_manager_afa));
manager->manager.load_archive = afa_load_archive;
manager->manager.exists_by_id = afa_exists_by_id;
manager->manager.get_by_id = afa_get_by_id;
manager->manager.exists_by_name = afa_exists_by_name;
manager->manager.get_by_name = afa_get_by_name;
manager->archives[0] = ar;
assets[type] = &manager->manager;
free(file);
}
@@ -391,7 +334,7 @@ void asset_manager_init(void)
// get ALD filenames
while ((d_name = readdir_utf8(dir))) {
// archive filename must begin with ain filename
if (strncmp(base, d_name, base_len))
if (strncasecmp(base, d_name, base_len))
goto loop_next;
const char *ext = file_extension(d_name);
if (!ext)
@@ -460,6 +403,8 @@ void asset_manager_init(void)
afa_filenames[ASSET_FLAT] = path_join(config.game_dir, d_name);
} else if (!strcmp(type, "Pact.afa")) {
afa_filenames[ASSET_PACT] = path_join(config.game_dir, d_name);
} else if (!strcmp(type, "AFF.afa")) {
afa_filenames[ASSET_FLASH] = path_join(config.game_dir, d_name);
}
} else if (!strcasecmp(ext, "fnl")) {
if (!config.fnl_path)
@@ -479,6 +424,7 @@ void asset_manager_init(void)
ald_init(ASSET_FLAT, ald_filenames[ASSET_FLAT], ald_count[ASSET_FLAT]);
ald_init(ASSET_PACT, ald_filenames[ASSET_PACT], ald_count[ASSET_PACT]);
ald_init(ASSET_DATA, ald_filenames[ASSET_DATA], ald_count[ASSET_DATA]);
ald_init(ASSET_FLASH, ald_filenames[ASSET_FLASH], ald_count[ASSET_FLASH]);
// open AFA archives
afa_init(ASSET_BGM, afa_filenames[ASSET_BGM]);
@@ -488,4 +434,5 @@ void asset_manager_init(void)
afa_init(ASSET_FLAT, afa_filenames[ASSET_FLAT]);
afa_init(ASSET_PACT, afa_filenames[ASSET_PACT]);
afa_init(ASSET_DATA, afa_filenames[ASSET_DATA]);
afa_init(ASSET_FLASH, afa_filenames[ASSET_FLASH]);
}
+64 -7
View File
@@ -43,8 +43,8 @@ void audio_init(void)
if (audio_initialized)
return;
id_pool_init(&wav);
id_pool_init(&bgm);
id_pool_init(&wav, 0);
id_pool_init(&bgm, 0);
for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) {
anonymous_channels[i] = -1;
@@ -54,6 +54,19 @@ void audio_init(void)
audio_initialized = true;
}
void audio_reset(void)
{
for (int i = id_pool_get_first(&wav); i >= 0; i = id_pool_get_next(&wav, i)) {
wav_unprepare(i);
}
for (int i = id_pool_get_first(&bgm); i >= 0; i = id_pool_get_next(&bgm, i)) {
bgm_unprepare(i);
}
for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) {
anonymous_channels[i] = -1;
}
}
void audio_update(void)
{
for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) {
@@ -85,6 +98,25 @@ bool audio_play_sound(int sound_no)
return false;
}
bool audio_play_archive_data(struct archive_data *dfile)
{
for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) {
if (anonymous_channels[i] == -1) {
int ch = wav_get_unused_channel();
if (!wav_prepare_from_archive_data(ch, dfile))
return false;
if (!wav_play(ch)) {
wav_unprepare(ch);
return false;
}
anonymous_channels[i] = ch;
return true;
}
}
WARNING("Failed to allocate anonymous audio channel");
return false;
}
bool wav_exists(int no) { return asset_exists(ASSET_SOUND, no); }
bool bgm_exists(int no) { return asset_exists(ASSET_BGM, no); }
@@ -104,11 +136,10 @@ int bgm_prepare(int id, int no) { return audio_prepare(&bgm, id, ASSET_BGM, no);
static int audio_unprepare(struct id_pool *pool, int id)
{
struct channel *ch = id_pool_get(pool, id);
struct channel *ch = id_pool_release(pool, id);
if (!ch)
return 0;
channel_close(ch);
id_pool_release(pool, id);
return 1;
}
@@ -330,10 +361,36 @@ static int audio_get_time_length(struct id_pool *pool, int id)
int wav_get_time_length(int id) { return audio_get_time_length(&wav, id); }
int bgm_get_time_length(int id) { return audio_get_time_length(&bgm, id); }
//int wav_get_group_num(int channel);
static int audio_get_data_no(struct id_pool *pool, int id)
{
struct channel *ch = id_pool_get(pool, id);
if (!ch)
return -1;
return channel_get_data_no(ch);
}
int wav_get_group_num(int id)
{
int no = audio_get_data_no(&wav, id);
if (no < 0)
return 0;
return wav_get_group_num_from_data_num(no);
}
//int bgm_get_group_num(int channel);
//int wav_prepare_from_file(int channel, char *filename);
//int bgm_prepare_from_file(int channel, char *filename);
static int audio_prepare_from_file(struct id_pool *pool, int id, char *filename)
{
if (id < 0)
return 0;
struct channel *ch = channel_open_file(filename);
struct channel *old = id_pool_set(pool, id, ch);
if (old)
channel_close(old);
return !!ch;
}
int wav_prepare_from_file(int id, char *filename) { return audio_prepare_from_file(&wav, id, filename); }
int bgm_prepare_from_file(int id, char *filename) { return audio_prepare_from_file(&bgm, id, filename); }
static int audio_prepare_from_archive_data(struct id_pool *pool, int id, struct archive_data *dfile)
{
+2 -2
View File
@@ -18,9 +18,9 @@
#include <stdlib.h>
#include "system4.h"
#include "system4/file.h"
#include "system4/little_endian.h"
#include "mixer.h"
#include "little_endian.h"
#include "xsystem4.h"
#define BGI_MAX 100
@@ -31,7 +31,7 @@ static struct bgi bgi_data[BGI_MAX];
static char *bgi_gets(char *buf, int n, FILE *fp)
{
char *s = buf;
int c;
int c = EOF;
while (--n > 0 && (c = fgetc(fp)) != EOF) {
c = c >> 4 | (c & 0xf) << 4; // decrypt
*s++ = c;
+63 -22
View File
@@ -22,6 +22,7 @@
#include "system4.h"
#include "system4/archive.h"
#include "system4/utfsjis.h"
#include "asset_manager.h"
#include "audio.h"
@@ -238,6 +239,7 @@ static int refill_stream(sts_mixer_sample_t *sample, void *data)
ch->fade.elapsed += frames_read;
if (ch->fade.elapsed >= ch->fade.frames) {
ch->fade.fading = false;
ch->volume = ch->fade.end_volume * 100.0;
if (ch->fade.stop) {
cb_seek(ch, 0);
r = STS_STREAM_COMPLETE;
@@ -437,6 +439,11 @@ int channel_get_time_length(struct channel *ch)
return muldiv(ch->info.frames, 1000, ch->info.samplerate);
}
int channel_get_data_no(struct channel *ch)
{
return ch->dfile ? ch->dfile->no : -1;
}
static sf_count_t channel_vio_get_filelen(void *data)
{
return ((struct channel*)data)->dfile->size;
@@ -519,6 +526,8 @@ struct channel *channel_open(enum asset_type type, int no)
ch->loop_end = clamp(0, ch->info.frames, bgi->loop_end);
ch->loop_count = max(0, bgi->loop_count);
ch->mixer_no = clamp(0, nr_mixers, bgi->channel);
} else {
ch->loop_count = 0;
}
}
ch->no = no;
@@ -526,20 +535,15 @@ struct channel *channel_open(enum asset_type type, int no)
return ch;
}
struct channel *channel_open_archive_data(struct archive_data *dfile)
static bool init_channel(struct channel *ch)
{
struct channel *ch = xcalloc(1, sizeof(struct channel));
ch->dfile = dfile;
// open file
ch->file = sf_open_virtual(&channel_vio, SFM_READ, &ch->info, ch);
if (sf_error(ch->file) != SF_ERR_NO_ERROR) {
WARNING("sf_open_virtual failed: %s", sf_strerror(ch->file));
goto error;
return false;
}
if (ch->info.channels > 2) {
WARNING("Audio file has more than 2 channels");
goto error;
return false;
}
// create stream
@@ -554,23 +558,57 @@ struct channel *channel_open_archive_data(struct archive_data *dfile)
ch->volume = 100;
ch->loop_start = 0;
ch->loop_end = ch->info.frames;
ch->loop_count = 0;
ch->loop_count = 1;
ch->mixer_no = 0;
ch->no = -1;
return ch;
return true;
}
error:
archive_free_data(dfile);
free(ch);
return NULL;
struct channel *channel_open_archive_data(struct archive_data *dfile)
{
struct channel *ch = xcalloc(1, sizeof(struct channel));
ch->dfile = dfile;
// open file
ch->file = sf_open_virtual(&channel_vio, SFM_READ, &ch->info, ch);
if (!init_channel(ch)) {
archive_free_data(dfile);
if (ch->file)
sf_close(ch->file);
free(ch);
return NULL;
}
return ch;
}
struct channel *channel_open_file(const char *path)
{
struct channel *ch = xcalloc(1, sizeof(struct channel));
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
ch->file = sf_wchar_open(wpath, SFM_READ, &ch->info);
free(wpath);
#else
ch->file = sf_open(path, SFM_READ, &ch->info);
#endif
if (!init_channel(ch)) {
if (ch->file)
sf_close(ch->file);
free(ch);
return NULL;
}
return ch;
}
void channel_close(struct channel *ch)
{
channel_stop(ch);
sf_close(ch->file);
archive_free_data(ch->dfile);
if (ch->dfile)
archive_free_data(ch->dfile);
free(ch);
}
@@ -586,6 +624,7 @@ void mixer_init(void)
mixers[0].name = strdup("Music");
mixers[1].name = strdup("Sound");
mixers[2].name = strdup("Master");
master = &mixers[2];
} else {
// NOTE: Older games don't have an explicit master channel.
// We add an extra mixer in this case for consistency.
@@ -663,19 +702,21 @@ void mixer_init(void)
int mixer_get_numof(void)
{
return nr_mixers;
// Return the number of mixers specified in System40.ini, even if
// xsystem4 added more mixers.
return config.mixer_nr_channels;
}
const char *mixer_get_name(int n)
{
if (n < 0 || n >= nr_mixers)
if (n < 0 || n >= config.mixer_nr_channels)
return NULL;
return mixers[n].name;
}
int mixer_set_name(int n, const char *name)
{
if (n < 0 || n >= nr_mixers)
if (n < 0 || n >= config.mixer_nr_channels)
return 0;
free(mixers[n].name);
mixers[n].name = strdup(name);
@@ -684,7 +725,7 @@ int mixer_set_name(int n, const char *name)
int mixer_get_volume(int n, int *volume)
{
if (n < 0 || n >= nr_mixers) {
if (n < 0 || n >= config.mixer_nr_channels) {
return 0;
}
SDL_LockAudioDevice(audio_device);
@@ -695,7 +736,7 @@ int mixer_get_volume(int n, int *volume)
int mixer_set_volume(int n, int volume)
{
if (n < 0 || n >= nr_mixers)
if (n < 0 || n >= config.mixer_nr_channels)
return 0;
SDL_LockAudioDevice(audio_device);
mixers[n].mixer.gain = clamp(0.0f, 1.0f, (float)volume / 100.0f);
@@ -704,7 +745,7 @@ int mixer_set_volume(int n, int volume)
}
int mixer_get_mute(int n, int *mute)
{
if (n < 0 || n >= nr_mixers)
if (n < 0 || n >= config.mixer_nr_channels)
return 0;
*mute = mixers[n].muted;
return 1;
@@ -712,7 +753,7 @@ int mixer_get_mute(int n, int *mute)
int mixer_set_mute(int n, int mute)
{
if (n < 0 || n >= nr_mixers)
if (n < 0 || n >= config.mixer_nr_channels)
return 0;
mixers[n].muted = !!mute;
return 1;
+81
View File
@@ -0,0 +1,81 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
*/
#include "system4.h"
#include "base64.h"
static const unsigned char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* base64_encode - Base64 encode
* @src: Data to be encoded
* @len: Length of the data to be encoded
* @out_len: Pointer to output length variable, or %NULL if not used
* Returns: Allocated buffer of out_len bytes of encoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer. Returned buffer is
* nul terminated to make it easier to use as a C string. The nul terminator is
* not included in out_len.
*/
unsigned char *base64_encode(const unsigned char *src, size_t len,
size_t *out_len)
{
unsigned char *out, *pos;
const unsigned char *end, *in;
size_t olen;
int line_len;
olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
olen += olen / 72; /* line feeds */
olen++; /* nul termination */
if (olen < len)
return NULL; /* integer overflow */
out = xmalloc(olen);
if (out == NULL)
return NULL;
end = src + len;
in = src;
pos = out;
line_len = 0;
while (end - in >= 3) {
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64_table[in[2] & 0x3f];
in += 3;
line_len += 4;
if (line_len >= 72) {
*pos++ = '\n';
line_len = 0;
}
}
if (end - in) {
*pos++ = base64_table[in[0] >> 2];
if (end - in == 1) {
*pos++ = base64_table[(in[0] & 0x03) << 4];
*pos++ = '=';
} else {
*pos++ = base64_table[((in[0] & 0x03) << 4) |
(in[1] >> 4)];
*pos++ = base64_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
line_len += 4;
}
if (line_len)
*pos++ = '\n';
*pos = '\0';
if (out_len)
*out_len = pos - out;
return out;
}
+142 -15
View File
@@ -24,7 +24,9 @@
#include <assert.h>
#include "system4/dasm.h"
#include "system4/file.h"
#include "system4/hashtable.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
@@ -33,11 +35,12 @@
#include "vm/page.h"
#include "debugger.h"
#include "little_endian.h"
#include "xsystem4.h"
bool dbg_dap = false;
bool dbg_enabled = true;
bool dbg_start_in_debugger = false;
struct dbg_info *dbg_info = NULL;
unsigned dbg_current_frame = 0;
static jmp_buf dbg_continuation;
@@ -72,24 +75,50 @@ void dbg_start(void(*fun)(void*), void *data)
fun(data);
}
static void _dbg_repl(void *_)
static void _dbg_repl(void *stop)
{
dbg_cmd_repl();
if (dbg_dap)
dbg_dap_repl(stop);
else
dbg_cmd_repl();
}
void dbg_repl(void)
void dbg_repl(enum dbg_stop_type type, const char *msg)
{
if (!dbg_enabled)
return;
dbg_start(_dbg_repl, NULL);
struct dbg_stop stop = {
.type = type,
.message = msg,
};
dbg_start(_dbg_repl, &stop);
}
void dbg_init(void)
void dbg_init(const char *debug_info_path)
{
dbg_cmd_init();
if (debug_info_path) {
dbg_info = dbg_info_load(debug_info_path);
} else {
// Try to load from the default location
char *path = gamedir_path("src/debug_info.json");
if (file_exists(path)) {
dbg_info = dbg_info_load(path);
}
free(path);
}
if (dbg_dap)
dbg_dap_init();
else
dbg_cmd_init();
#ifdef HAVE_SCHEME
dbg_scm_init();
#endif
if (!dbg_dap && dbg_start_in_debugger) {
dbg_repl(DBG_STOP_PAUSE, "");
}
}
void dbg_fini(void)
@@ -135,6 +164,38 @@ static struct breakpoint *get_breakpoint(uint32_t addr)
return ht_get_int(bp_table, addr, NULL);
}
bool dbg_clear_breakpoint(uint32_t addr, void(*free_data)(void*))
{
struct breakpoint *bp = get_breakpoint(addr);
if (!bp)
return false;
if (free_data)
free_data(bp->data);
delete_breakpoint(addr, bp);
return true;
}
struct dbg_foreach_data {
void (*fun)(int addr, struct breakpoint*, void *data);
void *data;
};
void dbg_foreach_breakpoint_cb(struct ht_slot *slot, void *d_)
{
if (!slot->value)
return;
struct dbg_foreach_data *d = d_;
d->fun(slot->ikey, slot->value, d->data);
}
void dbg_foreach_breakpoint(void (*fun)(int addr, struct breakpoint*, void *data), void *data)
{
if (!bp_table)
return;
struct dbg_foreach_data d = { fun, data };
ht_foreach(bp_table, dbg_foreach_breakpoint_cb, &d);
}
bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data)
{
char *name = utf2sjis(_name, 0);
@@ -157,7 +218,7 @@ bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*
LittleEndian_putW(ain->code, f->address, BREAKPOINT | bp->restore_op);
add_breakpoint(f->address, bp);
printf("Set breakpoint at function '%s' (0x%08x)\n", display_utf0(_name), f->address);
log_message("debug", "Set breakpoint at function '%s' (0x%08x)\n", display_utf0(_name), f->address);
return true;
}
@@ -185,7 +246,7 @@ bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*),
LittleEndian_putW(ain->code, address, BREAKPOINT | bp->restore_op);
add_breakpoint(address, bp);
printf("Set breakpoint at 0x%08x\n", address);
log_message("debug", "Set breakpoint at 0x%08x\n", address);
return true;
}
@@ -196,7 +257,8 @@ static void dbg_step_breakpoint_cb(struct breakpoint *bp)
if ((intptr_t)bp->data != call_stack_ptr)
return;
delete_breakpoint(instr_ptr, bp);
dbg_cmd_repl();
struct dbg_stop stop = { DBG_STOP_STEP, NULL };
_dbg_repl(&stop);
}
static void dbg_set_step_breakpoint(int32_t address, int call_index)
@@ -384,8 +446,9 @@ static void _dbg_handle_breakpoint(void *data)
if (bp->cb) {
bp->cb(bp);
} else {
printf("%s\n", bp->message);
dbg_cmd_repl();
log_message("debug", "%s\n", bp->message);
struct dbg_stop stop = { DBG_STOP_BREAKPOINT, NULL };
_dbg_repl(&stop);
}
}
@@ -408,8 +471,14 @@ void dbg_print_frame(unsigned no)
unsigned cs_no = call_stack_ptr - (1 + no);
struct ain_function *f = &ain->functions[call_stack[cs_no].fno];
uint32_t addr = no ? call_stack[cs_no+1].call_address : instr_ptr;
printf("%c #%d 0x%08x in %s\n", no == dbg_current_frame ? '*' : ' ',
sys_message("%c #%d 0x%08x in %s", no == dbg_current_frame ? '*' : ' ',
no, addr, display_sjis0(f->name));
int file, line;
if (dbg_info && dbg_info_addr2line(dbg_info, addr, &file, &line)) {
sys_message(" at %s:%d\n", display_utf0(dbg_info_source_name(dbg_info, file)), line);
} else {
sys_message("\n");
}
}
void dbg_print_stack_trace(void)
@@ -570,7 +639,7 @@ error:
static union vm_value dbg_eval_object(struct parse_object *object, struct ain_type *type_out)
{
if (!strcmp(object->ident->text, "this")) {
int32_t page_i = call_stack[call_stack_ptr-1].struct_page;
int32_t page_i = call_stack[call_stack_ptr- (dbg_current_frame + 1)].struct_page;
if (page_i < 0) {
DBG_ERROR("'this' used outside of method");
goto error;
@@ -695,6 +764,8 @@ static void dbg_indent(struct string **s, int indent_level)
}
static struct string *_dbg_value_to_string(struct ain_type *type, union vm_value value,
int recursive, int indent);
static struct string *_dbg_variable_to_string(
struct ain_type *type, struct page *page, int slot, int recursive, int indent);
static struct string *dbg_string_to_string(struct string *str)
{
@@ -719,10 +790,12 @@ static struct string *dbg_struct_to_string(struct ain_type *type, union vm_value
struct string *out = cstr_to_string("{\n");
for (int i = 0; i < page->nr_vars; i++) {
struct ain_variable *m = &ain->structures[type->struc].members[i];
if (m->type.data == AIN_VOID)
continue;
dbg_indent(&out, indent + 1);
string_append_cstr(&out, m->name, strlen(m->name));
string_append_cstr(&out, " = ", 3);
struct string *tmp = _dbg_value_to_string(&m->type, page->values[i],
struct string *tmp = _dbg_variable_to_string(&m->type, page, i,
recursive - 1, indent + 1);
string_append(&out, tmp);
free_string(tmp);
@@ -802,6 +875,34 @@ struct string *dbg_value_to_string(struct ain_type *type, union vm_value value,
return _dbg_value_to_string(type, value, recursive, 0);
}
static struct string *_dbg_variable_to_string(
struct ain_type *type, struct page *page, int slot, int recursive, int indent)
{
enum ain_data_type value_type;
switch (type->data) {
case AIN_REF_INT: value_type = AIN_INT; break;
case AIN_REF_LONG_INT: value_type = AIN_LONG_INT; break;
case AIN_REF_BOOL: value_type = AIN_BOOL; break;
case AIN_REF_FLOAT: value_type = AIN_FLOAT; break;
default:
return _dbg_value_to_string(type, page->values[slot], recursive, indent);
}
// get referenced value
int pageno = page->values[slot].i;
int varno = page->values[slot + 1].i;
if (pageno < 0) {
return cstr_to_string("NULL");
}
struct ain_type t = {.data = value_type};
return _dbg_value_to_string(&t, heap[pageno].page->values[varno], recursive, indent);
}
struct string *dbg_variable_to_string(struct ain_type *type, struct page *page, int slot, int recursive)
{
return _dbg_variable_to_string(type, page, slot, recursive, 0);
}
// the maximum number of instructions preceeding the instruction pointer to be displayed
#define DASM_REWIND 16
// the number of instructions following the instruction pointer to be displayed
@@ -1065,3 +1166,29 @@ void dbg_print_vm_state(void)
putchar('\n');
}
}
void dbg_print_current_line(void)
{
if (call_stack_ptr < 1) {
DBG_ERROR("VM not running");
return;
}
if (!dbg_info) {
DBG_ERROR("No debug information available");
return;
}
int file, line;
if (!dbg_info_addr2line(dbg_info, instr_ptr, &file, &line)) {
DBG_ERROR("No line number information for address: 0x%08x", instr_ptr);
return;
}
const char *src_line = dbg_info_source_line(dbg_info, file, line);
if (!src_line) {
DBG_ERROR("No source text for %s:%d", display_utf0(dbg_info_source_name(dbg_info, file)), line);
return;
}
printf("%s:%d\t%s\n", display_utf0(dbg_info_source_name(dbg_info, file)), line, display_utf1(src_line));
}
+232
View File
@@ -0,0 +1,232 @@
/* Copyright (C) 2024 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system4/file.h"
#include "cJSON.h"
#include "debugger.h"
struct mapping {
int addr;
int file;
int line;
};
struct source_content {
int nr_lines;
char *lines[];
};
struct dbg_info {
char *src_root;
int nr_sources;
char **sources;
struct source_content **source_contents;
int nr_mappings;
struct mapping *mappings;
};
struct dbg_info *dbg_info_load(const char *path)
{
char *json_text = file_read(path, NULL);
if (!json_text) {
DBG_ERROR("Cannot load debug information file %s: %s", path, strerror(errno));
return NULL;
}
cJSON *root = cJSON_Parse(json_text);
free(json_text);
cJSON *version = cJSON_GetObjectItem(root, "version");
if (!cJSON_IsString(version) || strcmp(version->valuestring, "alpha-1")) {
DBG_ERROR("%s: Unsupported debug information format", display_utf0(path));
cJSON_Delete(root);
return NULL;
}
struct dbg_info *info = xcalloc(1, sizeof(struct dbg_info));
cJSON *sources = cJSON_GetObjectItem(root, "sources");
info->nr_sources = cJSON_GetArraySize(sources);
info->sources = xcalloc(info->nr_sources, sizeof(char *));
cJSON *source;
int i = 0;
cJSON_ArrayForEach(source, sources) {
info->sources[i++] = xstrdup(source->valuestring);
}
cJSON *mappings = cJSON_GetObjectItem(root, "mappings");
info->nr_mappings = cJSON_GetArraySize(mappings);
info->mappings = xcalloc(info->nr_mappings, sizeof(struct mapping));
struct mapping *m = info->mappings;
cJSON *item;
cJSON_ArrayForEach(item, mappings) {
m->addr = cJSON_GetArrayItem(item, 0)->valueint;
m->file = cJSON_GetArrayItem(item, 1)->valueint;
m->line = cJSON_GetArrayItem(item, 2)->valueint;
m++;
}
cJSON_Delete(root);
info->src_root = xstrdup(path_dirname(path));
info->source_contents = xcalloc(info->nr_sources, sizeof(struct source_content *));
return info;
}
static struct source_content *load_source_content(const char *path)
{
char *text = file_read(path, NULL);
if (!text) {
DBG_ERROR("Cannot load source file %s", path);
return NULL;
}
int nr_lines = 1; // count the last line even if it doesn't end with '\n'
for (char *p = text; *p; p++) {
if (*p == '\n')
nr_lines++;
}
struct source_content *content = xcalloc(1, sizeof(struct source_content) + nr_lines * sizeof(char *));
content->nr_lines = nr_lines;
for (int i = 0;; i++) {
content->lines[i] = text;
char *next = strchr(text, '\n');
if (!next)
break;
*next = '\0';
text = next + 1;
}
return content;
}
const char *dbg_info_source_name(const struct dbg_info *info, int file)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
return info->sources[file];
}
char *dbg_info_source_path(const struct dbg_info *info, int file)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
return path_join(info->src_root, info->sources[file]);
}
const char *dbg_info_source_line(const struct dbg_info *info, int file, int line)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
if (!info->source_contents[file]) {
char *path = dbg_info_source_path(info, file);
info->source_contents[file] = load_source_content(path);
free(path);
if (!info->source_contents[file])
return NULL;
}
if (line <= 0 || line > info->source_contents[file]->nr_lines)
return NULL;
return info->source_contents[file]->lines[line - 1];
}
int dbg_info_find_file(const struct dbg_info *info, const char *filename)
{
if (is_absolute_path(filename)) {
char *target = realpath_utf8(filename); // Convert '\\' to '/' on Windows
char *prefix = realpath_utf8(info->src_root);
for (int i = 0; i < info->nr_sources; i++) {
char *path = path_join(prefix, info->sources[i]);
if (!strcasecmp(path, target)) {
free(path);
free(prefix);
free(target);
return i;
}
free(path);
}
free(prefix);
free(target);
} else {
for (int i = 0; i < info->nr_sources; i++) {
if (!strcasecmp(info->sources[i], filename) ||
!strcasecmp(path_basename(info->sources[i]), filename)) {
return i;
}
}
}
return -1;
}
bool dbg_info_addr2line(const struct dbg_info *info, int addr, int *file, int *line)
{
// Find the last mapping whose address is less than or equal to `addr`.
int left = 0, right = info->nr_mappings;
while (left < right) {
int mid = (left + right) / 2;
if (info->mappings[mid].addr <= addr)
left = mid + 1;
else
right = mid;
}
if (left == 0) {
assert(info->nr_mappings == 0 || addr < info->mappings[0].addr);
return false;
}
assert(info->mappings[left - 1].addr <= addr);
assert(left == info->nr_mappings || addr < info->mappings[left].addr);
if (file)
*file = info->mappings[left - 1].file;
if (line)
*line = info->mappings[left - 1].line;
return true;
}
int dbg_info_line2addr(const struct dbg_info *info, int file, int line)
{
if (file < 0 || file >= info->nr_sources || line <= 0)
return -1;
// Find the first mapping whose file is `file` and line is greater than or equal to `line`.
int left = 0, right = info->nr_mappings;
while (left < right) {
int mid = (left + right) / 2;
if (info->mappings[mid].file < file || (info->mappings[mid].file == file && info->mappings[mid].line < line))
left = mid + 1;
else
right = mid;
}
if (left == info->nr_mappings) {
assert(info->nr_mappings == 0 || info->mappings[info->nr_mappings - 1].file < file ||
(info->mappings[info->nr_mappings - 1].file == file && info->mappings[info->nr_mappings - 1].line < line));
return -1;
}
assert(left == 0 || info->mappings[left - 1].file < file ||
(info->mappings[left - 1].file == file && info->mappings[left - 1].line < line));
if (file < info->mappings[left].file)
return -1;
assert(file == info->mappings[left].file && line <= info->mappings[left].line);
return info->mappings[left].addr;
}
+80 -16
View File
@@ -26,6 +26,7 @@
#include "system4.h"
#include "system4/file.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
@@ -35,11 +36,18 @@
#include "scene.h"
#include "debugger.h"
#include "little_endian.h"
#include "input.h"
#include "xsystem4.h"
struct dbg_cmd_node;
static bool stepping = false;
static enum {
NOT_STEPPING = 0,
STEPPING_INTO,
STEPPING_OVER,
STEPPING_FINISH
} stepping = NOT_STEPPING;
static int stepping_file = 0;
static int stepping_line = 0;
struct dbg_cmd_list {
unsigned nr_commands;
@@ -66,17 +74,33 @@ static void dbg_cmd_backtrace(unsigned nr_args, char **args)
static void dbg_cmd_breakpoint(unsigned nr_args, char **args)
{
int addr = strtol(args[0], NULL, 0);
if (addr > 0) {
if (nr_args == 1) {
int addr = strtol(args[0], NULL, 0);
if (addr > 0) {
dbg_set_address_breakpoint(addr, NULL, NULL);
} else {
dbg_set_function_breakpoint(args[0], NULL, NULL);
}
} else if (nr_args == 2 && dbg_info) {
int file = dbg_info_find_file(dbg_info, args[0]);
if (file < 0) {
DBG_ERROR("No such file: %s", args[0]);
return;
}
int line = atoi(args[1]);
int addr = dbg_info_line2addr(dbg_info, file, line);
if (addr < 0) {
DBG_ERROR("No such line: %s:%d", args[0], line);
return;
}
dbg_set_address_breakpoint(addr, NULL, NULL);
} else {
dbg_set_function_breakpoint(args[0], NULL, NULL);
DBG_ERROR("Invalid breakpoint command");
}
}
static void dbg_cmd_continue(unsigned nr_args, char **args)
{
stepping = false;
dbg_continue();
}
@@ -102,7 +126,9 @@ static void dbg_cmd_locals(unsigned nr_args, char **args)
struct page *page = heap_get_page(call_stack[call_stack_ptr - (frame_no+1)].page_slot);
struct ain_function *f = &ain->functions[page->index];
for (int i = 0; i < f->nr_vars; i++) {
struct string *value = dbg_value_to_string(&f->vars[i].type, page->values[i], 1);
if (f->vars[i].type.data == AIN_VOID)
continue;
struct string *value = dbg_variable_to_string(&f->vars[i].type, page, i, 1);
printf("[%d] %s: %s\n", i, display_sjis0(f->vars[i].name), display_sjis1(value->text));
free_string(value);
}
@@ -114,7 +140,9 @@ static void log_handler(struct breakpoint *bp)
struct page *locals = local_page();
printf("%s(", display_sjis0(f->name));
for (int i = 0; i < f->nr_args; i++) {
struct string *value = dbg_value_to_string(&f->vars[i].type, locals->values[i], 1);
if (f->vars[i].type.data == AIN_VOID)
continue;
struct string *value = dbg_variable_to_string(&f->vars[i].type, locals, i, 1);
printf(i > 0 ? ", %s" : "%s", display_sjis0(value->text));
free_string(value);
}
@@ -151,7 +179,9 @@ static void dbg_cmd_members(unsigned nr_args, char **args)
assert(page->nr_vars == s->nr_members);
for (int i = 0; i < s->nr_members; i++) {
struct string *value = dbg_value_to_string(&s->members[i].type, page->values[i], 1);
if (s->members[i].type.data == AIN_VOID)
continue;
struct string *value = dbg_variable_to_string(&s->members[i].type, page, i, 1);
printf("[%d] %s: %s\n", i, display_sjis0(s->members[i].name), display_sjis1(value->text));
free_string(value);
}
@@ -183,21 +213,29 @@ static void dbg_cmd_scene(unsigned nr_args, char **args)
static void dbg_cmd_next(unsigned nr_args, char **args)
{
stepping_file = stepping_line = 0;
if (dbg_info)
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
if (!dbg_set_step_over_breakpoint()) {
DBG_ERROR("Can't step over this instruction");
return;
}
stepping = true;
stepping = STEPPING_OVER;
dbg_continue();
}
static void dbg_cmd_step(unsigned nr_args, char **args)
{
stepping_file = stepping_line = 0;
if (dbg_info)
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
if (!dbg_set_step_into_breakpoint()) {
DBG_ERROR("Can't step into this instruction");
return;
}
stepping = true;
stepping = STEPPING_INTO;
dbg_continue();
}
@@ -207,7 +245,7 @@ static void dbg_cmd_finish(unsigned nr_args, char **args)
DBG_ERROR("Can't set finish breakpoint from this location");
return;
}
stepping = true;
stepping = STEPPING_FINISH;
dbg_continue();
}
@@ -225,7 +263,7 @@ static void dbg_cmd_vm_state(unsigned nr_args, char **args)
static struct dbg_cmd dbg_default_commands[] = {
{ "backtrace", "bt", NULL, "Display stack trace", 0, 0, dbg_cmd_backtrace },
{ "breakpoint", "bp", "<function-or-address>", "Set breakpoint at a function or address", 1, 1, dbg_cmd_breakpoint },
{ "breakpoint", "bp", "<function> | <address> | <file> <line>", "Set breakpoint", 1, 2, dbg_cmd_breakpoint },
{ "continue", "c", NULL, "Resume execution", 0, 0, dbg_cmd_continue },
{ "finish", "fin", NULL, "Execute until the current function returns", 0, 0, dbg_cmd_finish },
{ "frame", "f", "<frame-number>", "Set the current frame", 1, 1, dbg_cmd_frame },
@@ -374,11 +412,19 @@ void dbg_cmd_add_module(const char *name, unsigned nr_commands, struct dbg_cmd *
#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
static int cmd_rl_event_hook(void)
{
handle_window_events();
return 1;
}
/*
* Get a string from the user. The result can be modified but shouldn't be free'd.
*/
static char *cmd_gets(void)
{
rl_event_hook = cmd_rl_event_hook;
static char *line_read = NULL;
free(line_read);
line_read = readline("dbg(cmd)> ");
@@ -394,6 +440,7 @@ static char *cmd_gets(void)
{
static char line[1024];
handle_window_events();
printf("dbg(cmd)> ");
fflush(stdout);
fgets(line, 1024, stdin);
@@ -467,10 +514,27 @@ static void execute_line(char *line)
void dbg_cmd_repl(void)
{
if (stepping)
dbg_print_vm_state();
else
if (stepping) {
if (stepping_line) {
int file, line;
dbg_info_addr2line(dbg_info, instr_ptr, &file, &line);
if (file == stepping_file && line == stepping_line) {
if (stepping == STEPPING_INTO)
dbg_set_step_into_breakpoint();
else if (stepping == STEPPING_OVER)
dbg_set_step_over_breakpoint();
dbg_continue();
}
}
stepping = NOT_STEPPING;
stepping_file = stepping_line = 0;
if (dbg_info)
dbg_print_current_line();
else
dbg_print_vm_state();
} else {
puts("Entering the debugger REPL. Type 'help' for a list of commands.");
}
while (1) {
char *line = cmd_gets();
if (line)
+1092
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -23,6 +23,7 @@
#include "system4/ain.h"
#include "system4/file.h"
#include "system4/instructions.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
#include "vm.h"
@@ -30,7 +31,6 @@
#include "vm/page.h"
#include "debugger.h"
#include "little_endian.h"
struct variable {
enum ain_data_type data_type;
+160 -17
View File
@@ -64,6 +64,7 @@ static struct copy_shader copy_key_shader;
static struct copy_shader copy_alpha_key_shader;
static struct copy_shader copy_use_amap_under_shader;
static struct copy_shader copy_use_amap_border_shader;
static struct copy_shader copy_grayscale_shader;
static struct copy_shader blend_amap_color_shader;
static struct copy_shader blend_amap_alpha_bright_shader;
static struct copy_shader blend_use_amap_color_shader;
@@ -121,6 +122,9 @@ void gfx_draw_init(void)
// copy shader that only keeps fragments above a certain alpha threshold
load_copy_shader(&copy_use_amap_border_shader, "shaders/render.v.glsl", "shaders/copy_use_amap_border.f.glsl");
// copy shader with grayscale conversion
load_copy_shader(&copy_grayscale_shader, "shaders/render.v.glsl", "shaders/copy_grayscale.f.glsl");
// copy shader that sets source RGB to a constant
load_copy_shader(&blend_amap_color_shader, "shaders/render.v.glsl", "shaders/blend_amap_color.f.glsl");
@@ -163,6 +167,7 @@ static void run_draw_shader(Shader *s, Texture *dst, Texture *src, mat4 mw_trans
struct gfx_render_job job = {
.shader = s,
.shape = GFX_RECTANGLE,
.texture = src ? src->handle : 0,
.world_transform = mw_transform[0],
.view_transform = wv_transform[0],
@@ -702,6 +707,16 @@ void gfx_copy_stretch_blend(struct texture *dst, int dx, int dy, int dw, int dh,
restore_blend_mode();
}
void gfx_copy_stretch_blend_screen(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh)
{
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE);
struct copy_data data = STRETCH_DATA(dx, dy, dw, dh, sx, sy, sw, sh);
run_copy_shader(&copy_shader.s, dst, src, &data);
restore_blend_mode();
}
void gfx_copy_stretch_blend_amap(struct texture *dst, int dx, int dy, int dw, int dh, struct texture *src, int sx, int sy, int sw, int sh)
{
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -729,6 +744,8 @@ void gfx_copy_stretch_blend_amap_alpha(struct texture *dst, int dx, int dy, int
// FIXME: this doesn't work correctly when the src rectangle crosses the edge of the CG.
static void copy_rot_zoom(Texture *dst, Texture *src, int sx, int sy, int w, int h, float rotate, float mag, Shader *shader)
{
w = min(w, src->w - sx);
h = min(h, src->h - sy);
// 1. scale src vertices to texture size
// 2. translate so that center point of copy region is at origin
// 3. rotate
@@ -768,18 +785,45 @@ void gfx_copy_rot_zoom_use_amap(Texture *dst, Texture *src, int sx, int sy, int
copy_rot_zoom(dst, src, sx, sy, w, h, rotate, mag, &hitbox_shader.s);
}
void gfx_copy_rot_zoom2(Texture *dst, float cx, float cy, Texture *src, float scx, float scy, float rot, float mag)
{
gfx_fill_amap(dst, 0, 0, dst->w, dst->h, 0);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
// 1. scale src vertices to texture size
// 2. translate so that center point of copy region is at origin
// 3. rotate
// 4. scale
// 4. tranlate to center point of dst texture
// (applied in reverse order)
mat4 mw_transform = GLM_MAT4_IDENTITY_INIT;
glm_translate(mw_transform, (vec3){ cx, cy, 0 });
glm_scale(mw_transform, (vec3){ mag, mag, 0 });
glm_rotate_z(mw_transform, -rot * (M_PI/180.0), mw_transform);
glm_translate(mw_transform, (vec3){ -scx, -scy, 0 });
glm_scale(mw_transform, (vec3){ src->w, src->h, 1 });
mat4 wv_transform = WV_TRANSFORM(dst->w, dst->h);
struct copy_data data = ROTATE_DATA(dst, 0, 0, src->w, src->h);
run_draw_shader(&hitbox_shader.s, dst, src, mw_transform, wv_transform, &data);
restore_blend_mode();
}
static void copy_rotate_y(Texture *dst, Texture *front, Texture *back, int sx, int sy, int w, int h, float rot, float mag, Shader *shader)
{
Texture *src = front;
vec3 scale = { src->w * mag, src->h * mag, 0 };
if (rot > 90.0 && rot <= 270.0) {
src = back;
scale[0] *= -1.0f;
}
gfx_fill_amap(dst, 0, 0, dst->w, dst->h, 0);
mat4 mw_transform = GLM_MAT4_IDENTITY_INIT;
glm_rotate_y(mw_transform, rot * (M_PI/180.0), mw_transform);
glm_scale(mw_transform, (vec3){ src->w * mag, src->h * mag, 0 });
glm_scale(mw_transform, scale);
glm_translate(mw_transform, (vec3){ -0.5, -0.5, 0});
mat4 proj_transform;
@@ -859,6 +903,23 @@ void gfx_copy_reverse_LR(Texture *dst, int dx, int dy, Texture *src, int sx, int
restore_blend_mode();
}
void gfx_copy_reverse_UD(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
mat4 mw_transform = MAT4(
src->w, 0, 0, -sx,
0, -src->h, 0, sy + h,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(w, h);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ONE);
struct copy_data data = COPY_DATA(dx, dy, sx, sy, w, h);
run_draw_shader(&copy_shader.s, dst, src, mw_transform, wv_transform, &data);
restore_blend_mode();
}
void gfx_copy_reverse_amap_LR(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
mat4 mw_transform = MAT4(
@@ -876,6 +937,23 @@ void gfx_copy_reverse_amap_LR(Texture *dst, int dx, int dy, Texture *src, int sx
restore_blend_mode();
}
void gfx_copy_reverse_LR_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
mat4 mw_transform = MAT4(
-src->w, 0, 0, sx + w,
0, src->h, 0, -sy,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(w, h);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
struct copy_data data = COPY_DATA(dx, dy, sx, sy, w, h);
run_draw_shader(&copy_shader.s, dst, src, mw_transform, wv_transform, &data);
restore_blend_mode();
}
void gfx_copy_width_blur(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h, int blur)
{
GLfloat f_rate = 1.0 / (blur * 2 + 1);
@@ -1006,30 +1084,95 @@ void gfx_copy_stretch_with_alpha_map(Texture *dst, int dx, int dy, int dw, int d
restore_blend_mode();
}
// XXX: Not an actual DrawGraph function; used for rendering text
void gfx_draw_glyph(Texture *dst, float dx, int dy, Texture *glyph, SDL_Color color, float scale_x, float bold_width)
void gfx_copy_grayscale(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
dx = roundf(dx);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ONE);
struct copy_data fill_data = COPY_DATA(dx, dy, dx, dy, glyph->w * scale_x, glyph->h);
fill_data.r = color.r / 255.0;
fill_data.g = color.g / 255.0;
fill_data.b = color.b / 255.0;
fill_data.a = 0.0;
fill_data.threshold = 0.001;
run_copy_shader(&fill_amap_under_border_shader.s, dst, dst, &fill_data);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX);
struct copy_data data = COPY_DATA(dx, dy, sx, sy, w, h);
run_copy_shader(&copy_grayscale_shader.s, dst, src, &data);
restore_blend_mode();
}
static void draw_line(Texture *dst, int x0, int y0, int x1, int y1, struct copy_data *data)
{
GLfloat w = dst->w;
GLfloat h = dst->h;
mat4 mw_transform = MAT4(
x1 - x0, 0, 0, x0,
0, y1 - y0, 0, y0,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(w, h);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, 0, 0, w, h);
struct gfx_render_job job = {
.shader = &fill_shader.s,
.shape = GFX_LINE,
.texture = 0,
.world_transform = mw_transform[0],
.view_transform = wv_transform[0],
.data = data
};
gfx_render(&job);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
}
void gfx_draw_line(Texture *dst, int x0, int y0, int x1, int y1, int r, int g, int b)
{
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ONE);
struct copy_data data = {
.r = r / 255.0,
.g = g / 255.0,
.b = b / 255.0,
.a = 1
};
draw_line(dst, x0, y0, x1, y1, &data);
restore_blend_mode();
}
void gfx_draw_line_to_amap(Texture *dst, int x0, int y0, int x1, int y1, int a)
{
glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_ONE, GL_ZERO);
struct copy_data data = { .a = a / 255.0 };
draw_line(dst, x0, y0, x1, y1, &data);
restore_blend_mode();
}
void gfx_draw_quadrilateral(Texture *dst, Texture *src, struct gfx_vertex vertices[4])
{
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ONE);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, 0, 0, dst->w, dst->h);
gfx_render_quadrilateral(src, vertices);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
restore_blend_mode();
}
// XXX: Not an actual DrawGraph function; used for rendering text
void gfx_draw_glyph(Texture *dst, float dx, int dy, Texture *glyph, SDL_Color color, float scale_x, float bold_width, bool blend)
{
if (blend) {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
} else {
glBlendFunc(GL_ONE, GL_ZERO);
glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX);
}
dx = roundf(dx);
struct copy_data data = STRETCH_DATA(
dx, dy, glyph->w * scale_x, glyph->h,
0, 0, glyph->w, glyph->h);
data.r = color.r / 255.0;
data.g = color.g / 255.0;
data.b = color.b / 255.0;
data.a = 1.0;
data.a = 0.01; // discard threshold
if (bold_width < 0.01) {
run_copy_shader(&blend_rmap_color_shader.s, dst, glyph, &data);
@@ -1049,7 +1192,7 @@ void gfx_draw_glyph_to_pmap(Texture *dst, float dx, int dy, Texture *glyph, Rect
data.r = color.r / 255.0;
data.g = color.g / 255.0;
data.b = color.b / 255.0;
data.a = 1.0;
data.a = 0.01; // discard threshold
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
run_copy_shader(&blend_rmap_color_shader.s, dst, glyph, &data);
restore_blend_mode();
@@ -1063,7 +1206,7 @@ void gfx_draw_glyph_to_amap(Texture *dst, float dx, int dy, Texture *glyph, Rect
data.r = 1.0;
data.g = 1.0;
data.b = 1.0;
data.a = 1.0;
data.a = 0.0; // discard threshold
glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_ONE, GL_ZERO);
run_copy_shader(&blend_rmap_color_shader.s, dst, glyph, &data);
restore_blend_mode();
+465 -73
View File
@@ -22,26 +22,12 @@
#include "dungeon/dgn.h"
#include "vm.h"
struct dgn *dgn_parse(uint8_t *data, size_t size)
struct dgn *dgn_new(uint32_t size_x, uint32_t size_y, uint32_t size_z)
{
struct buffer r;
buffer_init(&r, data, size);
if (strncmp(buffer_strdata(&r), "DUGN", 4))
return NULL;
buffer_skip(&r, 4);
struct dgn *dgn = xcalloc(1, sizeof(struct dgn));
dgn->version = buffer_read_int32(&r);
if (dgn->version != DGN_VER_RANCE6 && dgn->version != DGN_VER_GALZOO) {
WARNING("unknown DGN version: %d", dgn->version);
free(dgn);
return NULL;
}
dgn->size_x = buffer_read_int32(&r);
dgn->size_y = buffer_read_int32(&r);
dgn->size_z = buffer_read_int32(&r);
buffer_skip(&r, 40);
dgn->size_x = size_x;
dgn->size_y = size_y;
dgn->size_z = size_z;
int nr_cells = dgn_nr_cells(dgn);
dgn->cells = xcalloc(nr_cells, sizeof(struct dgn_cell));
uint32_t x = 0, y = 0, z = 0;
@@ -50,15 +36,62 @@ struct dgn *dgn_parse(uint8_t *data, size_t size)
cell->x = x;
cell->y = y;
cell->z = z;
if (++x == dgn->size_x) {
if (++x == size_x) {
x = 0;
if (++y == dgn->size_y) {
if (++y == size_y) {
y = 0;
z++;
}
}
cell->event_blend_rate = 255;
cell->floor = -1;
cell->ceiling = -1;
cell->north_wall = -1;
cell->south_wall = -1;
cell->east_wall = -1;
cell->west_wall = -1;
cell->north_door = -1;
cell->south_door = -1;
cell->east_door = -1;
cell->west_door = -1;
cell->stairs_texture = -1;
cell->stairs_orientation = -1;
cell->lightmap_floor = -1;
cell->lightmap_ceiling = -1;
cell->lightmap_north = -1;
cell->lightmap_south = -1;
cell->lightmap_east = -1;
cell->lightmap_west = -1;
cell->battle_background = -1;
cell->polyobj_index = -1;
cell->roof_orientation = -1;
cell->roof_texture = -1;
cell->roof_underside_texture = -1;
cell->pathfinding_cost = -1;
}
return dgn;
}
struct dgn *dgn_parse(uint8_t *data, size_t size, bool for_draw_field)
{
struct buffer r;
buffer_init(&r, data, size);
if (strncmp(buffer_strdata(&r), "DUGN", 4))
return NULL;
buffer_skip(&r, 4);
uint32_t version = buffer_read_int32(&r);
uint32_t size_x = buffer_read_int32(&r);
uint32_t size_y = buffer_read_int32(&r);
uint32_t size_z = buffer_read_int32(&r);
buffer_skip(&r, 40);
struct dgn *dgn = dgn_new(size_x, size_y, size_z);
int nr_cells = dgn_nr_cells(dgn);
for (int i = 0; i < nr_cells; i++) {
struct dgn_cell *cell = &dgn->cells[i];
cell->floor = buffer_read_int32(&r);
cell->ceiling = buffer_read_int32(&r);
cell->north_wall = buffer_read_int32(&r);
@@ -71,7 +104,13 @@ struct dgn *dgn_parse(uint8_t *data, size_t size)
cell->west_door = buffer_read_int32(&r);
cell->stairs_texture = buffer_read_int32(&r);
cell->stairs_orientation = buffer_read_int32(&r);
buffer_skip(&r, 13 * 4);
cell->lightmap_floor = buffer_read_int32(&r);
cell->lightmap_ceiling = buffer_read_int32(&r);
cell->lightmap_north = buffer_read_int32(&r);
cell->lightmap_south = buffer_read_int32(&r);
cell->lightmap_east = buffer_read_int32(&r);
cell->lightmap_west = buffer_read_int32(&r);
buffer_skip(&r, 7 * 4); // lightmap parameters for doors / stairs, 2 unknowns
cell->enterable = buffer_read_int32(&r);
cell->enterable_north = buffer_read_int32(&r);
cell->enterable_south = buffer_read_int32(&r);
@@ -87,64 +126,98 @@ struct dgn *dgn_parse(uint8_t *data, size_t size)
buffer_skip(&r, 4);
buffer_skip(&r, strlen(buffer_strdata(&r)) + 1);
}
buffer_skip(&r, 4);
cell->battle_background = buffer_read_int32(&r);
if (dgn->version != DGN_VER_GALZOO) {
cell->polyobj_index = -1;
cell->roof_orientation = -1;
cell->roof_texture = -1;
cell->roof_underside_texture = -1;
continue;
if (for_draw_field) {
if (version >= 9) {
cell->north_door_lock = buffer_read_int32(&r);
cell->west_door_lock = buffer_read_int32(&r);
cell->south_door_lock = buffer_read_int32(&r);
cell->east_door_lock = buffer_read_int32(&r);
cell->north_door_angle = buffer_read_float(&r);
cell->west_door_angle = buffer_read_float(&r);
cell->south_door_angle = buffer_read_float(&r);
cell->east_door_angle = buffer_read_float(&r);
cell->walked = buffer_read_int32(&r);
}
if (version >= 11) {
cell->polyobj_index = buffer_read_int32(&r);
cell->polyobj_scale = buffer_read_float(&r);
cell->polyobj_rotation_y = buffer_read_float(&r);
cell->polyobj_rotation_z = buffer_read_float(&r);
cell->polyobj_rotation_x = buffer_read_float(&r);
cell->polyobj_position_x = buffer_read_float(&r);
cell->polyobj_position_y = buffer_read_float(&r);
cell->polyobj_position_z = buffer_read_float(&r);
}
} else {
buffer_skip(&r, 4); // unknown
cell->battle_background = buffer_read_int32(&r);
if (version != DGN_VER_GALZOO) {
continue;
}
cell->polyobj_index = buffer_read_int32(&r);
cell->polyobj_scale = buffer_read_float(&r);
cell->polyobj_rotation_y = buffer_read_float(&r);
cell->polyobj_rotation_z = buffer_read_float(&r);
cell->polyobj_rotation_x = buffer_read_float(&r);
cell->polyobj_position_x = buffer_read_float(&r);
cell->polyobj_position_y = buffer_read_float(&r);
cell->polyobj_position_z = buffer_read_float(&r);
cell->roof_orientation = buffer_read_int32(&r);
cell->roof_texture = buffer_read_int32(&r);
buffer_skip(&r, 4); // unknown
cell->roof_underside_texture = buffer_read_int32(&r);
buffer_skip(&r, 4); // unknown
}
cell->polyobj_index = buffer_read_int32(&r);
cell->polyobj_scale = buffer_read_float(&r);
cell->polyobj_rotation_y = buffer_read_float(&r);
cell->polyobj_rotation_z = buffer_read_float(&r);
cell->polyobj_rotation_x = buffer_read_float(&r);
cell->polyobj_position_x = buffer_read_float(&r);
cell->polyobj_position_y = buffer_read_float(&r);
cell->polyobj_position_z = buffer_read_float(&r);
cell->roof_orientation = buffer_read_int32(&r);
cell->roof_texture = buffer_read_int32(&r);
buffer_skip(&r, 4);
cell->roof_underside_texture = buffer_read_int32(&r);
buffer_skip(&r, 4);
}
if (buffer_read_u8(&r) != 0 || buffer_read_int32(&r) != 1) {
if (buffer_read_u8(&r) != 0) {
dgn_free(dgn);
return NULL;
}
dgn->pvs = xcalloc(nr_cells, sizeof(struct packed_pvs));
for (int i = 0; i < nr_cells; i++) {
struct packed_pvs *pvs = &dgn->pvs[i];
int32_t len = buffer_read_int32(&r);
if (len % 8 != 4) {
WARNING("dgn_parse: unexpected PVS length");
dgn_free(dgn);
return NULL;
}
if (buffer_read_int32(&r) != nr_cells) {
WARNING("dgn_parse: bad PVS");
dgn_free(dgn);
return NULL;
}
pvs->nr_run_lengths = (len - 4) / 8;
pvs->run_lengths = xmalloc(pvs->nr_run_lengths * sizeof(struct pvs_run_lengths));
int32_t total = 0;
for (int j = 0; j < pvs->nr_run_lengths; j++) {
int32_t invisible = buffer_read_int32(&r);
int32_t visible = buffer_read_int32(&r);
pvs->run_lengths[j].invisible_cells = invisible;
pvs->run_lengths[j].visible_cells = visible;
pvs->nr_visible_cells += visible;
total += invisible + visible;
}
if (total != nr_cells) {
WARNING("dgn_parse: bad PVS");
dgn_free(dgn);
return NULL;
int has_pvs = buffer_read_int32(&r);
if (has_pvs) {
dgn->pvs = xcalloc(nr_cells, sizeof(struct packed_pvs));
for (int i = 0; i < nr_cells; i++) {
struct packed_pvs *pvs = &dgn->pvs[i];
int32_t len = buffer_read_int32(&r);
if (len % 8 != 4) {
WARNING("dgn_parse: unexpected PVS length");
dgn_free(dgn);
return NULL;
}
if (buffer_read_int32(&r) != nr_cells) {
WARNING("dgn_parse: bad PVS");
dgn_free(dgn);
return NULL;
}
pvs->nr_run_lengths = (len - 4) / 8;
pvs->run_lengths = xmalloc(pvs->nr_run_lengths * sizeof(struct pvs_run_lengths));
int32_t total = 0;
for (int j = 0; j < pvs->nr_run_lengths; j++) {
int32_t invisible = buffer_read_int32(&r);
int32_t visible = buffer_read_int32(&r);
pvs->run_lengths[j].invisible_cells = invisible;
pvs->run_lengths[j].visible_cells = visible;
pvs->nr_visible_cells += visible;
total += invisible + visible;
}
if (total != nr_cells) {
WARNING("dgn_parse: bad PVS");
dgn_free(dgn);
return NULL;
}
}
}
dgn->sphere_theta[0] = buffer_read_float(&r);
dgn->sphere_theta[1] = buffer_read_float(&r);
dgn->sphere_theta[2] = buffer_read_float(&r);
dgn->sphere_color_top = buffer_read_float(&r);
dgn->sphere_color_bottom = buffer_read_float(&r);
buffer_skip(&r, 4); // unknown
if (for_draw_field && version >= 10) {
dgn->back_color_r = buffer_read_int32(&r);
dgn->back_color_g = buffer_read_int32(&r);
dgn->back_color_b = buffer_read_int32(&r);
}
return dgn;
}
@@ -204,7 +277,7 @@ static int pvs_cell_compare(const void *a, const void *b)
return ((struct pvs_cell *)a)->distance - ((struct pvs_cell *)b)->distance;
}
struct dgn_cell **dgn_get_visible_cells(struct dgn *dgn, int x, int y, int z, int *nr_cells_out)
static struct dgn_cell **dgn_get_visible_cells_pvs(struct dgn *dgn, int x, int y, int z, int *nr_cells_out)
{
int cell_index = dgn_cell_index(dgn, x, y, z);
struct dgn_cell *cell = &dgn->cells[cell_index];
@@ -236,3 +309,322 @@ struct dgn_cell **dgn_get_visible_cells(struct dgn *dgn, int x, int y, int z, in
*nr_cells_out = pvs->nr_visible_cells;
return cell->visible_cells;
}
static struct dgn_cell **dgn_get_visible_cells_nopvs(struct dgn *dgn, int x, int y, int z, int *nr_cells_out)
{
int cell_index = dgn_cell_index(dgn, x, y, z);
struct dgn_cell *cell = &dgn->cells[cell_index];
int nr_cells = dgn_nr_cells(dgn);
if (!cell->visible_cells) {
struct pvs_cell *pvs_cells = xmalloc(nr_cells * sizeof(struct pvs_cell));
for (int i = 0; i < nr_cells; i++) {
struct dgn_cell *c = &dgn->cells[i];
int dx = c->x - x;
int dy = c->y - y;
int dz = c->z - z;
pvs_cells[i].cell = c;
pvs_cells[i].distance = dx * dx + dy * dy + dz * dz;
}
qsort(pvs_cells, nr_cells, sizeof(struct pvs_cell), pvs_cell_compare);
cell->visible_cells = xmalloc(nr_cells * sizeof(struct dgn_cell *));
for (int i = 0; i < nr_cells; i++) {
cell->visible_cells[i] = pvs_cells[i].cell;
}
free(pvs_cells);
}
*nr_cells_out = nr_cells;
return cell->visible_cells;
}
struct dgn_cell **dgn_get_visible_cells(struct dgn *dgn, int x, int y, int z, int *nr_cells_out)
{
if (dgn->pvs)
return dgn_get_visible_cells_pvs(dgn, x, y, z, nr_cells_out);
else
return dgn_get_visible_cells_nopvs(dgn, x, y, z, nr_cells_out);
}
#define LM_N (1 << 0)
#define LM_S (1 << 1)
#define LM_W (1 << 2)
#define LM_E (1 << 3)
#define LM_NW (1 << 4)
#define LM_NE (1 << 5)
#define LM_SW (1 << 6)
#define LM_SE (1 << 7)
static int lightmap_texture_index(uint8_t flags)
{
if (flags & LM_N || flags & LM_W) flags &= ~LM_NW;
if (flags & LM_N || flags & LM_E) flags &= ~LM_NE;
if (flags & LM_S || flags & LM_W) flags &= ~LM_SW;
if (flags & LM_S || flags & LM_E) flags &= ~LM_SE;
switch (flags) {
case LM_N | LM_W | LM_E: return 0;
case LM_W | LM_E: return 1;
case LM_S | LM_W | LM_E: return 2;
case LM_N | LM_S | LM_W: return 3;
case LM_N | LM_S: return 4;
case LM_N | LM_S | LM_E: return 5;
case LM_N | LM_W: return 6;
case LM_N: return 7;
case LM_N | LM_E: return 8;
case LM_W: return 9;
case LM_E: return 10;
case LM_S | LM_W: return 11;
case LM_S: return 12;
case LM_S | LM_E: return 13;
case LM_N | LM_S | LM_W | LM_E: return 14;
case LM_W | LM_NE | LM_SE: return 15;
case LM_N | LM_SW | LM_SE: return 16;
case LM_E | LM_NW | LM_SW: return 17;
case LM_S | LM_NW | LM_NE: return 18;
case LM_NW | LM_NE | LM_SW | LM_SE: return 19;
case LM_W | LM_NE: return 20;
case LM_N | LM_SE: return 21;
case LM_E | LM_SW: return 22;
case LM_S | LM_NW: return 23;
case LM_NE: return 24;
case LM_W | LM_SE: return 25;
case LM_N | LM_SW: return 26;
case LM_E | LM_NW: return 27;
case LM_S | LM_NE: return 28;
case LM_SE: return 29;
case LM_NE | LM_SE: return 30;
case LM_SW | LM_SE: return 31;
case LM_NW | LM_SW: return 32;
case LM_NW | LM_NE: return 33;
case LM_SW: return 34;
case LM_NW: return 35;
case LM_N | LM_W | LM_SE: return 36;
case LM_N | LM_E | LM_SW: return 37;
case LM_S | LM_E | LM_NW: return 38;
case LM_S | LM_W | LM_NE: return 39;
case LM_NW | LM_NE | LM_SW: return 40;
case LM_NW | LM_NE | LM_SE: return 41;
case LM_NE | LM_SW | LM_SE: return 42;
case LM_NW | LM_SW | LM_SE: return 43;
case LM_NE | LM_SW: return 44;
case LM_NW | LM_SE: return 45;
default: return -1;
};
}
static int calc_floor_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *north_cell, struct dgn_cell *south_cell,
struct dgn_cell *west_cell, struct dgn_cell *east_cell)
{
if (cell->floor == -1)
return -1;
uint8_t flags = 0;
if (cell->north_wall != -1 || cell->north_door != -1 ||
(north_cell && north_cell->stairs_texture != -1 && north_cell->stairs_orientation == 0))
flags |= LM_N;
if (cell->south_wall != -1 || cell->south_door != -1 ||
(south_cell && south_cell->stairs_texture != -1 && south_cell->stairs_orientation == 2))
flags |= LM_S;
if (cell->west_wall != -1 || cell->west_door != -1 ||
(west_cell && west_cell->stairs_texture != -1 && west_cell->stairs_orientation == 1))
flags |= LM_W;
if (cell->east_wall != -1 || cell->east_door != -1 ||
(east_cell && east_cell->stairs_texture != -1 && east_cell->stairs_orientation == 3))
flags |= LM_E;
if ((north_cell && (north_cell->west_wall != -1 || north_cell->west_door != -1)) ||
(west_cell && (west_cell->north_wall != -1 || west_cell->north_door != -1)))
flags |= LM_NW;
if ((north_cell && (north_cell->east_wall != -1 || north_cell->east_door != -1)) ||
(east_cell && (east_cell->north_wall != -1 || east_cell->north_door != -1)))
flags |= LM_NE;
if ((south_cell && (south_cell->west_wall != -1 || south_cell->west_door != -1)) ||
(west_cell && (west_cell->south_wall != -1 || west_cell->south_door != -1)))
flags |= LM_SW;
if ((south_cell && (south_cell->east_wall != -1 || south_cell->east_door != -1)) ||
(east_cell && (east_cell->south_wall != -1 || east_cell->south_door != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
static int calc_ceiling_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *north_cell, struct dgn_cell *south_cell,
struct dgn_cell *west_cell, struct dgn_cell *east_cell)
{
if (cell->ceiling == -1)
return -1;
uint8_t flags = 0;
if (cell->north_wall != -1 || cell->north_door != -1)
flags |= LM_N;
if (cell->south_wall != -1 || cell->south_door != -1)
flags |= LM_S;
if (cell->west_wall != -1 || cell->west_door != -1)
flags |= LM_E;
if (cell->east_wall != -1 || cell->east_door != -1)
flags |= LM_W;
if ((north_cell && (north_cell->east_wall != -1 || north_cell->east_door != -1)) ||
(east_cell && (east_cell->north_wall != -1 || east_cell->north_door != -1)))
flags |= LM_NW;
if ((north_cell && (north_cell->west_wall != -1 || north_cell->west_door != -1)) ||
(west_cell && (west_cell->north_wall != -1 || west_cell->north_door != -1)))
flags |= LM_NE;
if ((south_cell && (south_cell->east_wall != -1 || south_cell->east_door != -1)) ||
(east_cell && (east_cell->south_wall != -1 || east_cell->south_door != -1)))
flags |= LM_SW;
if ((south_cell && (south_cell->west_wall != -1 || south_cell->west_door != -1)) ||
(west_cell && (west_cell->south_wall != -1 || west_cell->south_door != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
static int calc_north_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *up_cell, struct dgn_cell *down_cell,
struct dgn_cell *west_cell, struct dgn_cell *east_cell)
{
if (cell->north_wall == -1)
return -1;
uint8_t flags = 0;
if (cell->ceiling != -1)
flags |= LM_N;
if (cell->floor != -1)
flags |= LM_S;
if (cell->west_wall != -1 || cell->west_door != -1)
flags |= LM_W;
if (cell->east_wall != -1 || cell->east_door != -1)
flags |= LM_E;
if ((up_cell && (up_cell->west_wall != -1 || up_cell->west_door != -1)) ||
(west_cell && (west_cell->ceiling != -1)))
flags |= LM_NW;
if ((up_cell && (up_cell->east_wall != -1 || up_cell->east_door != -1)) ||
(east_cell && (east_cell->ceiling != -1)))
flags |= LM_NE;
if ((down_cell && (down_cell->west_wall != -1 || down_cell->west_door != -1)) ||
(west_cell && (west_cell->floor != -1)))
flags |= LM_SW;
if ((down_cell && (down_cell->east_wall != -1 || down_cell->east_door != -1)) ||
(east_cell && (east_cell->floor != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
static int calc_south_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *up_cell, struct dgn_cell *down_cell,
struct dgn_cell *west_cell, struct dgn_cell *east_cell)
{
if (cell->south_wall == -1)
return -1;
uint8_t flags = 0;
if (cell->ceiling != -1)
flags |= LM_N;
if (cell->floor != -1)
flags |= LM_S;
if (cell->east_wall != -1 || cell->east_door != -1)
flags |= LM_W;
if (cell->west_wall != -1 || cell->west_door != -1)
flags |= LM_E;
if ((up_cell && (up_cell->east_wall != -1 || up_cell->east_door != -1)) ||
(east_cell && (east_cell->ceiling != -1)))
flags |= LM_NW;
if ((up_cell && (up_cell->west_wall != -1 || up_cell->west_door != -1)) ||
(west_cell && (west_cell->ceiling != -1)))
flags |= LM_NE;
if ((down_cell && (down_cell->east_wall != -1 || down_cell->east_door != -1)) ||
(east_cell && (east_cell->floor != -1)))
flags |= LM_SW;
if ((down_cell && (down_cell->west_wall != -1 || down_cell->west_door != -1)) ||
(west_cell && (west_cell->floor != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
static int calc_west_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *up_cell, struct dgn_cell *down_cell,
struct dgn_cell *north_cell, struct dgn_cell *south_cell)
{
if (cell->west_wall == -1)
return -1;
uint8_t flags = 0;
if (cell->ceiling != -1)
flags |= LM_N;
if (cell->floor != -1)
flags |= LM_S;
if (cell->south_wall != -1 || cell->south_door != -1)
flags |= LM_W;
if (cell->north_wall != -1 || cell->north_door != -1)
flags |= LM_E;
if ((up_cell && (up_cell->south_wall != -1 || up_cell->south_door != -1)) ||
(south_cell && (south_cell->ceiling != -1)))
flags |= LM_NW;
if ((up_cell && (up_cell->north_wall != -1 || up_cell->north_door != -1)) ||
(north_cell && (north_cell->ceiling != -1)))
flags |= LM_NE;
if ((down_cell && (down_cell->south_wall != -1 || down_cell->south_door != -1)) ||
(south_cell && (south_cell->floor != -1)))
flags |= LM_SW;
if ((down_cell && (down_cell->north_wall != -1 || down_cell->north_door != -1)) ||
(north_cell && (north_cell->floor != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
static int calc_east_lightmap(struct dgn *dgn, struct dgn_cell *cell,
struct dgn_cell *up_cell, struct dgn_cell *down_cell,
struct dgn_cell *north_cell, struct dgn_cell *south_cell)
{
if (cell->east_wall == -1)
return -1;
uint8_t flags = 0;
if (cell->ceiling != -1)
flags |= LM_N;
if (cell->floor != -1)
flags |= LM_S;
if (cell->north_wall != -1 || cell->north_door != -1)
flags |= LM_W;
if (cell->south_wall != -1 || cell->south_door != -1)
flags |= LM_E;
if ((up_cell && (up_cell->north_wall != -1 || up_cell->north_door != -1)) ||
(north_cell && (north_cell->ceiling != -1)))
flags |= LM_NW;
if ((up_cell && (up_cell->south_wall != -1 || up_cell->south_door != -1)) ||
(south_cell && (south_cell->ceiling != -1)))
flags |= LM_NE;
if ((down_cell && (down_cell->north_wall != -1 || down_cell->north_door != -1)) ||
(north_cell && (north_cell->floor != -1)))
flags |= LM_SW;
if ((down_cell && (down_cell->south_wall != -1 || down_cell->south_door != -1)) ||
(south_cell && (south_cell->floor != -1)))
flags |= LM_SE;
return lightmap_texture_index(flags);
}
void dgn_calc_lightmap(struct dgn *dgn)
{
int nr_cells = dgn_nr_cells(dgn);
for (int i = 0; i < nr_cells; i++) {
struct dgn_cell *cell = &dgn->cells[i];
struct dgn_cell *north = cell->z + 1 < dgn->size_z
? dgn_cell_at(dgn, cell->x, cell->y, cell->z + 1) : NULL;
struct dgn_cell *south = cell->z > 0
? dgn_cell_at(dgn, cell->x, cell->y, cell->z - 1) : NULL;
struct dgn_cell *west = cell->x > 0
? dgn_cell_at(dgn, cell->x - 1, cell->y, cell->z) : NULL;
struct dgn_cell *east = cell->x + 1 < dgn->size_x
? dgn_cell_at(dgn, cell->x + 1, cell->y, cell->z) : NULL;
struct dgn_cell *up = cell->y + 1 < dgn->size_y
? dgn_cell_at(dgn, cell->x, cell->y + 1, cell->z) : NULL;
struct dgn_cell *down = cell->y > 0
? dgn_cell_at(dgn, cell->x, cell->y - 1, cell->z) : NULL;
cell->lightmap_floor = calc_floor_lightmap(dgn, cell, north, south, west, east);
cell->lightmap_ceiling = calc_ceiling_lightmap(dgn, cell, north, south, west, east);
cell->lightmap_north = calc_north_lightmap(dgn, cell, up, down, west, east);
cell->lightmap_south = calc_south_lightmap(dgn, cell, up, down, west, east);
cell->lightmap_west = calc_west_lightmap(dgn, cell, up, down, north, south);
cell->lightmap_east = calc_east_lightmap(dgn, cell, up, down, north, south);
}
}
+120 -3
View File
@@ -14,13 +14,20 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "system4.h"
#include "system4/buffer.h"
#include "system4/cg.h"
#include "system4/file.h"
#include "system4/little_endian.h"
#include "xsystem4.h"
#include "dungeon/dtx.h"
#include "dungeon/mtrand43.h"
#define DTX_VERSION_DTL 0xffffffff
struct dtx *dtx_parse(uint8_t *data, size_t size)
{
@@ -68,12 +75,122 @@ void dtx_free(struct dtx *dtx)
free(dtx);
}
struct cg *dtx_create_cg(struct dtx *dtx, int type, int index)
struct cg *dtx_create_cg(struct dtx *dtx, enum dtx_texture_type type, int index)
{
if (type < 0 || type >= dtx->nr_rows || index < 0 || index >= dtx->nr_columns)
int row;
switch (dtx->version) {
case 0:
switch (type) {
case DTX_WALL: row = 0; break;
case DTX_FLOOR: row = 1; break;
case DTX_CEILING: row = 2; break;
case DTX_STAIRS: row = 3; break;
case DTX_DOOR: row = 4; break;
case DTX_SKYBOX: row = 5; break;
case DTX_LIGHTMAP: row = 6; break;
default: return NULL;
}
break;
case 1:
switch (type) {
case DTX_WALL: row = 0; break;
case DTX_FLOOR: row = 1; break;
case DTX_CEILING: row = 2; break;
case DTX_STAIRS: row = 3; break;
case DTX_DOOR: row = 4; break;
case DTX_SKYBOX: row = 5; break;
case DTX_LIGHTMAP: row = 7; break;
default: return NULL;
}
break;
case DTX_VERSION_DTL:
switch (type) {
case DTX_WALL: row = 0; break;
case DTX_FLOOR: row = 1; break;
case DTX_CEILING: row = 2; break;
case DTX_LIGHTMAP: row = 3; break;
default: return NULL;
}
break;
}
if (row >= dtx->nr_rows || index < 0 || index >= dtx->nr_columns)
return NULL;
struct dtx_entry *entry = &dtx->entries[type * dtx->nr_columns + index];
struct dtx_entry *entry = &dtx->entries[row * dtx->nr_columns + index];
if (!entry->data)
return NULL;
return cg_load_buffer(entry->data, entry->size);
}
struct dtx *dtx_load_from_dtl(const char *path, uint32_t seed)
{
uint8_t *index_buf = NULL;
struct dtx *dtx = NULL;
FILE *fp = file_open_utf8(path, "rb");
if (!fp) {
WARNING("Failed to open DTL file '%s': %s", display_utf0(path), strerror(errno));
goto err;
}
char header[12];
if (fread(header, sizeof(header), 1, fp) != 1 || strcmp(header, "DTL"))
goto err;
uint32_t version = LittleEndian_getDW((uint8_t*)header, 4);
uint32_t index_size = LittleEndian_getDW((uint8_t*)header, 8);
if (version != 0 || index_size <= 1 || index_size > 1000)
goto err;
index_buf = xmalloc(index_size * 4);
if (fread(index_buf, index_size * 4, 1, fp) != 1)
goto err;
// Randomly select an index based on the seed.
struct mtrand43 mt;
mtrand43_init(&mt, seed);
for (;;) {
int index = mtrand43_genrand(&mt) % (index_size - 1) + 1;
uint32_t offset = LittleEndian_getDW(index_buf, index * 4);
if (offset) {
if (fseek(fp, offset, SEEK_SET))
goto err;
break;
}
}
dtx = xcalloc(1, sizeof(struct dtx));
dtx->version = DTX_VERSION_DTL;
dtx->nr_rows = 4;
dtx->nr_columns = 46; // number of lightmap textures
dtx->entries = xcalloc(dtx->nr_rows * dtx->nr_columns, sizeof(struct dtx_entry));
uint8_t buf[4];
for (int row = 0; row < dtx->nr_rows; row++) {
if (fread(buf, sizeof(buf), 1, fp) != 1)
goto err;
uint32_t nr_items = LittleEndian_getDW(buf, 0);
for (int col = 0; col < nr_items; col++) {
if (fread(buf, sizeof(buf), 1, fp) != 1)
goto err;
uint32_t texture_size = LittleEndian_getDW(buf, 0);
if (col >= dtx->nr_columns) {
// skip the rest of the row
if (fseek(fp, texture_size, SEEK_CUR))
goto err;
continue;
}
struct dtx_entry *entry = &dtx->entries[row * dtx->nr_columns + col];
entry->size = texture_size;
entry->data = xmalloc(texture_size);
if (fread(entry->data, texture_size, 1, fp) != 1)
goto err;
}
}
free(index_buf);
fclose(fp);
return dtx;
err:
if (dtx)
dtx_free(dtx);
if (index_buf)
free(index_buf);
if (fp)
fclose(fp);
return NULL;
}
+415 -75
View File
@@ -30,64 +30,74 @@
#include "dungeon/dgn.h"
#include "dungeon/dtx.h"
#include "dungeon/dungeon.h"
#include "dungeon/generator.h"
#include "dungeon/map.h"
#include "dungeon/polyobj.h"
#include "dungeon/renderer.h"
#include "dungeon/tes.h"
#include "gfx/gfx.h"
#include "cJSON.h"
#include "json.h"
#include "sact.h"
#include "sprite.h"
#include "vm.h"
#include "vm/page.h"
#include "xsystem4.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
static const char plugin_name[] = "DrawDungeon";
static void dungeon_render(struct sact_sprite *sp);
static cJSON *dungeon_to_json(struct sact_sprite *sp, bool verbose);
static void dungeon_context_free(struct draw_plugin *plugin);
struct dungeon_context *dungeon_context_create(enum draw_dungeon_version version, int surface)
struct dungeon_context *dungeon_get_context(int surface)
{
struct sact_sprite *sp = sact_try_get_sprite(surface);
if (!sp || !sp->plugin || sp->plugin->name != plugin_name)
return NULL;
return (struct dungeon_context *)sp->plugin;
}
struct dungeon_context *dungeon_context_create(enum draw_dungeon_version version, int width, int height)
{
struct dungeon_context *ctx = xcalloc(1, sizeof(struct dungeon_context));
ctx->plugin.name = "DrawDungeon";
ctx->plugin.name = plugin_name;
ctx->plugin.free = dungeon_context_free;
ctx->plugin.update = dungeon_render;
ctx->plugin.debug_print = NULL;
ctx->plugin.to_json = dungeon_to_json;
ctx->version = version;
ctx->surface = surface;
struct sact_sprite *sp = sact_get_sprite(ctx->surface);
if (!sp)
VM_ERROR("DrawDungeon.Init: invalid surface %d", surface);
// Dungeon scene will be rendered to this texture.
struct texture *texture = sprite_get_texture(sp);
sprite_bind_plugin(sp, &ctx->plugin);
gfx_init_texture_blank(&ctx->texture, width, height);
glGenRenderbuffers(1, &ctx->depth_buffer);
glBindRenderbuffer(GL_RENDERBUFFER, ctx->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, texture->w, texture->h);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glm_perspective(GLM_PIf / 3.f, (float)width / height, 0.5f, 100.f, ctx->proj_transform);
ctx->map = dungeon_map_create(version);
if (version == DRAW_FIELD) {
ctx->characters = xcalloc(DRAWFIELD_NR_CHARACTERS, sizeof(struct drawfield_character));
}
return ctx;
}
void dungeon_context_free(struct dungeon_context *ctx)
static void dungeon_context_free(struct draw_plugin *plugin)
{
struct sact_sprite *sp = sact_try_get_sprite(ctx->surface);
if (sp)
sprite_bind_plugin(sp, NULL);
struct dungeon_context *ctx = (struct dungeon_context *)plugin;
if (ctx->dgn)
dgn_free(ctx->dgn);
if (ctx->dtx)
dtx_free(ctx->dtx);
if (ctx->tes)
tes_free(ctx->tes);
if (ctx->characters)
free(ctx->characters);
if (ctx->renderer)
dungeon_renderer_free(ctx->renderer);
gfx_delete_texture(&ctx->texture);
glDeleteRenderbuffers(1, &ctx->depth_buffer);
if (ctx->map)
dungeon_map_free(ctx->map);
@@ -158,7 +168,7 @@ bool dungeon_load(struct dungeon_context *ctx, int num)
struct archive_data *dgn = archive_get(dlf, num * 3);
if (dgn) {
ctx->dgn = dgn_parse(dgn->data, dgn->size);
ctx->dgn = dgn_parse(dgn->data, dgn->size, false);
archive_free_data(dgn);
}
struct archive_data *dtx = archive_get(dlf, num * 3 + 1);
@@ -172,6 +182,11 @@ bool dungeon_load(struct dungeon_context *ctx, int num)
archive_free_data(tes);
}
archive_free(dlf);
} else if (ctx->version == DRAW_DUNGEON_2) {
ctx->dgn = dgn_generate_drawdungeon2(num);
char *dtl_path = gamedir_path("Data/Texture.dtl");
ctx->dtx = dtx_load_from_dtl(dtl_path, (num * 25) | 1);
free(dtl_path);
} else if (ctx->version == DRAW_DUNGEON_14) {
char buf[100];
sprintf(buf, "Data/map%02d.dgn", num);
@@ -180,7 +195,7 @@ bool dungeon_load(struct dungeon_context *ctx, int num)
size_t len;
uint8_t *dgn = file_read(path, &len);
if (dgn) {
ctx->dgn = dgn_parse(dgn, len);
ctx->dgn = dgn_parse(dgn, len, false);
free(dgn);
}
@@ -220,7 +235,7 @@ bool dungeon_load(struct dungeon_context *ctx, int num)
if (!event_textures)
return false;
ctx->renderer = dungeon_renderer_create(ctx->version, ctx->dtx, event_textures, nr_event_textures, polyobj);
ctx->renderer = dungeon_renderer_create(ctx->version, num, ctx->dtx, event_textures, nr_event_textures, polyobj);
if (polyobj)
polyobj_free(polyobj);
@@ -231,6 +246,74 @@ bool dungeon_load(struct dungeon_context *ctx, int num)
return true;
}
bool dungeon_load_dungeon(struct dungeon_context *ctx, const char *filename, int num)
{
char *path = gamedir_path(filename);
size_t len;
uint8_t *dgn = file_read(path, &len);
if (!dgn) {
WARNING("Cannot load %s", path);
free(path);
return false;
}
free(path);
if (ctx->dgn)
dgn_free(ctx->dgn);
ctx->dgn = dgn_parse(dgn, len, ctx->version == DRAW_FIELD);
free(dgn);
if (!ctx->dgn)
return false;
dungeon_map_init(ctx);
ctx->loaded = ctx->dgn && ctx->dtx;
return true;
}
bool dungeon_load_texture(struct dungeon_context *ctx, const char *filename)
{
// load .dtx
char *path = gamedir_path(filename);
size_t dtx_len;
uint8_t *dtx = file_read(path, &dtx_len);
if (!dtx) {
WARNING("Cannot load %s", path);
free(path);
return false;
}
if (ctx->dtx)
dtx_free(ctx->dtx);
ctx->dtx = dtx_parse(dtx, dtx_len);
free(dtx);
if (!ctx->dtx) {
free(path);
return false;
}
// load .tes
if (ctx->tes)
tes_free(ctx->tes);
ctx->tes = NULL;
size_t path_len = strlen(path);
if (path_len > 4 && path[path_len - 4] == '.') {
strcpy(path + path_len - 4, ".tes"); // .dtx -> .tes
size_t tes_len;
uint8_t *tes = file_read(path, &tes_len);
if (tes) {
ctx->tes = tes_parse(tes, tes_len);
free(tes);
}
}
free(path);
if (ctx->renderer)
dungeon_renderer_free(ctx->renderer);
ctx->renderer = dungeon_renderer_create(ctx->version, 0, ctx->dtx, NULL, 0, NULL);
ctx->loaded = ctx->dgn && ctx->dtx;
return true;
}
void dungeon_set_camera(int surface, float x, float y, float z, float angle, float angle_p)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
@@ -239,23 +322,59 @@ void dungeon_set_camera(int surface, float x, float y, float z, float angle, flo
ctx->camera.pos[0] = x;
ctx->camera.pos[1] = y;
ctx->camera.pos[2] = -z;
ctx->camera.angle = -angle * M_PI / 180;
ctx->camera.angle_p = angle_p * M_PI / 180;
ctx->camera.angle = -glm_rad(angle);
ctx->camera.angle_p = glm_rad(angle_p);
if (ctx->version != DRAW_FIELD) {
ctx->player_pos[0] = roundf(x / 2.f);
ctx->player_pos[1] = roundf(y / 2.f);
ctx->player_pos[2] = roundf(z / 2.f);
}
}
static void model_view_matrix(struct camera *camera, mat4 out)
void dungeon_set_perspective(int surface, int width, int height, float near, float far, float deg)
{
// The actual camera position is slightly behind camera->pos.
float dist = 0.9;
vec3 d = {
dist * sin(camera->angle) * cos(camera->angle_p),
dist * sin(camera->angle_p),
dist * -cos(camera->angle) * cos(camera->angle_p),
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx)
return;
float aspect = (float)width / height;
// XXX: Pastel Chime Continue sets unnecessarily small near (0.1) and large
// far (10000), which degrades depth buffer precision.
near = max(near, 0.4f);
far = min(far, 500.f);
glm_perspective(glm_rad(deg), aspect, near, far, ctx->proj_transform);
}
void dungeon_set_player_pos(int surface, int x, int y, int z)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx)
return;
ctx->player_pos[0] = x;
ctx->player_pos[1] = y;
ctx->player_pos[2] = z;
}
static void model_view_matrix(struct dungeon_context *ctx, mat4 mv_out)
{
struct camera *camera = &ctx->camera;
float dist = 0.9f;
vec3 front = {
dist * sinf(camera->angle) * cosf(camera->angle_p),
dist * sinf(camera->angle_p),
dist * -cosf(camera->angle) * cosf(camera->angle_p),
};
vec3 eye;
glm_vec3_sub(camera->pos, d, eye);
vec3 up = {0.0, 1.0, 0.0};
glm_lookat(eye, camera->pos, up, out);
if (ctx->version != DRAW_FIELD) {
// The actual camera position is slightly behind camera->pos.
vec3 eye;
glm_vec3_sub(camera->pos, front, eye);
glm_lookat(eye, camera->pos, GLM_YUP, mv_out);
} else {
vec3 target;
glm_vec3_add(camera->pos, front, target);
glm_lookat(camera->pos, target, GLM_YUP, mv_out);
}
}
static void dungeon_render(struct sact_sprite *sp)
@@ -263,50 +382,64 @@ static void dungeon_render(struct sact_sprite *sp)
struct dungeon_context *ctx = (struct dungeon_context *)sp->plugin;
if (!ctx->loaded || !ctx->draw_enabled)
return;
sprite_dirty(sp);
struct texture *texture = sprite_get_texture(sp);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, texture, 0, 0, texture->w, texture->h);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, &ctx->texture, 0, 0, ctx->texture.w, ctx->texture.h);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ctx->depth_buffer);
if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
ERROR("Incomplete framebuffer");
glClearColor(ctx->dgn->back_color_r / 255.f, ctx->dgn->back_color_g / 255.f, ctx->dgn->back_color_b / 255.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.f, 0.f, 0.f, 1.f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
mat4 local_transform, view_transform, proj_transform;
glm_mat4_identity(local_transform);
model_view_matrix(&ctx->camera, view_transform);
glm_perspective(M_PI / 3.0, (float)texture->w / texture->h, 0.5, 100.0, proj_transform);
mat4 view_transform;
model_view_matrix(ctx, view_transform);
// Tweak the projection transform so that the rendering result is vertically
// flipped. If we render the scene normally, the resulting image will be
// bottom-up (the first pixel is at the bottom-left), but we want a top-down
// image (the first pixel is at the top-left).
proj_transform[1][1] *= -1;
glFrontFace(GL_CW);
int dgn_x = round(ctx->camera.pos[0] / 2.0);
int dgn_y = round(ctx->camera.pos[1] / 2.0);
int dgn_z = round(ctx->camera.pos[2] / -2.0);
int dgn_x, dgn_y, dgn_z;
if (ctx->version == DRAW_FIELD) {
dgn_x = 0;
dgn_y = ctx->dgn->size_y - 1;
dgn_z = 0;
} else {
dgn_x = ctx->player_pos[0];
dgn_y = ctx->player_pos[1];
dgn_z = ctx->player_pos[2];
}
int nr_cells;
struct dgn_cell **cells = dgn_get_visible_cells(ctx->dgn, dgn_x, dgn_y, dgn_z, &nr_cells);
dungeon_renderer_render(ctx->renderer, cells, nr_cells, view_transform, proj_transform);
dungeon_renderer_render(ctx->renderer, cells, nr_cells, ctx->characters, view_transform, ctx->proj_transform);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
dungeon_renderer_run_post_processing(ctx->renderer, &ctx->texture, sprite_get_texture(sp));
sprite_dirty(sp);
}
static void neighbor_reveal(struct dungeon_context *ctx, int x, int y, int z)
{
struct dgn_cell *cell = dgn_cell_at(ctx->dgn, x, y, z);
if (cell->floor < 0) {
cell->walked = 1;
dungeon_map_reveal(ctx, x, y, z, false);
switch (ctx->version) {
case DRAW_DUNGEON_1:
if (cell->floor < 0) {
cell->walked = 1;
dungeon_map_reveal(ctx, x, y, z, false);
}
break;
case DRAW_DUNGEON_2:
if (cell->floor >= 0) {
cell->walked |= 2;
dungeon_map_reveal(ctx, x, y, z, false);
}
break;
case DRAW_DUNGEON_14:
case DRAW_FIELD:
// do nothing
break;
}
}
@@ -315,9 +448,11 @@ void dungeon_set_walked(int surface, int x, int y, int z, int flag)
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx)
return;
dgn_cell_at(ctx->dgn, x, y, z)->walked = 1;
dungeon_map_reveal(ctx, x, y, z, false);
if (ctx->version == DRAW_DUNGEON_1) {
if (!dgn_is_in_map(ctx->dgn, x, y, z))
return; // avoid VM_ERROR in Pastel Chime Continue
if (flag) {
dgn_cell_at(ctx->dgn, x, y, z)->walked = ctx->version == DRAW_DUNGEON_2 ? 3 : 1;
dungeon_map_reveal(ctx, x, y, z, false);
if (x > 0)
neighbor_reveal(ctx, x - 1, y, z);
if (x + 1u < ctx->dgn->size_x)
@@ -326,6 +461,28 @@ void dungeon_set_walked(int surface, int x, int y, int z, int flag)
neighbor_reveal(ctx, x, y, z - 1);
if (z + 1u < ctx->dgn->size_z)
neighbor_reveal(ctx, x, y, z + 1);
} else {
dgn_cell_at(ctx->dgn, x, y, z)->walked = 0;
dungeon_map_hide(ctx, x, y, z);
}
}
void dungeon_set_walked_all(int surface)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx)
return;
int nr_cells = dgn_nr_cells(ctx->dgn);
for (struct dgn_cell *c = ctx->dgn->cells; c < ctx->dgn->cells + nr_cells; c++) {
if (ctx->version == DRAW_DUNGEON_2) {
if (c->floor >= 0) {
c->walked = 3;
dungeon_map_reveal(ctx, c->x, c->y, c->z, false);
}
} else {
c->walked = 1;
dungeon_map_reveal(ctx, c->x, c->y, c->z, false);
}
}
}
@@ -348,7 +505,7 @@ int dungeon_calc_conquer(int surface)
/*
* WalkData serialization format:
*
* struct WalkData {
* struct DrawDungeon1_WalkData {
* int version; // zero
* int nr_maps;
* struct {
@@ -357,17 +514,26 @@ int dungeon_calc_conquer(int surface)
* int cells[size_x * size_y * size_z];
* } maps[nr_maps];
* };
*
* struct DrawDungeon2_WalkData {
* int magic; // 1977
* int reserved; // zero
* int size_x, size_y, size_z;
* int cells[size_x * size_y * size_z];
* };
*/
#define DD2_WALKDATA_MAGIC 1977
enum {
WALK_DATA_NOT_FOUND = -1,
WALK_DATA_BROKEN = -2,
};
static int find_walk_data(struct page *array, int map_no, struct dgn *dgn)
static int dd1_find_walk_data(struct page *array, int map_no, struct dgn *dgn)
{
if (!array)
return WALK_DATA_BROKEN;
return WALK_DATA_NOT_FOUND;
if (array->type != ARRAY_PAGE || array->a_type != AIN_ARRAY_INT || array->array.rank != 1)
VM_ERROR("Not a flat integer array");
@@ -396,16 +562,13 @@ static int find_walk_data(struct page *array, int map_no, struct dgn *dgn)
return WALK_DATA_NOT_FOUND;
}
bool dungeon_load_walk_data(int surface, int map, struct page **page)
static bool dd1_load_walk_data(struct dungeon_context *ctx, int map, struct page **page)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->dgn)
return false;
struct page *array = *page;
int offset = find_walk_data(array, map, ctx->dgn);
int offset = dd1_find_walk_data(array, map, ctx->dgn);
if (offset < 0)
return false;
return offset != WALK_DATA_BROKEN;
offset += 4;
int nr_cells = dgn_nr_cells(ctx->dgn);
@@ -418,20 +581,62 @@ bool dungeon_load_walk_data(int surface, int map, struct page **page)
return true;
}
bool dungeon_save_walk_data(int surface, int map, struct page **page)
static bool dd2_load_walk_data(struct dungeon_context *ctx, struct page **page)
{
int nr_cells = dgn_nr_cells(ctx->dgn);
struct page *array = *page;
if (!array || array->nr_vars != 5 + nr_cells) {
WARNING("DrawDungeon2.LoadWalkData: invalid array size");
return false;
}
int ptr = 0;
if (array->values[ptr++].i != DD2_WALKDATA_MAGIC)
return false;
if (array->values[ptr++].i != 0)
return false;
uint32_t size_x = array->values[ptr++].i;
uint32_t size_y = array->values[ptr++].i;
uint32_t size_z = array->values[ptr++].i;
if (size_x != ctx->dgn->size_x || size_y != ctx->dgn->size_y || size_z != ctx->dgn->size_z)
return false;
for (struct dgn_cell *c = ctx->dgn->cells; c < ctx->dgn->cells + nr_cells; c++) {
c->walked = array->values[ptr++].i;
if (c->walked)
dungeon_map_reveal(ctx, c->x, c->y, c->z, true);
}
return true;
}
bool dungeon_load_walk_data(int surface, int map, struct page **page)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->dgn)
return false;
switch (ctx->version) {
case DRAW_DUNGEON_1:
case DRAW_DUNGEON_14:
return dd1_load_walk_data(ctx, map, page);
case DRAW_DUNGEON_2:
return dd2_load_walk_data(ctx, page);
case DRAW_FIELD:
break; // DrawField does not export LoadWalkData
}
return false;
}
static bool dd1_save_walk_data(struct dungeon_context *ctx, int map, struct page **page)
{
struct page *array = *page;
int nr_cells = dgn_nr_cells(ctx->dgn);
int offset = find_walk_data(array, map, ctx->dgn);
int offset = dd1_find_walk_data(array, map, ctx->dgn);
if (offset == WALK_DATA_NOT_FOUND) {
offset = array->nr_vars;
array->values[1].i += 1;
union vm_value dim = { .i = array->nr_vars + 4 + nr_cells };
offset = array ? array->nr_vars : 0;
union vm_value dim = { .i = offset + 4 + nr_cells };
*page = array = realloc_array(array, 1, &dim, AIN_ARRAY_INT, 0, false);
array->values[1].i += 1;
} else if (offset == WALK_DATA_BROKEN) {
if (array)
WARNING("Discarding broken walk data");
@@ -449,3 +654,138 @@ bool dungeon_save_walk_data(int surface, int map, struct page **page)
array->values[offset++].i = ctx->dgn->cells[i].walked;
return true;
}
static bool dd2_save_walk_data(struct dungeon_context *ctx, struct page **page)
{
struct page *array = *page;
int nr_cells = dgn_nr_cells(ctx->dgn);
if (!array || array->nr_vars != 5 + nr_cells) {
WARNING("DrawDungeon2.SaveWalkData: invalid array size");
return false;
}
int ptr = 0;
array->values[ptr++].i = DD2_WALKDATA_MAGIC;
array->values[ptr++].i = 0;
array->values[ptr++].i = ctx->dgn->size_x;
array->values[ptr++].i = ctx->dgn->size_y;
array->values[ptr++].i = ctx->dgn->size_z;
for (int i = 0; i < nr_cells; i++)
array->values[ptr++].i = ctx->dgn->cells[i].walked;
return true;
}
bool dungeon_save_walk_data(int surface, int map, struct page **page)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->dgn)
return false;
switch (ctx->version) {
case DRAW_DUNGEON_1:
case DRAW_DUNGEON_14:
return dd1_save_walk_data(ctx, map, page);
case DRAW_DUNGEON_2:
return dd2_save_walk_data(ctx, page);
case DRAW_FIELD:
break; // DrawField does not export SaveWalkData
}
return false;
}
void dungeon_paint_step(int surface, int x, int y, int z)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->dgn)
return;
dgn_paint_step(ctx->dgn, x, z);
}
void dungeon_set_chara_sprite(int surface, int num, int sprite)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->characters || num < 0 || num >= DRAWFIELD_NR_CHARACTERS)
return;
ctx->characters[num].sprite = sprite;
}
void dungeon_set_chara_pos(int surface, int num, float x, float y, float z)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->characters || num < 0 || num >= DRAWFIELD_NR_CHARACTERS)
return;
ctx->characters[num].pos[0] = x;
ctx->characters[num].pos[1] = y;
ctx->characters[num].pos[2] = -z;
}
void dungeon_set_chara_cg(int surface, int num, int cg)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->characters || num < 0 || num >= DRAWFIELD_NR_CHARACTERS)
return;
ctx->characters[num].cg_index = cg;
}
void dungeon_set_chara_cg_info(int surface, int num, int num_chara_x, int num_chara_y)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->characters || num < 0 || num >= DRAWFIELD_NR_CHARACTERS)
return;
ctx->characters[num].rows = num_chara_y;
ctx->characters[num].cols = num_chara_x;
}
void dungeon_set_chara_show(int surface, int num, bool show)
{
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->characters || num < 0 || num >= DRAWFIELD_NR_CHARACTERS)
return;
ctx->characters[num].show = show;
}
bool dungeon_project_world_to_screen(struct dungeon_context *ctx, vec3 world_pos, Point *screen_pos)
{
mat4 view_transform;
model_view_matrix(ctx, view_transform);
mat4 mvp;
glm_mat4_mul(ctx->proj_transform, view_transform, mvp);
vec4 world_pos_h = {
world_pos[0],
world_pos[1],
-world_pos[2], // Z is negated to match coordinate system
1.f
};
vec4 clip_coords;
glm_mat4_mulv(mvp, world_pos_h, clip_coords);
if (clip_coords[3] <= 0.f) {
return false; // point is behind the near clipping plane
}
vec3 ndc;
ndc[0] = clip_coords[0] / clip_coords[3];
ndc[1] = clip_coords[1] / clip_coords[3];
ndc[2] = clip_coords[2] / clip_coords[3];
screen_pos->x = roundf(((ndc[0] + 1.f) / 2.f) * ctx->texture.w);
// Y is inverted from NDC to screen space (NDC is bottom-up, screen is top-down)
screen_pos->y = roundf(((1.f - ndc[1]) / 2.f) * ctx->texture.h);
return true;
}
static cJSON *dungeon_to_json(struct sact_sprite *sp, bool verbose)
{
cJSON *obj, *cam;
struct dungeon_context *ctx = (struct dungeon_context *)sp->plugin;
obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "name", ctx->plugin.name);
cJSON_AddItemToObjectCS(obj, "camera", cam = cJSON_CreateObject());
cJSON_AddItemToObjectCS(cam, "pos", vec3_point_to_json(ctx->camera.pos, verbose));
cJSON_AddNumberToObject(cam, "angle", ctx->camera.angle);
cJSON_AddNumberToObject(cam, "angle_p", ctx->camera.angle_p);
return obj;
}
File diff suppressed because it is too large Load Diff
+257 -79
View File
@@ -25,38 +25,40 @@
#include "sact.h"
#include "vm.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
#define CS 12 // pixel size of a map cell
#define LMAP_WIDTH 32
#define LMAP_HEIGHT 32
static const SDL_Color unenterable_cell_color = {182, 72, 0, 0};
enum symbol_type {
SYMBOL_PLAYER_NORTH = 0,
SYMBOL_PLAYER_SOUTH = 1,
SYMBOL_PLAYER_EAST = 2,
SYMBOL_PLAYER_WEST = 3,
SYMBOL_CROSS = 4,
SYMBOL_EXIT = 5,
SYMBOL_STAIRS_UP = 6,
SYMBOL_STAIRS_DOWN = 7,
SYMBOL_EVENT = 8,
SYMBOL_HEART = 9,
SYMBOL_WARP = 10,
SYMBOL_TREASURE = 11,
SYMBOL_MONSTER = 12,
SYMBOL_YELLOW_STAR = 13,
enum dd1_symbol_type {
DD1_SYMBOL_PLAYER_NORTH = 0,
DD1_SYMBOL_PLAYER_SOUTH = 1,
DD1_SYMBOL_PLAYER_EAST = 2,
DD1_SYMBOL_PLAYER_WEST = 3,
DD1_SYMBOL_CROSS = 4,
DD1_SYMBOL_EXIT = 5,
DD1_SYMBOL_STAIRS_UP = 6,
DD1_SYMBOL_STAIRS_DOWN = 7,
DD1_SYMBOL_EVENT = 8,
DD1_SYMBOL_HEART = 9,
DD1_SYMBOL_WARP = 10,
DD1_SYMBOL_TREASURE = 11,
DD1_SYMBOL_MONSTER = 12,
DD1_SYMBOL_YELLOW_STAR = 13,
NR_SYMBOLS = 14
};
enum dd2_symbol_type {
DD2_SYMBOL_PLAYER_NORTH = 0,
DD2_SYMBOL_PLAYER_EAST = 1,
DD2_SYMBOL_PLAYER_WEST = 2,
DD2_SYMBOL_PLAYER_SOUTH = 3,
DD2_SYMBOL_START = 4,
DD2_SYMBOL_EXIT = 5,
};
struct dungeon_map {
struct texture *textures;
int nr_textures;
int symbol_sprites[NR_SYMBOLS];
int grid_size;
int small_map_floor;
int large_map_floor;
bool radar;
@@ -67,8 +69,8 @@ struct dungeon_map {
static Point texture_pos(struct dungeon_context *ctx, int x, int z)
{
return (Point){
.x = (ctx->map->offset_x + x) * CS,
.y = (ctx->map->offset_y + ctx->dgn->size_z - z - 1) * CS
.x = (ctx->map->offset_x + x) * ctx->map->grid_size,
.y = (ctx->map->offset_y + ctx->dgn->size_z - z - 1) * ctx->map->grid_size,
};
}
@@ -78,6 +80,18 @@ struct dungeon_map *dungeon_map_create(enum draw_dungeon_version version)
return NULL; // GALZOO does not use DrawDungeon's 2D map functions.
struct dungeon_map *map = xcalloc(1, sizeof(struct dungeon_map));
switch (version) {
case DRAW_DUNGEON_1:
case DRAW_FIELD:
map->grid_size = 12;
break;
case DRAW_DUNGEON_2:
map->grid_size = 11;
break;
case DRAW_DUNGEON_14:
ERROR("cannot happen");
break;
}
map->small_map_floor = -1;
map->large_map_floor = -1;
return map;
@@ -98,33 +112,65 @@ void dungeon_map_init(struct dungeon_context *ctx)
if (!map)
return;
if (map->textures) {
for (int i = 0; i < map->nr_textures; i++)
gfx_delete_texture(&map->textures[i]);
free(map->textures);
}
map->textures = xcalloc(dgn->size_y, sizeof(struct texture));
map->nr_textures = dgn->size_y;
// Calculate the bounding box on the XZ plane so that map is drawn in the
// center of the texture.
uint32_t min_x = dgn->size_x;
uint32_t min_y = dgn->size_z;
uint32_t max_x = 0;
uint32_t max_y = 0;
for (uint32_t y = 0; y < dgn->size_y; y++) {
for (uint32_t z = 0; z < dgn->size_z; z++) {
uint32_t map_y = ctx->dgn->size_z - z - 1;
for (uint32_t x = 0; x < dgn->size_x; x++) {
if (dgn_cell_at(ctx->dgn, x, y, z)->floor < 0)
continue;
min_x = min(min_x, x);
min_y = min(min_y, map_y);
max_x = max(max_x, x);
max_y = max(max_y, map_y);
}
}
}
map->offset_x = (LMAP_WIDTH - (max_x - min_x + 1)) / 2 - min_x;
map->offset_y = (LMAP_HEIGHT - (max_y - min_y)) / 2 - min_y;
int map_width;
int map_height;
switch (ctx->version) {
case DRAW_DUNGEON_1: {
// Calculate the bounding box on the XZ plane so that map is drawn in the
// center of the texture.
uint32_t min_x = dgn->size_x;
uint32_t min_y = dgn->size_z;
uint32_t max_x = 0;
uint32_t max_y = 0;
for (uint32_t y = 0; y < dgn->size_y; y++) {
for (uint32_t z = 0; z < dgn->size_z; z++) {
uint32_t map_y = ctx->dgn->size_z - z - 1;
for (uint32_t x = 0; x < dgn->size_x; x++) {
if (dgn_cell_at(ctx->dgn, x, y, z)->floor < 0)
continue;
min_x = min(min_x, x);
min_y = min(min_y, map_y);
max_x = max(max_x, x);
max_y = max(max_y, map_y);
}
}
}
map_width = 32;
map_height = 32;
map->offset_x = (map_width - (max_x - min_x + 1)) / 2 - min_x;
map->offset_y = (map_height - (max_y - min_y)) / 2 - min_y;
}
break;
case DRAW_DUNGEON_2:
map_width = 19;
map_height = 19;
map->offset_x = 0;
map->offset_y = 1;
break;
case DRAW_DUNGEON_14:
ERROR("cannot happen");
break;
case DRAW_FIELD:
map_width = dgn->size_x;
map_height = dgn->size_z;
map->offset_x = 0;
map->offset_y = 0;
break;
}
// Initial draw
for (uint32_t y = 0; y < dgn->size_y; y++) {
gfx_init_texture_rgba(&map->textures[y], LMAP_WIDTH * CS, LMAP_HEIGHT * CS, unenterable_cell_color);
gfx_init_texture_rgba(&map->textures[y],
map_width * map->grid_size,
map_height * map->grid_size,
unenterable_cell_color);
for (uint32_t z = 0; z < dgn->size_z; z++) {
for (uint32_t x = 0; x < dgn->size_x; x++) {
dungeon_map_update_cell(ctx, x, y, z);
@@ -133,13 +179,43 @@ void dungeon_map_init(struct dungeon_context *ctx)
}
}
void dungeon_map_reveal(struct dungeon_context *ctx, int x, int y, int z, bool transparent)
void dungeon_map_reveal(struct dungeon_context *ctx, int x, int y, int z, bool from_walk_data)
{
if (!ctx->map)
return;
struct texture *dst = &ctx->map->textures[y];
struct dgn_cell *cell = dgn_cell_at(ctx->dgn, x, y, z);
Point pos = texture_pos(ctx, x, z);
int alpha;
switch (ctx->version) {
case DRAW_DUNGEON_1:
alpha = from_walk_data ? 128 : 255;
gfx_fill_amap(dst, pos.x, pos.y, ctx->map->grid_size, ctx->map->grid_size, alpha);
break;
case DRAW_DUNGEON_2:
if ((x == ctx->dgn->start_x && z == ctx->dgn->start_y) || cell->floor_event == 17) {
// Do not overwrite the start/goal symbol.
gfx_fill_amap(dst, pos.x, pos.y, ctx->map->grid_size, ctx->map->grid_size, 255);
} else {
int rg = (cell->walked & 1) ? 0 : 255;
gfx_fill_with_alpha(dst, pos.x, pos.y, ctx->map->grid_size, ctx->map->grid_size, rg, rg, 0, 255);
}
break;
case DRAW_FIELD:
alpha = cell->floor == -1 ? 160 : 255;
gfx_fill_amap(dst, pos.x, pos.y, ctx->map->grid_size, ctx->map->grid_size, alpha);
default:
break;
}
}
void dungeon_map_hide(struct dungeon_context *ctx, int x, int y, int z)
{
if (!ctx->map)
return;
struct texture *dst = &ctx->map->textures[y];
Point pos = texture_pos(ctx, x, z);
gfx_fill_amap(dst, pos.x, pos.y, CS, CS, transparent ? 128 : 255);
gfx_fill_amap(dst, pos.x, pos.y, ctx->map->grid_size, ctx->map->grid_size, 0);
}
static void draw_hline(struct texture *dst, int x, int y, int w, SDL_Color col)
@@ -155,13 +231,13 @@ static void draw_vline(struct texture *dst, int x, int y, int h, SDL_Color col)
static void draw_symbol(struct dungeon_map *map, struct texture *dst, int x, int y, int type)
{
struct sact_sprite *sp = sact_get_sprite(map->symbol_sprites[type]);
if (!sp || sp->rect.w != CS - 2 || sp->rect.h != CS - 2)
if (!sp)
VM_ERROR("Invalid map cg %d", map->symbol_sprites[type]);
struct texture *src = sprite_get_texture(sp);
gfx_copy(dst, x, y, src, 0, 0, src->w, src->h);
}
void dungeon_map_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y, int dgn_z)
static void dd1_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y, int dgn_z)
{
if (!ctx->map)
return;
@@ -170,6 +246,7 @@ void dungeon_map_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y,
Point pos = texture_pos(ctx, dgn_x, dgn_z);
int x = pos.x;
int y = pos.y;
const int CS = ctx->map->grid_size;
if (cell->floor >= 0)
gfx_fill(dst, x, y, CS, CS, 0, 0, 0);
@@ -217,33 +294,126 @@ void dungeon_map_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y,
bool draw_all_symbols = ctx->map->radar || ctx->map->all_visible;
int symbol = -1;
switch (cell->floor_event) {
case 11: if (draw_all_symbols) symbol = SYMBOL_TREASURE; break;
case 12: if (draw_all_symbols) symbol = SYMBOL_MONSTER; break;
case 14: case 140: symbol = SYMBOL_EVENT; break;
case 15: symbol = SYMBOL_HEART; break;
case 16: if (draw_all_symbols) symbol = SYMBOL_CROSS; break;
case 17: symbol = SYMBOL_EXIT; break;
case 18: symbol = SYMBOL_WARP; break;
case 28: if (draw_all_symbols) symbol = SYMBOL_YELLOW_STAR; break;
case 11: if (draw_all_symbols) symbol = DD1_SYMBOL_TREASURE; break;
case 12: if (draw_all_symbols) symbol = DD1_SYMBOL_MONSTER; break;
case 14: case 140: symbol = DD1_SYMBOL_EVENT; break;
case 15: symbol = DD1_SYMBOL_HEART; break;
case 16: if (draw_all_symbols) symbol = DD1_SYMBOL_CROSS; break;
case 17: symbol = DD1_SYMBOL_EXIT; break;
case 18: symbol = DD1_SYMBOL_WARP; break;
case 28: if (draw_all_symbols) symbol = DD1_SYMBOL_YELLOW_STAR; break;
}
if (cell->stairs_texture >= 0)
symbol = SYMBOL_STAIRS_UP;
symbol = DD1_SYMBOL_STAIRS_UP;
else if (dgn_y && dgn_cell_at(ctx->dgn, dgn_x, dgn_y - 1, dgn_z)->stairs_texture >= 0)
symbol = SYMBOL_STAIRS_DOWN;
symbol = DD1_SYMBOL_STAIRS_DOWN;
if (symbol >= 0)
draw_symbol(ctx->map, dst, x + 1, y + 1, symbol);
}
static void dd2_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y, int dgn_z)
{
if (!ctx->map)
return;
struct dgn_cell *cell = dgn_cell_at(ctx->dgn, dgn_x, dgn_y, dgn_z);
struct texture *dst = &ctx->map->textures[0];
Point pos = texture_pos(ctx, dgn_x, dgn_z);
if (dgn_x == ctx->dgn->start_x && dgn_z == ctx->dgn->start_y) {
draw_symbol(ctx->map, dst, pos.x, pos.y, DD2_SYMBOL_START);
} else if (cell->floor_event == 17) {
draw_symbol(ctx->map, dst, pos.x, pos.y, DD2_SYMBOL_EXIT);
}
}
static void df_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y, int dgn_z)
{
if (!ctx->map)
return;
struct dgn_cell *cell = dgn_cell_at(ctx->dgn, dgn_x, dgn_y, dgn_z);
struct texture *dst = &ctx->map->textures[dgn_y];
Point pos = texture_pos(ctx, dgn_x, dgn_z);
int x = pos.x;
int y = pos.y;
const int CS = ctx->map->grid_size;
if (cell->floor >= 0)
gfx_fill(dst, x, y, CS, CS, 128, 64, 32);
else
gfx_fill(dst, x, y, CS, CS, 0, 0, 0);
const SDL_Color wall_color = {255, 255, 255, 255};
if (cell->north_wall >= 0)
draw_hline(dst, x, y, CS, wall_color);
if (cell->south_wall >= 0)
draw_hline(dst, x, y + CS - 1, CS, wall_color);
if (cell->west_wall >= 0)
draw_vline(dst, x, y, CS, wall_color);
if (cell->east_wall >= 0)
draw_vline(dst, x + CS - 1, y, CS, wall_color);
const SDL_Color door_colors[2] = {{0, 255, 0, 255}, {255, 0, 0, 255}};
if (cell->north_door >= 0) {
draw_hline(dst, x, y, CS / 2 - 1, door_colors[cell->north_door_lock]);
draw_hline(dst, x + CS / 2 - 1, y, 2, COLOR(0, 0, 0, 0));
draw_hline(dst, x + CS / 2 + 1, y, CS / 2 - 1, door_colors[cell->north_door_lock]);
}
if (cell->south_door >= 0) {
draw_hline(dst, x, y + CS - 1, CS / 2 - 1, door_colors[cell->south_door_lock]);
draw_hline(dst, x + CS / 2 - 1, y + CS - 1, 2, COLOR(0, 0, 0, 0));
draw_hline(dst, x + CS / 2 + 1, y + CS - 1, CS / 2 - 1, door_colors[cell->south_door_lock]);
}
if (cell->west_door >= 0) {
draw_vline(dst, x, y, CS / 2 - 1, door_colors[cell->west_door_lock]);
draw_vline(dst, x, y + CS / 2 - 1, 2, COLOR(0, 0, 0, 0));
draw_vline(dst, x, y + CS / 2 + 1, CS / 2 - 1, door_colors[cell->west_door_lock]);
}
if (cell->east_door >= 0) {
draw_vline(dst, x + CS - 1, y, CS / 2 - 1, door_colors[cell->east_door_lock]);
draw_vline(dst, x + CS - 1, y + CS / 2 - 1, 2, COLOR(0, 0, 0, 0));
draw_vline(dst, x + CS - 1, y + CS / 2 + 1, CS / 2 - 1, door_colors[cell->east_door_lock]);
}
if (cell->stairs_texture >= 0)
gfx_fill(dst, x + 2, y + 2, CS - 4, CS - 4, 0, 255, 255);
else if (dgn_y && dgn_cell_at(ctx->dgn, dgn_x, dgn_y - 1, dgn_z)->stairs_texture >= 0)
gfx_fill(dst, x + 2, y + 2, CS - 4, CS - 4, 0, 0, 255);
}
void dungeon_map_update_cell(struct dungeon_context *ctx, int dgn_x, int dgn_y, int dgn_z)
{
switch (ctx->version) {
case DRAW_DUNGEON_1:
dd1_update_cell(ctx, dgn_x, dgn_y, dgn_z);
break;
case DRAW_DUNGEON_2:
dd2_update_cell(ctx, dgn_x, dgn_y, dgn_z);
break;
case DRAW_DUNGEON_14:
// unused
break;
case DRAW_FIELD:
df_update_cell(ctx, dgn_x, dgn_y, dgn_z);
}
}
static int player_symbol(struct dungeon_context *ctx)
{
const int symbols[] = {
SYMBOL_PLAYER_NORTH,
SYMBOL_PLAYER_EAST,
SYMBOL_PLAYER_SOUTH,
SYMBOL_PLAYER_WEST,
const int dd1_symbols[] = {
DD1_SYMBOL_PLAYER_NORTH,
DD1_SYMBOL_PLAYER_EAST,
DD1_SYMBOL_PLAYER_SOUTH,
DD1_SYMBOL_PLAYER_WEST,
};
return symbols[(int)(round(ctx->camera.angle * 2 / M_PI)) & 3];
const int dd2_symbols[] = {
DD2_SYMBOL_PLAYER_NORTH,
DD2_SYMBOL_PLAYER_EAST,
DD2_SYMBOL_PLAYER_SOUTH,
DD2_SYMBOL_PLAYER_WEST,
};
const int *symbols = ctx->version == DRAW_DUNGEON_1 ? dd1_symbols : dd2_symbols;
return symbols[(int)(roundf(ctx->camera.angle * 2 / GLM_PIf)) & 3];
}
void dungeon_map_draw(int surface, int sprite)
@@ -251,22 +421,27 @@ void dungeon_map_draw(int surface, int sprite)
struct dungeon_context *ctx = dungeon_get_context(surface);
if (!ctx || !ctx->map)
return;
if (ctx->version == DRAW_DUNGEON_2) {
// Draw the entire map
dungeon_map_draw_lmap(surface, sprite);
return;
}
struct sact_sprite *sp = sact_get_sprite(sprite);
if (!sp)
VM_ERROR("DrawDungeon.DrawMap: invalid sprite %d", sprite);
sprite_dirty(sp);
struct texture *dst = sprite_get_texture(sp);
int player_x = round(ctx->camera.pos[0] / 2.0);
int player_y = round(ctx->camera.pos[1] / 2.0);
int player_z = round(ctx->camera.pos[2] / -2.0);
int level = ctx->map->small_map_floor;
if (level < 0)
level = player_y;
level = ctx->player_pos[1];
struct texture *src = &ctx->map->textures[level];
Point pos = texture_pos(ctx, player_x, player_z);
const int CS = ctx->map->grid_size;
Point pos = texture_pos(ctx, ctx->player_pos[0], ctx->player_pos[2]);
int x = pos.x - (dst->w - CS) / 2;
int y = pos.y - (dst->h - CS) / 2;
@@ -292,12 +467,9 @@ void dungeon_map_draw_lmap(int surface, int sprite)
sprite_dirty(sp);
struct texture *dst = sprite_get_texture(sp);
int player_x = round(ctx->camera.pos[0] / 2.0);
int player_y = round(ctx->camera.pos[1] / 2.0);
int player_z = round(ctx->camera.pos[2] / -2.0);
int level = ctx->map->large_map_floor;
if (level < 0)
level = player_y;
level = ctx->player_pos[1];
struct texture *src = &ctx->map->textures[level];
gfx_copy_with_alpha_map(dst, 0, 0, src, 0, 0, src->w, src->h);
@@ -306,9 +478,15 @@ void dungeon_map_draw_lmap(int surface, int sprite)
gfx_fill_amap(dst, 0, 0, dst->w, dst->h, 255);
// Draw the player symbol.
Point pos = texture_pos(ctx, player_x, player_z);
draw_symbol(ctx->map, dst, pos.x + 1, pos.y + 1, player_symbol(ctx));
gfx_fill_amap(dst, pos.x + 1, pos.y + 1, CS - 2, CS - 2, level == player_y ? 255 : 128);
Point pos = texture_pos(ctx, ctx->player_pos[0], ctx->player_pos[2]);
if (ctx->version == DRAW_FIELD) {
int ofs = (ctx->map->grid_size - 4) / 2;
gfx_fill(dst, pos.x + ofs, pos.y + ofs, 4, 4, 255, 192, 255);
} else {
draw_symbol(ctx->map, dst, pos.x + 1, pos.y + 1, player_symbol(ctx));
int size = ctx->map->grid_size - 2;
gfx_fill_amap(dst, pos.x + 1, pos.y + 1, size, size, level == ctx->player_pos[1] ? 255 : 128);
}
}
static void redraw_event_symbols(struct dungeon_context *ctx)
+104
View File
@@ -0,0 +1,104 @@
// Mersenne Twister engine with N=4, M=3
// Derived from mt19937int.c in libsys4
/* A C-program for MT19937: Integer version (1998/4/6) */
/* genrand() generates one pseudorandom unsigned integer (32bit) */
/* which is uniformly distributed among 0 to 2^32-1 for each */
/* call. sgenrand(seed) set initial values to the working area */
/* of 624 words. Before genrand(), sgenrand(seed) must be */
/* called once. (seed is any 32-bit integer except for 0). */
/* Coded by Takuji Nishimura, considering the suggestions by */
/* Topher Cooper and Marc Rieffel in July-Aug. 1997. */
/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Library General Public */
/* License as published by the Free Software Foundation; either */
/* version 2 of the License, or (at your option) any later */
/* version. */
/* This library 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 Library General Public License for more details. */
/* You should have received a copy of the GNU Library General */
/* Public License along with this library; if not, write to the */
/* Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */
/* 02111-1307 USA */
/* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. */
/* When you use this, send an email to: matumoto@math.keio.ac.jp */
/* with an appropriate reference to your work. */
/* REFERENCE */
/* M. Matsumoto and T. Nishimura, */
/* "Mersenne Twister: A 623-Dimensionally Equidistributed Uniform */
/* Pseudo-Random Number Generator", */
/* ACM Transactions on Modeling and Computer Simulation, */
/* Vol. 8, No. 1, January 1998, pp 3--30. */
#include "dungeon/mtrand43.h"
/* Period parameters */
#define N MTRAND43_STATE_SIZE
#define M 3
#define MATRIX_A 0x9908b0df /* constant vector a */
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
#define LOWER_MASK 0x7fffffff /* least significant r bits */
/* Tempering parameters */
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
#define TEMPERING_SHIFT_U(y) (y >> 11)
#define TEMPERING_SHIFT_S(y) (y << 7)
#define TEMPERING_SHIFT_T(y) (y << 15)
#define TEMPERING_SHIFT_L(y) (y >> 18)
/* initializing the array with a NONZERO seed */
void mtrand43_init(struct mtrand43 *mt, uint32_t seed)
{
/* setting initial seeds to mt[N] using */
/* the generator Line 25 of Table 1 in */
/* [KNUTH 1981, The Art of Computer Programming */
/* Vol. 2 (2nd Ed.), pp102] */
mt->st[0]= seed & 0xffffffff;
for (int i=1; i<N; i++)
mt->st[i] = (69069 * mt->st[i-1]) & 0xffffffff;
mt->i = N;
}
uint32_t mtrand43_genrand(struct mtrand43 *mt)
{
uint32_t y;
static const uint32_t mag01[2]={0x0, MATRIX_A};
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (mt->i >= N) { /* generate N words at one time */
int kk;
for (kk=0;kk<N-M;kk++) {
y = (mt->st[kk]&UPPER_MASK)|(mt->st[kk+1]&LOWER_MASK);
mt->st[kk] = mt->st[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
}
for (;kk<N-1;kk++) {
y = (mt->st[kk]&UPPER_MASK)|(mt->st[kk+1]&LOWER_MASK);
mt->st[kk] = mt->st[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
}
y = (mt->st[N-1]&UPPER_MASK)|(mt->st[0]&LOWER_MASK);
mt->st[N-1] = mt->st[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
mt->i = 0;
}
y = mt->st[mt->i++];
y ^= TEMPERING_SHIFT_U(y);
y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
y ^= TEMPERING_SHIFT_L(y);
return y;
}
// Returns a random float between [0.0, 1.0], inclusive.
float mtrand43_genfloat(struct mtrand43 *mt)
{
return (float)(mtrand43_genrand(mt)) / 4294967296.f;
}
+329 -43
View File
@@ -30,6 +30,12 @@
#include "dungeon/renderer.h"
#include "dungeon/skybox.h"
#include "gfx/gfx.h"
#include "sact.h"
enum {
COLOR_TEXTURE_UNIT,
LIGHT_TEXTURE_UNIT,
};
struct marker_info {
int event_type;
@@ -42,10 +48,18 @@ enum floor_marker {
// Rance VI
R6_MAGIC_CIRCLE = 1,
R6_RED_OCTAGRAM = 63,
// Dungeons & Dolls
DD_MAGIC_CIRCLE = 40,
DD_RED_OCTAGRAM = 41,
DD_WHITE_OCTAGRAM = 42,
// GALZOO Island
GZ_MAGIC_CIRCLE = 0,
};
static const struct marker_info empty_marker_info[] = {
{0},
};
static const struct marker_info rance6_marker_info[] = {
{ 11, 41, 3, R6_MAGIC_CIRCLE}, // treasure chest
{ 12, 51, 7, R6_RED_OCTAGRAM}, // enemy
@@ -58,6 +72,18 @@ static const struct marker_info rance6_marker_info[] = {
{0},
};
static const struct marker_info dolls_marker_info[] = {
{11, 43, 3, DD_WHITE_OCTAGRAM}, // treasure chest
{17, 0, 20, DD_MAGIC_CIRCLE}, // goal
{0},
};
static const struct marker_info dolls_boss_marker_info[] = {
{11, 43, 3, DD_WHITE_OCTAGRAM}, // treasure chest
{17, 20, 20, DD_RED_OCTAGRAM}, // boss
{0},
};
static const struct marker_info galzoo_marker_info[] = {
{11, 1, 0, GZ_MAGIC_CIRCLE}, // item
{12, 2, 0, GZ_MAGIC_CIRCLE}, // enemy
@@ -70,21 +96,33 @@ static const struct marker_info galzoo_marker_info[] = {
static const struct marker_info *marker_tables[] = {
[DRAW_DUNGEON_1] = rance6_marker_info,
[DRAW_DUNGEON_14] = galzoo_marker_info
[DRAW_DUNGEON_2] = dolls_marker_info,
[DRAW_DUNGEON_14] = galzoo_marker_info,
[DRAW_FIELD] = empty_marker_info
};
struct geometry;
struct material;
struct object3d;
struct raster_shader {
struct shader s;
GLint amp;
GLint t;
};
struct dungeon_renderer {
enum draw_dungeon_version version;
struct shader shader;
// Uniform variable locations
GLint local_transform;
GLint proj_transform;
GLuint alpha_mod;
// Attribute variable locations
GLint vertex_uv;
GLint alpha_mod;
GLint use_lightmap;
GLint light_texture;
GLint use_fog;
GLint uv_offset;
GLint uv_scale;
struct geometry *wall_geometry;
struct geometry *door_left_geometry;
@@ -108,6 +146,23 @@ struct dungeon_renderer {
struct material *polyobj_materials;
struct skybox *skybox;
struct raster_shader raster_shader;
bool raster_scroll;
float raster_amp;
bool enable_lightmap;
uint32_t draw_obj_flags;
};
enum draw_obj_flag_index {
DRAW_POLYOBJ = 0,
DRAW_FLOOR = 1,
DRAW_WALL = 2,
DRAW_DOOR = 3,
DRAW_CEILING = 4,
DRAW_STAIRS = 5,
NR_DRAW_OBJ_FLAGS
};
static const struct marker_info *get_marker_info(struct dungeon_renderer *r, int event_type)
@@ -211,10 +266,10 @@ static struct geometry *geometry_create(struct dungeon_renderer *r, const struct
glGenBuffers(1, &g->attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, g->attr_buffer);
glEnableVertexAttribArray(r->shader.vertex);
glVertexAttribPointer(r->shader.vertex, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)0);
glEnableVertexAttribArray(r->vertex_uv);
glVertexAttribPointer(r->vertex_uv, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(r->shader.vertex_pos);
glVertexAttribPointer(r->shader.vertex_pos, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)0);
glEnableVertexAttribArray(r->shader.vertex_uv);
glVertexAttribPointer(r->shader.vertex_uv, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)(3 * sizeof(GLfloat)));
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
glBindVertexArray(0);
@@ -332,15 +387,46 @@ static void init_polyobjs(struct dungeon_renderer *r, struct polyobj *po)
}
}
struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version version, struct dtx *dtx, GLuint *event_textures, int nr_event_textures, struct polyobj *po)
static bool is_dolls_boss_floor(int num)
{
switch (num) {
case 20:
case 40:
case 60:
case 80:
case 100:
case 120:
case 140:
case 150:
case 160:
case 180:
case 200:
case 300:
case 500:
return true;
default:
return false;
}
}
struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version version, int num, struct dtx *dtx, GLuint *event_textures, int nr_event_textures, struct polyobj *po)
{
struct dungeon_renderer *r = xcalloc(1, sizeof(struct dungeon_renderer));
r->version = version;
gfx_load_shader(&r->shader, "shaders/dungeon.v.glsl", "shaders/dungeon.f.glsl");
r->local_transform = glGetUniformLocation(r->shader.program, "local_transform");
r->proj_transform = glGetUniformLocation(r->shader.program, "proj_transform");
r->alpha_mod = glGetUniformLocation(r->shader.program, "alpha_mod");
r->vertex_uv = glGetAttribLocation(r->shader.program, "vertex_uv");
r->use_lightmap = glGetUniformLocation(r->shader.program, "use_lightmap");
r->light_texture = glGetUniformLocation(r->shader.program, "light_texture");
r->use_fog = glGetUniformLocation(r->shader.program, "use_fog");
r->uv_offset = glGetUniformLocation(r->shader.program, "uv_offset");
r->uv_scale = glGetUniformLocation(r->shader.program, "uv_scale");
glUseProgram(r->shader.program);
glUniform1i(r->use_fog, version != DRAW_FIELD);
glUniform2f(r->uv_offset, 0.0f, 0.0f);
glUniform2f(r->uv_scale, 1.0f, 1.0f);
glUseProgram(0);
r->wall_geometry = geometry_create(r, wall_vertices, sizeof(wall_vertices), GL_TRIANGLE_STRIP);
r->door_left_geometry = geometry_create(r, door_left_vertices, sizeof(door_left_vertices), GL_TRIANGLE_STRIP);
@@ -349,9 +435,8 @@ struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version versi
r->stairs_geometry = geometry_create(r, stairs_vertices, sizeof(stairs_vertices), GL_TRIANGLE_STRIP);
r->floating_marker_geometry = geometry_create(r, floating_marker_vertices, sizeof(floating_marker_vertices), GL_TRIANGLE_STRIP);
const int nr_types = DTX_DOOR + 1;
r->materials = xcalloc(nr_types * dtx->nr_columns, sizeof(struct material));
for (int type = 0; type < nr_types; type++) {
r->materials = xcalloc(DTX_NR_CELL_TEXTURE_TYPES * dtx->nr_columns, sizeof(struct material));
for (int type = 0; type < DTX_NR_CELL_TEXTURE_TYPES; type++) {
for (int i = 0; i < dtx->nr_columns; i++) {
struct cg *cg = dtx_create_cg(dtx, type, i);
if (!cg)
@@ -361,18 +446,26 @@ struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version versi
}
}
r->nr_dtx_columns = dtx->nr_columns;
r->nr_materials = nr_types * dtx->nr_columns;
r->nr_materials = DTX_NR_CELL_TEXTURE_TYPES * dtx->nr_columns;
r->event_textures = event_textures;
r->nr_event_textures = nr_event_textures;
r->marker_table = marker_tables[version];
r->draw_event_markers = true;
if (version == DRAW_DUNGEON_2 && is_dolls_boss_floor(num)) {
// Use special marker table for Dungeons & Dolls boss floor
r->marker_table = dolls_boss_marker_info;
} else {
r->marker_table = marker_tables[version];
}
if (po)
init_polyobjs(r, po);
r->skybox = skybox_create(dtx);
r->skybox = skybox_create(version, dtx);
r->enable_lightmap = true;
r->draw_obj_flags = (1 << NR_DRAW_OBJ_FLAGS) - 1;
return r;
}
@@ -402,18 +495,32 @@ void dungeon_renderer_free(struct dungeon_renderer *r)
free(r->polyobj_materials);
skybox_free(r->skybox);
if (r->raster_shader.s.program)
glDeleteProgram(r->raster_shader.s.program);
free(r);
}
static void set_lightmap_texture(struct dungeon_renderer *r, int texture_index)
{
struct material *material = (r->enable_lightmap && texture_index >= 0)
? get_material(r, DTX_LIGHTMAP, texture_index) : NULL;
glUniform1i(r->use_lightmap, !!material);
if (material) {
glActiveTexture(GL_TEXTURE0 + LIGHT_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->texture);
glUniform1i(r->light_texture, LIGHT_TEXTURE_UNIT);
}
}
static void draw(struct dungeon_renderer *r, struct geometry *geometry, GLuint texture, mat4 transform)
{
glUseProgram(r->shader.program);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, transform[0]);
glActiveTexture(GL_TEXTURE0);
glActiveTexture(GL_TEXTURE0 + COLOR_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(r->shader.texture, 0);
glUniform1i(r->shader.texture, COLOR_TEXTURE_UNIT);
glBindVertexArray(geometry->vao);
glDrawArrays(geometry->mode, 0, geometry->nr_vertices);
@@ -431,13 +538,13 @@ static void draw_door(struct dungeon_renderer *r, GLuint texture, float angle, m
glm_mat4_copy(transform, m);
m[3][0] += dx;
m[3][2] += dz;
glm_rotate_y(m, angle * (M_PI/180), m);
glm_rotate_y(m, glm_rad(angle), m);
draw(r, r->door_left_geometry, texture, m);
glm_mat4_copy(transform, m);
m[3][0] -= dx;
m[3][2] -= dz;
glm_rotate_y(m, angle * -(M_PI/180), m);
glm_rotate_y(m, -glm_rad(angle), m);
draw(r, r->door_right_geometry, texture, m);
}
@@ -478,7 +585,7 @@ static void draw_floating_marker(struct dungeon_renderer *r, const struct marker
glm_mat4_pick3t(view_mat, r);
glm_mat4_ins3(r, m);
} else {
float angle = t / -(M_PI * 100.0);
float angle = t / -(GLM_PIf * 100.f);
glm_rotate_y(m, angle, m);
}
@@ -521,7 +628,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
float y = 2.0 * cell->y;
float z = -2.0 * cell->z;
if (cell->floor >= 0) {
if (cell->floor >= 0 && r->draw_obj_flags & (1 << DRAW_FLOOR)) {
struct material *material = get_material(r, DTX_FLOOR, cell->floor);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -529,21 +636,37 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
0, 0, 1, y-1,
0, -1, 0, z,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_floor);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
if (cell->ceiling >= 0) {
if (cell->ceiling >= 0 && r->draw_obj_flags & (1 << DRAW_CEILING)) {
struct material *material = get_material(r, DTX_CEILING, cell->ceiling);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
-1, 0, 0, x,
0, 0, -1, y+1,
0, -1, 0, z,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
if (r->version == DRAW_FIELD) {
// Draw as a billboard.
mat4 m;
vec3 pos = {x, y, z};
glm_translate_make(m, pos);
mat3 r_bill;
glm_mat4_pick3t(view_transform, r_bill);
glm_mat4_ins3(r_bill, m);
glm_scale_uni(m, 1.7f);
draw(r, r->wall_geometry, material->texture, m);
} else {
mat4 m = MAT4(
-1, 0, 0, x,
0, 0, -1, y+1,
0, -1, 0, z,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_ceiling);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
}
if (cell->north_wall >= 0) {
if (cell->north_wall >= 0 && r->draw_obj_flags & (1 << DRAW_WALL)) {
struct material *material = get_material(r, DTX_WALL, cell->north_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -551,10 +674,12 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
0, 1, 0, y,
0, 0, 1, z-1,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_north);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
if (cell->south_wall >= 0) {
if (cell->south_wall >= 0 && r->draw_obj_flags & (1 << DRAW_WALL)) {
struct material *material = get_material(r, DTX_WALL, cell->south_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -562,10 +687,12 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
0, 1, 0, y,
0, 0, -1, z+1,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_south);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
if (cell->east_wall >= 0) {
if (cell->east_wall >= 0 && r->draw_obj_flags & (1 << DRAW_WALL)) {
struct material *material = get_material(r, DTX_WALL, cell->east_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -573,10 +700,12 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
0, 1, 0, y,
1, 0, 0, z,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_east);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
if (cell->west_wall >= 0) {
if (cell->west_wall >= 0 && r->draw_obj_flags & (1 << DRAW_WALL)) {
struct material *material = get_material(r, DTX_WALL, cell->west_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -584,10 +713,12 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
0, 1, 0, y,
-1, 0, 0, z,
0, 0, 0, 1);
set_lightmap_texture(r, cell->lightmap_west);
draw(r, r->wall_geometry, material->texture, m);
set_lightmap_texture(r, -1);
}
}
if (cell->north_door >= 0) {
if (cell->north_door >= 0 && r->draw_obj_flags & (1 << DRAW_DOOR)) {
struct material *material = get_material(r, DTX_DOOR, cell->north_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -598,7 +729,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
draw_door(r, material->texture, cell->north_door_angle, m, -1, 0);
}
}
if (cell->south_door >= 0) {
if (cell->south_door >= 0 && r->draw_obj_flags & (1 << DRAW_DOOR)) {
struct material *material = get_material(r, DTX_DOOR, cell->south_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -609,7 +740,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
draw_door(r, material->texture, cell->south_door_angle, m, 1, 0);
}
}
if (cell->east_door >= 0) {
if (cell->east_door >= 0 && r->draw_obj_flags & (1 << DRAW_DOOR)) {
struct material *material = get_material(r, DTX_DOOR, cell->east_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -620,7 +751,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
draw_door(r, material->texture, cell->east_door_angle, m, 0, -1);
}
}
if (cell->west_door >= 0) {
if (cell->west_door >= 0 && r->draw_obj_flags & (1 << DRAW_DOOR)) {
struct material *material = get_material(r, DTX_DOOR, cell->west_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
@@ -631,7 +762,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
draw_door(r, material->texture, cell->west_door_angle, m, 0, 1);
}
}
if (cell->stairs_texture >= 0) {
if (cell->stairs_texture >= 0 && r->draw_obj_flags & (1 << DRAW_STAIRS)) {
struct material *material = get_material(r, DTX_STAIRS, cell->stairs_texture);
if (material && material->opaque == render_opaque) {
mat4 m;
@@ -659,7 +790,7 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
glCullFace(GL_BACK);
}
}
if (cell->polyobj_index >= 0) {
if (cell->polyobj_index >= 0 && r->draw_obj_flags & (1 << DRAW_POLYOBJ)) {
vec3 pos = {
x + cell->polyobj_position_x,
y + cell->polyobj_position_y - 1,
@@ -677,12 +808,81 @@ static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool re
}
}
void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells, int nr_cells, mat4 view_transform, mat4 proj_transform)
static void draw_character(struct dungeon_renderer *r, struct drawfield_character *chara, mat4 view_mat)
{
if (!chara->show || chara->sprite <= 0)
return;
struct sact_sprite *sp = sact_try_get_sprite(chara->sprite);
if (!sp || !sp->texture.handle)
return;
if (chara->cols == 0) chara->cols = 1;
if (chara->rows == 0) chara->rows = 1;
glUseProgram(r->shader.program);
glUniform2f(r->uv_offset, (float)(chara->cg_index % chara->cols) / chara->cols, (float)(chara->cg_index / chara->cols) / chara->rows);
glUniform2f(r->uv_scale, 1.0f / chara->cols, 1.0f / chara->rows);
mat4 m;
glm_translate_make(m, chara->pos);
// Make it a billboard of 2.0 x 2.0
mat3 r_bill;
glm_mat4_pick3t(view_mat, r_bill);
glm_mat4_ins3(r_bill, m);
draw(r, r->wall_geometry, sp->texture.handle, m);
}
struct character_z {
struct drawfield_character *chara;
float z;
};
static int compare_character_z(const void *a, const void *b)
{
const struct character_z *cha = a;
const struct character_z *chb = b;
if (cha->z < chb->z)
return -1;
if (cha->z > chb->z)
return 1;
return 0;
}
static void draw_characters(struct dungeon_renderer *r, struct drawfield_character *characters, mat4 view_transform)
{
if (!characters)
return;
struct character_z sorted_characters[DRAWFIELD_NR_CHARACTERS];
int n = 0;
for (int i = 0; i < DRAWFIELD_NR_CHARACTERS; i++) {
struct drawfield_character *chara = characters + i;
if (!chara->show || chara->sprite <= 0)
continue;
sorted_characters[n].chara = chara;
vec4 pos;
glm_mat4_mulv(view_transform, (vec4){chara->pos[0], chara->pos[1], chara->pos[2], 1}, pos);
sorted_characters[n].z = pos[2];
n++;
}
qsort(sorted_characters, n, sizeof(struct character_z), compare_character_z);
for (int i = 0; i < n; i++)
draw_character(r, sorted_characters[i].chara, view_transform);
glUniform2f(r->uv_offset, 0.0f, 0.0f);
glUniform2f(r->uv_scale, 1.0f, 1.0f);
}
void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells, int nr_cells, struct drawfield_character *characters, mat4 view_transform, mat4 proj_transform)
{
glUseProgram(r->shader.program);
glUniformMatrix4fv(r->shader.view_transform, 1, GL_FALSE, view_transform[0]);
glUniformMatrix4fv(r->proj_transform, 1, GL_FALSE, proj_transform[0]);
glUniform1f(r->alpha_mod, 1.0);
glUniform1i(r->use_lightmap, 0);
// Render opaque objects, from near to far.
glDisable(GL_BLEND);
@@ -691,11 +891,15 @@ void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells
// Render the skybox.
skybox_render(r->skybox, view_transform, proj_transform);
glUseProgram(r->shader.program);
// Render transparent objects, from far to near.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// DrawField: draw characters.
draw_characters(r, characters, view_transform);
// Render transparent objects, from far to near.
for (int i = nr_cells - 1; i >= 0; i--)
draw_cell(r, cells[i], false, view_transform);
@@ -719,3 +923,85 @@ bool dungeon_renderer_is_floor_opaque(struct dungeon_renderer *r, struct dgn_cel
struct material *material = get_material(r, DTX_FLOOR, cell->floor);
return material && material->opaque;
}
static void prepare_raster_shader(struct gfx_render_job *job, void *data)
{
struct dungeon_renderer *r = data;
float width = job->world_transform[0];
glUniform1f(r->raster_shader.amp, r->raster_amp / width);
glUniform1f(r->raster_shader.t, SDL_GetTicks() / 1000.f);
}
static void load_raster_shader(struct raster_shader *s)
{
gfx_load_shader(&s->s, "shaders/render.v.glsl", "shaders/dungeon_raster.f.glsl");
s->amp = glGetUniformLocation(s->s.program, "amp");
s->t = glGetUniformLocation(s->s.program, "t");
s->s.prepare = prepare_raster_shader;
}
void dungeon_renderer_run_post_processing(struct dungeon_renderer *r, struct texture *src, struct texture *dst)
{
// This function flips the image upside down, because the image in `src`
// (rendered by dungeon_renderer_render) is bottom-up (the first pixel is at
// the bottom-left), but sprite textures are top-down (the first pixel is at
// the top-left).
if (r->raster_scroll) {
mat4 mw_transform = MAT4(
src->w, 0, 0, 0,
0, -src->h, 0, src->h, // flip vertically
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(src->w, src->h);
struct gfx_render_job job = {
.shader = &r->raster_shader.s,
.shape = GFX_RECTANGLE,
.texture = src->handle,
.world_transform = mw_transform[0],
.view_transform = wv_transform[0],
.data = r
};
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, 0, 0, dst->w, dst->h);
gfx_render(&job);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
} else {
gfx_copy_reverse_UD(dst, 0, 0, src, 0, 0, src->w, src->h);
}
}
void dungeon_renderer_set_raster_scroll(struct dungeon_renderer *r, int type)
{
r->raster_scroll = !!type;
if (r->raster_scroll && !r->raster_shader.s.program) {
load_raster_shader(&r->raster_shader);
}
}
void dungeon_renderer_set_raster_amp(struct dungeon_renderer *r, float amp)
{
r->raster_amp = amp;
}
void dungeon_renderer_enable_lightmap(struct dungeon_renderer *r, bool enable)
{
r->enable_lightmap = enable;
}
bool dungeon_renderer_get_draw_obj_flag(struct dungeon_renderer *r, int type)
{
if (type < 0 || type >= NR_DRAW_OBJ_FLAGS)
return false;
return r->draw_obj_flags & (1 << type);
}
void dungeon_renderer_set_draw_obj_flag(struct dungeon_renderer *r, int type, bool flag)
{
if (type < 0 || type >= NR_DRAW_OBJ_FLAGS)
return;
if (flag)
r->draw_obj_flags |= (1 << type);
else
r->draw_obj_flags &= ~(1 << type);
}
+13 -3
View File
@@ -20,6 +20,7 @@
#include "system4/cg.h"
#include "dungeon/dtx.h"
#include "dungeon/dungeon.h"
#include "dungeon/skybox.h"
#include "gfx/gfx.h"
@@ -33,8 +34,13 @@ struct skybox {
int nr_indices;
};
struct skybox *skybox_create(struct dtx *dtx)
struct skybox *skybox_create(enum draw_dungeon_version version, struct dtx *dtx)
{
if (version == DRAW_DUNGEON_2 || version == DRAW_FIELD) {
// Skybox is not used in these games.
return NULL;
};
struct skybox *s = xcalloc(1, sizeof(struct skybox));
gfx_load_shader(&s->shader, "shaders/dungeon_skybox.v.glsl", "shaders/dungeon_skybox.f.glsl");
s->proj_transform = glGetUniformLocation(s->shader.program, "proj_transform");
@@ -63,8 +69,8 @@ struct skybox *skybox_create(struct dtx *dtx)
glGenBuffers(1, &s->attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, s->attr_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(s->shader.vertex);
glVertexAttribPointer(s->shader.vertex, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(s->shader.vertex_pos);
glVertexAttribPointer(s->shader.vertex_pos, 3, GL_FLOAT, GL_FALSE, 0, 0);
glGenBuffers(1, &s->index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
@@ -103,6 +109,8 @@ struct skybox *skybox_create(struct dtx *dtx)
void skybox_free(struct skybox *s)
{
if (!s)
return;
glDeleteVertexArrays(1, &s->vao);
glDeleteBuffers(1, &s->attr_buffer);
glDeleteBuffers(1, &s->index_buffer);
@@ -113,6 +121,8 @@ void skybox_free(struct skybox *s)
void skybox_render(struct skybox *s, const mat4 view_transform, const mat4 proj_transform)
{
if (!s)
return;
glDepthFunc(GL_LEQUAL);
glUseProgram(s->shader.program);
+90 -79
View File
@@ -19,7 +19,6 @@
#include <SDL.h>
#include "effect.h"
#include "gfx/gfx.h"
#include "sact.h"
#include "system4.h"
#include "xsystem4.h"
@@ -76,6 +75,7 @@ const char *effect_names[NR_EFFECTS] = {
[EFFECT_BLUR_FADEOUT] = "EFFECT_BLUR_FADEOUT",
[EFFECT_BLUR_CROSSFADE] = "EFFECT_BLUR_CROSSFADE",
[EFFECT_2ROT_ZOOM_BLEND_BLUR] = "EFFECT_2ROT_ZOOM_BLEND_BLUR",
[EFFECT_VWAVE_CROSSFADE] = "EFFECT_VWAVE_CROSSFADE",
};
struct effect_shader {
@@ -92,6 +92,13 @@ struct effect_data {
float progress;
};
static struct {
bool on;
int type;
Texture old;
Texture view;
} effect = {0};
static void prepare_effect_shader(struct gfx_render_job *job, void *data)
{
struct effect_shader *s = (struct effect_shader*)job->shader;
@@ -115,18 +122,23 @@ static void load_effect_shader(struct effect_shader *s)
s->s.prepare = prepare_effect_shader;
}
static void render_effect_shader(struct effect_shader *shader, Texture *old, Texture *new, float progress)
static void render_effect_shader(struct effect_shader *shader, Texture *dst, Texture *old, Texture *new, float progress)
{
struct effect_data data = { .old = old, .new = new, .progress = progress };
mat4 world_transform = WORLD_TRANSFORM(new->w, new->h, 0, 0);
mat4 wv_transform = WV_TRANSFORM(config.view_width, config.view_height);
struct gfx_render_job job = {
.shader = &shader->s,
.shape = GFX_RECTANGLE,
.texture = new->handle,
.world_transform = new->world_transform[0],
.world_transform = world_transform[0],
.view_transform = wv_transform[0],
.data = &data
};
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, 0, 0,
dst->w, dst->h);
gfx_render(&job);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
}
#define EFFECT_SHADER(path) { .s = { .prepare = prepare_effect_shader }, .f_path = path}
@@ -143,8 +155,7 @@ static struct effect_shader turn_page_shader = EFFECT_SHADER("shaders/effects/tu
static struct effect_shader sepia_noise_crossfade_shader = EFFECT_SHADER("shaders/effects/sepia_noise_crossfade.f.glsl");
static struct effect_shader blur_fadeout_shader = EFFECT_SHADER("shaders/effects/blur_fadeout.f.glsl");
static struct effect_shader blur_crossfade_shader = EFFECT_SHADER("shaders/effects/blur_crossfade.f.glsl");
extern GLuint main_surface_fb;
static struct effect_shader vwave_crossfade_shader = EFFECT_SHADER("shaders/effects/vwave_crossfade.f.glsl");
static struct effect_shader *effect_shaders[NR_EFFECTS] = {
[EFFECT_CROSSFADE] = &crossfade_shader,
@@ -160,70 +171,63 @@ static struct effect_shader *effect_shaders[NR_EFFECTS] = {
[EFFECT_SEPIA_NOISE_CROSSFADE] = &sepia_noise_crossfade_shader,
[EFFECT_BLUR_FADEOUT] = &blur_fadeout_shader,
[EFFECT_BLUR_CROSSFADE] = &blur_crossfade_shader,
[EFFECT_VWAVE_CROSSFADE] = &vwave_crossfade_shader,
};
static struct {
bool on;
int type;
Texture old;
Texture new;
} effect = {0};
static void effect_fadeout(float rate)
static void effect_fadeout(Texture *dst, Texture *old, Texture *new, float rate)
{
gfx_copy_bright(gfx_main_surface(), 0, 0, &effect.old, 0, 0,
effect.old.w, effect.old.h, (1.0f - rate) * 255);
gfx_copy_bright(dst, 0, 0, old, 0, 0, old->w, old->h, (1.0f - rate) * 255);
}
static void effect_fadein(float rate)
static void effect_fadein(Texture *dst, Texture *old, Texture *new, float rate)
{
gfx_copy_bright(gfx_main_surface(), 0, 0, &effect.new, 0, 0,
effect.new.w, effect.new.h, rate * 255);
gfx_copy_bright(dst, 0, 0, new, 0, 0, new->w, new->h, rate * 255);
}
static void effect_whiteout(float rate)
static void effect_whiteout(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
gfx_fill(dst, 0, 0, dst->w, dst->h, 255, 255, 255);
gfx_blend(dst, 0, 0, &effect.old, 0, 0, dst->w, dst->h, (1.0f - rate) * 255);
gfx_blend(dst, 0, 0, old, 0, 0, dst->w, dst->h, (1.0f - rate) * 255);
}
static void effect_whitein(float rate)
static void effect_whitein(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
gfx_fill(dst, 0, 0, dst->w, dst->h, 255, 255, 255);
gfx_blend(dst, 0, 0, &effect.new, 0, 0, dst->w, dst->h, rate * 255);
gfx_blend(dst, 0, 0, new, 0, 0, dst->w, dst->h, rate * 255);
}
static void effect_zoom_lr(float rate)
static void effect_rotate_out(Texture *dst, Texture *old, Texture *new, float rate)
{
gfx_fill(dst, 0, 0, dst->w, dst->h, 0, 0, 0);
gfx_copy_rot_zoom(dst, old, 0, 0, old->w, old->h, rate * 360, 1.f - rate);
}
static void effect_zoom_lr(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
unsigned x_pos = roundf(dst->w * rate);
gfx_copy_stretch(dst, x_pos, 0, dst->w-x_pos, dst->h, &effect.old, 0, 0, effect.old.w, effect.old.h);
gfx_copy_stretch(dst, 0, 0, x_pos, dst->h, &effect.new, 0, 0, effect.new.w, effect.new.h);
gfx_copy_stretch(dst, x_pos, 0, dst->w-x_pos, dst->h, old, 0, 0, old->w, old->h);
gfx_copy_stretch(dst, 0, 0, x_pos, dst->h, new, 0, 0, new->w, new->h);
}
static void effect_zoom_rl(float rate)
static void effect_zoom_rl(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
unsigned x_pos = dst->w - roundf(dst->w * rate);
gfx_copy_stretch(dst, x_pos, 0, dst->w-x_pos, dst->h, &effect.new, 0, 0, effect.new.w, effect.new.h);
gfx_copy_stretch(dst, 0, 0, x_pos, dst->h, &effect.old, 0, 0, effect.old.w, effect.old.h);
gfx_copy_stretch(dst, x_pos, 0, dst->w-x_pos, dst->h, new, 0, 0, new->w, new->h);
gfx_copy_stretch(dst, 0, 0, x_pos, dst->h, old, 0, 0, old->w, old->h);
}
static void effect_oscillate(float rate)
static void effect_oscillate(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
int delta_x = (rand() % (dst->w / 10) - (dst->w / 20)) * (1.0f - rate);
int delta_y = (rand() % (dst->h / 10) - (dst->h / 20)) * (1.0f - rate);;
gfx_copy(dst, 0, 0, &effect.old, 0, 0, dst->w, dst->h);
gfx_copy(dst, delta_x, delta_y, &effect.new, 0, 0, dst->w, dst->h);
gfx_copy(dst, 0, 0, old, 0, 0, dst->w, dst->h);
gfx_copy(dst, delta_x, delta_y, new, 0, 0, dst->w, dst->h);
}
static void effect_tv_switch(float rate, Texture *src)
static void effect_tv_switch(Texture *dst, Texture *src, float rate)
{
Texture *dst = gfx_main_surface();
gfx_fill(dst, 0, 0, dst->w, dst->h, 0, 0, 0);
if (rate < 0.5f) {
int h = max(1, (int)(dst->h * (1.0f - rate * 2.0f)));
gfx_copy_stretch(dst, 0, dst->h * rate, dst->w, h,
@@ -236,19 +240,18 @@ static void effect_tv_switch(float rate, Texture *src)
}
}
static void effect_tv_switch_off(float rate)
static void effect_tv_switch_off(Texture *dst, Texture *old, Texture *new, float rate)
{
effect_tv_switch(rate, &effect.old);
effect_tv_switch(dst, old, rate);
}
static void effect_tv_switch_on(float rate)
static void effect_tv_switch_on(Texture *dst, Texture *old, Texture *new, float rate)
{
effect_tv_switch(1.0f - rate, &effect.new);
effect_tv_switch(dst, new, 1.0f - rate);
}
static void effect_zoom_in_crossfade(float rate)
static void effect_zoom_in_crossfade(Texture *dst, Texture *old, Texture *new, float rate)
{
Texture *dst = gfx_main_surface();
float scale = 1.0f + powf(rate, 4.0f) * 16.0f;
int w = roundf(dst->w * scale);
int h = roundf(dst->h * scale);
@@ -256,16 +259,17 @@ static void effect_zoom_in_crossfade(float rate)
int y = -((h - dst->h)/2);
int a = (1.0f - rate) * 255;
gfx_copy(dst, 0, 0, &effect.new, 0, 0, dst->w, dst->h);
gfx_copy_stretch_blend(dst, x, y, w, h, &effect.old, 0, 0, dst->w, dst->h, a);
gfx_copy(dst, 0, 0, new, 0, 0, dst->w, dst->h);
gfx_copy_stretch_blend(dst, x, y, w, h, old, 0, 0, dst->w, dst->h, a);
}
typedef void (*effect_fun)(float rate);
typedef void (*effect_fun)(Texture *dst, Texture *old, Texture *new, float rate);
static effect_fun effect_functions[NR_EFFECTS] = {
[EFFECT_FADEOUT] = effect_fadeout,
[EFFECT_FADEIN] = effect_fadein,
[EFFECT_WHITEOUT] = effect_whiteout,
[EFFECT_WHITEIN] = effect_whitein,
[EFFECT_ROTATE_OUT] = effect_rotate_out,
[EFFECT_OSCILLATE] = effect_oscillate,
[EFFECT_TV_SWITCH_OFF] = effect_tv_switch_off,
[EFFECT_TV_SWITCH_ON] = effect_tv_switch_on,
@@ -274,7 +278,26 @@ static effect_fun effect_functions[NR_EFFECTS] = {
[EFFECT_ZOOM_IN_CROSSFADE] = effect_zoom_in_crossfade,
};
int sact_TRANS_Begin(int type)
void effect_update_texture(int type, Texture *dst, Texture *old, Texture *new, float rate)
{
if (effect_functions[type]) {
effect_functions[type](dst, old, new, rate);
} else {
struct effect_shader *s = effect_shaders[type];
if (!s) {
if (effect_names[type])
WARNING("Unimplemented effect: %s", effect_names[type]);
else
WARNING("Unimplemented effect: %d", type);
s = effect_shaders[EFFECT_BLUR_CROSSFADE];
}
if (!s->s.program)
load_effect_shader(s);
render_effect_shader(s, dst, old, new, rate);
}
}
int effect_init(enum effect type)
{
if (type <= 0 || type >= NR_EFFECTS) {
WARNING("Invalid or unknown effect: %d", type);
@@ -287,7 +310,10 @@ int sact_TRANS_Begin(int type)
else {
struct effect_shader *s = effect_shaders[type];
if (!s) {
WARNING("Unimplemented effect: %s", effect_names[type]);
if (effect_names[type])
WARNING("Unimplemented effect: %s", effect_names[type]);
else
WARNING("Unimplemented effect: %d", type);
type = EFFECT_BLUR_CROSSFADE;
s = effect_shaders[type];
}
@@ -299,53 +325,38 @@ int sact_TRANS_Begin(int type)
effect.on = true;
effect.type = type;
gfx_delete_texture(&effect.old);
gfx_delete_texture(&effect.view);
gfx_copy_main_surface(&effect.old);
scene_render();
gfx_copy_main_surface(&effect.new);
gfx_copy_main_surface(&effect.view);
gfx_set_view(&effect.view);
return 1;
}
int sact_TRANS_Update(float rate)
int effect_update(float rate)
{
if (!effect.on) {
return 0;
}
gfx_clear();
if (effect_functions[effect.type]) {
effect_functions[effect.type](rate);
effect_functions[effect.type](&effect.view, &effect.old, gfx_main_surface(), rate);
} else {
render_effect_shader(effect_shaders[effect.type], &effect.old, &effect.new, rate);
Texture new;
gfx_copy_main_surface(&new);
render_effect_shader(effect_shaders[effect.type], &effect.view, &effect.old, &new, rate);
gfx_delete_texture(&new);
}
gfx_swap();
return 1;
}
int sact_TRANS_End(void)
int effect_fini(void)
{
effect.on = false;
gfx_reset_view();
gfx_delete_texture(&effect.old);
gfx_delete_texture(&effect.new);
return 1;
}
int sact_Effect(int type, int time, possibly_unused int key)
{
uint32_t start = SDL_GetTicks();
if (!sact_TRANS_Begin(type))
return 0;
uint32_t t = SDL_GetTicks() - start;
while (t < time) {
sact_TRANS_Update((float)t / (float)time);
uint32_t t2 = SDL_GetTicks() - start;
if (t2 < t + 16) {
SDL_Delay(t + 16 - t2);
t += 16;
} else {
t = t2;
}
}
sact_TRANS_End();
gfx_delete_texture(&effect.view);
return 1;
}
+298 -72
View File
@@ -55,59 +55,148 @@ bool library_function_exists(int libno, int fno)
#ifdef TRACE_HLL
#include "system4/string.h"
static void trace_hll_call(struct ain_library *lib, struct ain_hll_function *f,
struct traced_library {
const char *name;
bool (*should_trace)(struct ain_hll_function *f, union vm_value **args);
};
static bool chipmunk_should_trace(struct ain_hll_function *f, union vm_value **args)
{
const char *no_trace[] = {
"SYSTEM_SetReadMessageSkipping",
"Update",
"KeepPreviousView",
"Sleep",
};
for (unsigned i = 0; i < sizeof(no_trace) / sizeof(*no_trace); i++) {
if (!strcmp(f->name, no_trace[i]))
return false;
}
return true;
}
#include "parts.h"
#include "parts/parts_internal.h"
static bool gui_engine_should_trace(struct ain_hll_function *f, union vm_value **args)
{
const char *no_trace[] = {
"Parts_UpdateMotionTime",
"UpdateInputState",
"UpdateComponent",
"UpdateParts",
"GetMessageType",
"ReleaseMessage",
"Parts_SetPartsPixelDecide",
"Parts_GetPartsShow",
"Parts_IsCursorIn",
"Parts_GetPartsAlpha",
};
for (unsigned i = 0; i < sizeof(no_trace) / sizeof(*no_trace); i++) {
if (!strcmp(f->name, no_trace[i]))
return false;
}
if (!strcmp(f->name, "Parts_SetPartsCG")) {
static char *u = NULL;
if (!u)
u = utf2sjis("システム/ボタン/メニュー/通常", 0);
struct string ***strs = (void*)args;
struct string *s = *strs[1];
return strcmp(s->text, u);
}
if (!strcmp(f->name, "Parts_SetPos")) {
if (args[1]->i < 0 || args[2]->i < 0)
return false;
return PE_GetPartsX(args[0]->i) != args[1]->i || PE_GetPartsY(args[0]->i) != args[2]->i;
}
if (!strcmp(f->name, "Parts_SetZ"))
return PE_GetPartsZ(args[0]->i) != args[1]->i;
if (!strcmp(f->name, "Parts_SetShow"))
return PE_GetPartsShow(args[0]->i) != args[1]->i;
if (!strcmp(f->name, "Parts_SetAlpha"))
return PE_GetPartsAlpha(args[0]->i) != args[1]->i;
if (!strcmp(f->name, "Parts_SetPartsCGSurfaceArea")) {
return false;
struct parts *p = parts_try_get(args[0]->i);
if (!p)
return true;
Rectangle *r = &p->states[args[5]->i].common.surface_area;
return args[1]->i != r->x || args[2]->i != r->y
|| args[3]->i != r->w || args[4]->i != r->h;
}
if (!strcmp(f->name, "Parts_SetAddColor")) {
struct parts *p = parts_try_get(args[0]->i);
if (!p)
return true;
SDL_Color *c = &p->local.add_color;
return args[1]->i != c->r || args[2]->i != c->g || args[3]->i != c->b;
}
if (!strcmp(f->name, "Parts_GetPartsX"))
return false;
return true;
}
static bool parts_engine_should_trace(struct ain_hll_function *f, union vm_value **args)
{
if (!strcmp(f->name, "Update"))
return false;
if (!strcmp(f->name, "SetAddColor"))
return false;
if (!strcmp(f->name, "SetAlpha"))
return PE_GetPartsAlpha(args[0]->i) != args[1]->i;
if (!strcmp(f->name, "SetPartsCGSurfaceArea"))
return args[1]->i >= 0 && args[2]->i >= 0;
if (!strcmp(f->name, "SetPartsConstructionSurfaceArea"))
return args[1]->i >= 0 && args[2]->i >= 0;
if (!strcmp(f->name, "SetPartsMagX"))
return args[1]->f > 1.001 || args[1]->f < -1.001;
if (!strcmp(f->name, "SetPartsMagY"))
return args[1]->f > 1.001 || args[1]->f < -1.001;
if (!strcmp(f->name, "SetPartsOriginPosMode"))
return PE_GetPartsOriginPosMode(args[0]->i) != args[1]->i;
if (!strcmp(f->name, "SetPartsRotateZ"))
return args[1]->f > 0.001 || args[1]->f < -0.001;
if (!strcmp(f->name, "SetPos"))
return PE_GetPartsX(args[0]->i) != args[1]->i || PE_GetPartsX(args[0]->i) != args[2]->i;
if (!strcmp(f->name, "SetShow")) {
if (args[0]->i == 0)
return false; // ???
return PE_GetPartsShow(args[0]->i) != args[1]->i;
}
if (!strcmp(f->name, "SetZ"))
return PE_GetPartsZ(args[0]->i) != args[1]->i;
return true;
}
static bool sact_should_trace(struct ain_hll_function *f, union vm_value **args)
{
const char *no_trace[] = {
"Key_IsDown",
"Mouse_ClearWheel",
"Mouse_GetPos",
"Mouse_GetWheel",
"SP_GetWidth",
"SP_GetHeight",
"SP_ExistAlpha",
"Update",
};
for (unsigned i = 0; i < sizeof(no_trace) / sizeof(*no_trace); i++) {
if (!strcmp(f->name, no_trace[i]))
return false;
}
return true;
}
struct traced_library traced_libraries[] = {
{ "ChipmunkSpriteEngine", chipmunk_should_trace },
{ "GUIEngine", gui_engine_should_trace },
{ "PartsEngine", parts_engine_should_trace },
{ "SACTDX", sact_should_trace },
};
static void print_hll_trace(struct ain_library *lib, struct ain_hll_function *f,
struct hll_function *fun, union vm_value *r, void *_args)
{
/*
// list of traced libraries
if (!strcmp(lib->name, "StoatSpriteEngine") || !strcmp(lib->name, "ChipmunkSpriteEngine")) {
// list of excluded functions
if (!strcmp(f->name, "CharSprite_GetAlphaRate")) goto notrace;
if (!strcmp(f->name, "CharSprite_SetAlphaRate")) goto notrace;
if (!strcmp(f->name, "KEY_GetState")) goto notrace;
if (!strcmp(f->name, "Mouse_ClearWheel")) goto notrace;
if (!strcmp(f->name, "Mouse_GetPos")) goto notrace;
if (!strcmp(f->name, "Mouse_GetWheel")) goto notrace;
if (!strcmp(f->name, "MultiSprite_UpdateView")) goto notrace;
if (!strcmp(f->name, "SP_ExistAlpha")) goto notrace;
if (!strcmp(f->name, "SP_GetHeight")) goto notrace;
if (!strcmp(f->name, "SP_GetWidth")) goto notrace;
if (!strcmp(f->name, "SP_IsPtIn")) goto notrace;
if (!strcmp(f->name, "SP_IsPtInRect")) goto notrace;
if (!strcmp(f->name, "SYSTEM_SetReadMessageSkipping")) goto notrace;
if (!strcmp(f->name, "TRANS_Update")) goto notrace;
if (!strcmp(f->name, "Update")) goto notrace;
else goto notrace;
}
else if (!strcmp(lib->name, "VSFile"));
else if (!strcmp(lib->name, "GoatGUIEngine")) {
if (!strcmp(f->name, "Update")) goto notrace;
if (!strcmp(f->name, "IsMotion")) goto notrace;
if (!strcmp(f->name, "SetMotionTime")) goto notrace;
if (!strcmp(f->name, "GetClickPartsNumber")) goto notrace;
}
//else if (!strcmp(lib->name, "DrawGraph"));
else if (!strcmp(lib->name, "SACT2")) {
if (strncmp(f->name, "SP_", 3)) goto notrace;
if (!strcmp(f->name, "Key_IsDown")) goto notrace;
if (!strcmp(f->name, "Mouse_GetPos")) goto notrace;
if (!strcmp(f->name, "SP_ExistAlpha")) goto notrace;
if (!strcmp(f->name, "SP_IsPtInRect")) goto notrace;
if (!strcmp(f->name, "SP_IsPtIn")) goto notrace;
if (!strcmp(f->name, "SP_IsUsing")) goto notrace;
if (!strcmp(f->name, "SP_SetShow")) goto notrace;
if (!strcmp(f->name, "SP_SetPos")) goto notrace;
if (!strcmp(f->name, "Timer_Get")) goto notrace;
if (!strcmp(f->name, "Update")) goto notrace;
}
else*/ if (!strcmp(lib->name, "SengokuRanceFont")) {
if (!strcmp(f->name, "SP_SetReduceDescender")) goto notrace;
if (!strcmp(f->name, "SP_ClearState")) goto notrace;
}
else goto notrace;
sys_message("(%s) ", display_sjis0(ain->functions[call_stack[call_stack_ptr-1].fno].name));
sys_message("%s.%s(", lib->name, f->name);
union vm_value **args = _args;
for (int i = 0; i < f->nr_arguments; i++) {
@@ -183,8 +272,22 @@ static void trace_hll_call(struct ain_library *lib, struct ain_hll_function *f,
}
}
sys_message("\n");
return;
notrace:
}
static void trace_hll_call(struct ain_library *lib, struct ain_hll_function *f,
struct hll_function *fun, union vm_value *r, void *_args)
{
for (unsigned i = 0; i < sizeof(traced_libraries)/sizeof(*traced_libraries); i++) {
struct traced_library *l = &traced_libraries[i];
if (!strcmp(lib->name, l->name)) {
if (!l->should_trace || l->should_trace(f, _args)) {
print_hll_trace(lib, f, fun, r, _args);
return;
}
break;
}
}
ffi_call(&fun->cif, (void*)fun->fun, r, _args);
}
#endif /* TRACE_HLL */
@@ -200,13 +303,13 @@ void hll_call(int libno, int fno)
if (!fun->fun)
VM_ERROR("Unimplemented HLL function: %s.%s", ain->libraries[libno].name, f->name);
// XXX: Try to prevent the heap from being reallocated mid-call.
// This only works to the extent that HLL functions can guarantee
// no more than 64 heap allocations occur within the call...
heap_guarantee(64);
void *args[HLL_MAX_ARGS];
void *ptrs[HLL_MAX_ARGS];
// Copy reference arguments to the stack to protect against heap
// reallocation during HLL calls.
void *heap_ptrs[HLL_MAX_ARGS];
int heap_slots[HLL_MAX_ARGS];
for (int i = f->nr_arguments - 1; i >= 0; i--) {
switch (f->arguments[i].type.data) {
case AIN_REF_INT:
@@ -227,7 +330,9 @@ void hll_call(int libno, int fno)
break;
case AIN_REF_STRING:
stack_ptr--;
ptrs[i] = &heap[stack[stack_ptr].i].s;
heap_slots[i] = stack[stack_ptr].i;
heap_ptrs[i] = heap[stack[stack_ptr].i].s;
ptrs[i] = &heap_ptrs[i];
args[i] = &ptrs[i];
break;
case AIN_STRUCT:
@@ -238,7 +343,9 @@ void hll_call(int libno, int fno)
case AIN_REF_STRUCT:
case AIN_REF_ARRAY_TYPE:
stack_ptr--;
ptrs[i] = &heap[stack[stack_ptr].i].page;
heap_slots[i] = stack[stack_ptr].i;
heap_ptrs[i] = heap[stack[stack_ptr].i].page;
ptrs[i] = &heap_ptrs[i];
args[i] = &ptrs[i];
break;
default:
@@ -267,28 +374,48 @@ void hll_call(int libno, int fno)
j++;
break;
case AIN_REF_STRING:
case AIN_REF_STRUCT:
case AIN_REF_FUNC_TYPE:
case AIN_REF_ARRAY_TYPE:
heap[heap_slots[i]].s = heap_ptrs[i];
break;
case AIN_REF_STRUCT:
case AIN_REF_ARRAY_TYPE:
heap[heap_slots[i]].page = heap_ptrs[i];
break;
case AIN_REF_FUNC_TYPE:
break;
case AIN_ARRAY_TYPE:
// Sys41VM doesn't make a copy when passing an array by value.
if (ain->version <= 1)
break;
// fallthrough
default:
variable_fini(stack[stack_ptr+j], f->arguments[i].type.data);
variable_fini(stack[stack_ptr+j], f->arguments[i].type.data, false);
break;
}
}
if (f->return_type.data != AIN_VOID) {
if (f->return_type.data == AIN_STRING) {
int slot = heap_alloc_slot(VM_STRING);
heap[slot].s = r.ref;
stack_push(slot);
} else {
stack_push(r);
}
int slot;
switch (f->return_type.data) {
case AIN_VOID:
break;
case AIN_STRING:
slot = heap_alloc_slot(VM_STRING);
heap[slot].s = r.ref;
stack_push(slot);
break;
case AIN_BOOL:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
stack_push(*(bool*)&r);
#pragma GCC diagnostic pop
break;
default:
stack_push(r);
break;
}
}
extern struct static_library lib_ACXLoader;
extern struct static_library lib_ACXLoaderP2;
extern struct static_library lib_ADVSYS;
extern struct static_library lib_AliceLogo;
extern struct static_library lib_AliceLogo2;
@@ -296,8 +423,10 @@ extern struct static_library lib_AliceLogo3;
extern struct static_library lib_AliceLogo4;
extern struct static_library lib_AliceLogo5;
extern struct static_library lib_AnteaterADVEngine;
extern struct static_library lib_Array;
extern struct static_library lib_BanMisc;
extern struct static_library lib_Bitarray;
extern struct static_library lib_CalcTable;
extern struct static_library lib_CGManager;
extern struct static_library lib_ChipmunkSpriteEngine;
extern struct static_library lib_ChrLoader;
@@ -307,15 +436,25 @@ extern struct static_library lib_Confirm2;
extern struct static_library lib_Confirm3;
extern struct static_library lib_CrayfishLogViewer;
extern struct static_library lib_Cursor;
extern struct static_library lib_DALKDemo;
extern struct static_library lib_DALKEDemo;
extern struct static_library lib_Data;
extern struct static_library lib_DataFile;
extern struct static_library lib_DrawDungeon;
extern struct static_library lib_DrawDungeon2;
extern struct static_library lib_DrawDungeon14;
extern struct static_library lib_DrawEffect;
extern struct static_library lib_DrawField;
extern struct static_library lib_DrawGraph;
extern struct static_library lib_DrawMovie;
extern struct static_library lib_DrawMovie2;
extern struct static_library lib_DrawMovie3;
extern struct static_library lib_DrawNumeral;
extern struct static_library lib_DrawPluginManager;
extern struct static_library lib_DrawRain;
extern struct static_library lib_DrawRipple;
extern struct static_library lib_DrawSimpleText;
extern struct static_library lib_DrawSnow;
extern struct static_library lib_File;
extern struct static_library lib_File2;
extern struct static_library lib_FileOperation;
@@ -323,6 +462,7 @@ extern struct static_library lib_FillAngle;
extern struct static_library lib_GoatGUIEngine;
extern struct static_library lib_Gpx2Plus;
extern struct static_library lib_GUIEngine;
extern struct static_library lib_HTTPDownloader;
extern struct static_library lib_IbisInputEngine;
extern struct static_library lib_InputDevice;
extern struct static_library lib_InputString;
@@ -330,6 +470,8 @@ extern struct static_library lib_KiwiSoundEngine;
extern struct static_library lib_LoadCG;
extern struct static_library lib_MainEXFile;
extern struct static_library lib_MainSurface;
extern struct static_library lib_MamanyoDemo;
extern struct static_library lib_MamanyoSDemo;
extern struct static_library lib_MarmotModelEngine;
extern struct static_library lib_Math;
extern struct static_library lib_MapLoader;
@@ -338,25 +480,58 @@ extern struct static_library lib_MonsterInfo;
extern struct static_library lib_MsgLogManager;
extern struct static_library lib_MsgLogViewer;
extern struct static_library lib_MsgSkip;
extern struct static_library lib_MusicSystem;
extern struct static_library lib_NewFont;
extern struct static_library lib_OutputLog;
extern struct static_library lib_PassRegister;
extern struct static_library lib_PastelChime2;
extern struct static_library lib_PartsEngine;
extern struct static_library lib_PixelRestore;
extern struct static_library lib_PlayDemo;
extern struct static_library lib_PlayMovie;
extern struct static_library lib_ReignEngine;
extern struct static_library lib_SACT2;
extern struct static_library lib_SACTDX;
extern struct static_library lib_SengokuRanceFont;
extern struct static_library lib_Sound2ex;
extern struct static_library lib_SoundFilePlayer;
extern struct static_library lib_StoatSpriteEngine;
extern struct static_library lib_StretchHelper;
extern struct static_library lib_SystemService;
extern struct static_library lib_SystemServiceEx;
extern struct static_library lib_TapirEngine;
extern struct static_library lib_Timer;
extern struct static_library lib_Toushin3Loader;
extern struct static_library lib_vmAnime;
extern struct static_library lib_vmArray;
extern struct static_library lib_vmCG;
extern struct static_library lib_vmChrLoader;
extern struct static_library lib_vmCursor;
extern struct static_library lib_vmDalkGaiden;
extern struct static_library lib_vmData;
extern struct static_library lib_vmDialog;
extern struct static_library lib_vmDrawGauge;
extern struct static_library lib_vmDrawMsg;
extern struct static_library lib_vmDrawNumber;
extern struct static_library lib_vmFile;
extern struct static_library lib_vmGraph;
extern struct static_library lib_vmGraphQuake;
extern struct static_library lib_vmKey;
extern struct static_library lib_vmMapLoader;
extern struct static_library lib_vmMsgLog;
extern struct static_library lib_vmMsgSkip;
extern struct static_library lib_vmMusic;
extern struct static_library lib_vmSound;
extern struct static_library lib_vmSprite;
extern struct static_library lib_vmString;
extern struct static_library lib_vmSurface;
extern struct static_library lib_vmSystem;
extern struct static_library lib_vmTimer;
extern struct static_library lib_VSFile;
static struct static_library *static_libraries[] = {
&lib_ACXLoader,
&lib_ACXLoaderP2,
&lib_ADVSYS,
&lib_AliceLogo,
&lib_AliceLogo2,
@@ -364,8 +539,10 @@ static struct static_library *static_libraries[] = {
&lib_AliceLogo4,
&lib_AliceLogo5,
&lib_AnteaterADVEngine,
&lib_Array,
&lib_BanMisc,
&lib_Bitarray,
&lib_CalcTable,
&lib_CGManager,
&lib_ChipmunkSpriteEngine,
&lib_ChrLoader,
@@ -375,15 +552,25 @@ static struct static_library *static_libraries[] = {
&lib_Confirm3,
&lib_CrayfishLogViewer,
&lib_Cursor,
&lib_DALKDemo,
&lib_DALKEDemo,
&lib_Data,
&lib_DataFile,
&lib_DrawDungeon,
&lib_DrawDungeon2,
&lib_DrawDungeon14,
&lib_DrawEffect,
&lib_DrawField,
&lib_DrawGraph,
&lib_DrawMovie,
&lib_DrawMovie2,
&lib_DrawMovie3,
&lib_DrawNumeral,
&lib_DrawPluginManager,
&lib_DrawRain,
&lib_DrawRipple,
&lib_DrawSimpleText,
&lib_DrawSnow,
&lib_File,
&lib_File2,
&lib_FileOperation,
@@ -391,6 +578,7 @@ static struct static_library *static_libraries[] = {
&lib_GoatGUIEngine,
&lib_Gpx2Plus,
&lib_GUIEngine,
&lib_HTTPDownloader,
&lib_IbisInputEngine,
&lib_InputDevice,
&lib_InputString,
@@ -398,6 +586,8 @@ static struct static_library *static_libraries[] = {
&lib_LoadCG,
&lib_MainEXFile,
&lib_MainSurface,
&lib_MamanyoDemo,
&lib_MamanyoSDemo,
&lib_MarmotModelEngine,
&lib_Math,
&lib_MapLoader,
@@ -406,21 +596,53 @@ static struct static_library *static_libraries[] = {
&lib_MsgLogManager,
&lib_MsgLogViewer,
&lib_MsgSkip,
&lib_MusicSystem,
&lib_NewFont,
&lib_OutputLog,
&lib_PassRegister,
&lib_PastelChime2,
&lib_PartsEngine,
&lib_PixelRestore,
&lib_PlayDemo,
&lib_PlayMovie,
&lib_ReignEngine,
&lib_SACT2,
&lib_SACTDX,
&lib_SengokuRanceFont,
&lib_Sound2ex,
&lib_SoundFilePlayer,
&lib_StoatSpriteEngine,
&lib_StretchHelper,
&lib_SystemService,
&lib_SystemServiceEx,
&lib_TapirEngine,
&lib_Timer,
&lib_Toushin3Loader,
&lib_vmAnime,
&lib_vmArray,
&lib_vmCG,
&lib_vmChrLoader,
&lib_vmCursor,
&lib_vmDalkGaiden,
&lib_vmData,
&lib_vmDialog,
&lib_vmDrawGauge,
&lib_vmDrawMsg,
&lib_vmDrawNumber,
&lib_vmFile,
&lib_vmGraph,
&lib_vmGraphQuake,
&lib_vmKey,
&lib_vmMapLoader,
&lib_vmMsgLog,
&lib_vmMsgSkip,
&lib_vmMusic,
&lib_vmSound,
&lib_vmSprite,
&lib_vmString,
&lib_vmSurface,
&lib_vmSystem,
&lib_vmTimer,
&lib_VSFile,
NULL
};
@@ -529,16 +751,20 @@ static void link_libraries(void)
}
}
bool libraries_initialized = false;
void init_libraries(void)
{
library_run_all("_PreLink");
link_libraries();
library_run_all("_ModuleInit");
libraries_initialized = true;
}
void exit_libraries(void)
{
library_run_all("_ModuleFini");
if (libraries_initialized)
library_run_all("_ModuleFini");
}
void static_library_replace(struct static_library *lib, const char *name, void *fun)
+7
View File
@@ -189,6 +189,12 @@ static float fnl_font_size_char(struct font_size *_size, uint32_t code)
return r;
}
static float fnl_font_size_char_kerning(struct font_size *size, uint32_t code,
uint32_t code_next)
{
return fnl_font_size_char(size, code);
}
struct font *fnl_font_load(struct fnl *lib, unsigned index)
{
if (index >= lib->nr_fonts)
@@ -227,6 +233,7 @@ struct font *fnl_font_load(struct fnl *lib, unsigned index)
font->super.get_actual_size_round_down = fnl_font_get_actual_size_round_down;
font->super.get_glyph = fnl_font_get_glyph;
font->super.size_char = fnl_font_size_char;
font->super.size_char_kerning = fnl_font_size_char_kerning;
if (!game_rance7_mg)
gfx_text_advance_edges = true;
+46 -3
View File
@@ -18,6 +18,8 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_ADVANCES_H
#include <SDL.h>
#include "system4.h"
#include "system4/hashtable.h"
@@ -96,7 +98,7 @@ static float ft_font_get_actual_size(struct font *_, float size)
static Rectangle init_glyph_texture(Texture *dst, FT_Bitmap *glyph, int bitmap_left, int bitmap_top, int size, bool half_width)
{
// calculate block size and offsets
int block_width = (half_width ? size/2 : size);
int block_width = max(half_width ? size/2 : size, max(0, bitmap_left) + glyph->width);
int block_height = size + size/2;
int width = block_width + GLYPH_BORDER_SIZE*2;
int height = block_height + GLYPH_BORDER_SIZE*2;
@@ -185,16 +187,58 @@ static float ft_font_size_char(struct font_size *size, uint32_t code)
return is_half_width(code) ? size->size / 2 : size->size;
}
static float ft_font_size_char_kerning(struct font_size *size, uint32_t code,
uint32_t code_next)
{
struct font_ft *font = (struct font_ft*)size->font;
ft_font_set_size(font, size->size);
FT_UInt index = FT_Get_Char_Index(font->font, code);
FT_UInt next_index = FT_Get_Char_Index(font->font, code_next);
FT_Fixed advance;
FT_Get_Advance(font->font, index, FT_LOAD_DEFAULT, &advance);
if (!FT_HAS_KERNING(font->font)) {
return advance / 65536.f;
}
FT_Vector delta;
FT_Get_Kerning(font->font, index, next_index, FT_KERNING_DEFAULT, &delta);
return (advance / 65536.f) + (delta.x / 64.f);
}
struct font *ft_font_load(const char *path)
{
struct font_ft *font = xcalloc(1, sizeof(struct font_ft));
#ifdef __ANDROID__
// On Android, path may be an asset name which FT_New_Face cannot read
// directly, so use SDL_LoadFile to load the content into memory.
size_t size;
void *buf = SDL_LoadFile(path, &size);
if (!buf) {
free(font);
return NULL;
}
if (FT_New_Memory_Face(ft_lib, buf, size, 0, &font->font)) {
free(buf);
free(font);
return NULL;
}
#else
if (FT_New_Face(ft_lib, path, 0, &font->font)) {
free(font);
return NULL;
}
#endif
if (!font->font->charmap) {
WARNING("Font '%s' doesn't contain unicode charmap", path);
#ifdef __ANDROID__
free(buf);
#endif
free(font);
return NULL;
}
@@ -204,7 +248,6 @@ struct font *ft_font_load(const char *path)
font->super.get_actual_size_round_down = ft_font_get_actual_size;
font->super.get_glyph = ft_font_get_glyph;
font->super.size_char = ft_font_size_char;
font->super.size_char_kerning = ft_font_size_char_kerning;
return &font->super;
}
+30 -15
View File
@@ -37,10 +37,17 @@ struct fmt_spec {
enum fmt_type type;
int precision;
int padding;
bool zero_pad;
bool zero_pad;
bool zenkaku;
};
static inline bool is_integer_type(enum string_format_type t)
{
return t == STRFMT_INT
|| t == STRFMT_BOOL
|| t == STRFMT_LONG_INT;
}
static int read_number(const char **_fmt)
{
int n = 0;
@@ -54,7 +61,7 @@ static int read_number(const char **_fmt)
return n;
}
static bool parse_fmt_spec(const char **_fmt, struct fmt_spec *spec, enum ain_data_type target)
static bool parse_fmt_spec(const char **_fmt, struct fmt_spec *spec, enum string_format_type target)
{
bool r = false;
const char *fmt = (*_fmt) + 1;
@@ -64,33 +71,33 @@ static bool parse_fmt_spec(const char **_fmt, struct fmt_spec *spec, enum ain_da
switch (*fmt) {
case 'd':
spec->type = FMT_INT;
r = target == AIN_INT;
r = is_integer_type(target);
goto end;
case 'D':
spec->type = FMT_INT;
spec->zenkaku = true;
r = target == AIN_INT;
r = is_integer_type(target);
goto end;
case 'f':
spec->type = FMT_FLOAT;
r = target == AIN_FLOAT;
r = target == STRFMT_FLOAT;
goto end;
case 'F':
spec->type = FMT_FLOAT;
spec->zenkaku = true;
r = target == AIN_FLOAT;
r = target == STRFMT_FLOAT;
goto end;
case 's':
spec->type = FMT_STRING;
r = target == AIN_STRING;
r = target == STRFMT_STRING;
goto end;
case 'c':
spec->type = FMT_CHAR;
r = target == AIN_INT;
r = is_integer_type(target);
goto end;
case 'b':
spec->type = FMT_BOOL;
r = target == AIN_INT;
r = is_integer_type(target);
goto end;
case '0':
spec->zero_pad = true;
@@ -138,10 +145,18 @@ static void append_fmt(struct string **s, struct fmt_spec *spec, union vm_value
len = float_to_cstr(buf, DIGIT_MAX, arg.f, spec->padding, spec->zero_pad, spec->precision, spec->zenkaku);
string_append_cstr(s, buf, len);
break;
case FMT_STRING:
string_append(s, heap_get_string(arg.i));
case FMT_STRING: {
struct string *val = heap_get_string(arg.i);
int pad_len = spec->padding - val->size;
if (pad_len > 0) {
for (int i = 0; i < pad_len; i++) {
string_push_back(s, ' ');
}
}
string_append(s, val);
heap_unref(arg.i);
break;
}
case FMT_CHAR:
string_push_back(s, arg.i);
break;
@@ -154,9 +169,8 @@ static void append_fmt(struct string **s, struct fmt_spec *spec, union vm_value
}
}
struct string *string_format(struct string *fmt, union vm_value arg, enum ain_data_type type)
struct string *string_format(struct string *fmt, union vm_value arg, enum string_format_type type)
{
struct string *out = string_ref(&EMPTY_STRING);
for (const char *s = fmt->text; *s; s++) {
if (*s != '%')
continue;
@@ -164,11 +178,12 @@ struct string *string_format(struct string *fmt, union vm_value arg, enum ain_da
size_t size = s - fmt->text;
struct fmt_spec spec;
if (parse_fmt_spec(&s, &spec, type)) {
struct string *out = string_ref(&EMPTY_STRING);
string_append_cstr(&out, fmt->text, size);
append_fmt(&out, &spec, arg);
string_append_cstr(&out, s, strlen(s));
break;
return out;
}
}
return out;
return string_ref(fmt);
}
+52 -16
View File
@@ -27,6 +27,7 @@
#include "system4/utfsjis.h"
#include "gfx/gfx.h"
#include "input.h"
#include "xsystem4.h"
#include "hll/hll.h"
@@ -35,8 +36,9 @@ bool game_daibanchou_en = false;
bool game_rance02_mg = false;
bool game_rance6_mg = false;
bool game_rance7_mg = false;
bool id_indexed_afa = false;
bool game_rance8 = false;
bool game_rance8_mg = false;
bool game_dungeons_and_dolls = false;
static void write_instruction0(struct buffer *out, enum opcode op)
{
@@ -49,6 +51,42 @@ static void write_instruction1(struct buffer *out, enum opcode op, int32_t arg)
buffer_write_int32(out, arg);
}
void set_msgskip_delay(struct ain *ain, unsigned ms)
{
int orig_fno = ain_get_function(ain, "A");
if (orig_fno <= 0) {
WARNING("No 'A' function when applying msgskip delay");
return;
}
int new_fno = ain_dup_function(ain, orig_fno);
struct ain_function *override_f = &ain->functions[orig_fno];
// override void A(void) {
// super();
// if (key_is_down(VK_CONTROL))
// System.sleep(ms);
// }
struct buffer out;
buffer_init(&out, NULL, 0);
write_instruction1(&out, FUNC, orig_fno);
write_instruction1(&out, CALLFUNC, new_fno);
write_instruction1(&out, PUSH, VK_CONTROL);
write_instruction1(&out, CALLSYS, VM_XSYS_KEY_IS_DOWN);
uint32_t ifz_addr = out.index;
write_instruction1(&out, IFZ, 0);
write_instruction1(&out, PUSH, ms);
write_instruction1(&out, CALLSYS, SYS_SLEEP);
buffer_write_int32_at(&out, ifz_addr + 2, ain->code_size + out.index);
write_instruction0(&out, RETURN);
write_instruction1(&out, ENDFUNC, orig_fno);
override_f->address = ain->code_size + 6;
ain->code = xrealloc(ain->code, ain->code_size + out.index);
memcpy(ain->code + ain->code_size, out.buf, out.index);
ain->code_size += out.index;
}
// Rance 02 (MangaGamer version)
// -----------------------------
// Uses a custom proportional font implementation which makes assumptions
@@ -113,7 +151,7 @@ static void apply_rance02_hacks(struct ain *ain)
// multiply by config.text_x_scale (if not 1.0)
buffer_write_int32_at(&out, jump_addr + 2, out.index);
if (fabsf(1.0 - config.text_x_scale) > 0.01) {
if (fabsf(1.0f - config.text_x_scale) > 0.01) {
union { int32_t i; float f; } cast = { .f = config.text_x_scale };
write_instruction0(&out, ITOF);
write_instruction1(&out, F_PUSH, cast.i);
@@ -137,13 +175,6 @@ static void apply_rance6_hacks(struct ain *ain)
game_rance6_mg = true;
}
static void apply_rance7_hacks(struct ain *ain)
{
if (ain_get_library(ain, "SengokuRanceFont") < 0)
return;
game_rance7_mg = true;
}
// Daibanchou (English fan translation)
// ------------------------------------
// Gpx2Plus.CopyStretchReduceAMap behaves differently in order to work around
@@ -165,12 +196,17 @@ void apply_game_specific_hacks(struct ain *ain)
apply_rance02_hacks(ain);
} else if (!strcmp(game_name, "Rance6")) {
apply_rance6_hacks(ain);
} else if (!strcmp(game_name, "Sengoku Rance")) {
apply_rance7_hacks(ain);
} else if (!strcmp(game_name, "しゃーまんず・さんくちゅあり 巫女の聖域")) {
id_indexed_afa = true;
} else if (!strcmp(game_name, "大帝国")) {
id_indexed_afa = true;
} else if (!strcmp(game_name, "ランス・クエスト")) {
game_rance8 = true;
} else if (!strcmp(game_name, "Rance Quest")) {
game_rance8 = true;
game_rance8_mg = true;
} else if (!strcmp(game_name, "DUNGEONS&DOLLS")) {
game_dungeons_and_dolls = true;
}
free(game_name);
// XXX: we don't rely on game_name because mods change it
if (ain_get_library(ain, "SengokuRanceFont") >= 0)
game_rance7_mg = true;
}
+9 -13
View File
@@ -30,6 +30,7 @@
struct vm_pointer *heap = NULL;
size_t heap_size = 0;
uint32_t heap_next_seq;
// Heap free list
// This is a list of unused indices into the 'heap' array.
@@ -59,19 +60,6 @@ void heap_grow(size_t new_size)
heap_size = new_size;
}
void heap_guarantee(unsigned headroom)
{
if (heap_size - heap_free_ptr >= headroom)
return;
size_t new_size = heap_size;
while (new_size - heap_free_ptr < headroom) {
new_size += HEAP_ALLOC_STEP;
}
heap_grow(new_size);
}
void heap_init(void)
{
if (!heap) {
@@ -86,6 +74,7 @@ void heap_init(void)
heap_free_stack[i] = i;
}
heap_free_ptr = 1; // global page at index 0
heap_next_seq = 1;
}
int32_t heap_alloc_slot(enum vm_pointer_type type)
@@ -96,6 +85,7 @@ int32_t heap_alloc_slot(enum vm_pointer_type type)
int32_t slot = heap_free_stack[heap_free_ptr++];
heap[slot].ref = 1;
heap[slot].seq = heap_next_seq++;
heap[slot].type = type;
#ifdef DEBUG_HEAP
heap[slot].alloc_addr = instr_ptr;
@@ -110,6 +100,7 @@ int32_t heap_alloc_slot(enum vm_pointer_type type)
static void heap_free_slot(int32_t slot)
{
heap[slot].seq = 0;
heap_free_stack[--heap_free_ptr] = slot;
}
@@ -212,6 +203,11 @@ void exit_unref(int slot)
heap_free_slot(slot);
}
uint32_t heap_get_seq(int slot)
{
return heap_index_valid(slot) ? heap[slot].seq : 0;
}
bool heap_index_valid(int index)
{
return index >= 0 && (size_t)index < heap_size && heap[index].ref > 0;
+49 -2
View File
@@ -23,14 +23,14 @@
#include "system4/string.h"
#include "system4/acx.h"
#include "system4/file.h"
#include "system4/little_endian.h"
#include "hll.h"
#include "little_endian.h"
#include "vm/heap.h"
#include "vm/page.h"
#include "xsystem4.h"
struct acx *acx = NULL;
static struct acx *acx = NULL;
static bool ACXLoader_Load(struct string *filename)
{
@@ -131,3 +131,50 @@ HLL_LIBRARY(ACXLoader,
HLL_EXPORT(GetDataInt, ACXLoader_GetDataInt),
HLL_EXPORT(GetDataString, ACXLoader_GetDataString),
HLL_EXPORT(GetDataStruct, ACXLoader_GetDataStruct));
HLL_WARN_UNIMPLEMENTED(, void, ACXLoaderP2, GetError, int *nError, struct string **szErrorString);
static bool ACXLoaderP2_GetDataIntByKey(int key, int col, int *ptr)
{
if (!acx)
return false;
if (col < 0 || col >= acx->nr_columns)
return false;
for (int line = 0; line < acx->nr_lines; line++) {
if (key == acx_get_int(acx, line, 0)) {
*ptr = acx_get_int(acx, line, col);
return true;
}
}
return false;
}
static bool ACXLoaderP2_GetDataStringByKey(int key, int col, struct string **ptr)
{
if (!acx)
return false;
if (col < 0 || col >= acx->nr_columns)
return false;
for (int line = 0; line < acx->nr_lines; line++) {
if (key == acx_get_int(acx, line, 0)) {
if (*ptr)
free_string(*ptr);
*ptr = string_ref(acx_get_string(acx, line, col));
return true;
}
}
return false;
}
HLL_LIBRARY(ACXLoaderP2,
HLL_EXPORT(Load, ACXLoader_Load),
HLL_EXPORT(Release, ACXLoader_Release),
HLL_EXPORT(GetNumofLine, ACXLoader_GetNumofLine),
HLL_EXPORT(GetNumofColumn, ACXLoader_GetNumofColumn),
HLL_EXPORT(GetDataInt, ACXLoader_GetDataInt),
HLL_EXPORT(GetDataString, ACXLoader_GetDataString),
HLL_EXPORT(GetDataStruct, ACXLoader_GetDataStruct),
HLL_EXPORT(GetError, ACXLoaderP2_GetError),
HLL_EXPORT(GetDataIntByKey, ACXLoaderP2_GetDataIntByKey),
HLL_EXPORT(GetDataStringByKey, ACXLoaderP2_GetDataStringByKey)
);
+148 -61
View File
@@ -40,8 +40,14 @@ struct adv_log {
};
static bool enabled = true;
static unsigned nr_logs = 0;
struct adv_log *logs = NULL;
// A circular buffer of logs.
#define LOG_BUFFER_SIZE 1000
static struct adv_log *logs = NULL;
static unsigned log_first;
static unsigned log_last;
static void AnteaterADVEngine_PreLink(void);
static void init_log(struct adv_log *log)
{
@@ -50,20 +56,33 @@ static void init_log(struct adv_log *log)
log->nr_lines = 1;
}
static void free_log(struct adv_log *log)
{
for (unsigned i = 0; i < log->nr_lines; i++) {
free_string(log->lines[i]);
}
free(log->lines);
free(log->voices);
log->nr_lines = 0;
log->lines = NULL;
log->nr_voices = 0;
log->voices = NULL;
}
// Ensure there is at least one log allocated
static void ensure_log(void)
{
if (!nr_logs) {
logs = xcalloc(1, sizeof(struct adv_log));
if (!logs) {
logs = xcalloc(LOG_BUFFER_SIZE, sizeof(struct adv_log));
init_log(&logs[0]);
nr_logs = 1;
log_first = log_last = 0;
}
}
static struct adv_log *current_log()
{
ensure_log();
return &logs[nr_logs-1];
return &logs[log_last];
}
static struct string **current_line(struct adv_log *log)
@@ -75,17 +94,14 @@ static struct string **current_line(struct adv_log *log)
void ADVLogList_Clear(void)
{
// XXX: this function works even if logging is disabled
for (unsigned i = 0; i < nr_logs; i++) {
struct adv_log *log = &logs[i];
for (unsigned i = 0; i < log->nr_lines; i++) {
free_string(log->lines[i]);
}
free(log->lines);
free(log->voices);
if (!logs) return;
for (unsigned i = log_first;; i = (i + 1) % LOG_BUFFER_SIZE) {
free_log(&logs[i]);
if (i == log_last)
break;
}
free(logs);
logs = NULL;
nr_logs = 0;
}
void ADVLogList_AddText(struct string **text)
@@ -106,8 +122,12 @@ void ADVLogList_AddNewLine(void)
void ADVLogList_AddNewPage(void)
{
if (!enabled) return;
logs = xrealloc_array(logs, nr_logs, nr_logs + 1, sizeof(struct adv_log));
init_log(&logs[nr_logs++]);
log_last = (log_last + 1) % LOG_BUFFER_SIZE;
if (log_last == log_first) {
free_log(&logs[log_first]);
log_first = (log_first + 1) % LOG_BUFFER_SIZE;
}
init_log(&logs[log_last]);
}
void ADVLogList_AddVoice(int voice_no)
@@ -130,55 +150,55 @@ bool ADVLogList_IsEnable(void)
int ADVLogList_GetNumofADVLog(void)
{
return nr_logs ? nr_logs : 1;
if (!logs) return 1;
return (log_last - log_first + LOG_BUFFER_SIZE) % LOG_BUFFER_SIZE + 1;
}
static void check_log_nr(int log_no)
static struct adv_log *log_entry(int log_no)
{
// NOTE: AnteaterADVEngine.dll segfaults on invalid index
ensure_log();
if (log_no < 0 || (unsigned)log_no >= nr_logs)
if (log_no < 0 || (unsigned)log_no >= ADVLogList_GetNumofADVLog())
VM_ERROR("Invalid log number: %d", log_no);
return &logs[(log_first + log_no) % LOG_BUFFER_SIZE];
}
int ADVLogList_GetNumofADVLogText(int log_no)
{
check_log_nr(log_no);
return logs[log_no].nr_lines;
return log_entry(log_no)->nr_lines;
}
void ADVLogList_GetADVLogText(int log_no, int line_no, struct string **text)
{
check_log_nr(log_no);
struct adv_log *log = log_entry(log_no);
// NOTE: AnteaterADVEngine.dll segfaults on invalid index
if (line_no < 0 || (unsigned)line_no >= logs[log_no].nr_lines)
if (line_no < 0 || (unsigned)line_no >= log->nr_lines)
VM_ERROR("Invalid line number: %d", line_no);
*text = string_ref(logs[log_no].lines[line_no]);
*text = string_ref(log->lines[line_no]);
}
int ADVLogList_GetNumofADVLogVoice(int log_no)
{
check_log_nr(log_no);
return logs[log_no].nr_voices;
return log_entry(log_no)->nr_voices;
}
int ADVLogList_GetADVLogVoice(int log_no, int voice_no)
{
check_log_nr(log_no);
if (voice_no < 0 || (unsigned)voice_no >= logs[log_no].nr_voices)
struct adv_log *log = log_entry(log_no);
if (voice_no < 0 || (unsigned)voice_no >= log->nr_voices)
VM_ERROR("Invalid voice number: %d", voice_no);
return logs[log_no].voices[voice_no];
return log->voices[voice_no];
}
int ADVLogList_GetADVLogVoiceLast(int log_no)
{
check_log_nr(log_no);
unsigned nr_voices = logs[log_no].nr_voices;
struct adv_log *log = log_entry(log_no);
unsigned nr_voices = log->nr_voices;
if (!nr_voices)
return 0;
return logs[log_no].voices[nr_voices - 1];
return log->voices[nr_voices - 1];
}
bool ADVLogList_Save(struct page **iarray)
@@ -200,9 +220,10 @@ bool ADVLogList_Save(struct page **iarray)
struct iarray_writer b;
iarray_init_writer(&b, "ADL");
iarray_write(&b, 0);
int nr_logs = ADVLogList_GetNumofADVLog();
iarray_write(&b, nr_logs);
for (unsigned i = 0; i < nr_logs; i++) {
struct adv_log *log = &logs[i];
struct adv_log *log = &logs[(log_first + i) % LOG_BUFFER_SIZE];
iarray_write(&b, log->nr_lines);
for (unsigned i = 0; i < log->nr_lines; i++) {
iarray_write_string(&b, log->lines[i]);
@@ -241,14 +262,14 @@ bool ADVLogList_Load(struct page **data)
goto error;
}
logs = xcalloc(nr_pages + 1, sizeof(struct adv_log));
// XXX: For whatever reason this function always creates a blank page 0,
// and then loads the input pages after that. So a consecutive save
// and load will increase all page numbers by 1.
init_log(&logs[0]);
ensure_log();
for (int i = 1; i < nr_pages + 1; i++) {
struct adv_log *log = &logs[i];
ADVLogList_AddNewPage();
struct adv_log *log = current_log();
int nr_lines = iarray_read(&r);
if (nr_lines <= 0 || nr_lines > 10000) {
WARNING("Invalid value for nr_lines[%d]: %d", i, nr_lines);
@@ -273,7 +294,6 @@ bool ADVLogList_Load(struct page **data)
}
log->nr_voices = nr_voices;
}
nr_logs = nr_pages + 1;
if (iarray_read(&r) != 1 || r.error)
goto error;
@@ -283,10 +303,11 @@ error:
return false;
}
static struct {
unsigned n;
struct page **data;
} scenes = {0};
#define SCENE_BUFFER_SIZE 500 // Maximum in Rance 02
static struct page **scenes = NULL;
static unsigned scene_first = 0;
static unsigned scene_last = 0;
static int scene_struct_no(void)
{
@@ -309,13 +330,25 @@ bool ADVSceneKeeper_AddADVScene(struct page **page)
WARNING("Invalid struct type: %d", (*page)->index);
return false;
}
scenes.data = xrealloc_array(scenes.data, scenes.n, scenes.n+1, sizeof(struct page*));
if (!scenes) {
scenes = xcalloc(SCENE_BUFFER_SIZE, sizeof(struct page*));
scene_first = 0;
scene_last = 0;
} else {
scene_last = (scene_last + 1) % SCENE_BUFFER_SIZE;
if (scene_last == scene_first) {
free_page(scenes[scene_first]);
scenes[scene_first] = NULL;
scene_first = (scene_first + 1) % SCENE_BUFFER_SIZE;
}
}
// serialize
struct iarray_writer out;
iarray_init_writer(&out, "SCN");
iarray_write_struct(&out, *page);
scenes.data[scenes.n++] = iarray_to_page(&out);
iarray_write_struct(&out, *page, false);
scenes[scene_last] = iarray_to_page(&out);
iarray_free_writer(&out);
return true;
@@ -323,17 +356,21 @@ bool ADVSceneKeeper_AddADVScene(struct page **page)
int ADVSceneKeeper_GetNumofADVScene(void)
{
return scenes.n;
if (!scenes)
return 0;
return (scene_last - scene_first + SCENE_BUFFER_SIZE) % SCENE_BUFFER_SIZE + 1;
}
bool ADVSceneKeeper_GetADVScene(int index, struct page **page)
{
if (index < 0 || (unsigned)index >= scenes.n)
if (!scenes || index < 0 || (unsigned)index >= ADVSceneKeeper_GetNumofADVScene())
return false;
unsigned i = (scene_first + index) % SCENE_BUFFER_SIZE;
// deserialize
struct iarray_reader in;
if (!iarray_init_reader(&in, scenes.data[index], "SCN"))
if (!iarray_init_reader(&in, scenes[i], "SCN"))
return false;
if (*page) {
@@ -341,18 +378,22 @@ bool ADVSceneKeeper_GetADVScene(int index, struct page **page)
free_page(*page);
}
*page = iarray_read_struct(&in, scene_struct_no());
*page = iarray_read_struct(&in, scene_struct_no(), false);
return !in.error;
}
void ADVSceneKeeper_Clear(void)
{
for (unsigned i = 0; i < scenes.n; i++) {
free_page(scenes.data[i]);
if (!scenes)
return;
for (unsigned i = scene_first;; i = (i + 1) % SCENE_BUFFER_SIZE) {
if (scenes[i])
free_page(scenes[i]);
if (i == scene_last)
break;
}
free(scenes.data);
scenes.data = NULL;
scenes.n = 0;
free(scenes);
scenes = NULL;
}
bool ADVSceneKeeper_Save(struct page **iarray)
@@ -367,9 +408,11 @@ bool ADVSceneKeeper_Save(struct page **iarray)
struct iarray_writer w;
iarray_init_writer(&w, "SCS");
iarray_write(&w, 0); // version
iarray_write(&w, scenes.n);
for (unsigned i = 0; i < scenes.n; i++) {
iarray_write_array(&w, scenes.data[i]);
int nr_scenes = ADVSceneKeeper_GetNumofADVScene();
iarray_write(&w, nr_scenes);
for (int i = 0; i < nr_scenes; i++) {
unsigned idx = (scene_first + i) % SCENE_BUFFER_SIZE;
iarray_write_array(&w, scenes[idx]);
}
*iarray = iarray_to_page(&w);
iarray_free_writer(&w);
@@ -395,15 +438,35 @@ bool ADVSceneKeeper_Load(struct page **iarray)
if (nr_scenes < 0 || nr_scenes > 10000)
goto error;
scenes.n = 0;
scenes.data = xcalloc(nr_scenes, sizeof(struct page*));
if (nr_scenes == 0)
return true;
for (int i = 0; i < nr_scenes; i++) {
struct ain_type type = { .data = AIN_ARRAY_INT, .struc = 0, .rank = 0 };
scenes.data[scenes.n++] = iarray_read_array(&r, &type);
int start_offset = 0;
if (nr_scenes > SCENE_BUFFER_SIZE) {
start_offset = nr_scenes - SCENE_BUFFER_SIZE;
}
struct ain_type type = { .data = AIN_ARRAY_INT, .struc = 0, .rank = 0 };
for (int i = 0; i < start_offset; i++) {
struct page *p = iarray_read_array(&r, &type);
if (r.error)
goto error;
free_page(p);
}
int scenes_to_load = nr_scenes - start_offset;
if (scenes_to_load > 0) {
scenes = xcalloc(SCENE_BUFFER_SIZE, sizeof(struct page*));
scene_first = 0;
scene_last = scenes_to_load - 1;
for (int i = 0; i < scenes_to_load; i++) {
scenes[i] = iarray_read_array(&r, &type);
if (r.error)
goto error;
}
}
return true;
error:
ADVSceneKeeper_Clear();
@@ -415,7 +478,13 @@ static void AnteaterADVEngine_ModuleFini(void)
ADVSceneKeeper_Clear();
}
static void AnteaterADVEngine_ADVLogList_AddText_with_window(struct string **text, possibly_unused int window_no)
{
ADVLogList_AddText(text);
}
HLL_LIBRARY(AnteaterADVEngine,
HLL_EXPORT(_PreLink, AnteaterADVEngine_PreLink),
HLL_EXPORT(_ModuleFini, AnteaterADVEngine_ModuleFini),
HLL_EXPORT(ADVLogList_Clear, ADVLogList_Clear),
HLL_EXPORT(ADVLogList_AddText, ADVLogList_AddText),
@@ -438,3 +507,21 @@ HLL_LIBRARY(AnteaterADVEngine,
HLL_EXPORT(ADVSceneKeeper_Save, ADVSceneKeeper_Save),
HLL_EXPORT(ADVSceneKeeper_Load, ADVSceneKeeper_Load)
);
static struct ain_hll_function *get_fun(int libno, const char *name)
{
int fno = ain_get_library_function(ain, libno, name);
return fno >= 0 ? &ain->libraries[libno].functions[fno] : NULL;
}
static void AnteaterADVEngine_PreLink(void)
{
struct ain_hll_function *fun;
int libno = ain_get_library(ain, "AnteaterADVEngine");
assert(libno >= 0);
fun = get_fun(libno, "ADVLogList_AddText");
if (fun && fun->nr_arguments == 2) {
static_library_replace(&lib_AnteaterADVEngine, "ADVLogList_AddText", AnteaterADVEngine_ADVLogList_AddText_with_window);
}
}
+498
View File
@@ -0,0 +1,498 @@
/* Copyright (C) 2025 kichikuou <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, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
#include "vm/page.h"
static void check_array(struct page *array)
{
if (array->type != ARRAY_PAGE || array->a_type != AIN_ARRAY_INT || array->array.rank != 1)
VM_ERROR("Not a flat integer array");
}
//void Array_NV_copy(struct page **nArray, int nNum);
//void Array_NV_add(struct page **nArray, int nNum);
//void Array_NV_sub(struct page **nArray, int nNum);
//void Array_NV_mul(struct page **nArray, int nNum);
//void Array_NV_div(struct page **nArray, int nNum);
//void Array_NV_and(struct page **nArray, int nNum);
//void Array_NV_or(struct page **nArray, int nNum);
//void Array_NV_xor(struct page **nArray, int nNum);
//void Array_NV_min(struct page **nArray, int nNum);
//void Array_NV_max(struct page **nArray, int nNum);
//void Array_NN_copy(struct page **nArray, struct page *nArrayS);
//void Array_NN_add(struct page **nArray, struct page *nArrayS);
//void Array_NN_sub(struct page **nArray, struct page *nArrayS);
//void Array_NN_mul(struct page **nArray, struct page *nArrayS);
//void Array_NN_div(struct page **nArray, struct page *nArrayS);
//void Array_NN_and(struct page **nArray, struct page *nArrayS);
//void Array_NN_or(struct page **nArray, struct page *nArrayS);
//void Array_NN_xor(struct page **nArray, struct page *nArrayS);
//void Array_NN_min(struct page **nArray, struct page *nArrayS);
//void Array_NN_max(struct page **nArray, struct page *nArrayS);
//void Array_NS_copy(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_add(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_sub(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_mul(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_div(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_and(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_or(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_xor(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_min(struct page **nArray, struct page *sArray, int nMember);
//void Array_NS_max(struct page **nArray, struct page *sArray, int nMember);
//void Array_SV_copy(struct page **sArray, int nMember, int nNum);
//void Array_SV_add(struct page **sArray, int nMember, int nNum);
//void Array_SV_sub(struct page **sArray, int nMember, int nNum);
//void Array_SV_mul(struct page **sArray, int nMember, int nNum);
//void Array_SV_div(struct page **sArray, int nMember, int nNum);
//void Array_SV_and(struct page **sArray, int nMember, int nNum);
//void Array_SV_or(struct page **sArray, int nMember, int nNum);
//void Array_SV_xor(struct page **sArray, int nMember, int nNum);
//void Array_SV_min(struct page **sArray, int nMember, int nNum);
//void Array_SV_max(struct page **sArray, int nMember, int nNum);
//void Array_SN_copy(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_add(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_sub(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_mul(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_div(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_and(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_or(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_xor(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_min(struct page **sArray, int nMember, struct page *nArray);
//void Array_SN_max(struct page **sArray, int nMember, struct page *nArray);
//void Array_SS_copy(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_add(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_sub(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_mul(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_div(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_and(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_or(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_xor(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_min(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_SS_max(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS);
static int Array_NV_eneq(struct page *array, int num)
{
if (!array)
return 0;
check_array(array);
int count = 0;
for (int i = 0; i < array->nr_vars; i++) {
if (array->values[i].i == num)
count++;
}
return count;
}
//int Array_NV_enne(struct page *nArray, int nNum);
//int Array_NV_enlo(struct page *nArray, int nNum);
//int Array_NV_enhi(struct page *nArray, int nNum);
//int Array_NV_enra(struct page *nArray, int nMin, int nMax);
//int Array_NN_eneq(struct page *nArray, struct page *nArrayS);
//int Array_NN_enne(struct page *nArray, struct page *nArrayS);
//int Array_NN_enlo(struct page *nArray, struct page *nArrayS);
//int Array_NN_enhi(struct page *nArray, struct page *nArrayS);
//int Array_NS_eneq(struct page *nArray, struct page *sArray, int nMember);
//int Array_NS_enne(struct page *nArray, struct page *sArray, int nMember);
//int Array_NS_enlo(struct page *nArray, struct page *sArray, int nMember);
//int Array_NS_enhi(struct page *nArray, struct page *sArray, int nMember);
//int Array_SV_eneq(struct page *sArray, int nMember, int nNum);
//int Array_SV_enne(struct page *sArray, int nMember, int nNum);
//int Array_SV_enlo(struct page *sArray, int nMember, int nNum);
//int Array_SV_enhi(struct page *sArray, int nMember, int nNum);
//int Array_SV_enra(struct page *sArray, int nMember, int nMin, int nMax);
//int Array_SN_eneq(struct page *sArray, int nMember, struct page *nArray);
//int Array_SN_enne(struct page *sArray, int nMember, struct page *nArray);
//int Array_SN_enlo(struct page *sArray, int nMember, struct page *nArray);
//int Array_SN_enhi(struct page *sArray, int nMember, struct page *nArray);
//int Array_SS_eneq(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS);
//int Array_SS_enne(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS);
//int Array_SS_enlo(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS);
//int Array_SS_enhi(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS);
//void Array_NV_cheq(struct page **nArray, int nNum, int nChg);
//void Array_NV_chne(struct page **nArray, int nNum, int nChg);
//void Array_NV_chlo(struct page **nArray, int nNum, int nChg);
//void Array_NV_chhi(struct page **nArray, int nNum, int nChg);
//void Array_NV_chra(struct page **nArray, int nMin, int nMax, int nChg);
//void Array_NN_cheq(struct page **nArray, struct page *nArrayS, int nChg);
//void Array_NN_chne(struct page **nArray, struct page *nArrayS, int nChg);
//void Array_NN_chlo(struct page **nArray, struct page *nArrayS, int nChg);
//void Array_NN_chhi(struct page **nArray, struct page *nArrayS, int nChg);
//void Array_NS_cheq(struct page **nArray, struct page *sArray, int nMember, int nChg);
//void Array_NS_chne(struct page **nArray, struct page *sArray, int nMember, int nChg);
//void Array_NS_chlo(struct page **nArray, struct page *sArray, int nMember, int nChg);
//void Array_NS_chhi(struct page **nArray, struct page *sArray, int nMember, int nChg);
//void Array_SV_cheq(struct page **sArray, int nMember, int nNum, int nChg);
//void Array_SV_chne(struct page **sArray, int nMember, int nNum, int nChg);
//void Array_SV_chlo(struct page **sArray, int nMember, int nNum, int nChg);
//void Array_SV_chhi(struct page **sArray, int nMember, int nNum, int nChg);
//void Array_SV_chra(struct page **sArray, int nMember, int nMin, int nMax, int nChg);
//void Array_SN_cheq(struct page **sArray, int nMember, struct page *nArray, int nChg);
//void Array_SN_chne(struct page **sArray, int nMember, struct page *nArray, int nChg);
//void Array_SN_chlo(struct page **sArray, int nMember, struct page *nArray, int nChg);
//void Array_SN_chhi(struct page **sArray, int nMember, struct page *nArray, int nChg);
//void Array_SS_cheq(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS, int nChg);
//void Array_SS_chne(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS, int nChg);
//void Array_SS_chlo(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS, int nChg);
//void Array_SS_chhi(struct page **sArray, int nMember, struct page *sArrayS, int nMemberS, int nChg);
//void Array_NV_fweq(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fwne(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fwlo(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fwhi(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fwra(struct page *nArray, int nMin, int nMax, struct page **nArrayD);
//void Array_NV_faeq(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fane(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_falo(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fahi(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fara(struct page *nArray, int nMin, int nMax, struct page **nArrayD);
//void Array_NV_foeq(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fone(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_folo(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fohi(struct page *nArray, int nNum, struct page **nArrayD);
//void Array_NV_fora(struct page *nArray, int nMin, int nMax, struct page **nArrayD);
//void Array_NN_fweq(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fwne(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fwlo(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fwhi(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_faeq(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fane(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_falo(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fahi(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_foeq(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fone(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_folo(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NN_fohi(struct page *nArray, struct page *nArrayS, struct page **nArrayD);
//void Array_NS_fweq(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fwne(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fwlo(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fwhi(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_faeq(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fane(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_falo(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fahi(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_foeq(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fone(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_folo(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_NS_fohi(struct page *nArray, struct page *sArray, int nMember, struct page **nArrayD);
//void Array_SV_fweq(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fwne(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fwlo(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fwhi(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fwra(struct page *sArray, int nMember, int nMin, int nMax, struct page **nArrayD);
//void Array_SV_faeq(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fane(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_falo(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fahi(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fara(struct page *sArray, int nMember, int nMin, int nMax, struct page **nArrayD);
//void Array_SV_foeq(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fone(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_folo(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fohi(struct page *sArray, int nMember, int nNum, struct page **nArrayD);
//void Array_SV_fora(struct page *sArray, int nMember, int nMin, int nMax, struct page **nArrayD);
//void Array_SN_fweq(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fwne(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fwlo(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fwhi(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_faeq(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fane(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_falo(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fahi(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_foeq(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fone(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_folo(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SN_fohi(struct page *sArray, int nMember, struct page *nArrayS, struct page **nArrayD);
//void Array_SS_fweq(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fwne(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fwlo(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fwhi(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_faeq(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fane(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_falo(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fahi(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_foeq(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fone(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_folo(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
//void Array_SS_fohi(struct page *sArray, int nMember, struct page *sArrayS, int nMemberS, struct page **nArrayD);
static int Array_NV_sceq(struct page *array, int index, int num, int *out_index)
{
if (!array)
return 0;
check_array(array);
if (index >= 0) {
for (int i = index; i < array->nr_vars; i++) {
if (array->values[i].i == num) {
*out_index = i;
return 1;
}
}
} else {
// Negative index means backwards search
index &= 0x7fffffff;
if (index >= array->nr_vars)
return 0;
for (int i = index; i >= 0; --i) {
if (array->values[i].i == num) {
*out_index = i;
return 1;
}
}
}
return 0;
}
//int Array_NV_scne(struct page *nArray, int nIndex, int nNum, int *pnIndex);
//int Array_NV_sclo(struct page *nArray, int nIndex, int nNum, int *pnIndex);
//int Array_NV_schi(struct page *nArray, int nIndex, int nNum, int *pnIndex);
//int Array_NV_scra(struct page *nArray, int nIndex, int nMin, int nMax, int *pnIndex);
//int Array_SV_sceq(struct page *sArray, int nMember, int nIndex, int nNum, int *pnIndex);
//int Array_SV_scne(struct page *sArray, int nMember, int nIndex, int nNum, int *pnIndex);
//int Array_SV_sclo(struct page *sArray, int nMember, int nIndex, int nNum, int *pnIndex);
//int Array_SV_schi(struct page *sArray, int nMember, int nIndex, int nNum, int *pnIndex);
//int Array_SV_scra(struct page *sArray, int nMember, int nIndex, int nMin, int nMax, int *pnIndex);
//int Array_NN_sclowest(struct page *nArray, struct page **nArrayD, int *pnIndex);
//int Array_NN_schighest(struct page *nArray, struct page **nArrayD, int *pnIndex);
//int Array_NS_sclowest(struct page *nArray, struct page **sArray, int nMember, int *pnIndex);
//int Array_NS_schighest(struct page *nArray, struct page **sArray, int nMember, int *pnIndex);
//int Array_SN_sclowest(struct page *sArray, int nMember, struct page **nArray, int *pnIndex);
//int Array_SN_schighest(struct page *sArray, int nMember, struct page **nArray, int *pnIndex);
//int Array_SS_sclowest(struct page *sArray, int nMember, struct page **sArrayD, int nMemberS, int *pnIndex);
//int Array_SS_schighest(struct page *sArray, int nMember, struct page **sArrayD, int nMemberS, int *pnIndex);
//int Array_VN_add(struct page *nArray);
//int Array_VN_and(struct page *nArray);
//int Array_VN_or(struct page *nArray);
//int Array_VS_add(struct page *sArray, int nMember);
//int Array_VS_and(struct page *sArray, int nMember);
//int Array_VS_or(struct page *sArray, int nMember);
HLL_LIBRARY(Array,
HLL_TODO_EXPORT(NV_copy, Array_NV_copy),
HLL_TODO_EXPORT(NV_add, Array_NV_add),
HLL_TODO_EXPORT(NV_sub, Array_NV_sub),
HLL_TODO_EXPORT(NV_mul, Array_NV_mul),
HLL_TODO_EXPORT(NV_div, Array_NV_div),
HLL_TODO_EXPORT(NV_and, Array_NV_and),
HLL_TODO_EXPORT(NV_or, Array_NV_or),
HLL_TODO_EXPORT(NV_xor, Array_NV_xor),
HLL_TODO_EXPORT(NV_min, Array_NV_min),
HLL_TODO_EXPORT(NV_max, Array_NV_max),
HLL_TODO_EXPORT(NN_copy, Array_NN_copy),
HLL_TODO_EXPORT(NN_add, Array_NN_add),
HLL_TODO_EXPORT(NN_sub, Array_NN_sub),
HLL_TODO_EXPORT(NN_mul, Array_NN_mul),
HLL_TODO_EXPORT(NN_div, Array_NN_div),
HLL_TODO_EXPORT(NN_and, Array_NN_and),
HLL_TODO_EXPORT(NN_or, Array_NN_or),
HLL_TODO_EXPORT(NN_xor, Array_NN_xor),
HLL_TODO_EXPORT(NN_min, Array_NN_min),
HLL_TODO_EXPORT(NN_max, Array_NN_max),
HLL_TODO_EXPORT(NS_copy, Array_NS_copy),
HLL_TODO_EXPORT(NS_add, Array_NS_add),
HLL_TODO_EXPORT(NS_sub, Array_NS_sub),
HLL_TODO_EXPORT(NS_mul, Array_NS_mul),
HLL_TODO_EXPORT(NS_div, Array_NS_div),
HLL_TODO_EXPORT(NS_and, Array_NS_and),
HLL_TODO_EXPORT(NS_or, Array_NS_or),
HLL_TODO_EXPORT(NS_xor, Array_NS_xor),
HLL_TODO_EXPORT(NS_min, Array_NS_min),
HLL_TODO_EXPORT(NS_max, Array_NS_max),
HLL_TODO_EXPORT(SV_copy, Array_SV_copy),
HLL_TODO_EXPORT(SV_add, Array_SV_add),
HLL_TODO_EXPORT(SV_sub, Array_SV_sub),
HLL_TODO_EXPORT(SV_mul, Array_SV_mul),
HLL_TODO_EXPORT(SV_div, Array_SV_div),
HLL_TODO_EXPORT(SV_and, Array_SV_and),
HLL_TODO_EXPORT(SV_or, Array_SV_or),
HLL_TODO_EXPORT(SV_xor, Array_SV_xor),
HLL_TODO_EXPORT(SV_min, Array_SV_min),
HLL_TODO_EXPORT(SV_max, Array_SV_max),
HLL_TODO_EXPORT(SN_copy, Array_SN_copy),
HLL_TODO_EXPORT(SN_add, Array_SN_add),
HLL_TODO_EXPORT(SN_sub, Array_SN_sub),
HLL_TODO_EXPORT(SN_mul, Array_SN_mul),
HLL_TODO_EXPORT(SN_div, Array_SN_div),
HLL_TODO_EXPORT(SN_and, Array_SN_and),
HLL_TODO_EXPORT(SN_or, Array_SN_or),
HLL_TODO_EXPORT(SN_xor, Array_SN_xor),
HLL_TODO_EXPORT(SN_min, Array_SN_min),
HLL_TODO_EXPORT(SN_max, Array_SN_max),
HLL_TODO_EXPORT(SS_copy, Array_SS_copy),
HLL_TODO_EXPORT(SS_add, Array_SS_add),
HLL_TODO_EXPORT(SS_sub, Array_SS_sub),
HLL_TODO_EXPORT(SS_mul, Array_SS_mul),
HLL_TODO_EXPORT(SS_div, Array_SS_div),
HLL_TODO_EXPORT(SS_and, Array_SS_and),
HLL_TODO_EXPORT(SS_or, Array_SS_or),
HLL_TODO_EXPORT(SS_xor, Array_SS_xor),
HLL_TODO_EXPORT(SS_min, Array_SS_min),
HLL_TODO_EXPORT(SS_max, Array_SS_max),
HLL_EXPORT(NV_eneq, Array_NV_eneq),
HLL_TODO_EXPORT(NV_enne, Array_NV_enne),
HLL_TODO_EXPORT(NV_enlo, Array_NV_enlo),
HLL_TODO_EXPORT(NV_enhi, Array_NV_enhi),
HLL_TODO_EXPORT(NV_enra, Array_NV_enra),
HLL_TODO_EXPORT(NN_eneq, Array_NN_eneq),
HLL_TODO_EXPORT(NN_enne, Array_NN_enne),
HLL_TODO_EXPORT(NN_enlo, Array_NN_enlo),
HLL_TODO_EXPORT(NN_enhi, Array_NN_enhi),
HLL_TODO_EXPORT(NS_eneq, Array_NS_eneq),
HLL_TODO_EXPORT(NS_enne, Array_NS_enne),
HLL_TODO_EXPORT(NS_enlo, Array_NS_enlo),
HLL_TODO_EXPORT(NS_enhi, Array_NS_enhi),
HLL_TODO_EXPORT(SV_eneq, Array_SV_eneq),
HLL_TODO_EXPORT(SV_enne, Array_SV_enne),
HLL_TODO_EXPORT(SV_enlo, Array_SV_enlo),
HLL_TODO_EXPORT(SV_enhi, Array_SV_enhi),
HLL_TODO_EXPORT(SV_enra, Array_SV_enra),
HLL_TODO_EXPORT(SN_eneq, Array_SN_eneq),
HLL_TODO_EXPORT(SN_enne, Array_SN_enne),
HLL_TODO_EXPORT(SN_enlo, Array_SN_enlo),
HLL_TODO_EXPORT(SN_enhi, Array_SN_enhi),
HLL_TODO_EXPORT(SS_eneq, Array_SS_eneq),
HLL_TODO_EXPORT(SS_enne, Array_SS_enne),
HLL_TODO_EXPORT(SS_enlo, Array_SS_enlo),
HLL_TODO_EXPORT(SS_enhi, Array_SS_enhi),
HLL_TODO_EXPORT(NV_cheq, Array_NV_cheq),
HLL_TODO_EXPORT(NV_chne, Array_NV_chne),
HLL_TODO_EXPORT(NV_chlo, Array_NV_chlo),
HLL_TODO_EXPORT(NV_chhi, Array_NV_chhi),
HLL_TODO_EXPORT(NV_chra, Array_NV_chra),
HLL_TODO_EXPORT(NN_cheq, Array_NN_cheq),
HLL_TODO_EXPORT(NN_chne, Array_NN_chne),
HLL_TODO_EXPORT(NN_chlo, Array_NN_chlo),
HLL_TODO_EXPORT(NN_chhi, Array_NN_chhi),
HLL_TODO_EXPORT(NS_cheq, Array_NS_cheq),
HLL_TODO_EXPORT(NS_chne, Array_NS_chne),
HLL_TODO_EXPORT(NS_chlo, Array_NS_chlo),
HLL_TODO_EXPORT(NS_chhi, Array_NS_chhi),
HLL_TODO_EXPORT(SV_cheq, Array_SV_cheq),
HLL_TODO_EXPORT(SV_chne, Array_SV_chne),
HLL_TODO_EXPORT(SV_chlo, Array_SV_chlo),
HLL_TODO_EXPORT(SV_chhi, Array_SV_chhi),
HLL_TODO_EXPORT(SV_chra, Array_SV_chra),
HLL_TODO_EXPORT(SN_cheq, Array_SN_cheq),
HLL_TODO_EXPORT(SN_chne, Array_SN_chne),
HLL_TODO_EXPORT(SN_chlo, Array_SN_chlo),
HLL_TODO_EXPORT(SN_chhi, Array_SN_chhi),
HLL_TODO_EXPORT(SS_cheq, Array_SS_cheq),
HLL_TODO_EXPORT(SS_chne, Array_SS_chne),
HLL_TODO_EXPORT(SS_chlo, Array_SS_chlo),
HLL_TODO_EXPORT(SS_chhi, Array_SS_chhi),
HLL_TODO_EXPORT(NV_fweq, Array_NV_fweq),
HLL_TODO_EXPORT(NV_fwne, Array_NV_fwne),
HLL_TODO_EXPORT(NV_fwlo, Array_NV_fwlo),
HLL_TODO_EXPORT(NV_fwhi, Array_NV_fwhi),
HLL_TODO_EXPORT(NV_fwra, Array_NV_fwra),
HLL_TODO_EXPORT(NV_faeq, Array_NV_faeq),
HLL_TODO_EXPORT(NV_fane, Array_NV_fane),
HLL_TODO_EXPORT(NV_falo, Array_NV_falo),
HLL_TODO_EXPORT(NV_fahi, Array_NV_fahi),
HLL_TODO_EXPORT(NV_fara, Array_NV_fara),
HLL_TODO_EXPORT(NV_foeq, Array_NV_foeq),
HLL_TODO_EXPORT(NV_fone, Array_NV_fone),
HLL_TODO_EXPORT(NV_folo, Array_NV_folo),
HLL_TODO_EXPORT(NV_fohi, Array_NV_fohi),
HLL_TODO_EXPORT(NV_fora, Array_NV_fora),
HLL_TODO_EXPORT(NN_fweq, Array_NN_fweq),
HLL_TODO_EXPORT(NN_fwne, Array_NN_fwne),
HLL_TODO_EXPORT(NN_fwlo, Array_NN_fwlo),
HLL_TODO_EXPORT(NN_fwhi, Array_NN_fwhi),
HLL_TODO_EXPORT(NN_faeq, Array_NN_faeq),
HLL_TODO_EXPORT(NN_fane, Array_NN_fane),
HLL_TODO_EXPORT(NN_falo, Array_NN_falo),
HLL_TODO_EXPORT(NN_fahi, Array_NN_fahi),
HLL_TODO_EXPORT(NN_foeq, Array_NN_foeq),
HLL_TODO_EXPORT(NN_fone, Array_NN_fone),
HLL_TODO_EXPORT(NN_folo, Array_NN_folo),
HLL_TODO_EXPORT(NN_fohi, Array_NN_fohi),
HLL_TODO_EXPORT(NS_fweq, Array_NS_fweq),
HLL_TODO_EXPORT(NS_fwne, Array_NS_fwne),
HLL_TODO_EXPORT(NS_fwlo, Array_NS_fwlo),
HLL_TODO_EXPORT(NS_fwhi, Array_NS_fwhi),
HLL_TODO_EXPORT(NS_faeq, Array_NS_faeq),
HLL_TODO_EXPORT(NS_fane, Array_NS_fane),
HLL_TODO_EXPORT(NS_falo, Array_NS_falo),
HLL_TODO_EXPORT(NS_fahi, Array_NS_fahi),
HLL_TODO_EXPORT(NS_foeq, Array_NS_foeq),
HLL_TODO_EXPORT(NS_fone, Array_NS_fone),
HLL_TODO_EXPORT(NS_folo, Array_NS_folo),
HLL_TODO_EXPORT(NS_fohi, Array_NS_fohi),
HLL_TODO_EXPORT(SV_fweq, Array_SV_fweq),
HLL_TODO_EXPORT(SV_fwne, Array_SV_fwne),
HLL_TODO_EXPORT(SV_fwlo, Array_SV_fwlo),
HLL_TODO_EXPORT(SV_fwhi, Array_SV_fwhi),
HLL_TODO_EXPORT(SV_fwra, Array_SV_fwra),
HLL_TODO_EXPORT(SV_faeq, Array_SV_faeq),
HLL_TODO_EXPORT(SV_fane, Array_SV_fane),
HLL_TODO_EXPORT(SV_falo, Array_SV_falo),
HLL_TODO_EXPORT(SV_fahi, Array_SV_fahi),
HLL_TODO_EXPORT(SV_fara, Array_SV_fara),
HLL_TODO_EXPORT(SV_foeq, Array_SV_foeq),
HLL_TODO_EXPORT(SV_fone, Array_SV_fone),
HLL_TODO_EXPORT(SV_folo, Array_SV_folo),
HLL_TODO_EXPORT(SV_fohi, Array_SV_fohi),
HLL_TODO_EXPORT(SV_fora, Array_SV_fora),
HLL_TODO_EXPORT(SN_fweq, Array_SN_fweq),
HLL_TODO_EXPORT(SN_fwne, Array_SN_fwne),
HLL_TODO_EXPORT(SN_fwlo, Array_SN_fwlo),
HLL_TODO_EXPORT(SN_fwhi, Array_SN_fwhi),
HLL_TODO_EXPORT(SN_faeq, Array_SN_faeq),
HLL_TODO_EXPORT(SN_fane, Array_SN_fane),
HLL_TODO_EXPORT(SN_falo, Array_SN_falo),
HLL_TODO_EXPORT(SN_fahi, Array_SN_fahi),
HLL_TODO_EXPORT(SN_foeq, Array_SN_foeq),
HLL_TODO_EXPORT(SN_fone, Array_SN_fone),
HLL_TODO_EXPORT(SN_folo, Array_SN_folo),
HLL_TODO_EXPORT(SN_fohi, Array_SN_fohi),
HLL_TODO_EXPORT(SS_fweq, Array_SS_fweq),
HLL_TODO_EXPORT(SS_fwne, Array_SS_fwne),
HLL_TODO_EXPORT(SS_fwlo, Array_SS_fwlo),
HLL_TODO_EXPORT(SS_fwhi, Array_SS_fwhi),
HLL_TODO_EXPORT(SS_faeq, Array_SS_faeq),
HLL_TODO_EXPORT(SS_fane, Array_SS_fane),
HLL_TODO_EXPORT(SS_falo, Array_SS_falo),
HLL_TODO_EXPORT(SS_fahi, Array_SS_fahi),
HLL_TODO_EXPORT(SS_foeq, Array_SS_foeq),
HLL_TODO_EXPORT(SS_fone, Array_SS_fone),
HLL_TODO_EXPORT(SS_folo, Array_SS_folo),
HLL_TODO_EXPORT(SS_fohi, Array_SS_fohi),
HLL_EXPORT(NV_sceq, Array_NV_sceq),
HLL_TODO_EXPORT(NV_scne, Array_NV_scne),
HLL_TODO_EXPORT(NV_sclo, Array_NV_sclo),
HLL_TODO_EXPORT(NV_schi, Array_NV_schi),
HLL_TODO_EXPORT(NV_scra, Array_NV_scra),
HLL_TODO_EXPORT(SV_sceq, Array_SV_sceq),
HLL_TODO_EXPORT(SV_scne, Array_SV_scne),
HLL_TODO_EXPORT(SV_sclo, Array_SV_sclo),
HLL_TODO_EXPORT(SV_schi, Array_SV_schi),
HLL_TODO_EXPORT(SV_scra, Array_SV_scra),
HLL_TODO_EXPORT(NN_sclowest, Array_NN_sclowest),
HLL_TODO_EXPORT(NN_schighest, Array_NN_schighest),
HLL_TODO_EXPORT(NS_sclowest, Array_NS_sclowest),
HLL_TODO_EXPORT(NS_schighest, Array_NS_schighest),
HLL_TODO_EXPORT(SN_sclowest, Array_SN_sclowest),
HLL_TODO_EXPORT(SN_schighest, Array_SN_schighest),
HLL_TODO_EXPORT(SS_sclowest, Array_SS_sclowest),
HLL_TODO_EXPORT(SS_schighest, Array_SS_schighest),
HLL_TODO_EXPORT(VN_add, Array_VN_add),
HLL_TODO_EXPORT(VN_and, Array_VN_and),
HLL_TODO_EXPORT(VN_or, Array_VN_or),
HLL_TODO_EXPORT(VS_add, Array_VS_add),
HLL_TODO_EXPORT(VS_and, Array_VS_and),
HLL_TODO_EXPORT(VS_or, Array_VS_or)
);
+98 -37
View File
@@ -17,39 +17,101 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#include "system4/file.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/mt19937int.h"
#include "hll.h"
#include "audio.h"
#include "iarray.h"
#include "savedata.h"
#include "vm/page.h"
#include "xsystem4.h"
#define MD11_ENCRYPT_KEY 0x18ec03
#define MD11_HEADER_SIZE 8
static const uint8_t md11_signature[] = { 'M', 'D', 0x01, 0x01 };
static int BanMisc_SaveStruct(struct page *page, struct string *file_name)
{
// serialize
struct iarray_writer out;
iarray_init_writer(&out, NULL);
iarray_write_struct(&out, page, true);
size_t raw_size;
uint8_t *raw_buf = iarray_to_buffer(&out, &raw_size);
iarray_free_writer(&out);
// compress
unsigned long compressed_size = compressBound(raw_size);
uint8_t *buf = xmalloc(MD11_HEADER_SIZE + compressed_size);
memcpy(buf, md11_signature, sizeof(md11_signature));
LittleEndian_putDW(buf, 4, raw_size);
uint8_t *compressed = buf + MD11_HEADER_SIZE;
int r = compress2(compressed, &compressed_size, raw_buf, raw_size, Z_DEFAULT_COMPRESSION);
free(raw_buf);
if (r != Z_OK) {
free(buf);
return 0;
}
// encrypt
mt19937_xorcode(compressed, compressed_size, MD11_ENCRYPT_KEY);
// write
char *path = unix_path(file_name->text);
FILE *fp = file_open_utf8(path, "w");
if (!fp) {
WARNING("Failed to open file '%s': %s", display_utf0(path), strerror(errno));
free(path);
return 0;
}
free(path);
cJSON *json = page_to_json(page);
char *str = cJSON_Print(json);
cJSON_Delete(json);
bool ok = fputs(str, fp) != EOF;
free(str);
fclose(fp);
bool ok = file_write(path, buf, MD11_HEADER_SIZE + compressed_size);
if (!ok) {
WARNING("BanMisc.SaveStruct failed (fputs): %s", strerror(errno));
return 0;
WARNING("Failed to write file '%s': %s", display_utf0(path), strerror(errno));
}
return 1;
free(buf);
free(path);
return ok;
}
static struct page *load_md11(int struct_type, uint8_t *buf, size_t len)
{
if (len < MD11_HEADER_SIZE) {
WARNING("BanMisc.LoadStruct file too short");
return NULL;
}
unsigned long raw_size = LittleEndian_getDW(buf, 4);
if (raw_size % 4 != 0) {
WARNING("BanMisc.LoadStruct invalid file format");
return NULL;
}
buf += MD11_HEADER_SIZE;
len -= MD11_HEADER_SIZE;
// decrypt
mt19937_xorcode(buf, len, MD11_ENCRYPT_KEY);
// decompress
uint8_t *raw_buf = xmalloc(raw_size);
int r = uncompress(raw_buf, &raw_size, buf, len);
if (r != Z_OK) {
WARNING("BanMisc.LoadStruct uncompress failed: %d", r);
free(raw_buf);
return NULL;
}
// deserialize
int nr_vars = raw_size / 4;
struct page *tmp_page = alloc_page(ARRAY_PAGE, AIN_ARRAY_INT, nr_vars);
tmp_page->array.rank = 1;
for (int i = 0; i < nr_vars; i++) {
tmp_page->values[i].i = LittleEndian_getDW(raw_buf, i * 4);
}
struct iarray_reader in;
iarray_init_reader(&in, tmp_page, NULL);
struct page *page = iarray_read_struct(&in, struct_type, true);
free_page(tmp_page);
free(raw_buf);
return page;
}
static int BanMisc_LoadStruct(struct page **_page, struct string *file_name)
@@ -60,31 +122,30 @@ static int BanMisc_LoadStruct(struct page **_page, struct string *file_name)
}
char *path = unix_path(file_name->text);
FILE *fp = file_open_utf8(path, "r");
if (!fp) {
WARNING("Failed to open file '%s': %s", display_utf0(path), strerror(errno));
free(path);
return 0;
}
size_t len;
uint8_t *buf = file_read(path, &len);
free(path);
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buf = xmalloc(len + 1);
buf[len] = '\0';
if (fread(buf, len, 1, fp) != 1) {
WARNING("BanMisc.LoadStruct failed (fread): %s", strerror(errno));
free(buf);
if (!buf) {
return 0;
}
fclose(fp);
cJSON *json = cJSON_Parse(buf);
if (!memcmp(buf, md11_signature, sizeof(md11_signature))) {
page = load_md11(page->index, buf, len);
free(buf);
if (!page)
return 0;
if (*_page) {
delete_page_vars(*_page);
free_page(*_page);
}
*_page = page;
return 1;
}
// load from JSON (for backward compatibility)
cJSON *json = cJSON_Parse((char *)buf);
free(buf);
if (!json) {
WARNING("BanMisc.LoadStruct failed to parse JSON");
WARNING("BanMisc.LoadStruct invalid file format");
return 0;
}
if (!cJSON_IsArray(json)) {

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