Sound Files - XCOM:EU 2012

From Nexus Mods Wiki
Revision as of 01:06, 26 October 2014 by Dubiousintent (talk | contribs) (Music Files with UDK Tutorial: updated for clarification)
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 Mod Talk Forum.]

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

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

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

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

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

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

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

Programs and Tools

Details

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

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

SoundNodeWave

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

A soundnodewave is:

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

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

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

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

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

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

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

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

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

  • CompressedIPhoneData
  • CompressedFlashData

Which explains the two extra entries I'm seeing.

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

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

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

SoundNodeWave format guess:

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

UntypedBulkData_Mirror format guess:

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

Voice Banks

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

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

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

Voices

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

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

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

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

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

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

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

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.

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.

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.

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

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 Files with UDK Tutorial

Here's a tutorial for creating a custom language voice pack with sound banks. Credit goes to tracktwo of the Nexus XCOM Mod Talk Forum for the explorations and development of this tutorial.

Step 1: Prep work.

  • You need .WAV files in 16-bit PCM format to use as your content. The X-Com voices are 22050Hz and mono, but you can probably use higher sample rates and more channels, the files will just be bigger. You don't need a full set to start, just a few sounds is enough.
  • You also need to install the X-Com compatible UDK.

Step 2: Create stub version of the voice classes.

  • Create the folders XComGame and XComGame\Classes under Development\Src in the UDK installation. Inside the Classes folder, create the files XComCharacterVoice.uc and XComCharacterVoiceBank.uc'.
  • Open the XcomGame.upk in the CookedPCConsole folder of the XEW installation in UE Explorer and search for the XComCharacterVoiceBank class, and copy everthing into your .UC file of the same name. The function names aren't strictly needed, all you really need is the class declaration and all the var() declarations.
  • For XComCharacterVoice, the definition you want is slightly different from what you see in UE Explorer. Paste in this:

class XComCharacterVoice extends Object
   hidecategories(Object)
   native(Unit);

var() array<string> VoiceBankNames; var() SoundCue CharacterCustomizationCue;

Now add the XComGame package to the config files for the UDK. In Config\DefaultEngine.ini add these lines in the sections as noted, after any packages already listed in the section:

[Engine.ScriptPackages]
+NativePackages=XComGame

[UnrealEd.EditorEngine] +EditPackages=XComGame

  • Now compile the scripts, either from the command line or from the unreal frontend (Binaries\Unrealfrontend.exe in your UDK installation).

Step 3. Create the voice bank.

  • Open up the unreal editor (there is an icon in the unreal front end for UnrealEd)
  • In the content browser, click "Import" and select one or more of your files. It's easiest to import all the sounds at once, so <Shift-click> or <Ctrl-click> the files you want and hit Open.
  • In the dialog that pops up, choose a package name for your voice bank. I'll use MaleSoldier1_Robotic_Bank0, because this is the first (0th) bank of the first male soldier voice in my "robotic" language, which are truly awful sounding text-to-speech clips I created.
  • The name field should be populated with your sound file name. Whatever you used is fine for now, but Xcom uses a naming scheme like SM01Reload02 for the 2nd reload clip of the 1st male soldier. Its easiest to import if you use a consistent scheme and name your sound files appropriately.
  • Click the "auto-create cue" box under options.
  • Hit OK.
  • If you selected more than one file, it'll open up another dialog with your package already filled in. If it looks good, hit OK, or OK to all.
  • You'll now see a pane with a bunch of blue squares for your sounds and sound cues. You should also see your package name under "NewPackages" in the packages pane on the bottom left of the browser. <Right click> it and select "save". It'll prompt you for a place to save: I used the "Content" folder under the UDK installation.
  • Now click the "Actor Classes" tab at the top of the browser window. De-select all the checkboxes, and scroll down to the bottom of the list. You should see "XComCharacterVoiceBank" there, from the script you compiled earlier. Right click it and select "Create archetype". Enter your package name in the package box, and give the archetype a name. XCOM usually calls the banks by the same name as the package, so I used MaleSoldier1_Robotic_Bank0 again.
  • Click back to the content browser tab and you should see a new red square labelled archetype. <Double click> it and a dialog will open that has a big table of names corresponding to the cue variables in XComCharacterVoiceBank.
  • For each of the sounds you imported, click on the corresponding cue in the browser (not the sound, the cue) to highlight it. Then click the little green left-facing arrow next to the event you want to associate with that cue. The field should populate with the object name of the cue you selected. Repeat this process for all your cues. You can leave the rest of the events blank.
  • Save your package again. You now have a sound bank.

Step 4: Create the Voice Package

  • Now we're going to go through a similar process to create the voice package. This is the package that references all the voice banks for your voice pack. In the stock XCOM, each voice has 15 banks. We'll only create one, because it isn't actually working yet anyway.
  • Click back to the Actor Classes tab and locate the XComCharacterVoice entry. <Right click> it and create a new archetype. This time, choose a different package name: this one is for the voice, not the voice bank. I'll match the standard XCOM voice scheme and use MaleSoldier1_Robotic as the package name. And also to match XCOM, but be oddly inconsistent with the banks, I'll use Voice_MaleVoice1_Robotic as the archetype name. I don't know why they're prefixed with voice. (Fun fact: the DLC voices do it the other way around, using a Voice_ prefix on the package instead of the archetype.)
  • Back to the content browser, hit "Import" again. Import one sound cue to use as the "customization cue". This is the sample sound that plays when you select the voice in the soldier customization UI in-game. You can actually have multiple sound cues to choose from randomly in-game, but adding random cues is a bit more advanced, so we'll just stick with one for now. Remember to select "auto-create cue" and use the same package as your voice archetype.
  • Now double click the archetype. Under Voice Bank Names, Click the <green +> icon to add a new entry. Add the string XComCharacterVoiceBank MaleVoice1_Robotic_Bank0.MaleVoice1_Robotic_Bank0. That is, the name of the voice bank class, a space, then the name of your bank package, a dot, and the name of your archetype inside the voice package. Select your cue in the browser and then assign it to the customization cue field as you did before.
  • Save your new package, preferably in the same place you created the voice bank.

Step 5: Cook

  • Shut down the Unreal Editor. If you have other "Newpackages" lying around you can ignore them, as long as you remembered to save your voice bank and voice package.
  • Add your packages to the DefaultEngine.ini you edited in the script step, in the following section:

[Engine.PackagesToAlwaysCook]
+SeekFreePackage=MaleVoice1_Robotic_Bank0
+SeekFreePackage=MaleVoice1_Robotic

  • In the unreal front-end, click "cook" and wait for it to complete.

Step 6: Add the cooked packages to the game.

  • You should now have .UPK packages of the same name as your voice package and bank in the CookedPC sub-folder of the UDK, except suffixed with _SF. Copy them to the CookedPCConsole folder of XEW\XComGame. Do not copy the XComGame package from the UDK, it's only a stub. I also had some trouble where initially the game wasn't finding the cooked XComGame package, so the sound bank package had bits of XComCharacterVoiceBank defined within it. If you open the bank in UE Explorer, you should not see any "Classes" tab, and under "Content" you should see only your package name and another package with the same name and the _SF suffix. If not, try doing a clean recompile of the scripts and re-cooking in the front-end.
  • Edit the Config\DefaultContent.ini to register the voice with the game. You can do it as an entirely new language, or add it as a new soldier voice in an existing language. Adding a new language requires making other mods to the game: to expand the language enum list and adjust the number of languages to cycle in the customization UI. I'll describe using an existing language in this tutorial: we'll make a 7th male english voice. (Note: female voices work differently, unfortunately, they require .UPK mods to add additional voices beyond the default 6)
  • In defaultcontent.ini, add the following lines:

VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier1,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice1_English.Voice_MaleVoice1_English")

VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier2,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice3_English.Voice_MaleVoice3_English")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier3,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice4_English.Voice_MaleVoice4_English")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier4,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice5_English.Voice_MaleVoice5_English")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier5,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice6_English.Voice_MaleVoice6_English")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier6,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice7_English.Voice_MaleVoice7_English")
VoicePackageInfo=(VoiceType=eCharVoice_MaleSoldier7,Language=eCharLanguage_English,IsMec=false,ArchetypeName="MaleVoice1_Robotic.Voice_MaleVoice1_Robotic")

  • Only overwriting some of the voices and leaving the rest as defaults was a little buggy, so I specify them all. Note that the 2nd voice actually uses MaleVoice3_English, because there is no MaleVoice2_English package. MaleVoice7 will use your new package, so name the package and archetype correctly.

Step 7: Test it!

  • Load the game and customize a soldier. You should be able to select a 7th voice in the english language for male soldiers, and it should play your sound cue. Going into the tactical game, however, won't play anything. This is because the bank isn't fully set up yet. It's missing important data structures that the native code uses to load your bank.
Sounds can be tested with console command:

SoldierSpeak Speech

Where Speech is a ECharacterSpeech enum string without eCharSpeech_. Example:

SoldierSpeak Moving

Music Files with UDK Tutorial

I didn't hexedit anything, just cooked the new files with the UDK

Here are some simple foolpoof steps

  • Open UnrealFrontend.exe
  • Click UnrealEd
  • In the ContentBrowser <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:

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.

Bear in mind that combat music is not called GeoScape; that's used on the strategic side in Mission Control. 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="SarifHQ_Intro_Combat.SarifHQ_Intro_Combat_Cue"

I made some tests, soundnodewave name doesn't matter, probably only the package and cue names do.

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"

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: