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.
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.
* 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
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.
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.
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.
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.
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.
The 3rd argument to the instruction indicates the type of the second
argument. If there are multiple specifiers in the format string, the
first specifier matching the given type is substituted (NOT the first
specifier encountered when processing the format string).
Add 'step' (s) and 'next' (n) commands for single-stepping bytecode.
'step' is the 'step into' type which descends into function calls,
wheras 'next' is the 'step over' type which continues until the next
instruction within the current function.
Each delegate call needs to copy its arguments so that they aren't
double-freed by the next call.
Also fix a bug in FT_ASSIGNS where -1 was returned for non-existant
functions instead of 0.
Use `unlikely` (__builtin_expect) to help the compiler optimize error
checks in some hot functions (in my testing, stack_pop_var was one of
the hottest functions, right behind execute_instruction).
This function is not supposed to call destructors, but was calling
heap_unref instead of exit_unref on local pages.
Fixes a bug in Rance VI where the game would crash on game over.
Read .xsys4-debugrc from ~/.xsystem4 and/or the game directory on
startup. This is just a text file with one debugger command per line.
Can be used to set breakpoints or log functions automatically at
startup.
This can also be used to work around a limitation on Windows where the
IME doesn't work in the debugger, making it impossible to input Japanese
text.
Add vm-state command, which prints disassembly around the current
instruction pointer and the current contents of the stack.
It was necessary to change how breakpoints are implemented for this.
When adding a breakpoint, the original opcode now remains in the
instruction stream, bitwise-or'd with BREAKPOINT. A hash table indexed
by address is used to store breakpoint objects.
* Add "frame" command to change the current frame.
* Add "members" command to print struct members for current frame (if it's
a method frame).
* Support printing member variables (e.g. "this.var") in the "print"
command.
1. DG_CALL has to clean up the stack before jumping to the return
address.
2. DG_SET (contrary to its name) is an append operation; it's basically
a combination of DG_NEW_FROM_METHOD and DG_PLUSA.
3. DG_SET and DG_PLUSA need to check for and ignore duplicates.
4. Delegate pages store *references* to structs; the VM should NOT copy
the struct page when copying a delegate page.