1
0
mirror of https://github.com/pumpitupdev/pumptools.git synced 2024-12-26 05:04:50 +01:00
pumptools/doc/development/notes/extract-song-list.md

5.3 KiB
Raw Blame History

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:

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:

.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.

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:

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).