Maimai Classic and Xact
August 29, 2020•973 words
My first actual post to this blog will hopefully be the first in a series of posts about my findings in Maimai. Hopefully, we will have the knowledge and tools to make custom charts. And appreciate the underlying engine that powers the classic Maimai games from 2012 to 2019 and the new Maimai Deluxe (stylised as Maimai DX) that just released. We'll first start with how Maimai stores game sounds (i.e. music, sound effects, and voices)
Let's start with what Maimai is. Maimai is an arcade rhythm game developed and published by Sega. It was released in Japan in July 2012 and has since produced 15 releases (updates included.) The game has eight buttons and a touch screen, where the player has to press buttons and swipe the screen in time with the music. Watching a video of actual Maimai plays will give you a better idea of how the game works than my lame explanation. So go watch some if you're new to Maimai.
I will refer to the Maimai versions from 2012 up to Finale as Maimai Classic since the underlying engine is similar to old versions. Maimai DX (and DX Plus), the new version, is a complete rewrite of the game and released on a more recent cabinet. It shares almost none of the code from Maimai classic aside from chart files.
Maimai classic uses the Xact engine, part of DirectX 9, for audio playback. I will not detail how Xact works, but basically, the sounds are stored as a collection composed of two files, a sound bank (.xsb) and a wave bank (.xwb). The wave bank contains audio data, while the sound bank contains cues and other information about the sound. The following are the header information1 for one of the files in the game:
struct SoundBankHeader SBH_776 = {
.magic = 0x5344424B, // "KBDS"
.toolVersion = 45,
.formatVersion = 43,
.crc = 0xED95,
.lastModifiedLow = 0xBE495CDF,
.lastModifiedHigh = 0xEB69D401,
.platform = 0x01, // Windows
.numSimpleCues = 0x01,
.numComplexCues = 0,
.unkn3 = 0,
.numTotalCues = 0x10, // ????? should be 0x1
.numWaveBanks = 0x1,
.numSounds = 0x1,
.cueNameTableLen = 0xB,
.simpleCuesOffset = 0xD6,
.complexCuesOffset = 0xffffffff,
.cueNameOffset = 0x101,
.unknOffset = 0xffffffff,
.variationTableOffset = 0xffffffff,
.unknOffset2 = 0xffffffff,
.waveBankNameTableOffset = 0x8A,
.cueNameHashTableOffset = 0xDB,
.cueNameValsTableOffset = 0xFB,
.soundsOffset = 0xCA,
.name = "776"
};
struct WaveBankNameTableEntry WBNTE_776 = {
.name = "776"
};
struct SoundEntry SE_776 = {
.flags = 0x0,
.category = 0x1,
.unkn2 = 0x97,
.volume = 0x0,
.unkn3 = 0x0, // Pitch??
.entryLength = 0xC,
// flags is 0 therefore not a complex sound
.trackIndex = 0x0,
.waveBankIndex = 0x0
};
struct SimpleCueEntry SCE_776 = {
.flags = 0x4,
.soundOffset = 0xca
};
struct CueNameHashVal CNHV_776 = {
.nameOffset = 0x101, //Links to CueName struct below
.unkn = 0xFFFF
};
struct CueName CN_776 = {
.name = "GM_BGM_776"
}
Header information for Maimai's xwb file (Sample from 776):
struct WaveBankHeader WBH_776 = {
.magic = 0x444E4257, // "DNBW"
.toolVersion = 45,
.formatVersion = 43,
.waveBankData1Offset = 0x34,
.waveBankData1Length = 0x60,
//Not sure about these
.waveBankData2Offset = 0x94,
.waveBankData2Length = 0x18,
.waveBankData3Offset = 0xAC,
.waveBankData3Length = 0x00,
.waveBankData4Offset = 0x00,
.waveBankData4Length = 0x00,
.waveBankData5Offset = 0x800,
.waveBankData5Length = 0x4CE000,
};
struct WaveBankData WBD1_776 = {
.flags = 0x00080001, // Streaming or mask, has seektables
.entries = 0x1,
.name = "776", // name size is 64 bytes
.metadataElementSize = 0x0,
.nameElementSize = 0x0,
.alignment = 0x0,
.compactFormat = 0x18, // if 18, nSamplesPerSecond is 18
.buildTimeLow = 0x40,
.buildTimeHigh = 0x800,
};
struct WaveBankData WB2_776 = {
.flags = 0x040BA400, //???
.entries = 0x815888a,
//........
};
// WB3_776 and WB4_776 are 0
struct WaveBankData WB3_776 = {
.flags = 0x00100000, //
};
'm not sure about most of these fields; however, we can gather crucial information from this. The sound and wave banks from the game both have a tool and format version of 46 and 44. If we can find an Xact creation tool that produces the same tool and format version as those from the game, we can create our songs to replace or add2. After some digging, I've found that the Xact creation tool from DirectX SDK from March 20093 produces the same tool and format version.
When creating our wave and sound banks, it is essential that the following be done so the game will read the custom wave and sound banks:
- Wavebank name should be the three-digit numerical id of the chart (e.g. 776, 534, etc.)
- Wavebank should only contain the song.
- Wavebank should be compressed with ADPCM.
- Wavebank should be streaming and not in memory.
- Soundbank name should be the numerical id of the chart.
- Cue type should be simple.
- Cue name should be
GM_BGM_NumericalID
whereNumericalID
is the numerical id of the chart - Cue should only contain one track from the wave bank.
If these are followed and done correctly, we should create a compatible wave and sound banks for the game. I was supposed to add an actual tutorial, but I want this post short and simple. I've already given enough information for you to make compatible banks, and there are plenty of tutorials out there on the Xact creation tool. Maybe I'll do a follow-up tutorial if I feel like it. But there are more important posts in the future regarding Maimai. I hope that you learned from this post. See you next time.
-
More info about it on multimedia.cx wiki ↩
-
For now we can only replace existing songs. To add songs we still need to modify the game's database which will be covered in a future post. ↩
-
Can be downloaded from archive.org or from Microsoft ↩