tracktwo Posted September 23, 2014 Share Posted September 23, 2014 I 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 like you said 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. Again from looking at the definition of SoundNodeWave in Engine.upk, I believe these five elements are: RawDataCompressedPCData <- This is the only one with real data in itCompressedXboxDataCompressedPS3DataCompressedWiiUData Message #13 in this thread has a little more detail on this. My current implementation of REPLACE_SOUND in my fork of UPKUtils is basically a two-pass operation: Use the existing move/resize code to clone the existing entry to the new position and resize it, including copying the default properties. The second "pass" fixes up all the offsets in the UntypedBulkData_Mirror instances and injects the new ogg data. The utility that fixes up the offsets needs to understand the UPK format, because it has to find the right places to fix. Since the default property array is variable-length, they are not at a fixed offset from the start of the object. Does that make sense? Link to comment Share on other sites More sharing options...
wghost81 Posted September 23, 2014 Share Posted September 23, 2014 (edited) 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 think UntypedBulkData_Mirror serialized variables are int SavedBulkDataFlags, int SavedElementCount, int SavedBulkDataSizeOnDisk, int SavedBulkDataOffsetInFile, followed by binary data. It corresponds with your findings, tracktwo. I think, 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 I'm starting to work on serialization code. Edited September 23, 2014 by wghost81 Link to comment Share on other sites More sharing options...
tracktwo Posted September 23, 2014 Share Posted September 23, 2014 Yep, exactly what I believe the formats to be, except there is a 5th entry in SoundNodeWave: There are three entries after CompressedPCData, so there is a CompressedPS3Data after the xbox data. I'll have some time to look at this a bit more tomorrow. Link to comment Share on other sites More sharing options...
wghost81 Posted September 23, 2014 Share Posted September 23, 2014 After careful consideration I decided to start with two changes: UPDATE_REL patcher setting and BULK_DATA/BULK_FILE key. UPDATE_REL=TRUE will update relative offsets after each write operation to point to the next byte. If next byte is out of range and behavior is set to keep, relative offset won't be updated. BULK_DATA=/*hex data*/ will construct a BulkDataMirror object using data provided to set data size and current object + rel offset to calculate file offset. It will then serialize and write BulkDataMirror at current rel offset. It won't auto-resize object to avoid file offset shifting, so user will need to handle RESIZE operation by himself before using BULK_DATA. BULK_FILE=filename works same as BULK_DATA, but takes data from binary file provided. All this will allow to manually construct SoundNodeWave object: UPDATE_REL=TRUE OBJECT=SoundMissionControlMusic.MemorialScreen3:INPL RESIZE=12345 // new object size here (a size of entire object serial data) REL_OFFSET=4 // never rewrite PrevObjRef, it's an internal linker info! [MODDED_CODE] <Duration> // var name <FloatProperty> // property type name <%u 4> // property size <%u 0> // array idx <%f 58.742> // property value <NumChannels> <IntProperty> <%u 4> <%u 0> <%u 2> <SampleRate> <IntProperty> <%u 4> <%u 0> <%u 44100> <RawPCMDataSize> <IntProperty> <%u 4> <%u 0> <%u 10362096> <None> // end of list BULK_DATA=/*empty bulk data*/ BULK_FILE=path/to/binaryfile.ext BULK_DATA=/*empty bulk data*/ BULK_DATA=/*empty bulk data*/ BULK_DATA=/*empty bulk data*/ // end of object It still has a need for lot of manual calculation, but at least it works in one pass. Next step will be to code REPLACE_SOUND=SoundNodeWaveObjName:path/to/file.ogg key. :smile: But for this I'll need to do some reading on ogg data format. :smile: Sound data are not the only data, which use bulk data mirrors with absolute file offsets. All textures and movies are also stored this way. So BULK_DATA/BULK_FILE keys will also allow Drakous79 to modify Texture2D objects with little more comfort. :smile: I'm updating github sources with new functionality, but I haven't tested BULK_DATA/BULK_FILE keys yet. tracktwo, can you provide some testing material? Link to comment Share on other sites More sharing options...
tracktwo Posted September 23, 2014 Share Posted September 23, 2014 Awesome! Yes I can help with some testing a bit later today. As for the ogg format, if you're looking to read the duration, channels, etc. values to populate the property list, that is part of the vorbis audio encoding. The best place to start there is probably to use the open source libvorbisfile library. It has a simple API to read a file and get information about the stream. Link to comment Share on other sites More sharing options...
wghost81 Posted September 23, 2014 Share Posted September 23, 2014 (edited) About in-place resize. Some packages contain more than one embedded object with bulk data. Resizing one of those in place will result in broken package. So good-old move-and-resize approach can be actually better in this case. :smile: Edited September 23, 2014 by wghost81 Link to comment Share on other sites More sharing options...
tracktwo Posted September 24, 2014 Share Posted September 24, 2014 OK, so I have tested it a little bit and so far it works! Defining sound replacements in this way is a little awkward because of the need to compute the resize value, including the size of the property list (which I got wrong the first time and crashed the game :smile: ). I imagine once REPLACE_SOUND is implemented doing it this long way will be less important unless you want complete control over the particular property values that get inserted. About in-place resize. Some packages contain more than one embedded object with bulk data. Resizing one of those in place will result in broken package. So good-old move-and-resize approach can be actually better in this case. :smile: Yeah - For example, all the weapon packages. I used move & resize, but accidentally specified :MOVE on the object line, which ended up moving it twice :smile: The first time upon seeing :MOVE and the second when it hits the RESIZE=. Link to comment Share on other sites More sharing options...
wghost81 Posted September 24, 2014 Share Posted September 24, 2014 tracktwo, good. :smile: Yes, MOVE key forces object moving before each write operation. There was a discussion about preferred upk modding techniques (move vs replace) and always preserving original data, so I've coded this in, but never actually used it. I guess, this modifier should force object move only for the first time and then it should auto-switch to AUTO to avoid unnecessary object movement. Am I correct in that bulk data are pure ogg binary file data? No alterations to those are needed, I just have to construct DefaultProperties and embed an entire ogg file into object, correct? Link to comment Share on other sites More sharing options...
tracktwo Posted September 24, 2014 Share Posted September 24, 2014 Am I correct in that bulk data are pure ogg binary file data? No alterations to those are needed, I just have to construct DefaultProperties and embed an entire ogg file into object, correct? Yes, that's correct. It's just a plain ogg file. Link to comment Share on other sites More sharing options...
tracktwo Posted September 27, 2014 Share Posted September 27, 2014 Ok. So my latest adventure is trying to get X-COM to load brand new sounds, instead of just replacing existing ones. I successfully built a package containing a sound file with the UDK. For some reason I haven't yet worked out, it places the SoundNodeWave and the cue in two separate packages. I think this is ok, as it looks like the same thing is happening for the new DLC sounds, like the Annette voice banks (AnnetteVoice1_Bank0_SF.upk and AnnetteVoice2_Bank0_SF_LOC_INT.upk, for example). I force the package to load via the Engine.StartupPackages section, but no dice. It appears to load the package (I think?), but nothing is requesting to actually load the sound so it doesn't play, even through the dev console. Second attempt: Make a little mod from the instructions in the "Scripting with UDK" thread to actually load the sound content through the XComContentManager. So far I haven't had much luck getting the mod loaded by the game. Or, if it's loading, it isn't working. I can't tell which. I tried putting the `Log messages into StartMatch() for debugging, but they aren't appearing in the Launch.txt file. I'm going to keep plugging at it, but I am also wondering if the StartMatch() is too early to load the sounds anyway and I need to trigger a script off some other condition. Link to comment Share on other sites More sharing options...
Recommended Posts