Version 1.08 with public source code release.

This commit is contained in:
icex2
2020-10-03 20:56:55 +02:00
parent 656148121d
commit 762bf140c0
555 changed files with 55502 additions and 3 deletions
+43
View File
@@ -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.
![](images/architecture.png)
## 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.
+163
View File
@@ -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.
+217
View File
@@ -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;
}
```
+141
View File
@@ -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.
+107
View File
@@ -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
```
+9
View File
@@ -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.