mirror of
https://github.com/pumpitupdev/pumptools.git
synced 2026-09-22 22:28:03 +03:00
Version 1.08 with public source code release.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
# Architecture
|
||||
The follow image shows a rough outline of pumptools's architecture based on the project's namespacing. Arrows indicate
|
||||
architectural dependencies. Modules in red colored boxes expose APIs and blue colored boxes expose application/library
|
||||
binaries.
|
||||
|
||||

|
||||
|
||||
## util
|
||||
The kitchen sink, basically used by everything. Not going to elaborate the details here as it is quite self-explanatory
|
||||
when taking a look at its modules.
|
||||
|
||||
## crypt
|
||||
Kitchen sink for everything crypto related. Could be with `util` but I somewhat felt the need to have this separated.
|
||||
|
||||
## asset
|
||||
Modules for handling various types of asserts from pump games, e.g. usb profiles.
|
||||
|
||||
## pumpnet
|
||||
Modules to talk to pumpnet for usb profiles over sockets. Used by NX2 to Fiesta 2.
|
||||
|
||||
## sec
|
||||
Contains several sub-modules for security/dongle related topics such as full software emulation.
|
||||
|
||||
## capnhook
|
||||
This is the heart of pumptools to enable hooking of system and library calls. `capnhook` has two sub-modules, `hook`
|
||||
and `hooklib`. `hook` provides the foundation for hooking various things, typically IO related, such as files,
|
||||
filesystem, hardware IO (e.g. USB), signal handlers. `hooklib` provides helper modules based on `hook` that take care
|
||||
of common tasks such as path based redirects, USB device emulation and IO call monitoring.
|
||||
|
||||
## ptapi
|
||||
Pumptools's API package exposing APIs to the end-user. Also contains any libraries and tools used by the APIs.
|
||||
|
||||
## io
|
||||
Contains modules to talk to real IO hardware, e.g. `piuio`, `piubtn`.
|
||||
|
||||
## patch
|
||||
`patch` is a common package for various modules that are shared with multiple pump versions. A patch module takes care
|
||||
of patching, i.e. hooking and detouring usually, a single aspect like fixing a API quirk, enabling window mode etc.
|
||||
|
||||
# game hooks
|
||||
`game hooks` are composed of multiple `patch` modules making up the final hook library that you preload to the target
|
||||
application. The structure of each `game hook` is identical and only the patches and hooks applied differ per game
|
||||
version.
|
||||
@@ -0,0 +1,163 @@
|
||||
# Development
|
||||
This document covers various aspects of developing for pumptools. For developing using the API, see
|
||||
[the dedicated readme](../api/api.md).
|
||||
|
||||
## Architecture
|
||||
[This document](architecture.md) gives an introduction to the architecture of pumptools. This helps you to get familiar
|
||||
with the codebase and its features, definitely recommended to read this first.
|
||||
|
||||
## Development environment setup
|
||||
Pumptools is written entirely in C and built using cmake. Various helper scripts are written as bash/shell scripts.
|
||||
Therefore, you need the following tools on your development setup:
|
||||
* gcc
|
||||
* cmake
|
||||
* make
|
||||
* bash or compatible shell
|
||||
|
||||
Pumptools requires the following dependencies to be installed on your development OS:
|
||||
* libcurl4 i386
|
||||
* libusb 1.0 i386
|
||||
|
||||
Furthermore, header files, which come with the development packages of the libs, of the following libraries are
|
||||
required:
|
||||
* fmodex
|
||||
* libconfig
|
||||
* X11
|
||||
* libalsa
|
||||
* libusb-0.1 (**not** 1.0)
|
||||
|
||||
The project does not depend on any specific IDE. However, if you use CLion, the project can be imported easily.
|
||||
|
||||
## Code style
|
||||
The main style guidelines are defined by the `.clang-format` file and the code style can be easily applied to the
|
||||
whole `src` directory using the following command from the root folder:
|
||||
```shell script
|
||||
make clang-format
|
||||
```
|
||||
|
||||
Make sure that your contributions follow the style and apply it before submitting your changes.
|
||||
|
||||
For other source files like shell scripts, cmake, makefile or markdown documents, please follow the already established
|
||||
style in the existing documents.
|
||||
|
||||
## Building
|
||||
See the [main readme](../../README.md#building).
|
||||
|
||||
## Compatibility issues with newer glibc versions
|
||||
We have experienced various issues with different glibc versions which had a major impact on the builds. The following
|
||||
table gives you a list of tested glibc versions and issues that are either present or not with each version listed.
|
||||
"ok" indicates that the specified glibc version is compatible and the specific issue does not occur, "bad" indicates
|
||||
incompatibility and that the specific issue does occur when using a binary compiled with that glibc version.
|
||||
|
||||
| Issue/glibc version | 2.23 | 2.30 |
|
||||
|---------------------|------|------|
|
||||
| Issue 1 | ok | bad |
|
||||
| Issue 2 | ok | bad |
|
||||
|
||||
The issues are further described in the following sub-sections
|
||||
|
||||
### Issue 1: Crash during static initialization
|
||||
For a detailed report on this issue, see the [dedicated notes](notes/f2-crashing-on-modern-linux-post-mortem.md).
|
||||
|
||||
Summarized: Fiesta 2 and newer will segfault before even running the application's main method. The application fails
|
||||
on runtime initialization when initializing the static context. This is caused by ABI incompatibility of inlined code
|
||||
of C++'s STL, first occurring with string allocations during static initialization.
|
||||
|
||||
### Issue 2: sscanf not being detoured
|
||||
With NX2, Andamiro locked the USB drives to be used to a specific brand which they sold back then. That lock is patched
|
||||
out by detouring `sscanf` and manipulating the VID/PID and serial number parsing logic in the game to always return
|
||||
the values, the game wants to see to accept the connected USB drive.
|
||||
|
||||
However, some incompatibility on newer glibc versions does not setup the `sscanf` detour properly. The detour function
|
||||
is there and gets called by various parts of the game code in the beginning, but later calls to `sscanf` always go
|
||||
to the real function instead. Thus, the usb unlock feature does not work.
|
||||
|
||||
## Unit tests
|
||||
Unfortunately, this was started very late in the development of pumptools, but it's not too late to keep extending this
|
||||
with every change to ensure existing features don't break and future ones work.
|
||||
|
||||
When you have a change to commit, check if there is an existing unit-test which needs to be aligned with your change(s).
|
||||
If not, create one.
|
||||
|
||||
For testing code that gets hooked, there is a specific pattern that needs to be applied in order to allow testing
|
||||
hooks without having to actually hook them. As an example, you can take a look at the
|
||||
[usb-fix module](../../src/main/hook/propatch/usb-fix.h) for piu pro and the
|
||||
[corresponding test](../../src/test/hook/propatch/usb-fix/main.c). It shows you how to setup the hooks and create
|
||||
mocks for the functions that you can trap the calls to the real detoured functions to check if they are getting called
|
||||
or return fixed values for testing.
|
||||
|
||||
## Debugging
|
||||
### Pumptool's built-in tooling
|
||||
Pumptools has a few basic debugging features built in which helps analyzing common issues very quickly without the
|
||||
need of external tools.
|
||||
|
||||
#### Help and usage message of hook library
|
||||
When running a game with any of pumptools's `hook.so` libs attached, you can always add the parameter `-h` to display a
|
||||
help/usage message with all available command line and configuration parameters.
|
||||
|
||||
For example, when using `piueb` for bootstrapping:
|
||||
```
|
||||
./piueb run -h
|
||||
```
|
||||
|
||||
The `-h` is passed on as a parameter and appended to the execution command. You can make use of that with other
|
||||
available cmd parameters as well (see usage information).
|
||||
|
||||
#### Logging
|
||||
Logging to console and file as well as the level of verbosity can be controlled using the `hook.conf` configuration file
|
||||
or via command line arguments provided on execution.
|
||||
|
||||
All hooks implement various sanity checks for parameters provided, file checks etc. so there is a good coverage of
|
||||
common mistakes and misconfiguration that can be easily detected by watching out for any ERROR or WARNING level messages
|
||||
showing up in the log.
|
||||
|
||||
Example in configuration file:
|
||||
```
|
||||
util.log.file=pumptools.log
|
||||
util.log.level=4
|
||||
```
|
||||
|
||||
Furthermore, `piueb` outputs a separate log file `piueb.log` which contains logging data of the environment setup
|
||||
process of `piueb`.
|
||||
|
||||
#### File and IO hook monitoring
|
||||
In the configuration file or via cmd args, you can enable monitoring of file, filesystem, usb and/or open system calls
|
||||
for tracing and debugging. This can be useful, to check if the game cannot find certain files like game assets.
|
||||
|
||||
Example in configuration file:
|
||||
```
|
||||
patch.hook_mon.file=0
|
||||
patch.hook_mon.fs=0
|
||||
patch.hook_mon.io=0
|
||||
patch.hook_mon.open=1
|
||||
patch.hook_mon.usb=0
|
||||
```
|
||||
|
||||
#### Halt on SIGSEGV and attach debugger
|
||||
Also enabled via configuration or cmd args, the application is halted on SIGSEGV to allow attaching a debugger.
|
||||
|
||||
Example in configuration file:
|
||||
```
|
||||
patch.sigsegv.halt_on_segv=1
|
||||
```
|
||||
|
||||
### External tooling
|
||||
#### Remote debugging
|
||||
You can run the application with `gdbserver` using `piueb`:
|
||||
```shell script
|
||||
./piueb debug
|
||||
```
|
||||
|
||||
And connect with your favorite debugger (e.g. IDA).
|
||||
|
||||
#### strace
|
||||
You can run the application with `strace` using `piueb`:
|
||||
```shell script
|
||||
./piueb strace
|
||||
```
|
||||
|
||||
#### valgrind
|
||||
You can run the application with `valgrind` using `piueb`:
|
||||
```shell script
|
||||
./piueb valgrind
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,55 @@
|
||||
# Notes about Exceed's implementation of a sound manager
|
||||
## Pre-face
|
||||
To understand what the developers of AM did here, you should have some knowledge about how they had to handle sound on
|
||||
the MK3 hardware which is the predecessor of the MK5 hardware that was used with Exceed. Exceed builds on the codebase
|
||||
of the previous game(s), Prex 3 and Premiere 3, that ran on MK3/MK5. However, moving from Dos (MK3)/Windows XP Embedded
|
||||
(MK5) to Linux (MK5), the code had to be adjusted to work on the different kind of hardware and operating system.
|
||||
Their engine was targeted and optimized for the MK3 hardware platform and had to use various pieces of custom hardware
|
||||
to implement stuff like: security using the lock chip, MP3 decoding and effect playback and IO.
|
||||
|
||||
## Sound playback, higher level view
|
||||
For sound playback, the game had the following requirements:
|
||||
1. For playing a song, menu background music, title screen intro music or any other music tracks, sound data encoded as
|
||||
MP3 files are used. These needed to be loaded, decoded and played back whenever the game reaches certain points in the
|
||||
game scene.
|
||||
1. Various short sound effects, e.g. to give the user confirmation about hitting a button, have to be played back when
|
||||
a certain event is triggered, e.g. user input or a scripted event in a scene. On exceed, these are stored as WAV files.
|
||||
1. Audio played back by the above two items needs to be mixed because playback of both can happen simultaneously.
|
||||
|
||||
## MK3 hardware for decoding and audio playback
|
||||
The MK3 hardware came with a dedicated piece of hardware, a [MP3 decoder and playback chip](hardware.md). Therefore,
|
||||
audio data from disc was streamed encoded to the hardware for decoding and playback, and effects were triggered by
|
||||
commands to the appropriate chips as their audio data is stored in a ROM chip on the board.
|
||||
|
||||
## Exceed's sound manager
|
||||
With the move to Linux and less specialized hardware closer to a normal PC, the audio pipeline changed a lot. On Linux,
|
||||
alsa/libasound is used for sending data to the underlying audio driver. Decoding of the MP3 song data is done by the
|
||||
CPU. Effects are stored as WAV files and also played back using libasound.
|
||||
|
||||
The sound manager relies on hardware mixing using the same sound device, e.g. hw:0. This device is opened once on game
|
||||
init for playing back sound effects and another time for every song/background music to be played back for the current
|
||||
scene.
|
||||
|
||||
### Hardware sound tracks specs
|
||||
#### Effect track
|
||||
* Channels: 2
|
||||
* Buffer size in frames: 4096
|
||||
* Periods: 2
|
||||
* Sample format: 16-bit signed LE
|
||||
* Sample rate: 44100
|
||||
* Sample order: FL, FR
|
||||
|
||||
#### Song track
|
||||
* Channels: 2
|
||||
* Buffer size in frames: 16384
|
||||
* Periods: 8
|
||||
* Sample format: 16-bit signed LE
|
||||
* Sample rate: 44100
|
||||
* Sample order: FL, FR
|
||||
|
||||
### Issues on todays distros
|
||||
However, this way of mixing is not supported anymore by alsa. Instead, you have to use software mixing using dmix which
|
||||
is supported by alsa's built in plugin system today. But, that doesn't seem to work properly with different
|
||||
configurations from different versions of alsa on today's available distros.
|
||||
|
||||
Fortunately, this got fixed already on Exceed 2 which implements its own software mixer in the pump engine.
|
||||
@@ -0,0 +1,217 @@
|
||||
# Notes about song unlocks in Exceed 2
|
||||
Executable of v1.02, build 7th December 2004.
|
||||
|
||||
A bunch of copy-pastes from IDA checking which songs can be unlocked permanently.
|
||||
|
||||
## Internal song list and state structure
|
||||
```
|
||||
00000000 SongList struc ; (sizeof=0x48, mappedto_187) ; XREF: .data:songList/r
|
||||
00000000 trackNum dd ?
|
||||
00000004 ptrArtistNameKr dd ?
|
||||
00000008 ptrArtistNameEn dd ?
|
||||
0000000C ptrSongNameKr dd ? ; XREF: sub_805186E+7A/r
|
||||
0000000C ; sub_805186E+B4/r ...
|
||||
00000010 ptrSongNameEn dd ?
|
||||
00000014 ptrBpm dd ?
|
||||
00000018 channelInfo dd ?
|
||||
0000001C level dd 6 dup(?)
|
||||
00000034 enableSong db ?
|
||||
00000035 hiddenSong db ?
|
||||
00000036 enableDemoPlay db ?
|
||||
00000037 censor db ?
|
||||
00000038 pad db ?
|
||||
00000039 censorLock db ?
|
||||
0000003A enable_play db ?
|
||||
0000003B hidden_play db ?
|
||||
0000003C enableMode db 6 dup(?) ; string(C)
|
||||
00000042 enableMode_play db 6 dup(?) ; string(C)
|
||||
00000048 SongList ends
|
||||
```
|
||||
|
||||
## Internal song list
|
||||
```
|
||||
.data:080890E4 ; SongList songList
|
||||
.data:080890E4 songList dd 101h ; trackNum
|
||||
.data:080890E4 ; DATA XREF: sub_8062D22+38o
|
||||
.data:080890E4 ; GetSongRealIndex+33o ...
|
||||
.data:080890E4 dd 807BC28h ; ptrArtistNameKr
|
||||
.data:080890E4 dd 807BC2Dh ; ptrArtistNameEn
|
||||
.data:080890E4 dd 807BC33h ; ptrSongNameKr
|
||||
.data:080890E4 dd 0 ; ptrSongNameEn
|
||||
.data:080890E4 dd 0 ; ptrBpm
|
||||
.data:080890E4 dd 40624000h ; channelInfo
|
||||
.data:080890E4 dd 0, 0FFFFFFFFh, 6, 0Ch, 7, 0FFFFFFFFh; level
|
||||
.data:080890E4 db 1 ; enableSong
|
||||
.data:080890E4 db 0 ; hiddenSong
|
||||
.data:080890E4 db 1 ; enableDemoPlay
|
||||
.data:080890E4 db 0 ; censor
|
||||
.data:080890E4 db 0FFh ; pad
|
||||
.data:080890E4 db 0FFh ; censorLock
|
||||
.data:080890E4 db 0FFh ; enable_play
|
||||
.data:080890E4 db 0FFh ; hidden_play
|
||||
.data:080890E4 db 0,0,0,0,0,0 ; enableMode
|
||||
.data:080890E4 db 0,0,1,0,0,0 ; enableMode_play
|
||||
.data:080890E4 dd 102h ; trackNum
|
||||
.data:080890E4 dd 807BC28h ; ptrArtistNameKr
|
||||
.data:080890E4 dd 807BC2Dh ; ptrArtistNameEn
|
||||
.data:080890E4 dd 807BC44h ; ptrSongNameKr
|
||||
.data:080890E4 dd 0 ; ptrSongNameEn
|
||||
.data:080890E4 dd 0 ; ptrBpm
|
||||
.data:080890E4 dd 40668000h ; channelInfo
|
||||
.data:080890E4 dd 0, 0FFFFFFFFh, 6, 0Ch, 7, 0Fh; level
|
||||
.data:080890E4 db 1 ; enableSong
|
||||
.data:080890E4 db 0 ; hiddenSong
|
||||
.data:080890E4 db 1 ; enableDemoPlay
|
||||
.data:080890E4 db 0 ; censor
|
||||
.data:080890E4 db 0FFh ; pad
|
||||
.data:080890E4 db 0FFh ; censorLock
|
||||
.data:080890E4 db 0FFh ; enable_play
|
||||
.data:080890E4 db 0FFh ; hidden_play
|
||||
.data:080890E4 db 0,0,0,0,0,0 ; enableMode
|
||||
.data:080890E4 db 0,0,2,0,0,0 ; enableMode_play
|
||||
...
|
||||
```
|
||||
|
||||
## Settings struct first few fields
|
||||
```
|
||||
00000000 settings_struct struc ; (sizeof=0x162B, mappedto_186)
|
||||
00000000 ff_area db 1307 dup(?) ; string(C)
|
||||
0000051B unlocks_maybe db 16 dup(?) ; string(C)
|
||||
0000052B blacklist db 139 dup(?) ; string(C)
|
||||
000005B6 unkn2 db 117 dup(?) ; string(C)
|
||||
0000062B remain db 4096 dup(?) ; string(C)
|
||||
0000162B settings_struct ends
|
||||
```
|
||||
|
||||
## Function that checks for song unlocks
|
||||
Located at 0x0806307E
|
||||
|
||||
```
|
||||
int __usercall unlock_song@<eax>(int a1@<esi>, _DWORD *arg_0, _DWORD *songid)
|
||||
{
|
||||
int v3; // eax@1
|
||||
int v4; // ecx@1
|
||||
signed int v5; // edi@3
|
||||
int v6; // eax@6
|
||||
signed int v7; // eax@16
|
||||
signed int v8; // eax@23
|
||||
signed int v9; // eax@30
|
||||
signed int v10; // eax@37
|
||||
signed int v11; // eax@42
|
||||
int v13; // [sp+0h] [bp-18h]@3
|
||||
char result_unlocked_song; // [sp+7h] [bp-11h]@1
|
||||
|
||||
result_unlocked_song = 0;
|
||||
v3 = sub_8061C62(dword_80BED54, "PLAY");
|
||||
v4 = v3 - 200696;
|
||||
if ( !v3 )
|
||||
v4 = 0;
|
||||
v5 = 0;
|
||||
v13 = v4;
|
||||
do
|
||||
{
|
||||
if ( v5 )
|
||||
{
|
||||
if ( v5 != 1 )
|
||||
goto LABEL_11;
|
||||
if ( !(dword_80BECD4 & 2) )
|
||||
goto LABEL_43;
|
||||
v6 = sub_8056E76(v13, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !(dword_80BECD4 & 1) )
|
||||
goto LABEL_43;
|
||||
v6 = sub_8056E76(v13, 0);
|
||||
}
|
||||
a1 = v6;
|
||||
LABEL_11:
|
||||
if ( currentStateCount > 2
|
||||
&& !stationCurrentlySelected // 0 = arcade station
|
||||
&& *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECE4)) == 0xB13// ill give you all my love
|
||||
&& byte_80BECF5 & 2
|
||||
&& *(_DWORD *)(a1 + 456) <= 10 )
|
||||
{
|
||||
v7 = GetSongRealIndex(0xB19); // canon-d
|
||||
*arg_0 = 0;
|
||||
result_unlocked_song = 1;
|
||||
*songid = 0xB19; // canon-d
|
||||
songList.enableMode[72 * v7 + 4] = 0; // +4 mode offset = nightmare
|
||||
}
|
||||
if ( currentStateCount == 1 )
|
||||
{
|
||||
if ( stationCurrentlySelected == 1 // 1 = remix station
|
||||
&& *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB31// diva's remix
|
||||
&& *(_DWORD *)(a1 + 400) <= 20
|
||||
&& !*(_DWORD *)(a1 + 448)
|
||||
&& *(float *)(a1 + 368) >= 1.0 )
|
||||
{
|
||||
v8 = GetSongRealIndex(0xB54); // 2nd hidden remix
|
||||
*arg_0 = 3;
|
||||
result_unlocked_song = 1;
|
||||
*songid = 0xB54; // 2nd hidden remix
|
||||
songList.enableMode[72 * v8 + 4] = 0; // +4 mode offset = nightmare
|
||||
}
|
||||
if ( currentStateCount == 1 )
|
||||
{
|
||||
if ( stationCurrentlySelected == 1
|
||||
&& *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB30// deux remix
|
||||
&& *(_DWORD *)(a1 + 400) <= 20
|
||||
&& !*(_DWORD *)(a1 + 448)
|
||||
&& *(float *)(a1 + 368) >= 1.0 )
|
||||
{
|
||||
v9 = GetSongRealIndex(0xB29); // banya classic remix
|
||||
*arg_0 = 3;
|
||||
result_unlocked_song = 1;
|
||||
*songid = 0xB29; // banya classic remix
|
||||
songList.enableMode[72 * v9 + 4] = 0; // +4 mode offset = nightmare
|
||||
}
|
||||
if ( currentStateCount == 1 )
|
||||
{
|
||||
if ( stationCurrentlySelected == 1
|
||||
&& *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB29// banya classic remix
|
||||
&& *(_DWORD *)(a1 + 400) <= 20
|
||||
&& !*(_DWORD *)(a1 + 448)
|
||||
&& *(float *)(a1 + 368) >= 1.0 )
|
||||
{
|
||||
v10 = GetSongRealIndex(0xB51); // dignity full remix
|
||||
*arg_0 = 3;
|
||||
result_unlocked_song = 1;
|
||||
*songid = 0xB51; // dignity full remix
|
||||
songList.enableMode[72 * v10 + 2] = 0;// +2 mode offset = crazy
|
||||
}
|
||||
if ( currentStateCount == 1
|
||||
&& stationCurrentlySelected == 1
|
||||
&& *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB28// treme vook of the war
|
||||
&& byte_80BECED & 2 )
|
||||
{
|
||||
*(&songList.hiddenSong + 72 * GetSongRealIndex(0xB58)) = 0;// disable this song as a hidden song
|
||||
v11 = GetSongRealIndex(0xB58); // treme of the vook war another
|
||||
*arg_0 = 3;
|
||||
result_unlocked_song = 1;
|
||||
*(&songList.enableSong + 72 * v11) = 1;
|
||||
*songid = 0xB58;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LABEL_43:
|
||||
++v5;
|
||||
}
|
||||
while ( v5 <= 1 );
|
||||
if ( result_unlocked_song )
|
||||
sub_8074CA8(9, 0);
|
||||
if ( settings->unlocks_maybe[10] == 1 && stationCurrentlySelected == 1 )
|
||||
{
|
||||
*(&songList.hiddenSong + 0x48 * GetSongRealIndex(0xB57)) = 0;// disable this song as a hidden song
|
||||
*(&songList.enableSong + 0x48 * GetSongRealIndex(0xB57)) = 1;
|
||||
if ( !result_unlocked_song )
|
||||
{
|
||||
*arg_0 = 3;
|
||||
*songid = 0xB57; // canon-d full mix
|
||||
}
|
||||
result_unlocked_song = 1;
|
||||
}
|
||||
return (unsigned __int8)result_unlocked_song;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
# Extracting song list from game
|
||||
## NX2 and NXA
|
||||
The song list is compiled into the game's executable as a big table with the following structure:
|
||||
```c
|
||||
struct SongList_t
|
||||
{
|
||||
int IndexNum; // Index in the song list array
|
||||
int TrackNum; // Song ID
|
||||
int szArtistName_kr; // const char* to the null terminated string
|
||||
int szArtistName_en; // const char* to the null terminated string
|
||||
int szSongName_kr; // const char* to the null terminated string
|
||||
int szSongName_en; // const char* to the null terminated string
|
||||
int BPM;
|
||||
int ChannelInfo;
|
||||
int Level[6]; // Array indices correspond to the following and the content is the difficulty level for that mode
|
||||
// See GetRandomTrack()
|
||||
// 0 - eStepNormal
|
||||
// 1 - eStepHard
|
||||
// 2 - eStepCrazy
|
||||
// 3 - eStepHalfDouble
|
||||
// 4 - eStepFreestyle
|
||||
// 5 - eStepNightmare
|
||||
bool bEnable_m;
|
||||
bool bHidden_m;
|
||||
bool bEnableDemoPlay; // Allow playing in attract mode
|
||||
bool bCensor; // Don't show song in game (configurable)
|
||||
bool bPad;
|
||||
bool bCensorLock; // Permanently censored (not configurable)
|
||||
bool bEnable_play;
|
||||
bool bHidden_play;
|
||||
int TexID;
|
||||
bool bEnableMode_m[6]; // Modes where this song is allowed
|
||||
bool bEnableMode_play[6];
|
||||
short unknown1;
|
||||
short time; // Time value used to calculate kcal and VO2
|
||||
short SongMileage; // Cost to unlock this song
|
||||
short ModeMileage[3]; // Mileage required to play this song on the different modes
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
Probably the easiest method to locate that table is to find a string that any of the entries points to using your
|
||||
favorite decompiler. Song names are good candidates as most of them should be unique and easy to locate when your tool
|
||||
can expose all strings of the binary. Follow any references to the string and you should easily find a chunk of that
|
||||
that looks like this:
|
||||
```text
|
||||
.data:08130FDF db 0
|
||||
.data:08130FE0 ; int g_songListWithouBlazeEmotion[]
|
||||
.data:08130FE0 g_songListWithouBlazeEmotion dd 0F02h ; DATA XREF: HandleSongUnlocksMaybe:loc_806A5B5r
|
||||
.data:08130FE0 ; HandleSongUnlocksMaybe+26Br ...
|
||||
.data:08130FE4 dd offset aACn ; "8¥¦ 8ûì "
|
||||
.data:08130FE8 dd offset aYahpp ; "YAHPP "
|
||||
.data:08130FEC dd offset aRieX_1 ; "8¦Édà+ X.1 "
|
||||
.data:08130FF0 dd offset aCannonX_1 ; "Cannon X.1 "
|
||||
.data:08130FF4 dd offset a185 ; "185 "
|
||||
.data:08130FF8 dd 0
|
||||
.data:08130FFC dd 3
|
||||
.data:08131000 dd 0Ah
|
||||
.data:08131004 dd 14h
|
||||
.data:08131008 dd 0Dh
|
||||
.data:0813100C dd 16h
|
||||
.data:08131010 db 1
|
||||
.data:08131011 db 1
|
||||
.data:08131012 db 0
|
||||
.data:08131013 db 1
|
||||
.data:08131014 db 0
|
||||
.data:08131015 db 1
|
||||
.data:08131016 db 0
|
||||
.data:08131017 db 0
|
||||
.data:08131018 dd 0FFFFFFFFh
|
||||
.data:0813101C db 1
|
||||
.data:0813101D db 1
|
||||
.data:0813101E db 1
|
||||
.data:0813101F db 1
|
||||
.data:08131020 db 1
|
||||
.data:08131021 db 0
|
||||
.data:08131022 db 0
|
||||
.data:08131023 db 0
|
||||
.data:08131024 db 0
|
||||
.data:08131025 db 0
|
||||
.data:08131026 db 0
|
||||
.data:08131027 db 0
|
||||
.data:08131028 dw 79h
|
||||
.data:0813102A dw 0
|
||||
.data:0813102C dw 0
|
||||
.data:0813102E dw 0
|
||||
.data:08131030 dw 0
|
||||
.data:08131032 dw 0
|
||||
.data:08131034 dw 0
|
||||
.data:08131036 dw 0
|
||||
.data:08131038 dw 2
|
||||
.data:0813103A dw 0
|
||||
.data:0813103C dd 0F03h
|
||||
```
|
||||
|
||||
Find the beginning by going backwards and checking where the first valid entry with pointers to artist, title etc
|
||||
start. Usually, the first entry is also the primary reference for the game to address the song list.
|
||||
|
||||
This should allow you to find the function `GetSongRealIndex`. From there, you can easily find out the total length
|
||||
of the song list.
|
||||
```c
|
||||
int __cdecl GetSongRealIndex(int songNo)
|
||||
{
|
||||
signed int v1; // ebx@1
|
||||
int v2; // ecx@2
|
||||
int v3; // edx@3
|
||||
int v4; // eax@4
|
||||
|
||||
v1 = GetTotalTrackNum(); // 324
|
||||
if ( v1 <= 0 )
|
||||
return -1;
|
||||
v2 = 0;
|
||||
if ( g_songIdBlazingEmotion[0] != songNo )
|
||||
{
|
||||
v2 = 0;
|
||||
v3 = 0;
|
||||
while ( 1 )
|
||||
{
|
||||
++v2;
|
||||
if ( v2 == v1 )
|
||||
break;
|
||||
v4 = g_songListWithouBlazeEmotion[v3];
|
||||
v3 += 23;
|
||||
if ( v4 == songNo )
|
||||
return *(&g_songList[0].TrackNum + 23 * v2);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return *(&g_songList[0].TrackNum + 23 * v2);
|
||||
}
|
||||
```
|
||||
|
||||
With that info, you can dump the list straight from the binary, e.g. using `dd`:
|
||||
```shell script
|
||||
dd if=./piu bs=1 skip=954240 count=31200 of=songlist.bin
|
||||
```
|
||||
|
||||
Requires you to manually calculate the size by using the above struct as reference.
|
||||
|
||||
Note: This part does not cover the dumping of the referenced strings to build a full song list. This process was mainly
|
||||
used to get a song index mapping table for mapping absolute indices of the song table array to the song IDs and vice
|
||||
versa (for pumpnet).
|
||||
@@ -0,0 +1,255 @@
|
||||
# Post mortem: Fiesta 2 crashing on modern linux
|
||||
Date of writing: 17th November 2019
|
||||
|
||||
This is a port mortem about an issue that was puzzling me for months. In the following sections, I am documenting the
|
||||
whole story as detailed as possible hoping this is going to be useful for anyone else (or even myself) in the future.
|
||||
|
||||
## What happened?
|
||||
I have been running a multidisk setup with all pump games on it for a few years now and have been maintaining and
|
||||
improving pumptools in the process. I always kept things quite up to date because once you need some more tooling
|
||||
even on something like a dedicated machine, updating the system after it has not been updated for months or years
|
||||
becomes very difficult. Furthermore, things might stop working that have worked before because of one or even multiple
|
||||
issues that got introduced with the update.
|
||||
|
||||
Anyway, my cabinet with the multidisk setup yields the following on `uname -a`:
|
||||
`Linux piumd 4.15.11-1-ARCH #1 SMP PREEMPT Mon Mar 19 18:21:03 UTC 2018 x86_64 GNU/Linux`
|
||||
|
||||
My workstation is a bit more up-to-date with (though, I am behind a few months...):
|
||||
`Linux ambient 5.2.3-arch1-1-ARCH #1 SMP PREEMPT Fri Jul 26 08:13:47 UTC 2019 x86_64 GNU/Linux`
|
||||
|
||||
In the past, like more than a year ago, my workstation was also running a newer kernel, version of runtime libraries
|
||||
etc. but running Fiesta 2 and newer with pumptools wasn't an issue. However, Fiesta 2 and newer stopped working when
|
||||
I was continuing with my development efforts and just crashed with a segmentation fault. All old versions prior
|
||||
Fiesta 2 were still working perfectly fine.
|
||||
|
||||
## Error analysis: first information and debugging steps
|
||||
First, I enabled f2hooks built in logging to yield more output `util.log.level=4` and enabled halting on crashes to
|
||||
be able to attach a debugger `patch.sigsegv.halt_on_segv=1`. This is the output I got:
|
||||
```
|
||||
[I][f2hook][main.c:155]: Hooking finished
|
||||
[M][f2hook][main.c:168]: Calling orig_init
|
||||
[E][patch-sigsegv][sigsegv.c:18]: ===================
|
||||
[E][patch-sigsegv][sigsegv.c:19]: !!!!! SIGSEGV !!!!!
|
||||
[E][patch-sigsegv][sigsegv.c:28]: Backtrace (3 frames):
|
||||
[E][patch-sigsegv][sigsegv.c:31]: ./f2hook.so(+0x6a02) [0xf7e20a02]
|
||||
[E][patch-sigsegv][sigsegv.c:31]: ./f2hook.so(+0xf827) [0xf7e29827]
|
||||
[E][patch-sigsegv][sigsegv.c:31]: linux-gate.so.1(__kernel_sigreturn+0) [0xf7f07940]
|
||||
[E][patch-sigsegv][sigsegv.c:37]: Halting for debugger...
|
||||
```
|
||||
|
||||
Ok, so it seems like something crashed in f2hook.so. However, hooking up the debugger did not yield anything useful
|
||||
and this was just pointing to the sigsegv handler (thought it worked with debugging other issues and actually pointed
|
||||
to the location where it crashed...strange).
|
||||
|
||||
Next, I started removing stuff from f2hook to get a minimal version which still boots up to that point, no changes.
|
||||
|
||||
Then, I hooked up gdb, set a breakpoint before the crash happened and stepped my way through the whole libc
|
||||
runtime setup. I figured out, that the crash happens when the libc runtime is calling handler functions of the
|
||||
target application which setup various parts of the runtime environment, like static initialization.
|
||||
|
||||
And this is the place where the crash happened. There is one function in the piu executable which setups up all static
|
||||
variables, for example constructing static strings or other objects. Once it tried to construct the first static string
|
||||
of that static setup function, it crashed. To be more precise, it crashed in the string constructor when allocating
|
||||
memory for the string.
|
||||
|
||||
Great, we found the root cause for that. Let's figure out why that happens and what's different now that it crashes
|
||||
there.
|
||||
|
||||
## Tracing the bug down to libstdc++ and being totally mislead by various things
|
||||
Because there have been issues with incompatible libc/libcstd++ versions previously, I quickly came to the idea of
|
||||
comparing the version that worked on my cabinet and the version that did not work on my workstation. Indeed, these
|
||||
were different versions (considering the age gap of the two OSes, that wasn't very surprising).
|
||||
|
||||
I started digging into the libstdc++ source code, especially looking at what is going on when strings are allocated.
|
||||
Navigating the libstdc++ source is not a nice task. The naming is very confusing and, imo, non intuitive. With
|
||||
templating all over the place, this makes it even worse.
|
||||
|
||||
Finally, I found something that sounded very related to what I have discovered so far:
|
||||
In the file include/bits/basic_string.h, search for
|
||||
```
|
||||
_GLIBCXX_RESOLVE_LIB_DEFECTS
|
||||
3076. basic_string CTAD ambiguity
|
||||
```
|
||||
|
||||
and you will end up at a string constructor call. From that, I concluded that something was fixed related to
|
||||
constructor calls. I compared the string allocation call of both versions, the non working and working ones. The
|
||||
allocation for strings has changed.
|
||||
|
||||
Going back to the location of the crash in the piu executable, I saw that the string allocation was even inlined and
|
||||
not going to a library call of...boost filesystem. The first static object created is the path "../" which is used by
|
||||
a path object by boost filesystem. Because boost filesystem is a header only library, I assume that's why this got
|
||||
inlined.
|
||||
|
||||
However, I was using the libstdc++ version of my local machine where the string allocation scheme was different. This
|
||||
still did not truly explain why it crashed there with a jump to address 0, but things aligned pretty well so far.
|
||||
|
||||
So, the first idea for a solution was to use the old libstdc++ version from the machine where everything was fine
|
||||
and working. However, using it did not change anything. I went on and grabbed more libraries from the machine until
|
||||
I had all of them on my workstation and hooked them up with a `LD_LIBRARY_PATH`. But now, the application did crash
|
||||
very early and did not even get to setting up the runtime at all. I did some poking but could not figure out what
|
||||
was happening there. It just crashed with another jump to 0 very early in launching the elf.
|
||||
|
||||
Debugging continued for a bunch of weeks, on and off. I got more focused on digging deeper into the string allocation
|
||||
thing as this was giving me a direction and was aligning with my findings so far. I came up with a "solution" to hook
|
||||
and overwrite the string constructor calls of the newer libstdc++ version.
|
||||
|
||||
I started replacing them one by one to re-implement the old allocation scheme. However, this was not enough as many
|
||||
function calls that are working with string objects rely on the used allocation scheme, too. Therefore, I also started
|
||||
patching these until I realized that I have to re-implement, at least, everything basic_string related.
|
||||
|
||||
At that point, I was quite sure that this is the wrong way to go and stopped with this approach.
|
||||
|
||||
A few more months passed, I got back at this multiple times but couldn't come up with another solution or even
|
||||
direction.
|
||||
|
||||
## AppImage
|
||||
In a talk with a buddy, I had the idea of using some sort of sandboxing or environment boxing to pack things up nicely
|
||||
into a single blob to make it easier to handle, even distributable and maybe even more platform independent.
|
||||
|
||||
After some internet research, I found a solution that looked like it fits the pump game use-case very well which was
|
||||
AppImage. AppImage packs all your stuff into a single binary blob which is basically just a sqash fs image with a
|
||||
special runtime bootstrapping the whole image and running a bash script once the image is mounted. The provided features
|
||||
are very minimalistic but solve all our needs for packing pump games nicely.
|
||||
|
||||
The reference documentation also pointed out a few things about packing runtime libraries and how to set them up using
|
||||
`LD_LIBRARY_PATH` which I was already doing but just using a naked bash script. However, this reading was starting to
|
||||
point me into the right direction.
|
||||
|
||||
## Providing a platform/distro independent environment for your application down to the linker
|
||||
I started playing around with the libraries I used for Fiesta 2. First, I grabbed _all_ libraries from my cabinet which
|
||||
are required to run the game (using `ldd`). Then I started trying different combinations of including them using
|
||||
`LD_LIBRARY_PATH` and some used by my native system. Nope, either I was running into the static init crashing issue
|
||||
or things crashed even earlier on.
|
||||
|
||||
Wait, have I ever looked into why the application crashed so early, even before spinning up the runtime environment?
|
||||
After working on this on and off for months and totally losing track by getting to deep into this libstdc++ thing, I
|
||||
realized I didn't.
|
||||
|
||||
I set up the application to use all libraries from my cabinet and started debugging. First, I tried my luck with `gdb`
|
||||
but didn't get very far as it just showed me a jump to 0, again. Next, I got pointed out to using `valgrind`. This was
|
||||
a great hint as valgrind hooks into the bootstrapping process of the application and even displays a lot of information
|
||||
about library loading and setting up the environment even way before the runtime is set up. Recommendation: Use
|
||||
`valgrind -v -v -v` to get a lot of output.
|
||||
|
||||
However, in the end, valgrind also just pointed out the jump to 0 being the reason for the crash but not why this
|
||||
happened. The additional information was good to figure out the steps happening in the application setup process but
|
||||
did not yield any additional insights.
|
||||
|
||||
After giving this some more thought and making sure I used all runtime libraries from the cabinet, I did some
|
||||
internet research again and tried go get more information on how ELFs are bootstrapped and what happens. It turned out
|
||||
that information on that topic very sparse and difficult to find. However, this brought me to the following thread
|
||||
from the gnu mailing list:
|
||||
```
|
||||
Hi all;
|
||||
|
||||
I have a need to use a different runtime linker (ld-linux.so) for some
|
||||
of my applications. I _don't_ want to change the executables themselves
|
||||
(changing the path to the runtime linker in the ELF image for example).
|
||||
|
||||
I know that I can just run my application as an argument to ld-linux.so,
|
||||
and that actually does work very well. But, I am left with two
|
||||
problems:
|
||||
|
||||
* Some of my applications fork/exec other programs, and those other
|
||||
programs also need to use the other runtime linker. Just as I don't
|
||||
want to change the ELF image, I certainly don't want to have to
|
||||
recompile the programs to exec the runtime linker!
|
||||
|
||||
* Debugging: I can't debug because I can't find a way to convince GDB
|
||||
to invoke the program-to-be-debugged using an alternative runtime
|
||||
linker.
|
||||
|
||||
I was wondering if anyone has any ideas about how to do this? I checked
|
||||
the man pages and didn't see anything (I was looking for something like
|
||||
an environment variable that ld-linux.so might obey to re-exec a
|
||||
different version, or some such thing--not likely, I know).
|
||||
|
||||
If there's nothing like that available another idea is to use LD_PRELOAD
|
||||
to install a private version of the execve() system call, which would
|
||||
set the alternative ld-linux.so as the first argument. Any thoughts on
|
||||
this approach? Do you think it would solve the GDB problem as well?
|
||||
I'm going to proceed to work on this idea but I wanted to see if anyone
|
||||
had other thoughts about it.
|
||||
|
||||
|
||||
|
||||
PS. In case anyone's curious, I'm trying to run some programs natively
|
||||
on my desktop system which were actually compiled for a different
|
||||
system, and the native and target systems have different versions of
|
||||
glibc installed. I can't change either of the versions of glibc,
|
||||
but I do have the complete root filesystem of the target box in a
|
||||
subdirectory of my native box and I can use LD_LIBRARY_PATH, etc. to
|
||||
use them... I just need a way to get the runtime environment to
|
||||
initialize properly.
|
||||
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
Paul D. Smith <address@hidden> HASMAT--HA Software Mthds & Tools
|
||||
"Please remain calm...I may be mad, but I am a professional." --Mad Scientist
|
||||
-------------------------------------------------------------------------------
|
||||
These are my opinions---Nortel Networks takes no responsibility for them.
|
||||
```
|
||||
|
||||
Source: https://lists.gnu.org/archive/html/help-gnu-utils/2004-06/msg00020.html
|
||||
|
||||
Oh, wow. I was amazed that someone else was having a very similar use-case of running applications from legacy systems
|
||||
on a newer machine with different library versions and conflicts.
|
||||
|
||||
Reading on, this answer was finally revealing the last piece of the puzzle to understand the issue I was facing with
|
||||
Fiesta 2:
|
||||
```
|
||||
If you have an old a.elf main executable program that was linked
|
||||
to run with libc.so.6, and you want to run that a.elf on a machine
|
||||
whose libc.so.6 differs from the one which a.elf was linked against,
|
||||
and you want a reasonable guarantee that it will work, then it may
|
||||
be necessary to use something like rtldi to choose the matching
|
||||
ld-linux.so.2 and libc.so.6 and other libraries as of the time of
|
||||
static binding of a.elf. In theory, libc.so.6 is backwards compatibile
|
||||
(a given a.elf should run the same under any subsequent libc.so.6),
|
||||
but in practice there have been too many bugs in {ld-linux.so.2,
|
||||
libc.so.6, ...}. For example, many programs linked for libc.so.6
|
||||
under RedHat Linux 7.2 and earlier won't run under glibc-2.3.2.
|
||||
The most effective way to run such a program may be to install the
|
||||
old glibc-x.y.z[.rpm] in a different location, binary edit the PT_INTERP
|
||||
string of the a.elf, and use rtldi. This makes the old [edited] a.elf
|
||||
simultaneously interoperable with other programs from different
|
||||
libc.so.6 generations, and only the "oddball" program has to know.
|
||||
|
||||
...
|
||||
|
||||
ld-linux.so.2, libc.so.6, libdl.so, libnss*.so, and _many_ other shared
|
||||
libraries of a given released version, are a matched set. They must be
|
||||
used together, all from the same release, or not at all. Mixing and
|
||||
matching pieces from different glibc-x.y.z need not work. In order to
|
||||
use a consistent set that differs from the default set, you must invoke
|
||||
the ld-linux.so.2 using "--library-path PATH" to specify the location
|
||||
of the rest of the set. Using --library-path supersedes LD_LIBRARY_PATH
|
||||
for one execve() only, and does not interfere with chilren, execve()
|
||||
with no fork(), or other processes.
|
||||
```
|
||||
|
||||
Oh, I was not aware about ld-linux.so being THAT important to this set of libc.sp, libdl.so etc. Therefore, I grabbed
|
||||
the ld-linux.so file from my cabinet and ran the application with all libraries (except the GPU driver specific ones)
|
||||
on my workstation like this:
|
||||
```
|
||||
LD_PRELOAD="./f2hook.so" ./ld-linux.so.2 --library-path ./lib ./piu ./game/ --options ./hook.conf
|
||||
```
|
||||
|
||||
And the application starts up, passes runtime initializing, hits main and the game works.
|
||||
|
||||
I don't think there is anything else to add to the copy-pasted explanations above. Make sure to read them carefully to
|
||||
understand the whole issue. Once you know what is going on, the whole thing is not even super complex.
|
||||
|
||||
## Conclusions
|
||||
I would consider this being one of the major issues I have resolved so far for pumptools as this was so misleading,
|
||||
difficult to debug and find any general information on. The reason behind that and the solution are super simple, once
|
||||
you know them.
|
||||
|
||||
As we have already discussed various times in the past, this issue further points out how important proper solutions
|
||||
for sandboxing, containerizing or how you want to call it for legacy applications like pump games are. The naked
|
||||
bash script setting up everything is already quite good and doing the job. AppImage further enhances this by making
|
||||
the whole application more portable.
|
||||
|
||||
However, understanding the whole process behind ELF loading, library linking, setting up the process environment and
|
||||
setting up the runtime environment is very important. Otherwise, rather simple incompatibilities are the result which
|
||||
have a major impact on running the application.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Hardware used by Pump It Up Games
|
||||
## Main PC/CPU hardware
|
||||
### MK 1
|
||||
We have not seen this piece of hardware and the chances these boards still exist and are sold somewhere is rather low.
|
||||
However, there are pointers in the code of the game and the evolution of the hardware that it existed for the first
|
||||
few versions of PIU:
|
||||
* Songs were played back entirely from CD requiring to store them as 16-bit PCM audio. Therefore, the hardware did not
|
||||
have a chip for decoding MP3.
|
||||
* Absence of hardware security/lockchip.
|
||||
|
||||
We assume that this hardware was shipped and used with 1st, 2nd and 3rd. Starting 3rd SE, song files were stored as
|
||||
encoded MP3 files on the CD requiring a MP3 decoder available with [MK3 hardware](###MK 3).
|
||||
|
||||
### MK 3
|
||||
All games from 1st to Prex3/Premiere 3 are running on this revision of hardware. 1st to 3rd are compatible with this
|
||||
hardware.
|
||||
|
||||
System configuration dump using PC-DOCTOR DOS 3.0
|
||||
```
|
||||
SYSTEM CONFIGURATION=====================================================
|
||||
|
||||
Operating System - DOS 7.10 in HMA
|
||||
CPU Type - 333 MHz Intel Celeron 333
|
||||
CPUID - "GenuineIntel", Family 6, Model 6, Step 5
|
||||
- MMX available
|
||||
Coprocessor Type - 686
|
||||
Expansion Bus Type - ISA, PCI
|
||||
ROM BIOS Date - 04/20/99
|
||||
ROM BIOS Copyright - COPYRIGHT Award Software Inc.
|
||||
Additional ROM - C800[8kB]
|
||||
Base Memory - 640 kB
|
||||
Expanded Memory - N/A
|
||||
Extended Memory - 64512 kB (CMOS Configuration)
|
||||
XMS Memory - 62400 kB (XMS 3.00, Driver 3.95) A20=ON
|
||||
Serial Ports - None Installed
|
||||
Parallel Ports - None Installed
|
||||
Video Adapter - VGA: 3Dfx Interactive, Inc.
|
||||
- Total Memory : 8192 kB
|
||||
Fixed Disk Drives - 42 MB
|
||||
Floppy Disk Drives - 1 - 1=3.5"/1.44M
|
||||
Mouse - No Mouse
|
||||
Joysticks - None
|
||||
Sound Card - N/A
|
||||
CAS Fax/Modem Card - N/A
|
||||
Disk Compression - N/A
|
||||
CD-ROM Driver Version - MSCDEX V2.25, Drive: D:
|
||||
Disk Cache - Smartdrive 5.02, Hits 76%, Size 2048 kB
|
||||
Primary IDE Master - ATAPI: HL-DT-STDVD-ROM GDR8164B
|
||||
Primary IDE Slave - IDE: QUANTUM FIREBALL_TM1280A, S/N:692708422782
|
||||
Secondary IDE Master - No Drive
|
||||
Secondary IDE Slave - No Drive
|
||||
SCSI - N/A
|
||||
Network - N/A
|
||||
Power Management - APM V1.2, Power On
|
||||
USB Port - Installed on IRQ 10
|
||||
Chipset Type - Intel 440BX
|
||||
L2 Cache Type - 128 kB
|
||||
DRAM Row 0 - 64 MB
|
||||
DRAM Row 1 - Empty
|
||||
DRAM Row 2 - Empty
|
||||
DRAM Row 3 - Empty
|
||||
DRAM Row 4 - Empty
|
||||
DRAM Row 5 - Empty
|
||||
DRAM Row 6 - Empty
|
||||
DRAM Row 7 - Empty
|
||||
|
||||
|
||||
PC-DOCTOR DOS 3.0 Copyr 2004 PC-Doctor, Inc. 16:15 9/25/2000
|
||||
```
|
||||
|
||||
A bunch of custom chips for handling the following:
|
||||
* Sound
|
||||
* YAMAHA YMZ280B-F (8-Channel PCM/ADPCM Decoder)
|
||||
* 16-bit PCM sound effects are played from external memory
|
||||
* YAMAHA YAC516 (Delta Sigma Modulation D/A Converter with 8 times Over-sampling Filter)
|
||||
* MICRONAS DAC3550A C2 (Stereo Audio DAC)
|
||||
* MICRONAS MAS3507D F10 (MPEG 1/2 Layer 2/3 Audio Decoder)
|
||||
* Songs are stored MP3 encoded
|
||||
|
||||
### MK 5
|
||||
|
||||
### MK 6
|
||||
|
||||
### MK 7
|
||||
|
||||
### MK 9
|
||||
|
||||
### MK X
|
||||
|
||||
## IO hardware
|
||||
### ISA PIUIO
|
||||
|
||||
### USB PIUIO
|
||||
|
||||
### USB PIUIO 2
|
||||
|
||||
### PIUBTN
|
||||
|
||||
### XX Card reader
|
||||
|
||||
## Security
|
||||
### Lockchip
|
||||
|
||||
### USB dongles
|
||||
|
||||
### ds1963s
|
||||
Used for Pro 1/2 (and ITG 1/2).
|
||||
@@ -0,0 +1,141 @@
|
||||
# Notes about how NX2 handles saving and loading usb profile data
|
||||
Executable of v1.54.
|
||||
|
||||
This document outlines the relevant parts regarding how NX2 loads and stores profiles and how this had an impact on the
|
||||
design of the patch module `net-profile`, that hooks up usb profile loading over socket to a remote server.
|
||||
|
||||
## Syscall flow
|
||||
The game calls the following system calls in order per player, here player 1 (id 0), when loading usb profile data
|
||||
(assume all calls are successful here):
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
fread(fd_save) // stats area
|
||||
fread(fd_save) // encrypted area
|
||||
fclose(fd_save)
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin")
|
||||
fread(fd_rank) // encrypted area
|
||||
fclose(fd_rank)
|
||||
```
|
||||
|
||||
For saving the profile, this is the call flow:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
fseek(fd_save) // beginning
|
||||
fwrite(fd_save) // stats area
|
||||
fwrite(fd_save) // encrypted area
|
||||
fclose(fd_save)
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin")
|
||||
fseek(fd_rank) // beginning
|
||||
fwrite(fd_rank) // encrypted area
|
||||
fclose(fd_rank)
|
||||
```
|
||||
|
||||
We can see that in both scenarios, for loading and saving, the game first takes care of a single file before moving on
|
||||
to the next one.
|
||||
|
||||
There is another flow that is slightly different when playing World Max. After playing a song in World Max, the game
|
||||
writes the changes of the `nx2save.bin` file back:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
fseek(fd_save) // beginning
|
||||
fwrite(fd_save) // stats area
|
||||
fwrite(fd_save) // encrypted area
|
||||
fclose(fd_save)
|
||||
```
|
||||
|
||||
## Pumpnet RPCs
|
||||
The above set the requirements for how pumpnet needs to handle file loading and storing.
|
||||
|
||||
### First solution
|
||||
The initial solution was combining the loading and storing process of both files, `nx2save.bin` and `nx2rank.bin`
|
||||
into a single download/upload call.
|
||||
|
||||
Load phase:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
pumpnet_download_profiles()
|
||||
fread(fd_save) // read from buffered data
|
||||
fread(fd_save) // read from buffered data
|
||||
fclose(fd_save)
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin") // skip, data already buffered
|
||||
fread(fd_rank) // read from buffered data
|
||||
fclose(fd_rank) // clear all buffers and reset internal state
|
||||
```
|
||||
|
||||
Save phase:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
pumpnet_download_profiles()
|
||||
fseek(fd_save) // seek for buffers
|
||||
fwrite(fd_save) // write to buffers
|
||||
fwrite(fd_save) // write to buffers
|
||||
fclose(fd_save) // skip, do nothing
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin") // skip, data already buffered
|
||||
fseek(fd_rank) // seek for buffers
|
||||
fwrite(fd_rank) // write to buffers
|
||||
pumpnet_upload_profiles()
|
||||
fclose(fd_rank) // clear all buffers and reset internal state
|
||||
```
|
||||
|
||||
However, with the additional flow during World Max, this solution didn't work and broke internal state handling.
|
||||
Modifying this solution to incorporate saving just the `nx2save.bin` file wasn't feasible with the above architecture.
|
||||
|
||||
### Second solution
|
||||
Therefore, the pumpnet backend was re-designed to operate on on a single file level and separate for saving and
|
||||
loading.
|
||||
|
||||
Load phase:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
pumpnet_download_save()
|
||||
fread(fd_save) // read from buffered data
|
||||
fread(fd_save) // read from buffered data
|
||||
fclose(fd_save)
|
||||
pumpnet_clear_save_cache()
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin")
|
||||
pumpnet_download_rank()
|
||||
fread(fd_rank) // read from buffered data
|
||||
fclose(fd_rank)
|
||||
pumpnet_clear_rank_cache()
|
||||
```
|
||||
|
||||
Save phase:
|
||||
```c
|
||||
// Simplified pseudo-like C code omitting non relevant parameters
|
||||
fopen("/mnt/0/nx2save.bin")
|
||||
pumpnet_download_save()
|
||||
fseek(fd_save) // seek for buffers
|
||||
fwrite(fd_save) // write to buffers
|
||||
fwrite(fd_save) // write to buffers
|
||||
fclose(fd_save)
|
||||
pumpnet_upload_save()
|
||||
pumpnet_clear_save_cache()
|
||||
|
||||
fopen("/mnt/0/nx2rank.bin")
|
||||
fseek(fd_rank) // seek for buffers
|
||||
fwrite(fd_rank) // write to buffers
|
||||
fclose(fd_rank)
|
||||
pumpnet_upload_rank()
|
||||
pumpnet_clear_rank_cache()
|
||||
```
|
||||
|
||||
Buffer modifications are detected when `fwrite` is called and flagged accordingly that the module is aware of having
|
||||
to write back dirty data on the call to `fclose`.
|
||||
|
||||
State handling is also simplified as it only needs to be taken care of on a file level between `fopen` and `fclose`
|
||||
calls and not on a multi-file level.
|
||||
|
||||
This solution is definitely more robust and flexible than the previous one that operated on a full profile level.
|
||||
However, the call overhead to the remote server is four times higher compared to the previous solution. Considering
|
||||
that these calls are not being issued on a high frequency, server load is unlikely to be an issue.
|
||||
@@ -0,0 +1,448 @@
|
||||
# Pad command codes notes
|
||||
Raw data and notes about pad command codes which were extracted directly from the games because I could not find a
|
||||
complete and 100% accurate list per game.
|
||||
|
||||
All codes extracted from the MK3 games are based on the Linux port executables.
|
||||
|
||||
How did I find these? I used the `ac_sound_play_effect` with the second parameter set to the value `3` as my entry
|
||||
point. This plays the sound effect that you can hear when you entered the modifier code correctly. After I have found
|
||||
the right call, it's next to some code like this (taken from 03_3rd):
|
||||
```
|
||||
...
|
||||
if ( modFlags & 1 )
|
||||
{
|
||||
if ( modFlagsSpeed1P & 1 )
|
||||
result = func_2a2dc(0, 1);
|
||||
if ( modFlagsSpeed1P & 2 )
|
||||
result = func_2a2dc(0, 2);
|
||||
if ( modFlagsSpeed1P & 4 )
|
||||
result = func_2a2dc(0, 4);
|
||||
if ( modFlagsSpeed1P & 8 )
|
||||
result = func_2a2dc(0, 8);
|
||||
if ( modFlagsSpeed1P & 0x10 )
|
||||
result = func_2a2dc(0, 16);
|
||||
}
|
||||
if ( modFlags & 2 )
|
||||
{
|
||||
if ( modFlagsSpeed2P & 1 )
|
||||
result = func_2a2dc(1, 1);
|
||||
if ( modFlagsSpeed2P & 2 )
|
||||
result = func_2a2dc(1, 2);
|
||||
if ( modFlagsSpeed2P & 4 )
|
||||
result = func_2a2dc(1, 4);
|
||||
if ( modFlagsSpeed2P & 8 )
|
||||
result = func_2a2dc(1, 8);
|
||||
if ( modFlagsSpeed2P & 0x10 )
|
||||
result = func_2a2dc(1, 16);
|
||||
}
|
||||
if ( modFlagsSpeed1P & 0x1F )
|
||||
{
|
||||
checkIfModifierSet2();
|
||||
result = ac_sound_play_effect(0, 3u, 2u);
|
||||
}
|
||||
if ( modFlagsSpeed2P & 0x1F )
|
||||
{
|
||||
checkIfModifierSet();
|
||||
result = ac_sound_play_effect(0, 3u, 2u);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
I already renamed the function that is called prior the `ac_sound_play_effect` to `checkIfModifierSet` here. That
|
||||
function checks the last eight pad inputs from a list against a list of command codes. Once a code matches, flags are
|
||||
set accordingly.
|
||||
|
||||
Excerpt, again from 03_3rd:
|
||||
```
|
||||
signed int checkIfModifierSet()
|
||||
{
|
||||
const char *v0; // edx@1
|
||||
int v1; // ebp@1
|
||||
signed int v2; // ebx@1
|
||||
const char *v3; // edx@19
|
||||
unsigned int v4; // ebx@19
|
||||
signed int result; // eax@41
|
||||
signed int v6; // [sp+0h] [bp-1Ch]@1
|
||||
|
||||
v0 = (const char *)&codeList; // THIS is the code list
|
||||
v1 = *(_DWORD *)&modFlags;
|
||||
v2 = 0;
|
||||
v6 = *(_DWORD *)&data_3299a4;
|
||||
while ( memcmp((const char *)&data_44e9e8, v0, 8) )// iterate command list and currently post 8 inputs
|
||||
{
|
||||
++v2;
|
||||
v0 += 8; // each modifier code is 8 commands
|
||||
if ( v2 >= 13 ) // beyond command list size
|
||||
goto jump_2a17f;
|
||||
}
|
||||
switch ( v2 ) // found command id
|
||||
{
|
||||
case 0:
|
||||
*(_DWORD *)&data_3299a4 = 0;
|
||||
*(_DWORD *)&modFlags = *(_WORD *)&modFlags & 0xFFF;
|
||||
break;
|
||||
```
|
||||
|
||||
## 03_3rd
|
||||
```
|
||||
1 = DL
|
||||
2 = DR
|
||||
4 = C
|
||||
8 = UL
|
||||
16 = UR
|
||||
|
||||
-- cancel
|
||||
.data:0810833A db 4
|
||||
.data:0810833B db 4
|
||||
.data:0810833C db 4
|
||||
.data:0810833D db 4
|
||||
|
||||
.data:0810833E db 4
|
||||
.data:0810833F db 4
|
||||
.data:08108340 db 4
|
||||
.data:08108341 db 4
|
||||
--------------------------- vanish
|
||||
.data:08108342 db 4 center
|
||||
.data:08108343 db 4 center
|
||||
.data:08108344 db 10h up right
|
||||
.data:08108345 db 8 up left
|
||||
.data:08108346 db 4 center
|
||||
.data:08108347 db 2 down right
|
||||
.data:08108348 db 1 down left
|
||||
.data:08108349 db 4 center
|
||||
----------------- mirror
|
||||
.data:0810834A db 4
|
||||
.data:0810834B db 4
|
||||
.data:0810834C db 1
|
||||
.data:0810834D db 4
|
||||
.data:0810834E db 10h
|
||||
.data:0810834F db 2
|
||||
.data:08108350 db 4
|
||||
.data:08108351 db 8
|
||||
---- 8X
|
||||
.data:08108352 db 4
|
||||
.data:08108353 db 4
|
||||
.data:08108354 db 1
|
||||
.data:08108355 db 4
|
||||
.data:08108356 db 2
|
||||
.data:08108357 db 4
|
||||
.data:08108358 db 4
|
||||
.data:08108359 db 4
|
||||
- 4 X
|
||||
.data:0810835A db 4
|
||||
.data:0810835B db 4
|
||||
.data:0810835C db 2
|
||||
.data:0810835D db 4
|
||||
.data:0810835E db 4
|
||||
.data:0810835F db 8
|
||||
.data:08108360 db 4
|
||||
.data:08108361 db 4
|
||||
2X
|
||||
.data:08108362 db 4
|
||||
.data:08108363 db 4
|
||||
.data:08108364 db 8
|
||||
.data:08108365 db 4
|
||||
.data:08108366 db 4
|
||||
.data:08108367 db 4
|
||||
.data:08108368 db 10h
|
||||
.data:08108369 db 4
|
||||
random step
|
||||
.data:0810836A db 4
|
||||
.data:0810836B db 1
|
||||
.data:0810836C db 4
|
||||
.data:0810836D db 2
|
||||
.data:0810836E db 4
|
||||
.data:0810836F db 10h
|
||||
.data:08108370 db 4
|
||||
.data:08108371 db 8
|
||||
nonstep
|
||||
.data:08108372 db 8
|
||||
.data:08108373 db 10h
|
||||
.data:08108374 db 4
|
||||
.data:08108375 db 8
|
||||
.data:08108376 db 10h
|
||||
.data:08108377 db 4
|
||||
.data:08108378 db 8
|
||||
.data:08108379 db 10h
|
||||
synchro
|
||||
.data:0810837A db 4
|
||||
.data:0810837B db 4
|
||||
.data:0810837C db 8
|
||||
.data:0810837D db 10h
|
||||
.data:0810837E db 8
|
||||
.data:0810837F db 10h
|
||||
.data:08108380 db 4
|
||||
.data:08108381 db 4
|
||||
couple
|
||||
.data:08108382 db 4
|
||||
.data:08108383 db 4
|
||||
.data:08108384 db 10h
|
||||
.data:08108385 db 8
|
||||
.data:08108386 db 10h
|
||||
.data:08108387 db 8
|
||||
.data:08108388 db 4
|
||||
.data:08108389 db 4
|
||||
union
|
||||
.data:0810838A db 1
|
||||
.data:0810838B db 2
|
||||
.data:0810838C db 4
|
||||
.data:0810838D db 8
|
||||
.data:0810838E db 2
|
||||
.data:0810838F db 1
|
||||
.data:08108390 db 4
|
||||
.data:08108391 db 10h
|
||||
random vanish
|
||||
.data:08108392 db 8
|
||||
.data:08108393 db 10h
|
||||
.data:08108394 db 4
|
||||
.data:08108395 db 1
|
||||
.data:08108396 db 2
|
||||
.data:08108397 db 4
|
||||
.data:08108398 db 8
|
||||
.data:08108399 db 10h
|
||||
random speed
|
||||
.data:0810839A db 8
|
||||
.data:0810839B db 10h
|
||||
.data:0810839C db 2
|
||||
.data:0810839D db 4
|
||||
.data:0810839E db 1
|
||||
.data:0810839F db 4
|
||||
.data:081083A0 db 2
|
||||
.data:081083A1 db 1
|
||||
|
||||
```
|
||||
|
||||
Looking further into the code, there seems to be another command list with three entries right after that and it's
|
||||
checked in the same function. This time, the number of inputs are 10 instead of 8:
|
||||
```
|
||||
v3 = (const char *)&data_108d84; // THIS is the code list
|
||||
v4 = 0;
|
||||
while ( memcmp(data_44e9f8, v3, 10) )
|
||||
{
|
||||
++v4;
|
||||
v3 += 10;
|
||||
if ( (signed int)v4 >= 3 )
|
||||
goto jump_2a289;
|
||||
}
|
||||
if ( v4 < 1 )
|
||||
{
|
||||
if ( !v4 && data_44ea30 == 2 )
|
||||
data_108bad |= 0x80u;
|
||||
}
|
||||
else if ( v4 <= 1 )
|
||||
{
|
||||
if ( data_108bad & 4 )
|
||||
{
|
||||
*(_WORD *)&modFlags |= 0x820u;
|
||||
qmemcpy(data_44e488, &data_388e00[20 * (4 * data_108ba8 + 52)], 0x50u);
|
||||
}
|
||||
}
|
||||
else if ( v4 == 2
|
||||
&& (data_108bad & 2 && data_108ba8 >= 2 || data_108bad & 4 && !(modFlags & 0x20) && data_108ba8 == 1) )
|
||||
{
|
||||
data_108bae |= 1u;
|
||||
}
|
||||
```
|
||||
|
||||
Code list:
|
||||
```
|
||||
1 = DL
|
||||
2 = DR
|
||||
4 = C
|
||||
8 = UL
|
||||
16 = UR
|
||||
|
||||
.data:081083A2 data_108d84 db 2 ; DATA XREF: func_29ce8:jump_29e70o
|
||||
.data:081083A2 ; checkIfModifierSet:jump_2a17fo
|
||||
.data:081083A3 db 1
|
||||
.data:081083A4 db 8
|
||||
.data:081083A5 db 10h
|
||||
.data:081083A6 db 18h
|
||||
.data:081083A7 db 20h
|
||||
.data:081083A8 db 12h
|
||||
.data:081083A9 db 11h
|
||||
.data:081083AA db 10h
|
||||
.data:081083AB db 18h
|
||||
|
||||
.data:081083AC db 12h
|
||||
.data:081083AD db 18h
|
||||
.data:081083AE db 10h
|
||||
.data:081083AF db 1
|
||||
.data:081083B0 db 8
|
||||
.data:081083B1 db 2
|
||||
.data:081083B2 db 11h
|
||||
.data:081083B3 db 20h
|
||||
.data:081083B4 db 11h
|
||||
.data:081083B5 db 10h
|
||||
|
||||
.data:081083B6 db 8
|
||||
.data:081083B7 db 2
|
||||
.data:081083B8 db 18h
|
||||
.data:081083B9 db 12h
|
||||
.data:081083BA db 1
|
||||
.data:081083BB db 10h
|
||||
.data:081083BC db 11h
|
||||
.data:081083BD db 20h
|
||||
.data:081083BE db 2
|
||||
.data:081083BF db 18h
|
||||
```
|
||||
|
||||
However, the code list seems to have inputs with multiple bit fields set, e.g. 0x12, 0x11. Untested.
|
||||
|
||||
## 04_3se
|
||||
List of previous version extended by 3 more codes (last ones in the list).
|
||||
|
||||
```
|
||||
1 = DL
|
||||
2 = DR
|
||||
4 = C
|
||||
8 = UL
|
||||
16 = UR
|
||||
|
||||
data_12ae8c db 4 ; DATA XREF: func_2a8c0+23o
|
||||
.data:08123643 ; func_2ab98+23o
|
||||
.data:08123644 db 4
|
||||
.data:08123645 db 4
|
||||
.data:08123646 db 4
|
||||
.data:08123647 db 4
|
||||
.data:08123648 db 4
|
||||
.data:08123649 db 4
|
||||
.data:0812364A db 4
|
||||
|
||||
.data:0812364B db 4
|
||||
.data:0812364C db 4
|
||||
.data:0812364D db 10h
|
||||
.data:0812364E db 8
|
||||
.data:0812364F db 4
|
||||
.data:08123650 db 2
|
||||
.data:08123651 db 1
|
||||
.data:08123652 db 4
|
||||
|
||||
.data:08123653 db 4
|
||||
.data:08123654 db 4
|
||||
.data:08123655 db 1
|
||||
.data:08123656 db 4
|
||||
.data:08123657 db 10h
|
||||
.data:08123658 db 2
|
||||
.data:08123659 db 4
|
||||
.data:0812365A db 8
|
||||
|
||||
.data:0812365B db 4
|
||||
.data:0812365C db 4
|
||||
.data:0812365D db 1
|
||||
.data:0812365E db 4
|
||||
.data:0812365F db 2
|
||||
.data:08123660 db 4
|
||||
.data:08123661 db 4
|
||||
.data:08123662 db 4
|
||||
|
||||
.data:08123663 db 4
|
||||
.data:08123664 db 4
|
||||
.data:08123665 db 2
|
||||
.data:08123666 db 4
|
||||
.data:08123667 db 4
|
||||
.data:08123668 db 8
|
||||
.data:08123669 db 4
|
||||
.data:0812366A db 4
|
||||
|
||||
.data:0812366B db 4
|
||||
.data:0812366C db 4
|
||||
.data:0812366D db 8
|
||||
.data:0812366E db 4
|
||||
.data:0812366F db 4
|
||||
.data:08123670 db 4
|
||||
.data:08123671 db 10h
|
||||
.data:08123672 db 4
|
||||
|
||||
.data:08123673 db 4
|
||||
.data:08123674 db 1
|
||||
.data:08123675 db 4
|
||||
.data:08123676 db 2
|
||||
.data:08123677 db 4
|
||||
.data:08123678 db 10h
|
||||
.data:08123679 db 4
|
||||
.data:0812367A db 8
|
||||
|
||||
.data:0812367B db 8
|
||||
.data:0812367C db 10h
|
||||
.data:0812367D db 4
|
||||
.data:0812367E db 8
|
||||
.data:0812367F db 10h
|
||||
.data:08123680 db 4
|
||||
.data:08123681 db 8
|
||||
.data:08123682 db 10h
|
||||
|
||||
.data:08123683 db 4
|
||||
.data:08123684 db 4
|
||||
.data:08123685 db 8
|
||||
.data:08123686 db 10h
|
||||
.data:08123687 db 8
|
||||
.data:08123688 db 10h
|
||||
.data:08123689 db 4
|
||||
.data:0812368A db 4
|
||||
|
||||
.data:0812368B db 4
|
||||
.data:0812368C db 4
|
||||
.data:0812368D db 10h
|
||||
.data:0812368E db 8
|
||||
.data:0812368F db 10h
|
||||
.data:08123690 db 8
|
||||
.data:08123691 db 4
|
||||
.data:08123692 db 4
|
||||
|
||||
.data:08123693 db 1
|
||||
.data:08123694 db 2
|
||||
.data:08123695 db 4
|
||||
.data:08123696 db 8
|
||||
.data:08123697 db 2
|
||||
.data:08123698 db 1
|
||||
.data:08123699 db 4
|
||||
.data:0812369A db 10h
|
||||
|
||||
.data:0812369B db 8
|
||||
.data:0812369C db 10h
|
||||
.data:0812369D db 4
|
||||
.data:0812369E db 1
|
||||
.data:0812369F db 2
|
||||
.data:081236A0 db 4
|
||||
.data:081236A1 db 8
|
||||
.data:081236A2 db 10h
|
||||
|
||||
.data:081236A3 db 8
|
||||
.data:081236A4 db 10h
|
||||
.data:081236A5 db 2
|
||||
.data:081236A6 db 4
|
||||
.data:081236A7 db 1
|
||||
.data:081236A8 db 4
|
||||
.data:081236A9 db 2
|
||||
.data:081236AA db 1
|
||||
--- remix double
|
||||
.data:081236AB db 1
|
||||
.data:081236AC db 8
|
||||
.data:081236AD db 1
|
||||
.data:081236AE db 4
|
||||
.data:081236AF db 10h
|
||||
.data:081236B0 db 2
|
||||
.data:081236B1 db 10h
|
||||
.data:081236B2 db 4
|
||||
---- crazy mode
|
||||
.data:081236B3 db 4
|
||||
.data:081236B4 db 1
|
||||
.data:081236B5 db 4
|
||||
.data:081236B6 db 2
|
||||
.data:081236B7 db 4
|
||||
.data:081236B8 db 1
|
||||
.data:081236B9 db 4
|
||||
.data:081236BA db 2
|
||||
--- all select code? doesn't make sense and doesn't seem to work, probably unused
|
||||
.data:081236BB db 10h
|
||||
.data:081236BC db 4
|
||||
.data:081236BD db 1
|
||||
.data:081236BE db 10h
|
||||
.data:081236BF db 8
|
||||
.data:081236C0 db 4
|
||||
.data:081236C1 data_12af0a db 2 ; DATA XREF: func_2c380+139r
|
||||
.data:081236C1 ; func_2c51c+559r
|
||||
.data:081236C2 db 8
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
# Notes about working with Pump It Up Pro
|
||||
## Enable game's log output in arcade mode
|
||||
Go to `data0.zip/Data/Static/ini` and change the following lines under the category `[Options-arcade]`:
|
||||
```text
|
||||
LogToDisk=1
|
||||
ShowLogOutput=1
|
||||
```
|
||||
|
||||
The game logs all log levels including trace level.
|
||||
Reference in New Issue
Block a user