Difference between revisions of "Sound Files - XCOM:EU 2012"

From Nexus Mods Wiki
Jump to: navigation, search
m (Basic Process)
(Overview: added reference)
Line 12: Line 12:
 
<br>
 
<br>
 
== Overview ==
 
== Overview ==
[This information has been developed from the '[http://forums.nexusmods.com/index.php?/topic/987458-x-com-soundbites/ X-com soundbites?]' thread on the '''Nexus ''XCOM General Discussion'' Forum''', and the '[http://forums.nexusmods.com/index.php?/topic/980702-working-on-replacing-a-sound-file Working on replacing a sound file] thread on the '''Nexus ''Mod Talk'' Forum'''.]
+
[This information has been developed from the '[http://forums.nexusmods.com/index.php?/topic/987458-x-com-soundbites/ X-com soundbites?]' thread on the '''Nexus ''XCOM General Discussion'' Forum'''; and the '[http://forums.nexusmods.com/index.php?/topic/980702-working-on-replacing-a-sound-file Working on replacing a sound file] and [http://forums.nexusmods.com/index.php?/topic/1964864-sound-replacement-possible/ Sound replacement possible?] threads on the '''Nexus ''Mod Talk'' Forum'''.]
  
 
The '''OGG''' (open source format) sound files are stored in the '''UPK''' files located in '''''\XCom-Enemy-Unknown\XComGame\CookedPCConsole\''''', so look for a '''UPK''' filename with the ''.SoundNodeWave'' extension that might contain the sounds you're after. Several of them have "sound" or "music" in the filename, so that would be a good place start.
 
The '''OGG''' (open source format) sound files are stored in the '''UPK''' files located in '''''\XCom-Enemy-Unknown\XComGame\CookedPCConsole\''''', so look for a '''UPK''' filename with the ''.SoundNodeWave'' extension that might contain the sounds you're after. Several of them have "sound" or "music" in the filename, so that would be a good place start.

Revision as of 23:52, 23 October 2014


Overview

[This information has been developed from the 'X-com soundbites?' thread on the Nexus XCOM General Discussion Forum; and the 'Working on replacing a sound file and Sound replacement possible? threads on the Nexus Mod Talk Forum.]

The OGG (open source format) sound files are stored in the UPK files located in \XCom-Enemy-Unknown\XComGame\CookedPCConsole\, so look for a UPK filename with the .SoundNodeWave extension that might contain the sounds you're after. Several of them have "sound" or "music" in the filename, so that would be a good place start.

Bear in mind that the game treats the Strategy and Tactical phases of the game separately, so anything you hear on a mission is probably coming from Tactical UPK files specific to that mission. (The Unreal Development Kit calls these "levels".)

Strategy related sounds like the XCom alarm (AbductionMissionAlert.SoundNodeWave) are found in SoundStrategyCollection_SF.upk; specifically SoundStrategyUI.

Removing a sound *.UPK file from the \CookedPCConsole folder *will* remove the sound from the game. Tested this with the HQAmbientEngineering.SoundNodeWave file. When removed from "Cooked", the engineering ambient sound is gone in the game. When returned / replaced, the sound returns.

However, care must be taken when choosing to remove files as experience in other games has shown the the lack of an expected sound file can result in timing problems with some aspects of the game, such as dialogs. The replacement with an equivalent length of silence (named the same as the sound it replaces) may prove necessary to correct such a situation. The effect of differences in file size or length of particular sounds has not yet been determined.

For example, in tests the "HQAmbientEngineering" file CAN be replaced with its decompressed *.UPK file version, and it will play in the game just fine. This follows the pattern of other UPK files: decompressed loose files are used instead of the compressed version when found by the game engine as a method of patching the game.

So far, nothing attempted in modding replacement sound files has been successful. (See the "Mod Talk" thread(s).) All that is discussed here is how to locate and extract existing sound files. Further development and testing is needed.

Programs and Tools

Details

Information about the format of various forms of sound files are presented here.

The following primarily comes from the Nexus Mod Talk Forum thread Sound replacement possible?

SoundNodeWave

I [tracktwo] think I now have a pretty good understanding of the SoundNodeWave format. I don't know exactly how it maps into the structure definitions of SoundNodeWave and UntypedBulkData_Mirror as shown in UE Explorer, because there is no way to tell which fields are written to disk and which are not. But as for what appears on disk, am pretty confident in the structure. I called those fields "Pointer" because that's what I am guessing they are by looking at the the definition of UntypedBulkData_Mirror in Core.UPK. They are definitely absolute UPK file offsets, at any rate.

A soundnodewave is:

  • [From UObject]
    • 4 bytes of object reference common to all UObjects
    • variable number of bytes of default property list from UObject
  • [SoundNodeWave]
    • 5 instances of variable-length arrays. The ogg data sits between the 2nd and 3rd of these arrays.

That's all there is to it, from what I have seen! I believe the arrays are each UntypedBulkData_Mirror instances. Each consists of:

  • 4 bytes that are always 0, and I think these are flags, but nothing is ever set here in any sound object I've seen.
  • 4 bytes of size. Only the 2nd entry has non-zero values here, and its the size of the ogg data.
  • 4 bytes of size again. Again only the 2nd entry has a non-zero value, and it's the size of the ogg data again.
  • 4 bytes of "pointer". All 5 entries have this, and it's always the address immediately following the pointer itself. I.e. each one "points" directly to the next byte of the file.
  • "Size" number of bytes of audio data. Since only the 2nd entry has non-zero size, only the 2nd entry has anything here. In all other cases the next array element begins immediately after the pointer. The next object in the UPK begins directly after the last array element. So, array 1, 3, 4, and 5 are all exactly 16 bytes long, and 2 is 16 + ogg size long.

[wghost81: All the objects have both persistent and memory variables. Persistent variables get serialized and memory variables are not. You need C++ sources to tell which are which for sure.]

Again from looking at the definition of SoundNodeWave in Engine.UPK, I believe these five elements are:

  • RawData
  • CompressedPCData <- This is the only one with real data in it
  • CompressedXboxData
  • CompressedPS3Data
  • CompressedWiiUData

Looking at the definition of SoundNodeWave in the Engine.UPK sample in the UDK, there are far more fields there than I see in the actual ".UPK". The same is true for the definition of UntypedBulkData_Mirror, which is in Core.UPK. I looked at the SoundNodeWave files from the sample project in the UDK, and it's slightly different: there are 7 of the UntypedBulkData_Mirror entries instead of 5. Looking at the differences between the two definitions in Engine.UPK, my guess is that the 5 are:

  • RawData (not used)
  • CompressedPCData (this is where the ogg is for both Xcom and the sample UDK game)
  • CompressedXbox360Data (not used)
  • CompressedPS3Data (not used)
  • CompressedWiiUData (not used)

In the UDK, after the WiiU data entry it also has:

  • CompressedIPhoneData
  • CompressedFlashData

Which explains the two extra entries I'm seeing.

Each individual entry is only 16 bytes, though, far smaller than I would've thought the UntypedBulkData_Mirror object should be. I'm guessing the fields that are present are:

  • native const int SavedBulkDataFlags (?? maybe, it always seems to be 0 from what I've seen)

SoundNodeWave extends SoundNode and SoundNode extends Object. So, serialization order must be: Object -> (PrevRef + DefaultProperties) -> SoundNode (probably nothing) -> SoundNodeWave (UntypedBulkData_Mirror RawData, UntypedBulkData_Mirror CompressedPCData, UntypedBulkData_Mirror CompressedXbox360Data, UntypedBulkData_Mirror CompressedPS3Data, UntypedBulkData_Mirror CompressedWiiUData). From the look of things, I [wghost81] think UntypedBulkData_Mirror serialized variables are int SavedBulkDataFlags, int SavedElementCount, int SavedBulkDataSizeOnDisk, int SavedBulkDataOffsetInFile, followed by binary data. All the unrealscript variables are serialized via DefaultProperties and C++ specific data are serialized as binary data via C++ serialization code.

SoundNodeWave format guess:

  • UntypedBulkData_Mirror RawData
  • UntypedBulkData_Mirror CompressedPCData
  • UntypedBulkData_Mirror CompressedXbox360Data
  • UntypedBulkData_Mirror CompressedWiiUData

UntypedBulkData_Mirror format guess:

  • int SavedBulkDataFlags
  • int SavedElementCount
  • int SavedBulkDataSizeOnDisk
  • int SavedBulkDataOffsetInFile
  • SavedBulkDataSizeOnDisk bytes of binary data

Voice Banks

Character voices should be modifiable by creating/changing voice bank files. These have a pretty straightforward format: XComCharacterVoice. A voice bank file is a package that contains an XComVoiceBank object and a big set of SoundNodeWave and corresponding SoundCue objects for each of the voiced events in the game. The XComVoiceBank type is a class that extends Object and has a default property list of ObjectProperties for each of the sounds. Then, it contains an mapping of object references to the variables defined in XComGame.XComCharacterVoiceBank. Example:

XComCharacterVoiceBank : UObject
  ObjRef
  DefaultProperties:
     HunkerDown = ObjectProperty set to the "hunker down" cue in this package
     Reload = ObjectProperty set to the "reloading" cue in this package
     ...
     Strangled = ObjectProperty set to the "Strangled" cue in this package
  TMap<int32, UObjectReference> CueMap

The CueMap maps indices into the default property list to object references of the variables in XComGame.XComCharacterVoiceBank. Because of the way these are set up, I suspect that neither the names of the properties nor the order of the cues in the default property list actually matter. This makes sense because some sounds are mec-specific and others are human-specific, so this way there is no need to specify e.g. choke sounds for mecs or flamethrower sounds for humans by including "holes" in the sound bank. For example, the first entry maps index "1" (the HunkerDown property) to the object reference XComCharacterVoiceBank.HunkerDown in the import table.

Voices

A voice pack is a set of voice banks. The standard ones typically have 15 banks, so when you, for example, overwatch, it'll choose one of the available overwatch sounds from the list of banks associated with that voice. A voice package contains an object of type XComCharacterVoice. This object collects all the voice bank packages together into a set. Its structure is fairly simple:

XComCharacterVoice : UObject
  ObjRef
  DefaultProperties:
    VoiceBankNames = ArrayProperty of FStrings
    CharacterCustomizationCue = ObjectProperty

Note: because the size of each FString is dynamic, you can't easily seek within the array elements. E.g. even though the array may say it has 15 elements, you can't compute the start of the 3rd element as 3 * element_size because there isn't any one element size. You need to parse the first two to get their lengths first.

Each element of the VoiceBankNames array is a string of the format "Typename" <space> "object name". For example, the first entry in the male italian voice 1 bank 0 is "XComCharacterVoiceBank MaleVoice1_Italian_Bank0.MaleVoice1_Italian_Bank0", indicating that the first entry is of type XComCharacterVoiceBank and can be found by the name MaleVoice1_Italian_Bank0 in the package of the same name.

Again looking at the standard files, it looks like adding new voice packs is relatively straightforward. This is exactly what the DLC packs do to add the Zhang and Annette voices: they add the corresponding voice pack and bank files to DefaultEngine.ini as +SeekFreePackage, and then create a VoicePackageInfo line to DefaultContent.ini mapping the voice package to one of the voice enumeration values. E.g. Zhang uses eCharVoice_MaleSoldier1_Brash. There are a few extra, unused ones for each gender already there in the default XGGameData.ECharacterVoice enumeration.

When you get the Zhang character, the reward script simply generates a character with the voice set to eCharVoice_MaleSoldier1_Brash. You can make anyone sound like Zhang by setting their voice to that enum value. You can't choose it from the customization UI in-game, but you can do it with a custom script.

The bad news is, the mapping of voice packs to languages seems to be hardcoded in the executable. I'm still looking into this... more on it later.

Basic Replacement Process

Download and install the tools listed, or their equivalents. You will need:

  • A UPK package extractor,
  • An OGG formatted sound file extractor,
  • A sound file editor/converter from OGG compatible with the desired final sound file format (i.e. WAV, or MP3).
  • Optionally, a music player (like KMPlayer) to listen to the OGG sound file to avoid having to convert to determine it's nature.

Extract the embedded OGG sound files, and convert them to your preferred player format as needed. This has been successfully done with AbductionMissionAlert.SoundNodeWave.ogg and plays back in KMPlayer without any other conversion.

Extract & Convert

While specific tool names are used in this description, substitute your equivalent tools as needed.

  1. Locate a target sound file in, or at least a likely, UPK file.
  2. Drag and drop the UPK files onto Unreal Package Extractor (also known as UPK Extractor: filename extract.exe). Sound files will be extracted with a ". SoundNodeWave" file extension.
  3. Drag and drop the ".SoundNodeWave" file onto Oggextract.exe. Sound files will be extracted with a ".OGG" file extension, which can be played in a compatible music player such as VLC.
  4. Convert the ".OGG" files into a different format ("WAV", ".MP3") using an editor/conversion program like Audacity or similar.

Tutorial

[This information has been developed from tracktwo's tutorial in the Sound replacement possible thread on the Nexus Mod Talk Forum.]

How to manually hex-patch in a custom sound effect of the same length.

This is for a sound effect, not music, but the steps are likely similar.

  1. Used the Audacity program to create a simple replacement sound. Use 22050Hz 16-bit PCM for the format.
    I generated a short sample with the Generate->Chirp option, and used the same length as the target sound I was going to replace (about 1.6 seconds). Export it as an OGG Vorbis file.
  2. Find a target sound to replace.
    I used FemaleVoice6_French_Bank11.SF06LootSpotted07. Since I didn't want to muck around with resizing the UPK I made sure to find a sound that was smaller in data size (duration and other settings in the cue and soundwave entries don't seem to be required to match the new file, but it will probably impact timing if you don't change them).
    (Next steps will be to properly resize the UPK so the new sound size won't be constrained.)
    To properly resize an object, you have to edit its export table entry. PatcherGUI will work with any packages, not just scripts and maps, as the structure is the same. Use BEFORE_HEX/AFTER_HEX to automatically resize an object. (PatcherGUI and the UPK Format document are separate downloads from the UPKUtils package on that download page.)
  3. Decompress the voice bank found in XEW\XComGame\CookedPCConsole\Voice.
    In this case, decompress FemaleVoice6_French_Bank11_SF.upk.
  4. Open the decompressed file in UE Explorer and find the target soundnodewave entry in the object list. View the buffer: note the offset and the file size of the target object.
    In my case, 0x47bfe, with a file size of 10486 = 0x28F6.
  5. With a hex editor, paste the OGG you generated into the decompressed UPK loaded into UE Explorer at the offset found + 0x9C bytes. This is the start of the audio data, and should be the bytes 0x4F 0x67 0x67 0x53 (OggS). Make sure you overwrite, don't insert the bytes and change the file size.
  6. Now the more complicated bit. Because the new file is smaller, you have junk data at the end of the entry from the old sound. This needs to be fixed up, likely so the engine's UPK parser can find the next entry.
    If you look at any other sound entry, after the OGG data there is 0x30 bytes of stuff before the start of the next object.
    (See wghost81's UPK Format document for details).
    The important thing is the object entry format:
    1. 3 lines of 16 bytes, where each line is 12 bytes of 00s;
    2. followed by 4 bytes that are the absolute position in the UPK file of the first byte of the next object, which is immediately following those 4 bytes.
    3. After these 3 lines, the next object begins with 0xFF 0xFF 0xFF 0xFF.
      (I'm going to do some more experimenting because it looks like the first two lines don't do much, it's the 3rd (or subsequent) ones that matter.)
      So: in my example, the last byte of OGG data for my particular data file was at position 0x492D6. Therefore the next byte (the first byte of the object following my OGG sound file) is 0x492D7.
      1. So: 12 bytes of 0s, then 4 bytes for the address of the byte following those 16 bytes: 0x492E7 (shown byte-swapped in UE: 0xE7 0x92 0x04 0x00).
      2. 12 more bytes of 0, then the offset 0x492F7.
      3. Then twelve more bytes and we can just give it the offset of the start of the next object entry.
        Recall the target soundnodewave entry in the object list started at 0x47BFE (step 4 above) and is 0x28F6 bytes long, so the next one starts at 0x4A4F4. That's the address to put in the end of the third line: 0xF4 0xA4 0x04.
    (When I did this I actually zeroed out the whole block of bytes from the end of my OGG to the start of the next object, but I don't think that's required. With a proper resized upk you'd just need the 0x30 bytes without any wasted space.)
  7. Save the file and close UE Explorer, then load up the game.
    (UE Explorer appears to hold the file open and the engine won't load it otherwise.)
  8. Make sure you're in a Tactical mission, then open the console and play the sound "PlaySoundWave FemaleVoice6_French_Bank11.SF06LootSpotted07". You should hear the chirp.
    In testing, it looks like you need to actually have a soldier with that sound bank set in their customization or it won't load the sound. I also still need to verify it actually plays correctly when the sound is played normally, but I suspect it will. I should've chosen something like an overwatch sound so I can quickly trigger it.

References

Referred to by this article:



That refer to this article: