Assassin's Creed Odyssey

Goal

A common complaint from players is that the majority of mods are released for Resorep.  It's a powerful tool, but it's incompatible with Reshade and can be difficult to get working.  Locating textures is painful and the ID number format is tedious for both modders and users to manage.  I'm continually asked to make Forger versions of existing mods, which I wouldn't do without a direct request from the author.

My goal for writing this article is to provide another path for authors to release texture mods as forger2 files, either with or without support for Resorep.  Feel free to comment with any information you think would be helpful for other modders.

What will I need?

These are the things I expect to provide help with and answer questions about:


Other things you'll need that I will not be providing support for here:

  • (Very highly recommended) Experience with Resorep
  • A graphics editor of your choice that works with DDS files
  • A basic knowledge of DDS file and compression formats
  • A good hex editor like 010 Editor.  It includes a template that breaks down the DDS file header
  • Familiarity with JSON syntax or a text editor that can highlight parsing errors for you (like Visual Studio)

Getting Started

  • Read the Introduction to Forger article.  The most important information for you is at the top, but you should have a basic understanding of the patch file format as well.
  • Browse through the game assets with Blacksmith and familiarize yourself with the layout.  DataPC_SharedGroup_00 is the most complete and is the best forge to search for player, NPC, clothing and armor assets.
  • (optional) Find your target texture filenames with Resorep if you plan to support it
  • Find the target texture in Blacksmith.
    • Most of the items start with ACD_ and can be found with the Texture Map type.
    • Common abbreviations are:  CHM/CHF/CHS/CHA = character male/female/shared/animal, PLA = player, SHR = shared (usually NPC).  
    • Armor pieces usually have an ID number common to all in the set:  H01 (head), T01 (torso), W01 (waist), etc.  You can usually find the ID number using the hash id's (aka  FileIDs) from the CheatEngine Inventory list.  Simply convert them from hex to decimal, search the FileID data list, and usually the ID number is part of the asset name:  e.g.  Assassin's Belt waist 0x0000018562B759FA = FileID 1672398461434 = ACD_Waist_PLA_81_C_Hero_Of_Athen/ARMOR_SETTINGS = ACD_CHM_PLA_W81_A01_Belt_Mask1Map/TEXTURE_MAP, etc.
    • Texture names usually include DiffuseMap, NormalMap, Mask1Map, or (occasionally) Mask2Map
  • Save the texture to a DDS file with Blacksmith and take note of the asset name and FileID (on the status bar, double-click to copy to clipboard).
  • Open the DDS with a hex editor that will break down the header format for you.  In the 010 template, the mipmap data starts immediately after the last header byte.

What are mipmaps?

In 3D games, performance is key.  Zooming in and out on every texture as you move around the game world would get very computationally expensive, so textures are resized ahead of time at the expense of a little texture memory:  https://en.wikipedia.org/wiki/Mipmap.  Your mod will need to replace your target texture at every mipmap level.  Otherwise, players will see your texture change depending on how close or far away from it they are.

In Odyssey, every texture has a base asset of type TEXTURE_MAP with a unique FileID and name.  Texture maps store multiple mipmap levels one after another from largest to smallest, exactly as in the DDS file format.  Many textures also include 2 additional COMPILED_MIP assets having one higher-resolution mipmap each.  They correspond to the Texture Detail graphics setting and have names suffixed with _TopMip_1 for Medium detail and _TopMip_0 for High.  Resorep should export the 3 textures as separate files as it encounters them.  Blacksmith (as of v1.8) displays and saves only _TopMip_0 if it's available or all of the mips from the texture map.

Because the dimensions of textures are always powers of 2 in the game, there's a helpful mathematical property to be aware of.  DXT compression for textures uses 8 or 16 bytes per pixel, so the total number of bytes in a mipmap level will also be a power of 2.  Looking an example 2048x2048 DDS texture with mipmaps, there are 5592432 bytes of data after the header.  Converting this number to binary reveals a pattern:

10101010101010101110000 = 5592432
10000000000000000000000 = 4194304 = 2 ^ 22 bytes (mipmap 0)
+ 100000000000000000000 = 1048576 = 2 ^ 20 bytes (mipmap 1)
+   1000000000000000000 =  262144 = 2 ^ 18 bytes (mipmap 2)
+     10000000000000000 =  ‭ 65536 = 2 ^ 16 bytes (mipmap 3)
‭+       100000000000000 =  ‭ 16384 = 2 ^ 14 bytes (mipmap 4)‭
...

The pattern breaks down at the lowest levels due to the minimum byte requirement per mip level in DXT (8 or 16).  But given the total size, we could have quickly calculated the size of the first level:  log(5592432) / log(2) = 22.415.  We know at a glance that 22 "and some change" means that the first mip is 2 ^ 22 in size, the next will be a quarter (1 / 2 ^ 2) of that size, and so on.  If we added them all together, we would get about 2 ^ 22.415 bytes.

Ok, now what?

  • Image editor settings - Next, you'll need to verify that your image editor can save your edited texture in the same format as the original.  The game will be less forgiving than Resorep about differences.  Open your Blacksmith saved image and immediately re-save it under a new filename.  Use your hex editor to compare the first few lines of mip data (ignore the headers) between the files.  They don't need to match byte for byte, but they should be very similar if the DXT level and included channels are a match.  If they don't, change your Save settings until they do, or try different image editor.  You should also be aware of the number of mip levels your editor generates and how to change it.
  • Create a test image - If you're not starting with an existing texture, you'll need to create a modified texture that will be easy to recognize in game.  Scribble across each channel of your image in places that will be visible and save a copy with 1 mipmap level.
  • Determine the offset of your mipmap data - Open the test image with your hex editor and find the start of the data following the header.  It should be either at offset 0x94 (with a DX10 header) or 0x80 (without).

Creating a testing .forger2 patch

Create a new text file ForgerPatches\<your patch name>.forger2 using the downloadable JSON template in this mod files:

{
"Format": "ForgerPatch2",
"GameEXE": "ACOdyssey",
"CollectionTitle": "Texture template",
"CollectionVersion": 1,
"CollectionID": "Texture template unique ID",
"Patches": [
{
"PatchTitle": "Mod title",
"PatchDescription": "Texture template",
"PatchID": "Mod unique ID",
"PatchVersion": 1,
"Targets": [
{
"File": "
path\\to\\some.forge",
"FileID":
1234,
"Edits": [
{
"OffsetHex": "
5678",
"DataFile": "my texture.dds",
"DataFileOffsetHex": "94"
}
]
}
]
}
]
}


Begin filling in values for the data you know:

  • Make up CollectionID and PatchID strings that will be unique to your mod
  • Copy your DDS file into ForgerPatches and set the DataFile value (new in Forger v2.6)
  • Set the DataFileOffset to the value you determined earlier.  This is where Forger will start reading.
  • Your DDS file should have only a single mipmap, so Forger should be okay read to the end of the file.  If this is not the case for whatever reason, you can add a DataFileSize value to tell Forger how much to read.

To find the remaining values, consult the data files I provide in this mod.

There is rarely just a single a copy of any game asset.  Most common assets are duplicated up to 5 or 6 times in different forge files (thank Ubisoft for their sloppy distribution).  Only one copy is active at any time in the game, but the only way to be certain which one it is is by trail and error.  To make this easier, I've provided a FileID data in this mod in the format:

<target fileid>/<asset name>/<asset type>,<path to forge file>/<asset name>-<fileid>.acod, ...

Search the file for your target fileID matching the beginning of the line.  Following it is a list of copies of the asset as filenames, ordered from newest to oldest.  The active copy is usually the newest, so always try the first one first.

File - Fill in the path to the forge file for the given copy.  Note that backslashes in the string must be doubled up in JSON (eg:  dlc_50\\DataPC_50_dlc.forge)

FileID - copy the fileid number from the path into your patch file.  This may be different than the target ID you were looking for, and might also vary between the different copies.  This means that your target has been stored inside of another entry (you can confirm this in Blacksmith)

Next, consult the Textures data file that provides information about each individual copy:

<path to forge file>/<asset name>-<fileid>.acod TEXTURE_MAP texture=<asset name>/<target fileid> mipcount=<mips> mipoffsethex=<data offset> mipsize=<bytes>
<path to forge file>/<asset name>-<fileid>.acod COMPILED_MIP texture=<asset name>/<target fileid> mipoffsethex=<data offset> mipsize=<bytes> parent=<texture map fileid>

OffsetHex - mipoffsethex indicates where in the asset the mip data is located.  You may need to verify mipsize matches your DDS file mip data size.

Trying it out

Once everything is in place, start up Forger.  If your .forger2 file is parsed successfully, you should see your patch appear underneath the description you gave for it.  If not, there are several errors you might see:

  • "Invalid escape sequence" - Backslashes in your path strings need to be doubled up: \\
  • "Patch file version conflict" - You have multiple *.forger2 files in ForgerPatches that have the same CollectionID and CollectionVersion.  Increment the version number or rename unused files so they no longer match: *.forger2
  • Other parsing errors - The JSON library will give detailed errors.  If you're unable to find the problem, use a text editor that highlight syntax issues for you (like Visual Studio)

Now try checking the box to install the texture.  Some common errors are:

  • "Forge file not found" - Verify that file path you provided actually exists in your game folder.  It might belong to a DLC that you don't own.  The path also needs to include the .forge extension.
  • "FileEntry not found in ..." - Verify that your patch file references the FileID from the data files and not the target FileID.  They are not always the same.
  • "DataFile not found" - Did you forget to copy your source DDS to ForgerPatches?
  • "Memory stream is not expandable" - The patch is instructing Forger to write more data than there is in the asset.  You may be writing to the wrong asset, using the wrong source file, or have mismatched content.  Verify the sizes of the source and destination mip data.  Your source DDS that you expected to have a single mipmap might have more created by your image editor.  You could still use such a file if you're not able to disable mipmap generation, but you would need to add a DataFileSize attribute to tell Forger when to stop reading (use mipmap math to find the size of each level)

Next try launching the game.  Set your graphics to High and verify that the appropriate Texture Detail slider is set to High.  This should help ensure that TopMip_0 will be used and appear on the screen.  Find your texture in game and use the camera controls (F3) to zoom close to it. Hopefully your will see your changes and can proceed.

If the game freezes or crashes at any point, assume your patch has corrupted the asset.  Don't panic, just uncheck (uninstall) your patch to reverse the change, then review all of your values for mistakes.  Verify that the scenario listed above for error "Memory stream is not expandable" doesn't apply to your situation (even if there was no error).  After you've made corrections to your .forger2 file, click the Rescan button before reinstalling it.  If you get into trouble and the game still crashes, try Reset All Forges first, then verify the game files with Steam/Uplay.

If your changes are not visible, assume the most recent copy of the file is not the correct one.  You can try the second copy, or better, try a few at time.  The target object { "File": ... } inside "Targets": [ ... ] can be duplicated several times (separate them with commas).  It won't hurt anything to modify the inactive textures, but for simplicity it's best to include only the active copy when you release your mod.

You can continue on to the remaining mip levels at any time, or continue to refine your _TopMip_0 texture.  The best workflow to avoid problems is:

  • Make changes to the .forger2 file, then Rescan in Forger
  • Make changes to the source DDS files, then install/apply in Forger
  • Test in-game
  • Uninstall/remove in Forger before making more changes


Medium texture detail (_TopMip_1) and beyond

Most often, the active Medium texture is in the same forge file as _TopMip_0:

  • Consult the data files to find the _TopMip_1 asset data and the corresponding copy.
  • In your .forger2 file, make a copy of the target object { "File": ... } and separate them with a comma.  Edit your values to reflect _TopMip_1.
  • If you're not working with an existing Resorep texture, save a copy of your mod texture that has been reduced to 50% of width and height (25% total pixels).  It doesn't need to be the final version, just something you can recognize in game.
  • Save, Rescan or relaunch Forger and apply the patch.
  • In the game, set the appropriate Texture Detail setting to Medium, and confirm with the camera controls (F3) that your image appears when zoomed in close.

The active TEXTURE_MAP object with the remaining mips is often not in the same forge file as the TopMip:

  • Consult the Textures data file to find its FileID (parent attribute of each TopMip).  Try the most recently modified copy first
  • Create a new target object in your .forger2 and edit the values
  • To create the texture, reduce your Medium texture by 50%.  This time, enable your image editor's automatic mipmap generator when saving.  You should compare the total mipmap count reported in the editor with the mipcount in the Texture data file.
  • Save, Rescan or re-launch Forger and apply the patch
  • In the game, set the appropriate Texture Detail setting to Low for testing

When all 3 are installed and in place, switch back to High detail.  Zoom all the way in and out on your texture to verify the transition from high to low mipmaps looks smooth.


Packaging the mod for release

If you're maintaining compatibility with Resorep, simply add the .forger2 file to your existing archive.  Your mod users can either extract it to their Resorep folder where the extra file will be ignored, or to their ForgerPatches folder.

If you're not targeting Resorep, package your texture files into a subfolder and give them meaningful names.

Add Forger to your mods requirements links and direct your users to get version 2.6 or later


What if I don't need compatibility with Resorep?

You can combine all of the miplevels into a single source DDS file.  Simply save the image in Blacksmith (being a _TopMip_0) and let your image editor generate all of the mipmaps levels.  This way you have only one texture to edit.

In your .forger2 file, you'll need to need to modify the DataFileOffset and add DataFileSize:

"FileID": <file id for _TopMip_0>,
"Edits": [
{
"OffsetHex": "4A",
"DataFile": "<texture>.dds",
"DataFileOffsetHex": "94",
"DataFileSize": 4194304
}

For the largest mip, we start at the beginning of the data (0x94) as usual.  We'll also use the example from the mipmap section to find the size of the first mip and specify it as the DataFileSize.

Next, for _TopMip_1, we'll have to start reading data at the second mipmap.  Our offset will be 0x94 + 4194304 (or 0x‭400000‬) = ‭0x400094‬:

"FileID": <file id for _TopMip_1>,
"Edits": [
{
"OffsetHex": "4A",
"DataFile": "<texture>.dds",
"DataFileOffsetHex": "400094",
"DataFileSize": 1048576


Last, for the texture map, our offset will be 0x400094 + 1048576 (or 0x100000) = 0x500094.  In this case we want Forger to read to the end of the file, so there's no need to specify DataFileSize:

"FileID": <file id for texture_map>,
"Edits": [
{
"OffsetHex": "E3",
"DataFile": "<texture>.dds",
"DataFileOffsetHex": "500094",



What happens when new updates are released for the game?

You won't typically need to make changes after minor releases.  Major releases include new forge file contents in the form of a new DLC numbers (eg.  dlc_xx\DataPC_xx_dlc.forge).  Most the time you only need to change the target forge File and test.  I intend to provide new data files in this mod within 1-2 days of a major release, but you could also determine the "active" forge with a little guesswork.

Looking at the create date for subfolders in my game, dlc_43, dlc_53, and dlc_55 were new with the 1.5.0 release.  dlc_53 and dlc_55 are each under 100mb and aren't likely to hold a lot of important content.  dlc_43 has two large files totalling 5gb.  Testing with a texture that exists in both reveals that the larger file is also the more recent, and hence "active".

The 1.5.3 update had one new forge for the Ezio outfit: dlc_56\DataPC_56_dlc.forge.  More significantly, DataPC_patch_01 had a few content updates, but all of its timestamps were updated.  It's now the most likely place to find an active texture.

The latest forges for the last two major game version are:

v1.5.3 - DataPC_patch_01.forge, dlc_56\DataPC_56_dlc.forge (Ezio only)
v1.5.0 - dlc_43\DataPC_ACD_Greece_43.forge, dlc_43\DataPC_43_dlc.forge
v1.4.0 - dlc_50\DataPC_50_dlc.forge

Article information

Added on

Edited on

Written by

hypermorphic