mirror of
https://github.com/pumpitupdev/pumptools.git
synced 2024-12-26 21:24:08 +01:00
141 lines
5.3 KiB
Markdown
141 lines
5.3 KiB
Markdown
|
# 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).
|