Sound Files - XCOM:EU 2012

From Nexus Mods Wiki
Jump to: navigation, search


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

The most important piece of equipment for recording a voice pack is a decent microphone. It doesn’t need to be a ridiculously expensive professional mic, but a webcam or a gaming headset is probably not going to cut it. The higher quality the input the better the voice pack will sound.
  • Recording Environment
The next most important thing is a good room to record in. The biggest problem I had with processing is echo in the recordings. If you have a room with lots of bare walls and floor and an omnidirectional mic, you might run into problems with the mic picking up your voice echoing off the walls. This becomes even more pronounced after processing and can ruin the clips. You might be able to dampen these echos by recording in another room or by putting some sound absorbing materials around the room or just around your mic or wherever you’re recording. This is the perfect excuse to build a blanket fort.
Also try to avoid sources of noise: your computer fan, your barking dog, your squeaky chair, the window near the road with cars going by and horns honking. Some low-level background noise is unavoidable but can be filtered out. Filtering out intermittent noises like a chair squeak can be harder if it ends up right on top of your voice.
  • Time
Be prepared to sink a few hours into this, especially the first time.

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.

Went through the voice banks for male soldier english #1 and pulled out the number of unique sound node names listed throughout all the banks. The only really interesting things found were that the "run" and "dash" events actually have two sounds in most banks, for a total of 26 and 27 unique sounds for those two events, respectively. All other events have exactly 1 sound per bank, but most start to periodically recycle sounds from previous banks. For example, there are only 3 distinct StabilizingAlly sounds for this voice, and they repeat throughout the 15 banks.

The distributions aren't exactly the same across the other voices, but they're fairly close. There are gaps in the numbering scheme on the sounds for some of them, which could be just clips they recorded from the voice actors but omitted for whatever reason.

Adding Voices

Unreal Engine supports incremental changes to configuration files.

Basically what happens is that on launch the Unreal Engine starts with the Default<type>.ini file located in the XComGame/Config folder, and then merges additional config data located in XCom<type>.ini config files that can be in any other subfolder within the XComGame folder.

As an example, the Long War Team created a new Long War Voices folder in the CookedPCConsole folder (analgous to the vanilla Voices folder). So far we have "testing files" which are :

  • MaleVoice1_Australian_Bank0_SF.upk
  • MaleVoice1_Australian_SF.upk
  • MaleVoice1_British_Bank0_SF.upk
  • MaleVoice1_British_SF.upk

Note that these aren't complete voices (those in vanilla have 15 banks per voice), but are valid for testing purposes.

We configured these by creating a NEW XComContent.ini file in the Long War Voices folder.

This file contains:

; add Voice Banks
[XComGame.XComContentManager]
;;;;;;;;;;;;;;;;;;; British (7) ;;;;;;;;;;;;;;;;;;;
;male soldier
+VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1, Language=7, isMec=false, ArchetypeName="MaleVoice1_British.Voice_MaleVoice1_British")

;;;;;;;;;;;;;;;;;;; Australian (8) ;;;;;;;;;;;;;;;;;;; ;male soldier +VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1, Language=8, isMec=false, ArchetypeName="MaleVoice1_Australian.Voice_MaleVoice1_Australian")

Note the prefixed '+' symbol preceding each line, which indicates to the UE launcher that the lines are to be appended to the merged XComContent.ini file that is put in the My Games folder. No changes were made to the DefaultContent.ini folder in the XComGame/Config folder at all.

The resultant merged XComContent.ini in the My Games folder has the following VoicePackageInfo section:

VoicePackageInfo=(VoiceType=eCharVoice_FemaleSoldier1_Brash,ArchetypeName="Voice_AnnetteVoice1.AnnetteVoice1")
VoicePackageInfo=(VoiceType=eCharVoice_MaleBlueshirt1,ArchetypeName="Voice_BSMVoice1.BSMVoice1")
VoicePackageInfo=(VoiceType=eCharVoice_FemaleBlueshirt1,ArchetypeName="Voice_BSFVoice1.BSFVoice1")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1, Language=7, isMec=false, ArchetypeName="MaleVoice1_British.Voice_MaleVoice1_British")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1, Language=8, isMec=false, ArchetypeName="MaleVoice1_Australian.Voice_MaleVoice1_Australian")

This has appended the additional VoicePackage info onto the voices defined in DefaultContent.ini.

Upon launching the game, the new voices are available in the StrategyGame customize UI and play properly.

Unique Voice Nodes Table

Here's the raw data for the first voice. Some of these seem really over-represented (really, 13 combat stim voices?), but the main takeaway is to have lots of move/dash options, and the rest look to average somewhere around 10 except for the really rare events.

Unique Voice Nodes
Voice unique nodes
Reload 15
Overwatching 11
Moving 26
Dashing 27
JetPackMove 9
LowAmmo 11
OutOfAmmo 15
Suppressing 15
AreaSuppressing 10
FlushingTarget 15
HealingAlly 6
StabilizingAlly 3
RevivingAlly 6
CombatStim 13
FragOut 10
SmokeGrenadeThrown 7
SpyGrenadeThrown 15
FiringRocket 13
GhostModeActivated 8
JetPackDeactivated 11
ArcThrower 8
RepairSHIV 7
Kill 15
MultiKill 15
Missed 14
TargetSpotted 15
TargetSpottedHidden 9
HeardSomething 15
TakingFire 11
FriendlyKilled 6
Panic 6
PanickedBreathing 2
Wounded 15
Died 4
Flanked 15
Suppressed 11
PsiControlled 15
CivilianRescued 15
MeldSpotted 3
MeldCollected 4
RunAndGun 7
GrapplingHook 5
AlienRetreat 4
AlienNotStunned 7
DisablingShot 13
ShredderRocket 5
PsionicsMindfray 4
PsionicsPanic 3
PsionicsInspiration 2
PsionicsTelekineticField 7
SoldierControlled 9
StunnedAlien 7
Explosion 4
RocketScatter 5
PsiRift 2
Poisoned 5
Strangled 1

No duplication is needed and object names are unimportant. We can have any number of random samples and voice banks, those are not hard-coded. The game can also handle an empty sample - it plays no sound in this case.

Only problem we have is that 6 different voice types are hard-coded for females. But still, we don't need to fill all 6, as we can have, say, 3 voices and the other 3 will be just empty. See the Female Voices section below.

There is no requirement that the new voices have 15 banks, or the banks have exactly the same number of unique clips. You need to have a clip for each event in each bank you choose to have. Having partially empty banks would mean that if that bank happens to be cued up when the event needs to play, it'll play silence instead of trying to find another bank that actually has something in it. Ideally then, the number of clips per event should all be divisible by our max banks per voice (move and dash notwithstanding), so if we keep to 15, 3 or 5 would be best. However, the stock clips aren't that careful; lots have 6, 7, or 11 clips.

Basically, a number of banks is determined by a number of different sounds you want to have for a least frequently occurring event. The most frequent events, like moving and shooting, can be randomized by adding 4-5 additional random sounds to each bank. The more sounds you have in one bank, the more memory you'll be consuming in game. Apparently, vanilla system tries to balance sounds variety over memory usage.

The naming scheme is just a convention for organization purposes and doesn't really play any part in the actual game logic. The 01 in SM01 just means voice 1. In general they map to the voice # you select in the UI, but not exactly. For females it seems to be the case, but for males it works a little differently. There is no male 2 english package, voice number 2 in the UI is actually the male 3 english package.

Female Voices

Amineri (from the Nexus Mod Talk Forum and the Long War Team) did some experimention with converting GetPossibleVoices from a native function to an UnRealScript function (with a bit of black-magic hackery), and discovered a few things:

  • The problem with regard to a number of female voices in excess of the 6 standard vanilla voices isn't an issue with GetPossibleVoices itself, but with some initialization code auto-adding values to the VoicePackageInfo config variable.

With the rewritten GetPossibleVoices function, the 6 vanilla female voices always return 6 when GetPossibleVoices scans through the VoicePackageInfo config variable, even when fewer female voices are defined. If I explicitly return only 4 voices then the spinner properly has only 4. There is some initialization code that is auto-populating the VoicePackageInfo config variable with entries. I tested completely removing one of the female english voice files from the Voices folder and it had no effect, which presumably means that some code is filling out entries "blind".

  • The rewritten GetPossibleVoices does return the correct number of female (and male, and MEC) voices for languages above 6.

This is an improvement over the vanilla native GetPossibleVoices, which appears to always statically return the 6 enums for female voices.

Here's the change in UPKmodder format:

MODFILEVERSION=4
UPKFILE=XComGame.upk
GUID=1C 18 A1 1A 2B C3 34 4E 8B 2C 72 33 CD 16 7E 3E // XComGame_EW_patch3.upk
FUNCTION=GetPossibleVoices@XComContentManager
RESIZE=290

// rewrite native function to fix problem with female voices always returning 6 options
[BEFORE_HEX] [HEADER] 26 00 00 00 16 00 00 00 [/HEADER] [code] //NativeParm(Gender) 29 6B 51 00 00 //NativeParm(LanguageIdx) 29 6A 51 00 00 //NativeParm(bIsMec) 29 69 51 00 00 //NativeParm(VoiceIds) 29 68 51 00 00 //NOP 0B //EOS 53
[/CODE]
//footer flags 00 00 00 00 04 42 00 // native function //E7 3A 00 00 00 00 00 00 [/BEFORE_HEX]

[AFTER_HEX] [HEADER] EA 03 00 00 A6 02 00 00 [/HEADER] [code] //VoiceIDs.AddItem(0) // add extra element to use as loop counter 55 48 68 51 00 00 03 00 24 00 16
//if(VoiceIds[0] < VoicePackageInfo.Length) 07 D7 03 96 10 25 48 68 51 00 00 36 01 13 4F 00 00 16
//if(VoicePackageInfo[VoiceIds[0]].Language == LanguageIdx) 07 C7 03 9A 35 F1 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 00 6A 51 00 00 16
//if(VoicePackageInfo[VoiceIds[0]].ArchetypeName != "") 07 92 03 7B 35 63 4E 00 00 64 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 1F 00 16
//if(VoiceIds.Find(VoicePackageInfo[VoiceIds[0]].VoiceType) == -1 07 8F 03 9A 46 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16 1D FF FF FF FF 16
//if(VoicePackageInfo[VoiceIds[0]].IsMec == bIsMec) //07 2E 02 F2 35 F0 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2D 00 69 51 00 00 16
//if(bIsMec) 07 36 02 2D 00 69 51 00 00
//if(Gender == 1) // male 07 88 01 9A 38 3A 00 6B 51 00 00 2C 01 16
//if((VoicePackageInfo[VoiceIds[0]].VoiceType >= 53 && (VoicePackageInfo[VoiceIds[0]].VoiceType <= 54)) 07 85 01 82 99 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 35 16 18 2F 00 98 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 36 16 16
//VoiceIDs.AddItem(VoicePackageInfo[VoiceIds[0]].VoiceType) // add extra element to use as loop counter 55 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16
//else 06 33 02
//if(Gender == 2) // female 07 33 02 9A 38 3A 00 6B 51 00 00 2C 02 16
//if((VoicePackageInfo[VoiceIds[0]].VoiceType >= 57 && (VoicePackageInfo[VoiceIds[0]].VoiceType <= 58)) 07 33 02 82 99 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 39 16 18 2F 00 98 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 3A 16 16
//VoiceIDs.AddItem(VoicePackageInfo[VoiceIds[0]].VoiceType) // add extra element to use as loop counter 55 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16
//else 06 8F 03
//if(Gender == 1) // male 07 E4 02 9A 38 3A 00 6B 51 00 00 2C 01 16
//if((VoicePackageInfo[VoiceIds[0]].VoiceType >= 1 && (VoicePackageInfo[VoiceIds[0]].VoiceType <= 9)) 07 E1 02 82 99 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 01 16 18 2F 00 98 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 09 16 16
//VoiceIDs.AddItem(VoicePackageInfo[VoiceIds[0]].VoiceType) // add extra element to use as loop counter 55 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16
//else 06 8F 03
//if(Gender == 2) // female 07 8F 03 9A 38 3A 00 6B 51 00 00 2C 02 16
//if((VoicePackageInfo[VoiceIds[0]].VoiceType >= 12 && (VoicePackageInfo[VoiceIds[0]].VoiceType <= 17)) 07 8F 03 82 99 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 0C 16 18 2F 00 98 38 3A 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 2C 11 16 16
//VoiceIDs.AddItem(VoicePackageInfo[VoiceIds[0]].VoiceType) // add extra element to use as loop counter 55 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16
//else // found a blank Archetype 06 C7 03
//VoiceIds.RemoveItem(VoicePackageInfo[VoiceIds[0]].VoiceType) 56 48 68 51 00 00 29 00 35 F2 4E 00 00 F3 4E 00 00 00 00 10 10 25 48 68 51 00 00 01 13 4F 00 00 16
//++ VoiceIds[0] A3 10 25 48 68 51 00 00 16
//while loop 06 0F 00
//VoiceIds.Remove(0, 1) // remove the extra element 40 48 68 51 00 00 25 26 16
//return 04 0b
0b 0b 0b
//EOS 53 [/CODE]
//footer flags 00 00 00 02 01 02 00 // simulated, not native function //00 26 00 00 00 00 00 00
[/AFTER_HEX]

Since the converted native function has no local variables in unrealscript, I add an extra element to the head of the VoiceIds dynamic array to function as a counter stepping through the list. After the VoicePackageInfo config array has been stepped through, that element is removed.

Additional Language Voices

Amineri writes:

Some more notes regarding "single hex change" to enable flexible configuration of additional voices :

Working with Tracktwo, and borrowing from all the great work done before, I think we've assembled a set of hex changes that defines pretty much every possible language desired into a single set of hex/localization changes.

Here's the set of languages and language values that are being proposed:

m_arrLanguages[0]="American English"
m_arrLanguages[1]="French"
m_arrLanguages[2]="German"
m_arrLanguages[3]="Italian"
m_arrLanguages[4]="Polish"
m_arrLanguages[5]="Russian"
m_arrLanguages[6]="Spanish"
m_arrLanguages[7]="British English"
m_arrLanguages[8]="Australian English"
m_arrLanguages[9]="Scottish"
m_arrLanguages[10]="Irish"
m_arrLanguages[11]="South African"
m_arrLanguages[12]="Canadian"
m_arrLanguages[13]="Chinese"
m_arrLanguages[14]="Japanese"
m_arrLanguages[15]="Hindi"
m_arrLanguages[16]="Korean"
m_arrLanguages[17]="Bosnian"
m_arrLanguages[18]="Hebrew"
m_arrLanguages[19]="Egyptian"
m_arrLanguages[20]="Brazilian"
m_arrLanguages[21]="UNUSED"
m_arrLanguages[22]="Mexican"
m_arrLanguages[23]="Ukrainian"
m_arrLanguages[24]="Nigerian"
m_arrLanguages[25]="Argentinian"
m_arrLanguages[26]="Greek"
m_arrLanguages[27]="Honduran"
m_arrLanguages[28]="Swedish"
m_arrLanguages[29]="Norwegian"
m_arrLanguages[30]="Dutch"
m_arrLanguages[31]="Belgian"
m_arrLanguages[32]="UNUSED"
m_arrLanguages[33]="UNUSED"
m_arrLanguages[34]="UNUSED"
m_arrLanguages[35]="UNUSED" 

The first 7 languages are the vanilla languages, unchanged. The total number of languages is 36, with five of them unused and reserved for future use. That means 22 new languages are defined. Some of these may never see a voice pack created for them, but I thought I'd include them for the sake of completeness (e.g. "Canadian" is defined as a separate 'language' in case someone wants to create special voice packs for Canadian soldiers).

How the system works :

  1. The native GetPossibleVoices function was rewritten in UnRealScript so that it works properly for female voices with new languages. [See Above.]
  2. The customization UI spinner will only show languages that have voices configured, accounting for gender/MEC (e.g. if a Japanese male soldier voice is added, female soldier/MECs won't show Japanese in the spinner)
  3. With the language option turned on, newly created soldiers (on campaign start or hired/FC Reward/Abduction Reward) will attempt to use the particular language for their country. If no voices are configured (again considering gender/MEC), the language will default to the vanilla cases (e.g. UK/Canada/etc will default to "American English").

Eventually this will get folded into Long War, which will allow easier "end-user" configuration of voice banks for users of the mod. Once the code is stable enough I'd expect a standalone version compatible with PatcherGUI to be developed.

For developer reference, here's the complete set of functions that ended up being modded :

  • XComCharacterVoice.PlaySoundForEvent -- builds mapping of ECharacterSpeech to SoundCue into UnRealScript to work around inability to build native code into new UDK voice banks.
  • XComContentManager.GetPossibleVoices -- rebuilt native function into UnRealScript to fix issue with female voices always returning 6 possible voices regardless of configuration.
  • XGCharacterGenerator.CreateTSoldier -- modified so newly created soldiers will attempt to use new languages, but default to others if no voices are defined.
  • XGCharacterGenerator.GetLanguageByCountry -- rewritten to provide mapping from country to extended language set.
  • XGCharacterGenerator.GetNextMaleVoice/GetNextFemaleVoice -- modified to return status about available languages for use in CreateTSoldier.
  • XGCharacterGenerator.m_aLastMaleVoice/m_aLastFemaleVoice -- array size extended from 7 to 36 to support extended language set.
  • XGGameData.ECharacterLanguage -- enum extended from 7 to 36 to support extended language set.
  • XGCustomizeUI.AdvanceLanguage -- rewritten to loop over all possible languages (36), but skip any languages without voices defined.

Note that the UNUSED languages can have voices defined (and presumable the XComStrategyGame.INT modified) and such language will show up in the Customize UI with no further hex changes, but will not be automatically assigned to newly created soldiers based on nationality.


P.S. I fairly quickly put together the list of "Language Names", so please don't go crazy. I recognize for example that Argentinian isn't a completely distinct language, but I'm not sure if "Rioplatenese Spanish" will fit within the spinner in the Customization UI, plus it made it easier for me to work out the switch/case statements while I was working. There's still some more experimentation to do in regarding language naming.

Voice Language Packages - EW

Here is a chart for voice packages, by language, in vanilla EW as quick reference.

Female voices in EW
Suffix En Fr Ge It Po Ru Sp
SF01 - SF06 x x x x x x x
Male voices in EW
Suffix En Fr Ge It Po Ru Sp
SM01 x x x x x x x
SM02 x x x
SM03 x x x x x x
SM04 x x x x x
SM05 x x x x
SM06 x x x x x
SM07 x x x x x x x
SM08
SM09 x x x x x

Verified that SM01, 03-07 are the same as in Enemy Unknown by listening to "solid copy" and "yes commander" cues in OGG format extracted from EU and compared them to sample sounds heard on EW customization screen. For an example EW SM03 = EU SM03. Female voices were just ported over.

SM02, 08-09 voices could be reintroduced to EW or used for testing. Temporary DL for EU SM02 (5.5 MB, 539 ogg files).

There is apparently an additional, specialized Unreal Engine tool for handling SoundCues: the UE SoundCue Editor.

This appears to be a visually-driven graph-type editing system for configuring various sound effects for different cues to map to particular sounds. In particular there is a Random Node, with description:

The Random Node is used to randomly trigger a Sound Node Wave from within a group of possible Sound Node Waves. Weight controls the probability a Sound Node Wave will be triggered relative to other Sound Node Waves in the Actor. The check box RandomWithoutReplacement will exhaust the entire list of possible Sound Nodes before repetition. Inputs are added for each audio file by right clicking on the Random Node and selecting Add Input. Sound Node Waves may be connected directly to the Random Node but you can also add nodes between them for additional control.

So apparently the Random Node can assign non-uniform weight from a single cue to multiple possible sound files. If this tool was used to create the vanilla VoiceBanks, it may also have to be used in order to create new VoiceBanks.

The customization cue generally uses a random cue to choose between 2 or 3 sounds to play when you select the voice in the customization UI, and the move and dash cues in the sound banks have random cues between two sounds for most of the banks.

The Sound Cue Editor can be accessed from within the UDK Editor application by selecting a Sound Cue, right-clicking, and selecting "Edit Using Sound Cue Editor".

New Sound Cues are created by selecting a package/folder, clicking new, and then selecting Factory / SoundCue.

Bank names are actually tied to voice packages through VoiceBankNames string and array of XComCharacterVoice object.

Game simply ignores the fact that native variable binary data are missing.

References:

Music

Adding/replacing music is doable. Exactly how easy it is to replace what depends on the kind of music you want to replace, since they're implemented differently in the game. There are particular tracks that play for squad loadout, the menu, the memorial, ambient music in missions and in HQ, combat music, etc. As an experiment, I created my own combat music. This was pretty easy to do, as the way combat music is implemented makes it very easily moddable.

The HoloGlobe Music is used for "Mission Control".

The combat music is in the packages CombatMusic1_SF.upk through ComatMusic9_SF.upk. These are very simple packages that you can create with the UDK, and contain only a sound node and a corresponding sound cue. I created a custom package in the UDK and imported a sound and created the cue. Instructions on how to do this are in my earlier tutorial post about creating voice banks with the UDK, but these are simpler because there is no need for any archetype. Just import the sound and create the cue, cook it and you're done. In the cue editor, you'll probably want to add a looping node so the track will repeat. It'll sound best if the cut between the end of the track and the beginning is pretty seamless so it loops nicely.

To actually get the sound into the game, you can just replace one of the existing sounds by using the same package and cue name as an existing sound and then just swapping the package on disk. What I did is a little fancier, which was make the list of combat music cues customizable so you can put them in the defaultcontent.ini file and add as many as you like. The way the combat music tracks are set up makes this really simple, it just requires four very small hex edits to the XComGame.upk file. I can give you the details of what to change if you're interested in doing this. Basically it just involves making XComGame.XComTacticalSoundManager config(Content), and making the CombatMusicCues array a configurable array. Then you can list all the tracks you want to use in the defaultcontent.ini file, like this:

[XComGame.XComTacticalSoundManager]

CombatMusicCues="CombatMusic1.ActionMusic1Cue"
CombatMusicCues="CombatMusic2.ActionMusic2Cue"
CombatMusicCues="CombatMusic3.ActionMusic3Cue"
CombatMusicCues="CombatMusic4.ActionMusic4Cue"
CombatMusicCues="CombatMusic5.ActionMusic5Cue"
CombatMusicCues="CombatMusic6.ActionMusic6Cue"
CombatMusicCues="CombatMusic7.ActionMusic7Cue"
CombatMusicCues="CombatMusic8.ActionMusic8Cue"
CombatMusicCues="CombatMusic9.ActionMusic9Cue"

This just sets the combat music to the defaults that were already there. Add your own by just adding a line, setting it to the package name and sound cue name inside your package.

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

Tutorials

Voice Pack for Long War

[This section is taken from TakeTwo's Tutorial folder on Google Drive and is used with permission.]

I’m not an audio engineer. I did this cause it was a fun project and wanted to give back to the community and the LW devs for making something great for free. If you have suggestions on how to process audio in a better way, I’d love to hear it!

Getting a custom voice pack to play in game requires a bunch of hex mods to the game itself. These are all included as part of Long War b15+. Without it you can create a voice pack but won’t be able to test it out in-game without also following the instructions above under Section 3 Details.

Structure of a Voice Pack

A voice package consists of a set of voice segments (a single line spoken by a soldier in response to some action) across a set of events (the particular action or response). There are 54 events in total that you need to record for human soldiers, and each one requires a number of segments so the soldiers aren’t saying the same line over and over every time you do something. You can expect to use somewhere between 300 and 500 individual segments for a single voice package.

The game divides the voice files into a set of banks. In general, there are 15 banks per voice pack, although there doesn’t appear to be any requirement that a pack contain exactly 15 banks. Using other bank amounts has not been tested.

Each voice bank contains one or more segments for each event. The game engine cycles through the banks each time it needs to play a sound. For example, you may move your soldier, see a meld canister, and then go on overwatch. The game may play the Moving segment from bank #13, the MeldSpotted segment from bank #14, then cycle back and play the Overwatching segment from bank #0. Yes, it starts at bank 0.

Each bank needs to have at least one segment for each event: the game chooses the bank first and then pulls the segment from it. If the bank is missing the segment for an event after the game has decided to use that bank, it will play nothing rather than try to find another bank with a segment for that event. They don’t have to be unique segments for each bank - in fact most of the vanilla packs have fewer than 15 segments for most of the events. When there are fewer available segments than banks, the developers just copied the same segment into multiple banks.

Some banks may have more than one segment for a single event. This will be discussed later in more detail, but in the vanilla packs it is typically only the Moving and Dashing events that have more than 15 distinct segments. This gives more variety in the more frequently used events.

In addition to the banks, there is another package file that ties the banks together. Here is a screenshot of a complete voice package showing the 15 banks and the package file:
A complete voice pack
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]

Recording

In the same place as the original of this document is a script put together by JohnnyLump that he’s graciously allowed me to reuse for this tutorial. It contains the common lines from the vanilla voice packs for each of the required events. You can use these lines for the voice pack, or create your own.

I recommend recording each event in a single file, leaving a gap of about 1s between each segment. It is also not a bad idea to record several takes of each segment so you can quickly pick the best one of the set of the same segment, and avoid having to go back and re-record if some noise sneaks into the background or you flub a line but didn’t notice.

Some additional recording tips:

  • Record in WAV format! Not AAC or MP3 or any other lossy, compressed format. The files should ultimately be mono with a 22050Hz sample rate, but if you forget to record in that format you can convert to it later.
  • Adjust your Mic gain to record at a reasonable level. You don’t want the raw input files to be clipping, but you also don’t want the level to be too low. See the "Processing a Wave Form" screenshot below. The files used by XCOM need to be VERY loud to match the in-game volume, and amplifying them by huge amounts because they were recorded very low can add some odd distortions.
Processing a Wave Form
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]
The red circles indicate clipping: the waveform has reached the maximum and minimum amplitudes possible and has flattened out at that point. This is bad - the mic gain was too high and the sound will be very distorted. On the other hand, if you can barely see the waveform at all you need to turn up the gain. The green circled area is a better level. This was an image I sent to someone, so you probably can’t read the text. It wasn’t that important anyway.
  • Watch out for echo, especially when shouting.
  • Don’t record all of the events up front. Record a small sample of two or three events (Moving and Overwatching are good candidates because they are easy to trigger in-game on any soldier) and go all the way through the process to produce a partial voice pack that only includes sounds for those events. Listen to it in game and make sure it sounds OK. It may just require some different processing steps, but you may also find that you need to take a different approach altogether with recording and need to re-record. Better to discover this earlier than later.

Processing

After the files have been recorded, the next step is to process them so they sound similar to the vanilla voices. Note that the vanilla voice files are highly distorted and extremely loud, so don’t be too upset if your processing makes your segments sound bad compared to the smooth sounds of the originals. You really need to listen to them in game and compare them against the vanilla voices to get a good feel for the right amount of processing. Just listening to them in isolation can be very misleading.

There is no one recipe in this section to process the files, they all require some individual attention and tweaking. Note that the processed sounds should all be mono and 22050Hz sample rate. This is not the standard in most recording software, so you will want to change it as part of the file exporting or in your recording setup. You can record the originals in those settings or just convert them as part of the processing and it shouldn’t make a huge difference.

My workflow tends to have three copies of the files in different folders:

  • Raw files (as submitted)
  • Cut files (chopped to one segment per file, with noise reduction applied but no other processing effects)
  • Processed files (all post-processing applied)
First pass: noise removal and cutting
  1. For each event, apply noise reduction to remove any regular background noise. In Audition I’ll generally select a quiet section between segments to capture the noise print and then apply it to the entire file via the noise reduction effect. This is very important, especially if the sounds need to be amplified a lot to bring them up to the normal game volume (and they probably will need to be amplified). This amplification will also amplify the noise and you’ll get a hum or static between words unless the noise is removed.
  2. Using the “Sound finder” diagnostic utility in Audition, analyze the file and mark each detected segment. Or just highlight each by hand and create a marker/section for it.
  3. Review each segment. The detection usually does a pretty good job of finding and marking each individual segment, but it may create more than one marker for segments with long pauses in between words that you need to manually combine. It also tends to put the end of the marker too close to the end of the detected sound and I manually pull the marker out a few more ms to ensure it isn’t cutting off the last sound in the segment. I usually leave a little slop on the end of the markers until the last step to make sure I’m not cutting anything off. In this step I’ll also pick the best take out of any that has multiple takes for the same segment and delete the others, or sometimes just remove a segment entirely if there are no other takes and it just doesn’t feel right.
  4. Export all remaining markers/sections to individual files in the “cut files” folder. This is a good time to set the names to the standard naming convention mentioned in the import step below, because then the script I provide to create the banks will work right away.
Second pass: bulk applying effects

This is to get into the ballpark of the sound I want. I’ll open up all of the cut files for every event and bulk process them. I’ll do a couple of them manually to get a set of effects that sounds okay for that particular voice, and then bulk apply it to the rest of the set. The effects generally include:

  1. If the recording has a lot of dynamic range in many of the segments (i.e. loud bits and quiet bits, the waveform frequently looks high and low in different places), I’ll apply some compression to flatten out some of the peaks a bit first.
  2. Some EQ to mimic the “radio” effect. The settings depend on the voice, but a high pass 400Hz and -6dB/octave and a low pass at 6kHz at -6dB/octave sounded reasonable for most voices I worked with. This narrows the sound quite a bit, making it sound very tinny and hollow.
  3. Some distortion (Effects->Special->Distortion in Audition). Exactly how much depends on the voice. “Until it sounds right”. I had more success repeatedly applying fairly light distortion than trying to apply heavier distortion once.
  4. If the clips are very loud, the distortion may have amplified them to the point of clipping. A tiny bit more compression helps with that (and they’re already super distorted, so it’s NBD).
  5. Increase the volume as needed. I use Audition’s “Match Volume” utility to bring everything up to -5dB perceived loudness. For most voices and most segments, they’re recorded far below this level (-15 ~ -20dB or lower). This amplified volume is not quite loud enough to match the in-game sounds, but gets it close.
  6. Export all the files to the Processed Files folder.
Third pass: individual adjustments and finalize the volume

In this pass I’ll go over each segment in the processed files folder individually and adjust things. Normally this is just performing some final cropping of the file, eliminating any stray clicks that sneaked in during recording (the click remover often works well, otherwise things can sometimes be erased out of the spectral frequency analysis view with the eraser tool), and bumping the volume up to the needed level.

The last volume adjustment will either be done with just flat amplification or with some more compression with a little bit of output gain, depending on the file. As mentioned before, the vanilla segments are really loud, and frequently clip. They’re so distorted that’s often not really noticeable anyway, but you will need to get the volume up really high in this pass, often to close to 0dB.

Fourth through Nth pass

Realize you’ll probably going to need to go over them all again and possibly several more times to get everything sounding right. Better go get another beer.

Also note that volume matching is tricky business. The ear is a funny thing and just because the computer says they’re mathematically all the same average volume doesn’t mean they really sound that way. You need to listen to each segment and adjust as necessary.

Packaging

Now that the files are processed, they can be imported into the UDK and packaged. Steps:

  1. Install the needed packages
  2. Bulk import the processed sounds
  3. Create archetypes for the voice package and each individual voice bank
  4. Map sound cues to events in the bank archetype for each bank
  5. Map the customization cue in the voice package archetype
  6. Fill in the voice bank list in the package archetype
  7. Add the new packages to the UDK configuration files
  8. Cook the packages
Installing the needed packages

This step only needs to be done once.

  • First install the UDK.
  • Then copy the XComGame.U file (from same place as the original of this document) to your <UDK install folder>\UDKGame\Script folder. The sources for this package are in XComCharacterVoice.uc and XComCharacterVoiceBank.uc in the same place. You can copy those to the <UDK install folder>\Development\Src\XComGame\Classes folder (you’ll need to create the last two folders) if you want to regenerate the package from source. This step installs the required archetypes for voice banks to the UDK so you can create and edit the packages in the editor.

Before the UDK will recognize them, you need to modify the UDK configuration files to add the new XCOM package. In <UDK Install folder>\UDKGame\Config\DefaultEngine.INI, search for the [UnrealEd.EditorEngine] section and add the following line after any already there:

+EditPackages=XComGame

Likewise, also search for the [Engine.ScriptPackages] section and add the following if not already present:

+NativePackages=XComGame

Bulk Importing Sounds

The next step is to import the processed sounds into the UDK. The bulk import utility requires the sounds to have a particular directory structure:

Folder1\

Folder2\
VoicePackageFolder\
\SoundFile1.wav
\SoundFile2.wav
Bank0\
\SoundFile1.wav
\SoundFile2.wav
Bank1\
\SoundFile1.wav
\SoundFile2.wav

The names of “folder1” and “folder2” don’t matter, they will appear in the UDK Package Explorer but are otherwise unused. I typically use the name of the voice actor for folder1 and the language name for folder2, just to keep all the packages completely isolated from each other, but this doesn’t matter much. The “VoicePackageFolder”, “Bank0”, “Bank1”, etc. level of folders will use the name of folder as the name of the package that will be created in the UDK and will be referenced by the game. The convention used by XCom is to name the voice package “<Gender>Voice<Number>_<Language>”, e.g. MaleVoice1_Spanish. The banks to have the same structure but be suffixed with “_Bank<Number>”, .e.g. MaleVoice1_Spanish_Bank0 through MaleVoice1_Spanish_Bank14. Under these bank folders go all of your sound files for the bank. Under the “package” folder (i.e. MaleVoice1_Spanish) go 2-3 sound files to use as samples when the voice is selected in the soldier customization screen. I generally grab a couple of the Moving segments that have wording like “Aye aye, commander” or “orders confirmed”.

It doesn’t really matter where these folders are, but if you put this folder tree somewhere under your UDK installation things will be slightly easier to share between multiple people. The technical details are the import will refer to the original location of those files with relative paths if they are rooted somewhere under the UDK, but will use absolute paths if they are not. So if you share the development packages between multiple people but didn’t put them somewhere under the UDK everyone needs to have the files in the same place or the reimport will not work.

Creating the banks

Dividing up all those processed files into the 15 banks is a tedious business, so I wrote a PowerShell script to do it for me. It’s called “makebanks.ps1” (and also “makebanks2.ps1” for an alternate version); you can find them in the same place as the original of this document. From your “processed files” folder with all of the processed files, run “makebanks <Package name>”, e.g. makebanks MaleVoice1_British. This will create the 15 folders for the banks and distribute the available sound files among those banks. For events with fewer than 15 segments, it’ll go back to the first segment for the event after running out of segments, so some banks will have the same copy of the segment as others. If there are more than 15 segments for an event, it’ll wrap back around to bank0 and copy remaining segments one by one into the banks again. For example, if you have 5 segments for Explosion and 25 for Moving, banks 0-4, 5-9, and 10-14 will have the same copies of the Explosion segments. Banks 0-9 will have two Moving segments each, while banks 10-14 will have only one.

In order to work, the script requires that your sound files all have a specific naming convention. The need to be of the form XXNNEventYY.wav, where XX is either the letters “SM” for male soldier or “SF” for female soldier, NN is the two-digit unique code for the soldier (just pick an unused one), Event is the name of the event as in the script document (e.g. it’s FlushingTarget, not Flush), and YY is a two digit number starting at 01 and counting up for each segment you have. For example, SM32Moving01.wav is the first segment for the Moving event for male soldier 32. The script will warn you if it can’t find any segments for a particular event.

I also have a script fixnames.ps1 that slightly changes the names of the files. This is because when exporting multiple files from Audition it wants to use an underscore (_) between the base name and the file number. That is, if you tell it to export with a basename of SM13RunAndGun, it’ll create SM13RunAndGun_01.wav, SM13RunAndGun_02.wav, etc. This script just gets rid of the underscore.

After running makebanks, you can copy the resulting bank folders to your “Folder2” folder. Don’t forget to also copy a few of the Moving segments to the Voice Package folder to use in the soldier customization screen.

If you don’t use the script, the names don’t really matter. The names of the sounds are not used by the game in any way, it’s just an organizational thing.

Performing the Import

Whew. Now we can import the files. Load up the UDK editor (you can launch UnrealFrontend.exe in the UDK\binaries folder and then click “UnrealEd” from the button bar to launch it, you’ll need the frontend later).

The content browser should open up, with a bunch of stuff from the default sample map that comes with the UDK. You can ignore all of it. Right click somewhere in the folder tree and select Bulk import sounds. Choose the Folder1 folder you created that holds all of your sounds, and hit OK. In the next popup window you’ll see the first sound file it found. It should have auto-populated the package name (which should be the full name of the package file or bank, e.g. MaleVoice1_Scottish or FemaleVoice3_Australian_Bank0) and file name. You can leave everything at the default, except make sure you check the Auto-create cue box. Then click Ok to all and it will import everything without any more prompting. When it’s done, you’ll see some new entries in the content browser for your new packages.

An imported package
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]
The folder tree is on the left. My folder1 is “Ian Dransfield (British Male)” and folder2 is “SM18British”, followed by MaleVoice1_British and all the corresponding banks. In the main part of the browser you see colored boxes for the sounds themselves (dark blue) and the generated sound cues (cyan). The red archetype you’ll create in the next step.

Create the Archetypes

The next step is to create the archetypes for the voice package and banks. In the content browser, click the Actor Classes tab and uncheck the 3 checkboxes. Type “XCom” into the search box and you should see XComCharacterVoice and XComCharacterVoiceBank at the bottom of the list. If they aren’t there, the package file XComGame.U wasn’t installed correctly: go back to the first step in this section.

Right-click XComCharacterVoice and select Create archetype…. Under Package enter or select your voice package name (e.g. MaleVoice1_British). Leave Grouping blank, and under Name, enter the name you would like to use for the voice package. The vanilla voices use Voice_<package name>, e.g. Voice_MaleVoice1_British. Click OK to create it.

Right-click on XComCharacterVoiceBank and select Create archetype…. Again fill in the package name, although this time you want the first bank package name (e.g. MaleVoice1_British_Bank0). Again leave Grouping blank and under Name enter whatever you’d like to use. The vanilla voices use the name of the package again.

Repeat that last step to create XComCharacterVoiceBank archetypes for the remaining 14 banks. Copy and paste is your friend, as is using the package name for the archetype name.

Map the Sound Cues to the Archetypes

Click back on the Content Browser tab and click on the first bank in your package to highlight it. You should see a table listing a bunch of objects with columns “Name”, “Type”, etc., and a bunch of multi-colored squares with names like “Archetype”, “SoundCue”, and “SoundNodeWave”.

Find the red “Archetype” square with the name of the archetype you just created for that bank and double-click it. This will bring up a Properties dialog with a big table containing many rows, one for each event. Now you need to map the sound cue created by the import process to the event in the archetype. Click on a sound cue in the content browser to highlight it. Then find the corresponding event row in the Archetype Properties, and click the green arrow button to assign the cue to that property. The second column should fill in with text of the form SoundCue’MaleVoice1_British_Bank0.SM18AlienNotStunned1_Cue. Make sure you are highlighting the Cue and not the SoundNodeWave object or it won’t work. Also note that not all events in the Properties window have cues that you’ve recorded. This is okay: some are just not used by soldiers at all (e.g. AlienMoving and ExaltChatter) and others are only used for Mecs (e.g. CollateralDamage). You can leave these blank.

There is a filter at the top of the content browser you can use to show only Sound Cues. Use it to make your life easier in this step.

If you make a mistake, click the little grey square button on the right hand side from the green arrow button to clear the row.

Repeat that process for all of the events in the bank to assign them all. Then, repeat this process again for each of the other 14 banks. It’s very tedious and boring work, so fire up a video to watch while you’re doing it. If anyone knows a better way to do this (see my Potential Shortcut below) or keyboard shortcuts to make this less awful, please let me know!

A filled out archetype
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]

Creating random cues

If your bank has more than one segment for some events, e.g. Moving or Dashing, you need to create a new “random” cue to randomly choose one of the available segments. Right click in the blank space near the colored squares and select New Sound Cue. The package name should be filled in automatically, you just need to give the cue a name. Then hit OK, and the Sound Cue Editor window will open. Find the two or more SoundNodeWave objects for the event in the content browser and highlight them all by clicking the first and then <Ctrl-Clicking> the others. Then <Right-Click> in the Sound Cue Editor and select Random: <sound name>. This will create a “random” node and one sound node for each of the highlighted sounds, and connect them up appropriately. Now you just need to drag a line from the left-hand black box on the side of the “random” node to the black box on the right hand side of the “speaker” box. This will create a link between the two. Now close the Cue Editor.

A completed random sound cue
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]

Now link the newly created random cue to the event in the Properties window by clicking it and then clicking the green arrow as before.

When the game selects a cue to play from a bank, and it’s a “random” cue, it’ll randomly choose one of the sounds associated with that random cue. This is the only way to get more sounds for an event than there are banks.

After you’re done, save all the packages either by <Right-Clicking> on them and selecting Save or choosing Save All from the File menu.

A Potential Shortcut

One thing that I’ve not yet tried but I believe will work is to only create the mappings for the Bank_0 and Bank_10 banks, and then copy them to the other banks and hex edit the files to rename the package names. If you aren’t comfortable in a hex editor, stick to the slow method, otherwise you can try this.

Map the 0 and 10 banks and save the packages, then exit the UDK. The “raw” packages will appear in the UDK\UDKGame\Content\Folder1\Folder2 folder. Copy the Bank_0 bank file to files named banks 1 through 9, and the Bank_10 to Banks 11 through 14. Then edit each with a hex editor to replace any occurrence of “Bank0” or “Bank10” with the new bank name. This is why you mapped both Bank0 and Bank10, so you can create copies for both the single-digit and double-digit banks without changing their sizes. Go back into the UDK editor and hopefully the new banks will appear in the content browser, but of course all of the sounds will be clones of the ones in banks 0 and 10 because the packages themselves contain sound files, and you just copied them. You can solve this by re-importing the sounds as described below, which should update them to the correct versions from the correct banks. If any of these banks have different numbers of some events (e.g. they don’t all have 2 Move events) then you’ll need to manually fix up those events.

This will also only work if each of the sounds has the same name across all the banks, e.g. the Died sound is always SM34Died1 for each bank. This isn’t the case for the banks created by the makebanks script: it maintains the name of the segment from the original processed file name. So bank 0 might have SM34Died01 and bank 1 has SM34Died02. The makebanks2 script accounts for this by renaming the files in the banks so that they always start at 1 and count upward for multiple segments in the same bank. That is, they’d all be called SM34Died1 in each bank, and if there are two Move sounds they’d be SM34Moving1 and SM34Moving2. This should allow for the renaming hex trick and re-import to work.

Setting up the Voice Package Archetype

After mapping all the banks, you can set up the Voice Archetype. Select the voice package and <Double-Click> the XComCharacterVoice archetype you created to open up the Properties window.

  • The first step is to construct a random cue for the customization cue, as you did above for the banks. Use all the Orders Confirmed sounds you imported into this package. Map this random cue to the Character Customization Cue setting in the Properties window.
  • Next under Voice Bank Names: <Click> the little green + button to create an entry and a line marked [0] will appear below Voice Bank Names. <Click> in the next column to the right of the [0] and type in "XComCharacterVoiceBank <Package name for bank 0>.<Archetype name for bank 0>" (without the quotes). Don’t forget the space between XComCharacterVoiceBank and the package name, and a dot between the package name and the archetype name.
  • Then click the Leftmost button in that row (where the green arrow is for sound cues) to copy it. Copy it 14 times so you have a total of 15 entries, and edit the text to update each to be the next bank in turn. i.e. line 0 is bank 0, line 6 is bank 6, and line 14 is bank 14. Don’t forget to change both the package name and the archetype name. If you named the archetypes with the same name as the package, there are two places to change in each line.

A completed voice package archetype
[Image by TrackTwo, used with permission under Creative Commons Attribution-Share Alike (CC BY-SA) license terms.]

Setting the Sound Class

Highlight all of the banks and the voice package in the package tree. Then in the Filter at the top of the content browser select Sound Cues so only sound cues are visible in the window. Now select a sound cue in the table from one of the packages and hit <Ctrl-A> to select all of the sound cues across all of the packages in your voice pack. <Right Click> and find the menu entry Sound Classes: Master, hover over it to expand and then hover over the Voice entry in the pop-up menu. Select Voice (Parent) as the class. This assigns the sound class for all cues.

Now you can Save all the packages, and you’re done in the UDK Editor.

Configuring the new package in the UDK

Open up the file <UDK installation folder>\UDKGame\Config\DefaultEngine.INI and search for [Engine.PackagesToAlwaysCook]. Under this section, add lines of the following form, substituting your own package names:

+SeekFreePackage=MaleVoice1_British_Bank0
+SeekFreePackage=MaleVoice1_British_Bank1
+SeekFreePackage=MaleVoice1_British_Bank2
+SeekFreePackage=MaleVoice1_British_Bank3
+SeekFreePackage=MaleVoice1_British_Bank4
+SeekFreePackage=MaleVoice1_British_Bank5
+SeekFreePackage=MaleVoice1_British_Bank6
+SeekFreePackage=MaleVoice1_British_Bank7
+SeekFreePackage=MaleVoice1_British_Bank8
+SeekFreePackage=MaleVoice1_British_Bank9
+SeekFreePackage=MaleVoice1_British_Bank10
+SeekFreePackage=MaleVoice1_British_Bank11
+SeekFreePackage=MaleVoice1_British_Bank12
+SeekFreePackage=MaleVoice1_British_Bank13
+SeekFreePackage=MaleVoice1_British_Bank14
+SeekFreePackage=MaleVoice1_British

That is, there should be a line for each bank and the voice package itself. Don’t forget the “+” before SeekFreePackage. Save this file.

Cooking the packages

Now go back to the Unreal Frontend and <Click> the Cook button on the menu bar. A few minutes later it will finish and you should have some brand new cooked packages in the <UDK installation folder>\UDKGame\CookedPC folder. They will appear under the same Folder1\Folder2 structure as here.

Adding the new voice to the Game

The final step is adding the new packages to the game.

  • First, copy the cooked packages from your UDK\UDKGame\CookedPC\Folder1\Folder2 folder to the Xcom EW game XEW\XComGame\CookedPCConsole\Voices folder. The game will search for and find packages anywhere under CookedPCConsole so you don’t have to put them in Voices; they can go in another subfolder if you prefer. Be sure to get all 16 packages.
  • Second, create a line in the <Path to XCom>\XEW\XComGame\Config\DefaultContent.INI file for your new package, near the existing lines for other voice packs:

VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1,Language=7,ArchetypeName="MaleVoice1_British.Voice_MaleVoice1_British")

The ArchetypeName' field must match your package name and voice archetype name. The VoiceType indicates both whether the voice is Male or Female, and which voice number it is assigned to. The Language field identifies which language the voice is associated with. Creating new languages that aren’t already present in the game or in Long War is possible, but outside the scope of this tutorial and requires some additional hex changes. Feel free to post about it on the Nexus XCOM Mod Talk Forum if you’re interested.

If you’re distributing your voices, the .INI file change and the sixteen .UPKs that make up the Cooked Package are all you need to distribute.

Doing it Again (Cause you got it wrong the first time)

You might (read: will most probably) not be happy with the sounds the first time you hear them in-game. It might sound under or over processed or not be at the right volume. Adjusting them after creating the package is thankfully much simpler than creating the package the first time.

Go back to whatever step you need to redo the recording or processing, and re-create the bank files again with the makebanks script. Copy these new banks over your old ones that you originally imported into the UDK, and launch the UDK Editor. Find your voice pack in the Package Tree in the content browser and highlight all the packages in your voice pack. <Right Click> and select Fully Load to get the UDK to load all the files. Then in the Filter at the top of the content browser select only Sound Wave Data so only the sound files appear in the window. (You might need to switch to the All Types tab to find it if it isn’t favorited yet). Highlight all of the sound files across all of the packages (i.e. <Click> on one and then hit <Ctrl-A>). <Right Click> and select Reimport sound node wave. This will re-import all the sounds for this package, overwriting what was previously in your packages. That’s it. All the mappings should remain the same and don’t need to be changed.

Except when they do:

Reimporting works best if you use the makebanks2 script to create the banks, or if you have only changed the contents of the sounds and haven’t deleted or added any files. If you used makebanks.ps1 script, the imported sounds keep their original names, so you might see SM18Moving03 in bank 2 and SM18Moving04 in bank 3. If you then decide that SM18Moving03 just isn’t any good and delete it, the next time you re-create the banks you might get SM18Moving04 in bank 2 and SM18Moving05 in bank 3. Then when you try to re-import it won’t work, because it won’t be able to find a file with the same name to re-import again. The makebanks2 script solves this by renaming all the files in the banks so they always start at 1 and count up, e.g. each bank has SM18AlienNotStunned1.wav, regardless of what the original file name was. So deleting files is not a problem, unless you delete one of the ones that has more than 15 entries, at which time one of the banks will only have 1 sound when it used to have 2. That’s rare enough that I just manually fix it up in the UDK by re-importing the sound and then changing the cue mapping in the archetype from the old random cue to just the sole remaining cue. Renaming the files with bank2 does make it more difficult to tell what the original source file was for which bank because the original name is lost, but I find the flexibility in being able to remove segments to be worth it.

After reimporting, just save the packages. Re-cook and copy the new files to the XCom folder.

All Done.

See? It’s just that easy. Happy voice pack creating.

Music Files with UDK Tutorial

Didn't hexedit anything; just cooked the new files with the UDK.

Bear in mind that the game is divided into two stages: strategic and tactical. Music played in Mission Control, such as GeoScape is strategic. Replacing the music for the wrong stage of the game will make it not appear where you want it.

Here are some simple foolpoof steps.

  • Open UnrealFrontend.exe
  • Click UnrealEd
  • In the Content Browser tab <Right click> and select New SoundNodeWave ( check the "include looping node" box).
  • Make sure you get the package and cue names right if you are replacing a music track.
Here is an example for replacing the soldier select track on the strategic side in Mission Control:

XCOM SelectSoldier Cue
[image by tracktwo, used with permission]

The existing package is "HQSound_SoldierSelect"
The existing Soundnode name is "ReadyForBattleMusic"
The existing cue is named "ReadyForBattleMusicCue"
Mind that the UDK sets the cue name to <name>_Cue (meaning it defaults for Voice banks).
(Voice banks have the underscore, while music packs don't.)
You have to remove the underscore by renaming it to match the cue name in the music track you are replacing.
Just check the right names using UE Explorer in the UPK you want to replace.
  • Click "import" (lower left buttons) and select the WAV file which contains your replacement music file.
    • Once imported: rename it to match the original soundnode (i.e. "ReadyForBattleMusic"). The UDK will prompt you to replace the existing empty soundnodewave.
  • Save the package and close the UDK.
  • Open <install folder>/UDK/UDK-2011-09/UDKGame/Config/UDKEngine.ini.
  • Find "[Engine.PackagesToAlwaysCook]" and add "SeekFreePackage=<name of your package>"
  • Go to the UnrealFrontend and hit "Cook".
  • Your files will be cooked to <install folder>/UDK/UDK-2011-09/UDKGame/CookedPC.
  • Backup and replace the XCOM files with the new UPKs.
  • Done! just mind the names.

// Reference: SoundCue'HQSound_SoldierSelect.ReadyForBattleMusicCue'

// Reference: SoundNodeWave'SoundReadyForBattleMusic.ReadyForBattleMusic'

It is even easier for battle music: you just follow the above instructions and cook with any names you want.

Combat music is the music played in the tactical missions when you are fighting. Customizing combat music doesn't require any fixed naming convention, but it does require applying the UPK patch posted below to enable customization in the .INI file.

Remember to add the new combat music in defaultContent with the structure <package>.<cue name>.

I.e. CombatMusicCues="Pic_BossFight.Pic_BossFight_Cue""

XCOM NewCombatMusic Cue
[image by tracktwo, used with permission]

The new package is "Pic_BossFight"
The new Soundnode name is "Pic_BossFight"
The new cue is named "Pic_BossFight_Cue"

Instead of <Right click>ing on the Content Browser tab and selecting New SoundNodeWave in the menu, pressing the NEW button near the IMPORT button in the lower left corner has the same effect, but you then have to set the Factory field to "soundnodewave". This creates an empty soundnodewave and soundnodewave_cue. So you can use either method to add your new music track.

IMPORT loads the WAV file into the UDK package. You could name the soundnodewave field anyway you like as long as the Cue has the correct name.

Also the game will load music files even if they are in a subfolder of CookedPCconsole, this way it's a bit tidier.

Here is a PatcherGUI mod for Combat Music, by tracktwo:

MOD_NAME=Combat Music Config Enabler
AUTHOR=Tracktwo
DESCRIPTION=Allows setting customized combat music packages from DefaultContent.ini
Version: 1.0
Compatible with XCOM Enemy Within versions:
- all
UPK_FILE=XComGame.upk


// Make XComTacticalSoundManager Config(Content) OBJECT=XComTacticalSoundManager [BEFORE_CODE] 12 00 80 00 <Class.Core.Object> <None> [AFTER_CODE] 16 00 80 00 <Class.Core.Object> <Content>
// Change XComTacticalSoundManager.CombatMusicCues from array<string> to config array<config string> OBJECT=XComTacticalSoundManager.CombatMusicCues FIND_HEX=00 00 40 00 MODDED_HEX= 00 40 40 00
OBJECT=XComTacticalSoundManager.CombatMusicCues.CombatMusicCues FIND_HEX=00 00 40 00 MODDED_HEX= 00 40 40 00

After this you can add this section to defaultcontent.ini to set the combat music to the defaults. Change or add any element to the array to add more music or remove tracks.

[XComGame.XComTacticalSoundManager]
CombatMusicCues="CombatMusic1.ActionMusic1Cue"
CombatMusicCues="CombatMusic2.ActionMusic2Cue"
CombatMusicCues="CombatMusic3.ActionMusic3Cue"
CombatMusicCues="CombatMusic4.ActionMusic4Cue"
CombatMusicCues="CombatMusic5.ActionMusic5Cue"
CombatMusicCues="CombatMusic6.ActionMusic6Cue"
CombatMusicCues="CombatMusic7.ActionMusic7Cue"
CombatMusicCues="CombatMusic8.ActionMusic8Cue"
CombatMusicCues="CombatMusic9.ActionMusic9Cue"

UPK Music

TRekNexus from the Nexus Mod Talk Forum has provide an example of a list of music packages in UPK files, along with the WAV filenames in each package. This way people won't have to decompress those UPK files in order to determine what music files and cues they already contain. Remember that the soundnodewave name format is not significant, while the soundnodewave_cue name format is. Music files do not have the "_" (underscore) in the "cue" suffix.

[Combat Music / "Battlescape"]
CombatMusic1_SF.upk:
ActionMusic1Cue                 (cue file)
CombatMusic1                    (wav file)

CombatMusic2_SF.upk: ActionMusic2Cue (cue file) CombatMusic2 (wav file) ; (the rest have the same naming; only thing that differs are the "music" numbers)

[XCom Base Music / "Geoscape"] HQSound_Act1Music_SF.upk: HQSoundtrackAct1Cue (cue file) HQ1McCann (wav file)
HQSound_Act2Music_SF.upk: HQSoundtrackAct2Cue (cue file) HQ2McCann (wav file)
HQSound_Act3Music_SF.upk: HQSoundtrackAct3Cue (cue file) HQ3McCann (wav file)

[Air Battle Music / Intercept Music] HQSound_InterceptionMusic_SF.upk: InterceptionMusicCue (cue file) McCannInterceptionMusic (wav file)

[Soldier Select / Mission Prepare Music] HQSound_SoldierSelect_SF.upk: ReadyForBattleMusicCue (cue file) ReadyForBattleMusic (wav file)

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

Other sounds

Overlapping Sounds Fix

A PatcherGUI mod by tracktwo for fix a problem with overlapping sounds. This was inspired by a desire to reduce the overlap in the Engineering "research complete" sounds.

The fix is to only call Realize() from AddDialog() if the dialog just added is the only element in the array. Adding additional dialogs will add them to the list, but not Realize(). Realize() is called again when the dialogs are dismissed, and this will pop up the next dialog in the list and play the sound.

UPK_FILE=XComGame.upk
AUTHOR=Tracktwo
DESCRIPTION=Fixes the extremely loud overlapping SFX when research unlocks many items/projects/facilities.
Version: 1.0
Compatible with XCOM Enemy Within versions:
- all
OBJECT=UIDialogueBox.AddDialog:AUTO

[BEFORE_CODE] //Realize(); // return; 1C <UIDialogueBox.Realize> 16 04 0B
[AFTER_CODE] // if (m_arrData.Length == 1) { 07 01 01 9A 36 01 <@m_arrData> 26 16 // Realize() 1C <UIDialogueBox.Realize> 16 // } // return; 04 0B

Loud Overwatch Fix

Another PatcherGUI mod by tracktwo to reduce the volume of the "On Overwatch" sound.

UPK_FILE=XComGame.upk
AUTHOR=Tracktwo
DESCRIPTION=Fixes the extremely loud overlapping SFX when using team overwatch
Version: 1.0
Compatible with XCOM Enemy Within versions:
- all

The volume of the overwatch SFX can be controlled by changing one value in this script. It defaults to "2", which plays up to two copies of the SFX assuming at least two soldiers activate overwatch. This can be increased or decreased to adjust the volume. Change the number in the NUM_OW alias near the top of this file to adjust the setting, with higher numbers being louder.
// Maximum number of OW sounds to player. Bigger = louder. ALIAS=NUM_OW:<%b 2>
// Add a new boolean instance variable bForceTeamOverwatch to XComTacticalCheatManager
[ADD_NAME_ENTRY] <%u20> // string length (including terminating null) <%t"bForceTeamOverwatch"> // ASCII null-terminated string <%u0x00000000> // flags L (always the same) <%u0x00070010> // flags H (always the same)
[ADD_EXPORT_ENTRY] <Core.BoolProperty> // Type <NullRef> // ParentClassRef <Class.XComTacticalCheatManager> // OwnerRef <bForceTeamOverwatch> // NameIdx <NullRef> // ArchetypeRef <%u0x00000000> // flags H <%u0x00070004> // flags L <%u40> // serial size <%u0> // serial offset <%u0> // export flags <%u0> // net objects count <%u0> // GUID1, zero if net objects count == 0 <%u0> // GUID2, zero if net objects count == 0 <%u0> // GUID3, zero if net objects count == 0 <%u0> // GUID4, zero if net objects count == 0 <%u0> // unknown, zero if net objects count == 0
OBJECT=XComTacticalCheatManager.bForceTeamOverwatch REL_OFFSET=16 [MODDED_CODE] <%s 1> <%s 0> <%u 0> <%u 0> <None> <NullRef>
// Add a new integer instance variable iOverwatchCount to XComTacticalCheatManager
[ADD_NAME_ENTRY] <%u16> // string length (including terminating null) <%t"iOverwatchCount"> // ASCII null-terminated string <%u0x00000000> // flags L (always the same) <%u0x00070010> // flags H (always the same)
[ADD_EXPORT_ENTRY] <Core.IntProperty> // Type <NullRef> // ParentClassRef <Class.XComTacticalCheatManager> // OwnerRef <iOverwatchCount> // NameIdx <NullRef> // ArchetypeRef <%u0x00000000> // flags H <%u0x00070004> // flags L <%u40> // serial size <%u0> // serial offset <%u0> // export flags <%u0> // net objects count <%u0> // GUID1, zero if net objects count == 0 <%u0> // GUID2, zero if net objects count == 0 <%u0> // GUID3, zero if net objects count == 0 <%u0> // GUID4, zero if net objects count == 0 <%u0> // unknown, zero if net objects count == 0
OBJECT=XComTacticalCheatManager.iOverwatchCount REL_OFFSET=16 [MODDED_CODE] <%s 1> <%s 0> <%u 0> <%u 0> <None> <NullRef>
// Replace XComTacticalCheatManager.ForceOverwatch to set the bForceTeamOverwatch flag to 'true' and the // new iOverwatchCount value to 2 before triggering overwatches on each player.
OBJECT=XComTacticalCheatManager.ForceOverwatch:AUTO [REPLACEMENT_CODE] // Default parameter 49 02 00 28 15
// kUnit = XComTacticalController(Outer.ViewTarget.Owner).m_kActiveUnit; 0F 00 <.kUnit> 19 2E <Class.XComTacticalController> 19 19 01 <Core.Object.Outer> 09 00 <Engine.PlayerController.ViewTarget> 00 01 <Engine.PlayerController.ViewTarget> 09 00 <Engine.Actor.Owner> 00 01 <Engine.Actor.Owner> 09 00 <XComTacticalController.m_kActiveUnit> 00 01 <XComTacticalController.m_kActiveUnit>
// if (!kUnit.IsIdle()) // { // Outer.PlaySound(soundcue'NegativeSelection2Cue', true, true); // } 07 [@NotIdle] 81 19 00 <.kUnit> 0A 00 <XGUnit.IsIdle.ReturnValue> 00 1B <IsIdle> 16 16 19 01 <Core.Object.Outer> 09 00 <NullRef> 00 1C <Engine.Actor.PlaySound> 20 <SoundUI.NegativeSelection2Cue> 27 27 4A 4A 4A 16 04 0B [#NotIdle]
// bForceTeamOverwatch = bTeamXCOM 14 2D 01 <@bForceTeamOverwatch> 2D 00 <.bTeamXCOM>
// iOverwatchCount = 2 0F 01 <@iOverwatchCount> 24 <!NUM_OW>
// foreach Outer.AllActors(class 'XGUnit', kUnit) 2F 19 01 <Core.Object.Outer> 09 00 <NullRef> 00 61 30 20 <Class.XGUnit> 00 <.kUnit> 4A 16 [@EndOfLoop]
// if (!bTeamXCOM == (kUnit.m_kBehavior != none)) 07 [@TeamXCom] F2 81 2D 00 <.bTeamXCOM> 16 77 19 00 <.kUnit> 09 00 <XGUnitNativeBase.m_kBehavior> 00 01 <XGUnitNativeBase.m_kBehavior> 2A 16 16
// if (kUnit.IsIdle()) 07 [@IsIdle] 19 00 <.kUnit> 0A 00 <XGUnit.IsIdle.ReturnValue> 00 1B <IsIdle> 16
// kAbility = kUnit.FindAbility(22, none); 0F 00 <.kAbility> 19 00 <.kUnit> 0D 00 <XGUnitNativeBase.FindAbility.ReturnValue> 00 1B <FindAbility> 2C 16 2A 16
// if ((kAbility != none) && kAbility.CheckAvailable()) 07 [@IsAvailable] 82 77 00 <.kAbility> 2A 16 18 20 00 19 00 <.kAbility> 0A 00 <XGAbility.CheckAvailable.ReturnValue> 00 1C <XGAbility.CheckAvailable> 16 16
// kUnit.PerformAbility(22); 19 00 <.kUnit> 0C 00 <NullRef> 00 1B <PerformAbility> 24 16 16
[#IsAvailable] [#IsIdle] [#TeamXCom] 31 // Next iterator [#EndOfLoop] 30 // Pop iterator
// bForceTeamOverwatch = false 14 2D 01 <@bForceTeamOverwatch> 28 // iOverwatchCount = 0 0F 01 <@iOverwatchCount> 25 // return 04 0B 53
// Modify XGAbilityTree.ApplyEffectsToTarget to only play the overwatch sound according to the following logic: // if (there exists a cheat manager and it has the "bForceTeamOverwatch" flag set true) { // if (iOverwatchCount > 0) { // play the overwatch sound // decrement the iOverwatchCount counter // } // } else { // play the overwatch sound // } // // Since this function is very, very big, don't replace it wholesale with REPLACEMENT_CODE. Instead, replace the // code that plays the overwatch sound with a jump to just beyond the return statement, and fill out the remaining // difference in bytes between the original play sound statement and the jump statement with dummy references and bytes // to ensure it remains the same size. This keeps all the existing jump offsets the same. OBJECT=XGAbilityTree.ApplyEffectsToTarget:AUTO [BEFORE_CODE] // PlaySound(SoundCue(DynamicLoadObject("SoundUI.OverwatchCue", class'SoundCue')), true); 1C <Engine.Actor.PlaySound> 2E <Engine.SoundCue> 1C <Core.Object.DynamicLoadObject> 1F <%t "SoundUI.OverwatchCue"> 20 <Engine.SoundCue> 4A 16 27 4A 4A 4A 4A 16
[AFTER_CODE] // JMP <end of function> 06 05 35
// We need 4 references and 31 bytes to ensure the size of the modified block doesn't change in either on disk or // in memory. 00 <.kTarget> 00 <.kTarget> 00 <.kTarget> 00 <.kTarget> 1F <%t "abcdefghijklmnopqrstuvwxy">
// Replace the "return" EOS with our new code. [BEFORE_CODE] 04 0B 53
[AFTER_CODE] 04 0B // if (XComTacticalCheatManager(GetALocalPlayerController().CheatManager) != none && // XComTacticalCheatManager(GetALocalPlayerController().CheatManager).bForceTeamOverwatch) 07 3D 36 82 77 2E <Class.XComTacticalCheatManager> 19 1C <Engine.Actor.GetALocalPlayerController> 16 09 00 <Engine.PlayerController.CheatManager> 00 01 <Engine.PlayerController.CheatManager> 2A 16 18 3F 00 // skip 63 = 0x3F bytes 19 2E <Class.XComTacticalCheatManager> 19 1C <Engine.Actor.GetALocalPlayerController> 16 09 00 <Engine.PlayerController.CheatManager> 00 01 <Engine.PlayerController.CheatManager> 0A 00 <XComTacticalCheatManager.bForceTeamOverwatch> 00 2D 01 <XComTacticalCheatManager.bForceTeamOverwatch> 16
// if (XComTacticalCheatManager(GetALocalPlayerController().CheatManager).iOverwatchCount > 0) 07 3A 36 97 19 2E <Class.XComTacticalCheatManager> 19 1C <Engine.Actor.GetALocalPlayerController> 16 09 00 <Engine.PlayerController.CheatManager> 00 01 <Engine.PlayerController.CheatManager> 0A 00 <XComTacticalCheatManager.iOverwatchCount> 00 01 <XComTacticalCheatManager.iOverwatchCount> 25 16
// PlaySound(SoundCue(DynamicLoadObject("SoundUI.OverwatchCue", class'SoundCue')), true); 1C <Engine.Actor.PlaySound> 2E <Engine.SoundCue> 1C <Core.Object.DynamicLoadObject> 1F <%t "SoundUI.OverwatchCue"> 20 <Engine.SoundCue> 4A 16 27 4A 4A 4A 4A 16
// XComTacticalCheatManager(GetALocalPlayerController().CheatManager).iOverwatchCount--; A6 19 2E <Class.XComTacticalCheatManager> 19 1C <Engine.Actor.GetALocalPlayerController> 16 09 00 <Engine.PlayerController.CheatManager> 00 01 <Engine.PlayerController.CheatManager> 0A 00 <XComTacticalCheatManager.iOverwatchCount> 00 01 <XComTacticalCheatManager.iOverwatchCount> 16
// Jump over 'else' 06 7F 36
// else block: // PlaySound(SoundCue(DynamicLoadObject("SoundUI.OverwatchCue", class'SoundCue')), true); 1C <Engine.Actor.PlaySound> 2E <Engine.SoundCue> 1C <Core.Object.DynamicLoadObject> 1F <%t "SoundUI.OverwatchCue"> 20 <Engine.SoundCue> 4A 16 27 4A 4A 4A 4A 16
// Jump back to the spot we snipped out 06 54 33 53

Related Discoveries

So, it seems like both engine and licensee package numbers affect serialization process. As far as I understand the matter from UDN articles, each time developers change serialization code, they should increase version number, because this affects how packages are saved and loaded. But this seems to be the case for UE developers, not for game developers, using paid UE version.

The free UDK has the licensee number set to zero. It uses pure engine-version matching serialization code to read/write packages and rejects any packages with non-zero licensee number.

Game developers have their own non-zero licensee numbers, which are different for every game. And when they make their own serialization code changes, they do not increase engine version number (which is logical), but tie that new code to their unique licensee number instead. This results in game being able to read both packages with zero licensee number and game specific packages with corresponding licensee number.

It's believed this the one of the reasons why we can't open XCOM maps and other packages with the UDK: when we try to use those as they are, UDK says licensee number is wrong, and when we hex edit those to set licensee number to zero, the UDK crashes, most probably because of the unexpected XCOM specific serialized data.

This fact prevents us from modding existing packages, but we can also use it to our advantage, as we did with voice packages: we can use the UDK to create a common engine-version dependent part and handle a XCOM-specific part inside a script (when it is possible).

This seems to be true for materials as well.

  • Licensee 00 - default material used:

[0048.45] Log: Material CrapMat has outdated uniform expressions; regenerating.
[0048.45] Log: Can't compile CrapMat with seekfree loading path on console, will attempt to use default material instead
[0048.45] Warning: Warning, Failed to compile Material D79TestPackage.CrapMat for platform PC-D3D-SM3, Default Material will be used in game.

  • Licensee 40 - the game crashes:

[0071.45] Critical: appError called: Material D79TestPackage.CrapMat: Serial size mismatch: Got 232, Expected 208

When you think of it, it becomes logical. There are hundreds of games, sharing the same engine version, but also using their own C++ code and adding a native serialized data to packages. Since licensee number appears to be game unique, it's a good way to distinguish base UE code and game specific code.

There are a number of entries, which have both SoundCue variable and event enum assigned, but are not used. Those can be "activated" by calling UnitSpeak(EventEnum) function. For example, ability voices are triggered inside ApplyAbility with kAbility.m_kUnit.UnitSpeak(EventEnum) call.

We can also try to add completely new sounds for completely new events with mutators. This will require creating a new classes, which extend XComCharacterVoice and XComCharacterVoiceBank classes, using those classes instead of original ones to create sound packages and inserting appropriate UnitSpeak calls to appropriate places.


References

Referred to by this article:



That refer to this article: