Example Of Sequenced VGM Analysis: PS1 Hokuto no Ken

Have you read How to Analyze Sequenced Video Game Music? This article is an actual example of it.

Note that this is a sort of platform-independent tip. Here I analyze sequences without disassembly.

日本語に翻訳する

Search a part of sequence

I set my first analysis target to the following song "水のテーマ":

I used XEBRA (PS1 emulator) to access the raw music sequence, because many other emulators freeze here and there.

First of all, you should make a savestate *1 at the beginning of the song. This is because the on-memory sequence data location may be changed for each time the song plays.

After that, I saved main memory image to view the sequence data with my favorite hex editor.

If your emulator does not have such an export function, go to the next section.

I used my MeloSearch tool to find the main melody.

C:\work>MeloSearch PSXMEM.BIN "g8f+8e16f+16g16b16<d"
- 00084FF7: 43 42 40 42 43 47 4A
- 000854FB: 43 42 40 42 43 47 4A

Note that the above dump omits bytes in between note numbers.

What a easy job! Don't you think so? :)

There are many 0x1Es (30) and 0x0Fs (15) around the note number. I guess these numbers are delta-time and duration of a note. So that TPQN of the sequence looks 60 (0x3C).

Search score pointers

If your emulator has a built-in RAM Search and Memory Viewer, you can use them. However, here I use an external tool, MHS, since XEBRA does not have them.

Important Note: In most cases, you do not need to use external memory tool. I wanted to use PSXjin to search those memory addresses, but the game is able to run only with XEBRA, and it does not have a built-in memory tool. That's why I used the external tool to do the search. Again, you do not need it in most cases.

Run MHS and click "File → Open Process", then choose the emulator process from the list.

Nothing happens. Yes, now we need to start a new search manually. First, you should limit the search range only to the related RAMs, if possible. Visit Memory Addresses Of Emulators page and copy the PC address of XEBRA's Main RAM region, then paste it to the Expression Evaluator.

In this situation, Main RAM seems to start from 2ED1000 (PC address), and end at 30D1000 (because PS1 Main RAM is 2MB).

Let's start a new search. Make sure the target song has started playing, then pause the game and click the single loupe icon in Found Addresses pane.

Data-Type Search dialog will appear. I set the search conditions as follows:

  • Data Type: Unsigned Long (because PS1 address is 32bit)
  • Evaluation Type, Value: Not Equal To 0 (song has been started, so pointers must not be null)
  • Search Range: From 2ED1000, To 30D1000 (to search in Main RAM)

Click OK. A message dialog will popup:

Found 372354 results (0.023000 seconds, 16189304.347826 results per second).

To decrease results, we need to do a sub-search. Unpause the game and let the main melody play the next note, then pause the game again and click the double loupes icon.

Choose "Different from Before" or "Increased" (if you are sure that the melody does not do backjump) then click OK. Repeat the sub-search until you can decrease the number of results enough. If results will not decrease well, probably you should do "Same as Before" (without altering the melody state) and/or "Decreased" (use a savestate to rewind the song) sub-search.

I somehow managed to find the target address, 02F4ABD0. By double-clicking the address, you can add it to the main address list. If you find other tracks at the same time, I suggest add them too.

Since the emulator allocates the Main RAM dynamically, the found address will become invalid in the next run. To prevent it and edit some additional things, double-click the added row in the main address list, then Modify Address dialog will appear.

In Main tab, you can edit the description of the address. Also, you make the value display to hexadecimal by checking "Show as Hex". As you can see, you can even edit the value in the address, but I do not need it at this moment. So click "Normal Address" tab.

Check "Use Complex Address" and enter the PC address formula, to match the simple address value, then click OK.

Usually, pointers for each tracks should be located in a fixed interval. If you find two or more pointers, it's easy to guess the locations of other pointers. So add the all pointers out!

Manipulate on-memory score data and research the format details

In previous section, I managed to find every track pointers.

The pointer of track 7 (initial target melody) shows 0x8008487C. This is a PlayStation address. To edit the data pointer points, convert the address to PC address first (use Expression Evaluator as shown in screenshot).

Open "Tools → Hex Editor", and enter the PC address to the input box in Helper pane, to jump there.

You can edit the data and see what happens. As I have said in How to Analyze Sequenced Video Game Music, you can research the effect of parameters and the length of a message.

In this case, I was able to understand the specification of main messages quite easily, because it was very similar to the standard PS1 SEQ.

Since we can view the pointers for every tracks, we can view the head of the sequence, by pausing the game immediately when the song starts.

 ADDRESS   D  E  F  0  1  2  3  4  5  6  7  8  9  A  B  C
02F5583D: 44 4D 46 00 00 00 00 00 01 08 00 3C 00 00 00 2C  DMF........<....
02F5584D: 00 00 00 40 00 00 03 0D 00 00 04 41 00 00 07 7F  ...@.......A....
02F5585D: 00 00 0C 7B 00 00 12 40 00 00 14 10 00 9D 00 9C  ...{...@........
02F5586D: 02 49 F0 75 9C 02 CF 67 03 9C 0C B7 35 B9 18 9B  ...u...g....5...

My thoughts on the above:

  • DMF looks like a signature
  • 0x08 at 0x02F55846 is equal to the track count
  • 0x3C at 0x02F55848 is equal to the expected TPQN
  • 8 numbers (32-bit big-endian) within 02F55849-02F55868 seem to be sorted in ascending-order. By measuring the distances of initial values of each pointers, I verified that they are start offsets for each tracks
  • Actual score data seem to start after those offsets

At this moment, I understood how can I parse the sequence, so I started to make a simple parser. (actually it was a MIDI converter, because listening is easier to understand than reading. ((Does making a MIDI writer sound difficult? A lot of major programming languages have a library to write a MIDI file, so you can do it even if you do not know the specification of SMF. In this research, I used Java, but I may also recommend Python, Ruby or Lua.))) If you have some programming skill, you definitely should make such a tool, and you will be able to read many sequences at the same time.

Export RAM to file

If your emulator does not have an export function, you might want to do that. (XEBRA does not need it)

MHS's hex editor has "Save File As" function, but it seems to be available only for a file input, not process memory. So you need to do the following steps:

  1. Select the range you want to save to a file (use Edit → Select Range), then copy it.
  2. Create a new file whose size is equal to the selection size. (use your favorite tool)
  3. Open the file in MHS's hex editor, then paste the RAM content and save the file.

Find sequence data from ROM by on-memory sequence

I searched sequence data in ROM by on-memory sequence data. Something must be found if your game does not compress and encrypt data, but in this situation I found nothing.

I found a similar data though.

DATA/BGMALL04.PAC:
 ADDRESS   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
000000A0: 08 14 44 4D 46 55 00 00 00 01 08 00 3C 5E 00 2C  ..DMFU......<^.,
000000B0: 62 00 40 00 00 00 00 03 0D 00 00 04 41 00 00 07  b.@.........A...
000000C0: 7F 00 00 0C 7B 00 00 02 00 12 13 00 14 10 00 9D  ....{...........
000000D0: 00 9C 02 49 F0 75 9C 02 CF 67 00 00 03 9C 0C B7  ...I.u...g......
RAM:
 ADDRESS   D  E  F  0  1  2  3  4  5  6  7  8  9  A  B  C
02F5583D: 44 4D 46 00 00 00 00 00 01 08 00 3C 00 00 00 2C  DMF........<....
02F5584D: 00 00 00 40 00 00 03 0D 00 00 04 41 00 00 07 7F  ...@.......A....
02F5585D: 00 00 0C 7B 00 00 12 40 00 00 14 10 00 9D 00 9C  ...{...@........
02F5586D: 02 49 F0 75 9C 02 CF 67 03 9C 0C B7 35 B9 18 9B  ...u...g....5...

Hmm, what is the difference? I started to compare them.

08 14
	44 4D 46
	55 00 => 00 00 00
	00 00 01 08 00 3C
	5E 00 => 00 00 00
	2C
	62 00 => 00 00 00
	40 00 00
00 00
	03 0D 00 00 04 41 00 00 07 7F 00 00 0C 7B 00 00
02 00
	12
	13 00 => 40 00 00
	14 10 00 9D 00 9C 02 49 F0 75 9C 02 CF 67

Obviously it is LZSS-compressed. So that I have written a decompressor tool and I managed to access the raw data from ROM.

At Last

Actually, I read disassembly later and noticed there were several errors on my assumption. However, the most of things were correct and they helped me a lot in reading disassembly.

I hope my articles will help you. If you have questions, do not hesitate to ask. *2

*1:In XEBRA, a savestate is called Running Image.

*2:I cannot answer to game-specific questions. If you are going to ask that, please generalize your question before you ask.

Remove all ads