Jump to content

Helldivers 2 Model Extraction Help!


Skelethor

Recommended Posts

On 2/23/2024 at 11:20 PM, Xaymar said:

Thanks to the new BMS scripts, I got exporting of files working almost perfectly now:

spacer.png

Remaining questions:

  • vertex20_t:
    • What is offset 0x0C for? Could fit 4 bytes, 2 half/shorts, or 1 float. Can reasonably rule out halfs, since some of the values are NaN.
  • vertex24_t:
    • Same questions as vertex20_t, but with +0x4 to all offsets.
    • What is offset 0x00 for?
  • vertex28_t:
    • Same questions as vertex20_t.
    • What is offset 0x14 for?
    • What is offset 0x18 for?
  • vertex32_t:
    • Same questions as vertex28_t, but with +0x4 to all offsets.
  • vertex36_t:
    • Same questions as vertex28_t.
    • What is offset 0x1C for?
    • What is offset 0x20 for?
  • vertex40_t:
    • Same questions as vertex36_t, but with +0x4 to all offsets.
  • If the meshes have multiple UV maps, what model format could I switch to that is easy to create, yet supports that?
    • Wavefront OBJ is limited to a single UV layer per group.
  • Where is the "mesh name" that would group the whole set into one?
    • Where is the relative LOD index to the "mesh name" group?

Code on github is up-to-date again, and below is the latest 010 file that I used to figure all this out.

  Reveal hidden contents
//------------------------------------------------
//--- 010 Editor v14.0 Binary Template
//
//      File: 
//   Authors: 
//   Version: 
//   Purpose: 
//  Category: 
// File Mask: 
//  ID Bytes: 
//   History: 
//------------------------------------------------
OutputPaneClear();LittleEndian();

typedef uint64 uint64_t;
typedef uint32 uint32_t;
typedef uint16 uint16_t;
typedef char uint8_t;
typedef uint16_t half;

local uint32 i,j,k,l,m,n,o;

struct Vector2 {
    float x, y;
};

struct Vector3 {
    float x, y, z;
};

struct Vector4 {
    float x, y, z, w;
};

struct Matrix3x4{
    Vector3 x, y, z, w;
};

struct Matrix4x4{
    Vector4 x, y, z, w;
};

struct HEADER {
    uint32 __unk00[12];
    uint32 Unknown3Offset;
    uint32 Unknown4Offset;
    uint32 Unknown5Offset;
    uint32 Unknown6Offset;
    uint32 __unk01[3];
    uint32 Unknown1Offset;
    uint32 Unknown2Offset;
    uint32 __unk02;
    uint32 UnknownOffset;
    uint32 DataTypeOffset;
    uint32 Unknown7Offset;;
    uint32 MeshInfoOffset;
    uint32 __unk04[2];
    uint32 MaterialHashMapOffset;
}Header;

if(Header.Unknown6Offset > 0) {
    FSeek(Header.Unknown6Offset);
    struct Unknown6 {
        char __unk00[16];
    }Unknown6Info;
}

if(Header.Unknown5Offset > 0) {
    FSeek(Header.Unknown5Offset);
    struct Unknown5 {
        uint32 Count;
        char __unk00[12];
        struct {
            char __unk00[8];
            float textureWidth, textureHeight, textureDepth;
            char __unk01[92];
        }Unknown5Data[Count];
    }Unknown5Info;
}

if(Header.Unknown3Offset > 0) {
    FSeek(Header.Unknown3Offset);
    struct Unknown3 {
        uint32 Count;
        struct{
            char __unk00[159];
        }Unknown3Data[Count];
    }Unknown3Info;
}

if(Header.Unknown4Offset > 0) {
    FSeek(Header.Unknown4Offset);
    struct Unknown4 {
        uint32 Count;
        struct {
            uint32 __unk00[34];
        }Unknown4Data[Count];
        // 22192 bytes total?, 56B0
    }Unknown4Info;
}

if(Header.Unknown1Offset > 0) {
    FSeek(Header.Unknown1Offset);
    struct Unknown1 {
        uint32 Count;
        uint32 __unk00;
    }Unknown1Info;
}

if(Header.Unknown2Offset > 0) {
    FSeek(Header.Unknown2Offset);
    struct Unknown2 {
        struct {
            uint32 __unk00[8];
        }Unknown2Data[3];
    }Unknown2Info;
}

if(Header.UnknownOffset > 0) {
    FSeek(Header.UnknownOffset);
    struct Unknown {
        uint32 count;
        uint32 offsets[count];
        struct {
            for (i=0; i < count; i++){
                FSeek(startof(UnknownInfo)+offsets[i]);
                struct {
                    uint32 __unk00[17];
                }UnknownData;
            }    
        }UnknownDatas;
    }UnknownInfo;
}

if(Header.DataTypeOffset > 0) {
    FSeek(Header.DataTypeOffset);
    struct dataTypeInfo {
        uint32 count;
        uint32 offsets[count];
        uint32 __identicalAcrossTypes[count];
        uint32 __unk00;
        struct dataTypes {
            for (i=0; i < count; i++){
                FSeek(startof(DataTypeInfo)+offsets[i]);
                struct dataType{
                    uint32 __unk00[2];
                    uint32 __varies00[2];
                    uint32 __unk01[3];
                    uint32 __varies01[2];
                    uint32 __unk02[3];
                    uint32 __varies02[2];
                    uint32 __unk03[2];
                    uint32 __unk04;
                    uint32 __varies03[3];
                    uint32 __unk05[2];
                    uint32 __varies04[2];
                    uint32 __unk06[3];
                    uint32 __varies05[2];
                    uint32 __unk07[3];
                    uint32 __unk08[56];
                    uint32 vertex_count;
                    uint32 vertex_stride;
                    uint32 __unk09[8];
                    uint32 index_count;
                    uint32 __unk10[5];
                    uint32 vertex_offset;
                    uint32 vertex_size;
                    uint32 index_offset;
                    uint32 index_size;
                    uint32 __unk11[4];
                }DataType;
            }    
        }DataTypes;
    }DataTypeInfo;
}

if(Header.MeshInfoOffset > 0) {
    FSeek(Header.MeshInfoOffset);
    struct {
        uint32 count;
        uint32 offsets[count];
        uint32 __identicalAcrossTypes[count];
        struct {
            for (j=0; j < MeshInfo.count; j++){
                FSeek(startof(MeshInfo.offsets[0])+MeshInfo.offsets[j]);
                struct {
                    uint32 __unk00;
                    uint32 __varies00[7];
                    uint32 __unk01;
                    uint32 __sameAsIndenticalAcrossTypesAtIdx;
                    uint32 __varies01[2];
                    uint32 __unk02;
                    uint32 __varies02;
                    uint32 dataTypeIndex;
                    uint32 __unk03[10];
                    uint32 material_count2;
                    uint32 __unk04[3];
                    uint32 material_count;
                    uint32 modelinfo_offset;
                    uint32 materials[material_count];
                    uint32 __unk05;
                    FSeek(startof(Mesh[j])+modelinfo_offset);
                    uint32 vertex_offset;
                    uint32 vertex_count;
                    uint32 index_offset;
                    uint32 index_count;
                    
                    //dataTypeInfo.dataTypes.dataType DataTypeInfo.DataTypes[dataTypeIndex];
                }Mesh;
            };
        } Meshes;
    }MeshInfo;
}

if(Header.MaterialHashMapOffset > 0) {
    FSeek(Header.MaterialHashMapOffset);
    struct {
        uint32 Count;
        uint32 DataTypeHashes[Count];
        uint64 MaterialHash[Count];
    }MaterialHashMap;
}

if(Header.Unknown7Offset > 0) {
    FSeek(Header.Unknown7Offset);
    struct Unknown7 {
        uint32 __unk00[2];
    }Unknown7Info;
}

 

Edit: Some more models that successfully extract now:

spacer.pngspacer.pngspacer.png

Edit 2: Figured out why UVs didn't work right: V is 1..0 instead of 0..1.

spacer.png

Now I have UV mapping, for one of the UV layers. Turns out there's a few more than expected.

Edit 4:

> Stride: 48

> Stride: 56

> Stride: 60

> Stride: 96

> Stride: 112

*thousandyardstare.png*

Edit 5:

Stride60: float x, y, z; uint32 __unk00; half u0, v0; half u1, v1; uint32 __unk01; uint32 __unk02[8];

Edit 6:

Well, this sucks. The vertex stride is not what decides the layout, so there's something else defining the actual layout. There's one 16 and 20 byte layout, 3 different 24 byte layout, 18 different 60 byte layouts, ... yaaay.

Quick question, do you know what ID/name I should look for while trying to extract the external parts of the super destroyer?

Link to comment
Share on other sites

Thanks for your work with the Hellextractor!  Was attempting to extract the sound files.  Many of the banks with short sound effects work fine for the most part, but particularly with the music sound bank, they come out scrambled (except for maybe 5-6 of them).  Results seem consistent with multiple unpackers (wwise audio unpacker and ravioli extractor).  I wonder if it's something where the longer files are more likely to be thrown off by formatting.

Link to comment
Share on other sites

23 hours ago, Xaymar said:

Great news, everyone! Hellextractor is now at a reasonably advanced point, and can be used for more what it's originally intended for. Lots of thanks to everyone here, as well as the contributors on other platforms. Does not yet handle file conversion, but with the new modular code, more functionality is not far away anymore.

Usage: hellextractor <mode> ...
It'll print the help if you provide an invalid mode, or provide -h as the first argument to a mode

Features

  • Generate hashes for arbitrary text using the "hash" mode.
  • Extract files from container files using the "extract" mode.
    • Filter input files using the new "-i" option.
    • Filter ouput files using the new "-f" option.
    • Provide a Type Hash Translation file using the new "-t" option.
    • Provide a Name Hash Translation file using the new "-n" option.
    • Provide a generic String Hash Translation file using the new "-s" option.
    • Make the whole thing only print text, and not do anything with the "-d" option. (Dry run!)
  • Hash Translation files now allow converting hashes back into normal text, sample files are provided here.

Also thanks to ThePotato97 on Github for automating the build process. If you want a more up to date version, log in on github, click the actions tab, and find the most recent build, and click the asset to download.

Download: on Github

 

 

Question on using the new Extractor: I'm hunting for .obj files, and I've been using your .bat method from a few days ago to accomplish this piecemeal, section by section. However, I'm trying to do the same act in one fell swoop across all files in 'data'. This is where I'm at right now:


hellextractor extract -f ".*\.obj$" -o "output" "source"

image.png.b18ec7ab3d3e66f2a06fb724755d4c84.png

Running this doesn't output .obj's from the test files I'm running it on. I can still find the .obj's using the older method (works fine, I've actually 3d printed a few of my finds already), but I'm hoping to use the updated Hellextractor to do it all in one fell swoop. Any advice?

Edited by indy_
Link to comment
Share on other sites

10 minutes ago, indy_ said:

Running this doesn't yield results I'm looking for.

  1. You need to provide translation files, see README.adoc for how to use them.
  2. The game itself does not use the .obj format. The format is .unit
  • Like 1
Link to comment
Share on other sites

I fixed a number of issues in the past hour which should fix some of the file exports, particularly exports with a gpu_resources section. Also, it seems like h3x3r was correct with the main+stream+gpu_resources idea. I'm not exactly sure how it works, but it seems like the last offset in the .unit structure (which I've dubbed __unk7) is actually the offset to the mesh data itself in the entire file. Though this does mean that math for decoding it is slightly more complicated than intended - should be easy enough to handle.

On 2/27/2024 at 7:32 PM, Unordinal said:

I used it to extract some models with rigs from VT2, but VT2 doesn't have the `.gpu_resources` files and the format's probably diverged quite a bit, since `.unit` files held the meshes/scene data/bones/etc for that game.

From what I can see, there's no actual difference between .stream and .gpu_resources. It's probably one of the features of Stingray that is rarely used, as there's likely some up and downsides to it. At least from what it looks like, it seems like there are 5 possible file offsets - thus you could likely have main, .stream, .gpu_resources, and 2 more files. The game itself appears to only be using main, .stream and .gpu_resources, though some files do have offsets recorded for the 2 missing ones. Never any size though, so far it's worked out.

On 2/27/2024 at 8:51 AM, Helldiver said:

My guess would be the stream is the main texture data, and gpu_resources is mipmaps texture data.

Surprisingly this ended up likely to be correct in a different way entirely. The 0xC0 header points at lower quality variants of the texture that appears to be streamed in using a common header (which resides in main). Not entirely sure how this works yet, but it's been consistent across files for now. And if there are no alternative versions, the pointer is set to 0xFFFFFFFF.

Edit: I'm not actually sure how the header is parsed yet. I do know that the two previously unknown values are clearly the width and height combination. I do not know if the other two values prefixing it are offset and size, or something else entirely.

Edit 2: Figured it out. The first 3 uint32s are still unknown, but then there is a section of 15 times (uint32, uint32, uint16, uint16). The first number is offset relative to the end of the DDS data, the is the size of data to load, the third is the width, the fourth is the height. The game engine therefore can simply leave the areas as 0 that it does not actually need and limit the ingame mip level to the one that is loaded. Kinda ingenious design. Explains why only the header section is in the main file.

Edit 3: And the section in gpu_resources is simply the default quality the game will load, before it streams in/out mip levels. It's once again duplicating data.

Edited by Xaymar
Link to comment
Share on other sites

9 hours ago, Moo said:

but particularly with the music sound bank, they come out scrambled (except for maybe 5-6 of them).

The music bank only contains partial audio, probably used to help referencing the separate wems, that contain the full music. On top of that, the bank is encrypted, Tools like Wwiser can take care of that with a key. I think Hogwarts Legacy used the same system.

Link to comment
Share on other sites

On 2/28/2024 at 11:47 AM, Xaymar said:
  1. You need to provide translation files, see README.adoc for how to use them.
  2. The game itself does not use the .obj format. The format is .unit

I am new to this so I want to know how to use the .unit file? trying to view the model that are extracted

Link to comment
Share on other sites

9 hours ago, nblock said:

here is the xor key for BKHD chunk's 0x8-0x10: ACBC1192387010A3

If that means, the key should look something like this (00000000 00000000 ACBC1192 387010A3 00000000), it's not working on my end.

Link to comment
Share on other sites

2 hours ago, Myrkur said:

so must be in the files somewhere

content/fac_helldivers/vehicles/combat_walker? It's the only vehicle that I have found so far, and even guessed the name for

  

3 hours ago, KIMMM said:

I am new to this so I want to know how to use the .unit file? trying to view the model that are extracted

At the current time, you use 010 Editor to inspect what we know about the format. We don't know a lot about it.

Edited by Xaymar
Link to comment
Share on other sites

14 minutes ago, Myrkur said:

the new scripts can get names now?

Not exactly. We have a way to translate file name hashes back to file names, but we do not actually have a way to turn a hash back into a name. Such is the nature of hashes - they're designed for a one way trip only.

14 minutes ago, Myrkur said:

more has been leaked recently such as a buggy, also haven't been keeping up, the new scripts can get names now?

Could be the content/fac_helldivers/vehicles/lav content/fac_helldivers/vehicles/frv vehicles? There's also content/fac_helldivers/vehicles/eagle (which iirc is the thing that is called eagle in the game too), and content/fac_helldivers/vehicles/shuttle_gunship (which is what picks you up at the end).

Link to comment
Share on other sites

Yo, I was looking for something like this and Hellextractor is looking awesome!

Took a look to see if we can get the preimage from murmurhash (from what I gathered by reading this thread this is the hash function that Stingray uses, correct me if I'm wrong) and it seems like it may be possible?

http://bitsquid.blogspot.com/2011/08/code-snippet-murmur-hash-inverse-pre.html

Murmurhash in the post is not quite the same as the one in Hellextractor but some of the lines do seem to match up.

Edited by Disturbo
Link to comment
Share on other sites

11 minutes ago, Disturbo said:

Took a look to see if we can get the preimage from murmurhash (from what I gathered by reading this thread this is the hash function that Stingray uses, correct me if I'm wrong) and it seems like it may be possible?

http://bitsquid.blogspot.com/2011/08/code-snippet-murmur-hash-inverse-pre.html

While this does get back a number that will result in the same hash, the number will be effectively pointless to us. We'd end up with a number for which the meaning is "inverse of the hash", and we couldn't even use it to guess anything about length, characters in the string, or similar. Still nice to see that the author of the engine even uploaded that in the first place.

Link to comment
Share on other sites

12 hours ago, FunnyML said:

If that means, the key should look something like this (00000000 00000000 ACBC1192 387010A3 00000000), it's not working on my end.

yes, if you're using wwiser, the full xorpad.bin it needs is 00000000 00000000 ACBC1192 387010A3 00000000 00000000
you can verify it by xoring 0x8 and if it'll be 0x8C/140 for wwise 2021.1

Link to comment
Share on other sites

Thanks for all the work guys

4 hours ago, nblock said:

yes, if you're using wwiser, the full xorpad.bin it needs is 00000000 00000000 ACBC1192 387010A3 00000000 00000000
you can verify it by xoring 0x8 and if it'll be 0x8C/140 for wwise 2021.1

Sorry but i don't know this program and its format. I'd like to extract the sound effects and music from the minigame Stratagem Hero. 

If i'm understanding this right, is there a bank file with all the sounds/musics? Does the extractor already extract this bank?

Thanks!

Link to comment
Share on other sites

16 minutes ago, Kalirhan said:

is there a bank file with all the sounds/musics?

Soundbanks aren't needed to extract the sounds.

17 minutes ago, Kalirhan said:

Does the extractor already extract this bank?

HellExtractor extracts wwise_streams. These are in wem format, which vgmstream can play and export as normal wav files.

Link to comment
Share on other sites

10 hours ago, nblock said:

yes, if you're using wwiser, the full xorpad.bin it needs is 00000000 00000000 ACBC1192 387010A3 00000000 00000000
you can verify it by xoring 0x8 and if it'll be 0x8C/140 for wwise 2021.1

Thanks for confirming that. Still not working on my end though for some reason (unsupported bank version 9183650).

Edited by FunnyML
Link to comment
Share on other sites

Has anyone managed to find and extract the Stratagems icons?  I used Hellextractor and browsed through all the DDS files GIMP could open, and I can't seem to locate them.  Not looking for meshes or anything like that, just the icons for all the Stratagems.

Link to comment
Share on other sites

6 hours ago, Ghoste said:

Okay I'm new to this mess but what would the best way for me to get the audio files

Both Hellextractor and helldivers2-rs are able to mass extract all audio without problems. DTZxPorter's tool should also be able to, but I haven't tested it, and wems and banks are considered raw data and aren't shown by default.

4 hours ago, KingJulz said:

.bnk(a bunch of .wem files)

No, but the rest the info a bank has doesn't matter to most, so pretty much.

Link to comment
Share on other sites

On 3/2/2024 at 5:39 PM, FrostyWolf said:

Has anyone managed to find and extract the Stratagems icons?  I used Hellextractor and browsed through all the DDS files GIMP could open, and I can't seem to locate them.  Not looking for meshes or anything like that, just the icons for all the Stratagems.

the file name for all strat's is c9dbfb5111ef2d89.dds

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...