View on GitHub

Metal Gear Solid V: The Phantom Pain technical information

All pictures are pretty big, right click->view image to see the details.

FV2

FV2 files (Form Variation) are used in MGSV:GZ and MGSV:TPP to change the appearance of models.

They are used to show and hide meshes, apply different textures to meshes, apply physical simulation files, attach models and probably something else.

Before going into details, I need to explain how MGSV handles models.

Models

This info relates only to player and NPC models, wasn't interested in vehicles/weapons/animals.

TPP models usually consist of 2 parts - head and body.

It allows the game to modify face without affecting the body and vice versa, i.e. equipping balaclava or changing your camo pattern.

Game uses it's own mechanism to attach head to the body, probably using .parts files (without using fv2 to do it). I think that this mechanism is used in function Player.AttachGasMask() and Player.VisibleGasMask() (mission 43 Shining Lights, when Snake puts on his respirator).

Models have mesh, bones and connection point identificators in them. These identificators are used in .fv2 in hashed form to specify the element to modify. Partial list of these hashes was compiled by YouRePrettyGood. Hashing method is unknown. Only first 4 bytes of these hashes are used in fv2, full hashes can be found in fmdl.


It means that in order to hide some mesh on Snake's face we need to:

Unfortunately there are problems.
Problem #1: it is hard to find hashed name of mesh, fmdl is not fully explored (or at least I am unaware of it).
Problem #2: you need a command to show/hide meshes.

FV2 commands

In order to manipulate parts of model, fv2 needs not only names of these parts, but also commands on how to manipulate them.

These commands are also hashed in the same way as mesh names, but there are no unhashed names. A couple of them are included in list above (Base_Tex_SRGB for example), but others are a complete mystery. Command are usually followed by a link to hashed filepath. Providing wrong filepath or wrong filetype will usually crash the game, i.e. the 'apply texture' command accepts only textures, 'hide mesh' accepts hashed mesh names.

You will also need to use right syntax, which differs from command to command. This is also a big problem.

Format structure

First structure was made by YouRePrettyGood. Most of that info is right, but some sections are doubtful. Please keep in mind that I digged format with only one purpose - to combine 2 fmdls, so some bytes were ignored.

I am using file master\chunk0\fpks\plfova_sna0_face6_v00_fpk\Assets\tpp\fova\chara\sna\sna0_face6_v00.fv2 as an example.


Header
Index

Index contains info about section offsets, amount of referenced files, amount of filetypes (?).

Address: 0x8-0x27.

Section 1

Section 1 contains links to files from section 4 and commands on how to use them. This differs from file to file, sometimes section 1 is really small, sometimes it is really big. Usually it has a link to the main model, 0000 in our case.

Address: 0x28-until the beginning of section 2.

In the file sna0_face6_v00.fv2 section 1 is pretty small and has only 2 entries.

Section 2

Section 2 also contains links to files from section 4 (2 bytes per link) and commands on how to use them (4 bytes per command). This also differs from file to file, sometimes section 2 is empty, sometimes it is really big. It has links to models and textures. In our case it is empty.

Address: referenced in index section.

Section 3

Section 3 is always empty, as YouRePrettyGood claims. I haven't seen section 3 with elements either.

Address: referenced in index section.

Section 4

Section 4 contains reversed hashed filenames, 8 bytes long. They are hashed using CityHash64.

Address: referenced in index section.

How to read hashes: Now you can use that hash to find the corresponding file like this:
unknown@vault MINGW64 /j/mgs/mgsv_lua_dump/tpp/master (master)
$ grep -ri --include=*\.txt --include=*\.inf a588a1ea5a42
inf/0/00/fpks/output.txt:tpp/chara/sna/Scenes/sna0_face6_cov    a588a1ea5a42
inf/chunk0/fpks/output.txt:tpp/chara/sna/Scenes/sna0_face6_cov  a588a1ea5a42
I am using grep bundled with git for windows. Filehashes for all files in .fpk(d) and .dat files can be found at this repository. You can use github search as well, but sometimes it refuses to find text, so I advise you to clone the repo and search for hashes locally. If you cannot find the file, remove a couple of symbols from the beginning, i.e. 8a1ea5a42.

Getting hashes for new files is also possible by using mgsv_path_hasher.

Extensions are (probably) constant and you can easily recognize them:

HashType
A2 84fmdl
91 34 / 93 34sim
6B 15 / 69 15 / 68 15ftex (usually texture/mask/another mask)
Example image, section 4 full of textures:

Commands

As stated before, FV2 files have commands that do various actions with linked files. Known commands:

HashCommandCount
53A31F5ETENTION_CHEST6
DFA48733Base_Tex_LIN12
AE22FDC3TENSION_LUARM30
41CAD26BTENSION_RUARM30
2F8DA4F0TensionSubNormalMap_Tex_NRM74
E507F094TENSION_EYER76
7CFDFE5ETENSION_EYEL76
9B283BFDTENSION_FHEAD76
AFD90D35TENSION_CHEEKR76
95FA7041TENSION_CHEEKL76
C6D67A80TENSION_NECK82
0AF744DDTranslucent_Tex_LIN107
7E6BAFC4TENSION_RTHIGH137
777C4B49TENSION_LTHIGH137
06DBACB8TENTION_LUARM152
2F780EBETENTION_RUARM152
A619A499MatParamIndex_3153
8E402E96VShift_UV176
EB160E5EUShift_UV176
CBB6CB7BDirty_Tex_LIN292
0E2F5F06MatParamIndex_2341
87F69B21Base_Tex2_SRGB570
77206689MatParamIndex_1593
E01A5105NormalMap_Tex_NRM678
FF8784E3MatParamIndex_0782
8558FD2DMatParamMap_Tex_LIN1450
728B1AF9LayerMask_Tex_LIN1972
E6DB2698Layer_Tex_SRGB2124
0EB1986BSpecularMap_Tex_LIN2342
611BE87BBase_Tex_SRGB3919
0E642277mostly used in MGO in hat fv219
EB77FEBFused for combining 2 models together31
Table of targets:
HashTargetCount
4896BADFMESH_eqpt2_DCL_OL0_IV4
8EECFA93MESH_bagU4
DEA095E4MESH_greF_LOD64
6E9ECA61MESH_bag_B1_IV4
F82C8371head_hair6
6849DFDFANIME_EYE6
01D16FA9fabric_mid_glossy6
8B9295EFbody_fox6
B9DB0121body26
903B0217skin_white_male26
D0DF8575body36
D16E43F2mout_main0_def6
D1C2B74Eface_patch6
DD6D7773body16
CF07DACDope_glove_L_mat8
ADCE4DDCope_glove_R_mat8
8CFD744EMESH_arm9
E5FFD741MESH_rubberglobes_5fingers_IV13
96715738dds0_body0_mat13
93F8BC81dds0_eqip0_mat17
989F16BF(ALWAYS FIRST SKL HASH)54
0185E8A9MESH_head97
4E7638DEhair101
1BDD0582skin236
06C7BF63body380
49A05886eye548

These are commands that I've found in fv2 by using tables provided by YouRePrettyGood. Division between targets and command is based on command's name. Count numbers may be slightly off (±5) due to mods and me being unaccurate with files.

Sometimes commands have really weird syntax. Let's take a look at chunk0\plparts_hospital_fpk\Assets\tpp\fova\chara\sna\sna1_v04.fv2. This is fova responsible for adding dirt and cuts to snake during prologue.

As we can see, commands (1 and 2) are repeating without any file links. Links are put separately from commands (3) in some sort of array. The amount of FFFF-padding is also huge. I have a feeling that this is related to this, but I am not interested in textures that much.
Side note: this fova is using 7 textures:

2898587abb9ab.ftexface
10a9f5617b387.ftexfacemask
25b1e1d8ac451.ftexbandage
19db40a9af383.ftexbody
2be12abeb1323.ftexbodymask
25bd197efbf1e.ftexpants
3fc288b2d1607.ftexpants mask
Only one texture for bandage, why mask is not needed? This fova also has a command to toggle meshes on and off, should look into it (for possible removal of snake's eyepatch for example) .

Important commands

After weeks of digging, I've found 2 commands which apparently allow you to attach models to other models.

HashCommandCount
0E642277mostly used in MGO in hat fv219
EB77FEBFused for combining 2 models together31

First hash (0E642277) is used in both MGO and TPP. It's present in mgo hat fv2, using it in TPP fovas does nothing, so it may be MGO-only. Still, it should be noted.

Second hash (EB77FEBF) is what I was looking for. It takes 2 arguments:

It should be noted that this command puts referenced model in the center of base model.

Example:

If you don't provide the command, leaving just a link to the fmdl file, it will be rendered at your feet. Link has to be put in some special order, moving it by 2 bytes left or right makes it disappear. Command has to be present in some special position as well, otherwise nothing will render (or the game will crash).

In the example above you can see a link to head model (0000) without any commands and yet head is placed in the right place. However, replacing the head model with another non-head model (I used mgo hats) moves the new 'head' to your feet.

I have 2 explanations for that:

Model rendering also depends on byte 0x24 and 0x25 in index section. In order to render both models both bytes have to have a value of 01. I was unable to find a relation between the amount of models and the fact of render.

ByteValueNote
0x2400No models rendered
0x2401Both models rendered
0x2402Both models rendered
0x2500New model moves to feet
0x2501Both models rendered

There are only 18 fv2 files with 2 models which can be used for further research on that command. My mods are based on cm_unq_v003_eye1.fv2 since it's the cleanest one, other fovas such as pfs ones have a lot of stuff inside.

master\chunk0\cm_unq_v000_eye1_fpk\Assets\tpp\fova\common_source\chara\cm_head\face\cm_unq_v000_eye1.fv2
master\chunk0\cm_unq_v003_eye1_fpk\Assets\tpp\fova\common_source\chara\cm_head\face\cm_unq_v003_eye1.fv2
master\chunk0\cm_unq_v006_eye0_fpk\Assets\tpp\fova\common_source\chara\cm_head\face\cm_unq_v006_eye0.fv2
master\chunk0\cm_unq_v007_eye0_fpk\Assets\tpp\fova\common_source\chara\cm_head\face\cm_unq_v007_eye0.fv2
master\chunk0\plfova_sna0_arm1_v00_fpk\Assets\tpp\fova\chara\sna\sna0_arm1_v00.fv2
master\chunk0\plfova_sna0_arm2_v00_fpk\Assets\tpp\fova\chara\sna\sna0_arm2_v00.fv2
master\chunk1\ddr0_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr0_main0_v00.fv2
master\chunk1\ddr0_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr0_main0_v02.fv2
master\chunk1\ddr0_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr0_main1_v00.fv2
master\chunk1\ddr0_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr0_main1_v04.fv2
master\chunk1\ddr1_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr1_main0_v02.fv2
master\chunk1\ddr1_main0_v00_fpk\Assets\tpp\fova\chara\dds\ddr1_main1_v02.fv2
master\chunk1\pfa0_main0_v00_fpk\Assets\tpp\fova\chara\pfs\pfa0_v00_a.fv2
master\chunk1\pfa0_main0_v00_fpk\Assets\tpp\fova\chara\pfs\pfa0_v00_b.fv2
master\chunk1\pfa0_main0_v00_fpk\Assets\tpp\fova\chara\pfs\pfa0_v00_c.fv2
master\chunk1\sva0_main0_v00_fpk\Assets\tpp\fova\chara\svs\sva0_v00_a.fv2
master\chunk2\f30011_fpk\Assets\tpp\fova\chara\vol\vol2_v01.fv2
master\chunk4\s10140_d03_fpk\Assets\tpp\fova\chara\dds\ddr0_main0_v00.fv2
There are no fv2 with 3 and more models. My attempts in adding a third one were futile, game either crashed or ignored new fmdls. Feel free to try yourself.

Exploring the fv2

Copy hex values of fv2 file to your favorite text editor, split file by sections, then use the list of hashes to replace all known hashes with text. It will make file much clearer:

464F563277696E0150005000000003005000000000000000000000000000000000000000010100000000FFFFFFFFFFFF0100FFFFEB77FEBF989F16BF0200FFFFFFFFFFFFFFFFFFFF00000000000000005F7E60E40985A2849CDC9C9A4501913475F64D17D875A384
becomes
header
464F563277696E01

index
    5000 - section 2 offset
    5000 - section 4 offset
    0000 - section 2 entries
    0300 - section 4 entries
    5000 - section 3 offset
    0000 - section 3 entries
unknown stuff
    0000 0000 0000 0000 0000 0000 0000 0000 
byte 24-25
    0101 
unknown stuff
    0000

section 1
    0000FFFF - link to head .fmdl
    FFFFFFFF - padding?
    0100FFFF - link to .sim
    Attach_fmdl - command
    (ALWAYS FIRST SKL HASH) - skl target
    0200FFFF - link to new .fmdl
    FFFFFFFF - padding?
    FFFFFFFF - padding?
    00000000 - padding?
    00000000 - padding?

section 4
    5F7E60E40985A284 - head .fmdl
    9CDC9C9A45019134 - .sim
    75F64D17D875A384 - new .fmdl
You can also use script for Hex Workshop by YouRePrettyGood, but it is really slow on some fv2. It is useful for big files, but text editor is faster.

Applying the fova

After making modifications you need to apply the fova to your model. However there are problems. Player.ApplyFormVariationWithFile doesn't work with head fovas at all, but only with body fovas. There might be an additional parameter to that function to choose the region, but I was unable to find one. It means that for head you will have to replace existing head fovas. Fortunately there are only 16 of them (not including battledress headcovers, mgs1 snake etc.)

FilenameNote
plfova_plf0_msk0_v00.fpkFemale DD soldier (+chicken mask)
plfova_plf0_msk1_v00.fpkFemale DD soldier (+chick mask)
plfova_sna0_face0_v00.fpkSmall horn
plfova_sna0_face1_v00.fpkMedium horn
plfova_sna0_face2_v00.fpkLarge horn
plfova_sna0_face4_v00.fpkSmall horn + bandana
plfova_sna0_face5_v00.fpkMedium horn + bandana
plfova_sna0_face6_v00.fpkLarge horn + bandana
plfova_sna0_msk0_v00.fpkMale DD soldier/Snake (+chicken mask)
plfova_sna0_msk1_v00.fpkMale DD soldier/Snake (+chick mask)
plfova_sna7_msk0_v00.fpkParasite suit (+chicken mask)
plfova_sna7_msk1_v00.fpkParasite suit (+chick mask)
Gold/Silver heads below(?)----------
plfova_sna9_face0_v00.fpkSmall horn snake
plfova_sna9_face1_v00.fpkSnake (bandana+mullet)
plfova_sna9_face2_v00.fpkSnake (bandana+mullet)
plfova_sna9_face3_v00.fpkSnake (bandana+mullet)
Fovas with (+chick/chicken mask) have masks models in them. By changing chicken fova, you will change your regular appearance (even when you are not in chicken mode), chick fova is for chick hat mode only.

Let's use plfova_sna0_face4_v00 as an example. Original:

464F563277696E0138004000000002004000000000000000000000000000000000000000010000000000FFFFFFFFFFFF0100FFFF0000000000000000000000005F7E60E40985A2849CDC9C9A45019134
Modded:
464F563277696E0150005000000003005000000000000000000000000000000000000000010100000000FFFFFFFFFFFF0100FFFFEB77FEBF989F16BF0200FFFFFFFFFFFFFFFFFFFF00000000000000005F7E60E40985A2849CDC9C9A450191345227B06BAAA9A384

Diff:

Index: changed addresses for section 2 and section 4 since we've added a new command (3800 4000 -> 5000 5000). Since there is a new file hash, amount of section 4 entries has to be changed as well (0200 -> 0300). Bytes 0x24-0x25 must be 0101 instead of 0100 to render models.
Section 1: command EB77FEBF to attach model, target 989F16BF, link to the file #0200 and some FFFF padding.
Section 4: new file 5227B06BAAA9A384 - tpp\chara\dds\Scenes\dds3_eqhd3_cov.fmdl. This a DD beret taken from chunk2\s10050_d02_fpk. You will need to add that file to plfova_sna0_face4_v00.fpk or tpp\pack\resident\resident00.fpk. I suggest using resident00.fpk to avoid duplicating models saving disk size.

Result:

There is some clipping, but this is definitely working. There are problems with distance between added cosmetic and head model. Distance changes because of player's base stance. Male and female soldiers have different poses. I think it was counted for MGO cosmetics (hence male and female hat versions), but there are no 'legal' cosmetics for TPP. Poses also change for different outfits (same head fova):

Another problem: attached hats are not counted as player's model. They are not disappearing in first person, obstructing view. It it unavoidable for soldier-wide cosmetics (see fmdl fixes below).

Soldier-wide cosmetics' fixes

As stated above, you can change appearance of all your soldiers by modifying fova with chicken hat (plfova_plf0_msk0_v00.fpk, plfova_sna0_msk0_v00.fpk). But only 2 models work with it without having to enable chicken mode:

Other models don't appear unless you turn on chicken mode. There is a fix, which requires a little bit of hex editing. Simply replace
MESH_ROOT (ALWAYS FIRST MESH HASH) 35D648F974580000
with
MESH_ROOT_V2 782A0D778E800000
in fmdl. That will make model to render no matter what. Game will crash if you change player character (i.e. snake-ddsoldier-snake).

MESH_ROOT_V2 was found by comparing glasses models using 3dsmax fmdl script by Jayveerk. I had to rewrite it in Python 2 to avoid launching 3dsmax every time I need to look at model's structure - link. I've also expanded fmdl hashes list a little with hashes from skullface's hat (GZ and TPP versions, 62e7efe72cb98bb3d8b88977dcc9414f.fmdl - wsp0_eqhd0_def.fmdl) and both glasses. New hashes list.

Important note on visibility:
Models used in *face* fovas can be hidden in first-person mode - just use MESH_ROOT for them and they will be hidden.
However, models used in *msk* fovas must have MESH_ROOT_V2 to be shown and will obstruct your vision. If you want to play with glasses, I suggest adding them to *face* fovas and leave hats for *msk* fova

Getting more hashes / exploring fmdl

Python script above was made to print fmdl sections in human form. Most sections have descriptions from the author in the script. Notable sections:

If you want to get more command hashes and their names, you can compare similar files from GZ and TPP. GZ fmdls have names at the end of the file, while TPP has them in section 1.22.

You can also move fmdls without 3dsmax with python script:

move_fmdl = True
print_sections = True
offset = [0, 0, 0.1]
parse_fmdl(r'C:\1.fmdl', print_sections, move_fmdl, offset)
will make: C:\1_moved.fmdl and C:\1_sections.txt. There are some offsets at the beginning of the script.

Script can apply mesh fixes as well: fmdl_mesh_fix(r'C:\1.fmdl') will overwrite the file with fixed mesh.

MGO hats

MGO hats can be added using the same way. Problems:

In the first image you can clearly see the offset between cap and glasses+beret combo (beret is the violet ugly thing). Second image shows that offset ingame. It can be fixed by providing frdv file and adding it to your fv2 file.

How to mod MGO hats

Find a model you want to use (let's say hat0_main0_def.fmdl). Parse it with fmdl script to find out shift for section 1.22 and 1.21 (print_sections=True). Open fmdl in hex editor, replace 35D648F974580000 with 782A0D778E800000 (section 1.22) to make it visible.


Copy hashes from section 1.21 and reverse them (order is important):
156b3dcb9e506351
156855d8625e430c
156884483e1c10b0
156938337d71ab4b
1569886cb15d7938
Unpack texture0.dat using MGSV_QAR_TOOL. Open texture0.inf and search for last 8 characters from hashes (i.e. 625e430c). Copy files you found (33dcb9e506351.2.ftexs and 33dcb9e506351.3.ftexs) to any folder you like. Find related pftxs - hat_m00.pftxs. Unpack it using gsz_tool. Gather all textures in one folder. Rename then in the same order as they were in fmdl:
156b3dcb9e506351 - tex1.ftex
156855d8625e430c - tex2.ftex
156884483e1c10b0 - tex3.ftex
156938337d71ab4b - tex4.ftex
1569886cb15d7938 - tex5.ftex
Rename ftexs in the same way (tex1.1.ftexs).
Unpack 0\00\Assets\tpp\pack\resident\resident00.pftxs with gsz_tool. Go to Assets\tpp\hats\textures\mgo\allclass\hat0 (create missing folders). Put your textures there. Create test.dat.xml:
<?xml version="1.0"?>
<ArchiveFile xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xsi:type="QarFile" Name="test.dat" Flags="3150304">
  <Entries>
    <Entry FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1.ftex" Compressed="false" />
    <Entry FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex2.ftex" Compressed="false" />
    <Entry FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex3.ftex" Compressed="false" />
    <Entry FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex4.ftex" Compressed="false" />
  </Entries>
</ArchiveFile>
Put your new renamed textures into test_dat directory with same paths. Pack it into .dat file with gsz_tool. Add
/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1
/Assets/tpp/hats/textures/mgo/allclass/hat0/tex2
/Assets/tpp/hats/textures/mgo/allclass/hat0/tex3
/Assets/tpp/hats/textures/mgo/allclass/hat0/tex4
to your dictionary.txt for MGSV_QAR_TOOL. Unpack test.dat, open test.inf. Get hashes out of it (don't forget to reverse them):
08 ff 7e cd b4 81 6a 15 | Assets\tpp\hats\textures\mgo\allclass\hat0\tex1.ftex
64 0e 7e ab 16 9e 6a 15 | Assets\tpp\hats\textures\mgo\allclass\hat0\tex2.ftex
cd 84 cc a6 d7 f2 6b 15 | Assets\tpp\hats\textures\mgo\allclass\hat0\tex3.ftex
d3 73 85 dc df 5b 6b 15 | Assets\tpp\hats\textures\mgo\allclass\hat0\tex4.ftex
70 dc d6 27 ec 93 69 15 | Assets\tpp\hats\textures\mgo\allclass\hat0\tex5.ftex
Open hat0_main0_def.fmdl with your favorite hex editor. Go to the address of section 1.21 (provided by python script). Replace hashes in the same order, one by one. Save fmdl.
Add textures to resident00.pftxs:
<Entry>
    <Entries xmlns="Pftxs">
        <EntryData FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1.ftex" />
        <EntryData FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1.1.ftexs" />
        <EntryData FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1.2.ftexs" />
        <EntryData FilePath="/Assets/tpp/hats/textures/mgo/allclass/hat0/tex1.3.ftexs" />
    </Entries>
</Entry>
Pack resident.pftxs. Add your model to resident00.fpk using gsz_tool or MGSV_FPK_TOOL. Calculate hash for fmdl using method above for textures - this is important. Edit fv2 template, replace last 8 bytes (FFFFFFFFFFFFFFFF) with fmdl hash. Pack fv2. Launch the game. If the model is in the wrong position, move it using either python script, or manually in 3dsmax with vertex script (how?).

For the lazy

I've ported almost all MGO hats to TPP.
https://imgur.com/a/5rVAE - previews.
You can find models and textures here - mega.
How to use:

FPK/PFTXS structure (truncated):
You can use combinations of 2 fovas to have many cosmetics on snake (plfova_sna0_msk0_v00 + plfova_sna0_face). I also believe that you can have up to 4 cosmetics on dd soldiers (by editing hair, face deco and skin fovas for one particular soldier - see bioengie).
Some hats have clipping with your model (especially full-head models). It happens because soldiers have different head shapes and you need to adjust model coordinates for every soldier. Can be solved by making a proper frdv file (or just move them with python script).
Physics are missing for some models. You can enable them by adding .sim file to fv2. You can find .sim files in models/ddmale/sim directory.
Three hats are broken - MGO dd beret, afro and ponytail. Maybe it has something to do with MESH_ROOT_V2, I was unable to make them look good. Use beret from tpp instead.

Pics: