INTRODUCTION

This is a fairly technical write-up. Nothing in this article is ground-breaking stuff, but this is not meant for someone who doesn't care about the nuts and bolts under the hood. If you just want to use the maps, you definitely don't this level of detail to download the files and start using them for whatever project you want.

MAP TEXTURE FILE NAMING SCHEME

As a primer for reversing how the game puts the map together, here's a couple example file names for a 256px tile in the texture archives:

MENU_MapTile_M00_L0_01_00_00000000
MENU_MapTile_M00_L0_16_16_0000000c
MENU_MapTile_M01_L0_07_27_00000003

Ignoring the 'MENU-MapTile_' prefix, we can generalize the pattern with a regular expression:

M[0-9]{2}_L[0-9]_[0-9]{2}_[0-9]{2}_[0-9A-Fa-f]{8}
(note [0-9] was used instead of \d to be a bit more legible)

To tokenize the naming a bit clearer (not regex):

M[M:00-01]_L[L:0-4]_[X:00-40]_[Y:00-40]_[BYTES:8-hex-digits, 0-F]
Given the above, here is what I could put together about the naming convention:

  • prefix: not important, ignore
  • M: the map, 00 for overworld, 01 for underground
  • L: the detail level or how zoomed in the map is, 0-4 (where 0 is most zoomed in and 4 is most zoomed out)
  • X: the X-coordinate of the tile, 00-40 (where 00 is left and 40 is right)
  • Y: the Y-coordinate of the tile, 00-40 (where 00 is bottom and 40 is top)
  • BYTES: see MAP TILES AND BIT MASKS section

MAP LAYOUT

Essentially, the in-game maps are broken up into tiles of 256x256 pixels at different detail levels. Detail level 0 is the most detailed/zoomed in, and level 4 is the least detailed/zoomed out. For this mod, only detail level 0 is used (if you want a smaller map, you can just downscale it with any number of tools). I'm not a game dev, but my presumption is this is done to optimize how the map is displayed, memory management, draw calls, and all those kind of buzzwords. The full game map is a 41x41 tile grid, which starts at the BOTTOM-LEFT corner and grows to TOP-RIGHT corner (e.g. X coordinates go from left to right, and Y coordinates go from bottom to top).

A crude text representation of the grid would look something like this:

         ____________________
  Y-max |__|__|__|__|__|__|__|
        |__|__|__|__|__|__|__|
        |__|__|__|__|__|__|__|
        |__|__|__|__|__|__|__|
        |__|__|__|__|__|__|__|
        |__|__|__|__|__|__|__|
 X,Y=00 |__|__|__|__|__|__|__|
                          X-max

To have a repeatable process, I put together a python script using the Pillow library (https://pillow.readthedocs.io/en/stable/) for some basic image processing, mostly for stitching the map together but also to provide debug information for the trial and error process of selecting tiles per coordinate.

FLAGS AND BIT-WISE OPERATIONS

This section is a primer for how tiles are selected in the map. You can skip this section if you don't care about programming, or don't want the nitty gritty on this kind of stuff. Alternatively, if you want a really in-depth video on the topic, this video by CodeVault is a great deep dive on flags and bit-wise operators: https://www.youtube.com/watch?v=6hnLMnid1M0

Please note the difference between a BIT and a BYTE: a BYTE is 8 BITS, and a BYTE can be represented as TWO hexadecimal numbers (00-FF). See APPENDIX A: DECIMAL, HEX, AND BINARY TABLE for a table you can reference on the conversions for numbers 0-15 (0x0-0xF, or 4 bits). Please note that it is customary to write BINARY numbers with a "0b" prefix, and hexadecimal numbers with a "0x" prefix.

And just so we're on the same page, the flags we will be covering are 8 hexadecimal digits long, or 4 BYTES (meaning 4x8 = 32 BITS, or 32 possible "switches" to turn on and off).

From the appendix, you can see there's a couple of numbers where only one bit is set:

  • 1: 0001
  • 2: 0010
  • 4: 0100
  • 8: 1000

You'll also notice these are all powers of 2, which makes sense given binary is base 2. These specific numbers allow us to do "bit-wise operations" that can extract or store a bit without affecting the other numbers.

For example, take the number 12. In binary, this is 0b1100 (or 0xC in hex). If I want to check if the third bit is set, I can mask it with 4 (or 0100). Masking means the bit-wise AND operator (&):

  1100 <-- the number
& 0100 <-- the flag
-------
  0100 <--- (result == flag) means the flag was set

Typically in programming this is used in a conditional check such as "(number & flag) == flag", or sometimes simplified to "number & flag" (e.g. checking if the result is NOT 0, when you know for certain the flag is not 0). A simple C++ example would be:

unsigned int number = 0b1100;
unsigned int flag   = 0b1000;

if(number & flag)
{
   //...do stuff...
}

Similarly, the bit-wise OR operator (single pipe "|" character) can be used to combine bits into a single number:

  0001 <-- the number
| 0100 <-- another number
-------
  0101 <-- result now has both flags

Another similar but distinct use case is "storing" or "enabling" a bit. This usually involves an assignment operation with the bit-wise OR. A simple C++ example:

unsigned int number = 0b0000;
unsigned int flag1  = 0b1000;
unsigned int flag2  = 0b0010;

// compound assignment operator with bit-wise OR
number |= flag1;   // number is now 0b1000
number |= flag2;   // number is now 0b1010

There are plenty other use cases for these concepts but this covers enough to understand the map flags system. The takeaway from this section is that flags provide a way to store and do multiple conditional checks in an efficient manner.

MAP TILES AND FLAGS

As the prior section explains, flags can be used to store values in a simple and efficient manner. The way the game seems to use these is by indicating what tile should be drawn in each coordinate (depending if the player has the fragment or not). My speculation is that when you pick up a map fragment, a certain "bit" is set somewhere in memory to indicate that you have that map when the UI is opened. For the same speculative reasons mentioned in prior sections, it seems the game stores different copies of each map tile based on what bits are set for map fragments to quickly draw the correct tile (rather than "layering" the maps and wasting resources).

So for example, take the location Lake-Facing Cliffs site of grace at grid coordinate (11, 16). This tile at detail level 0 is actually broken up between the Limgrave and Liurnia (East) map fragments. From what I've been able to gather from assembling these maps, Limgrave uses the flag 0x00000001 and Liurnia (East) uses the flag 0x00000008. So for this specific tile, to display the fully known map tile, the flag must be 0x00000009 (e.g. 0x1 | 0x8). If either flag is not present, you will get the brown unknown map style for the "missing" portion. See APPENDIX B: MAP FLAG VALUES for what I've gathered on these values.

A visual example might be easier to rationalize:



However, to complicate this assembly process of the map tiles, there seem to exist unrelated tiles in certain grid coordinates. Taking the example above of (11, 16), there is a another version of the (11, 16) tile which applies to 0x00000020 -- which otherwise refers to the Liurnia (West) map fragment. This is an oddity with how the files are structured in the texture archive, so I'm not quite sure how this tile is used within the game. My best guess is either these are junk tiles (which there are quite a few of -- such as around Redmane Castle, there's a bunch of hand-drawn developer stuff), or they're used in some different orientation of the map like when you haven't discovered certain areas yet.

Nevertheless, you can see the tile clearly doesn't fit what it should be based on the grid coordinates when fully assembled:



To better visualize this, I wrote a python script that assembles the maps on the fly with debug information (coordinate, flag in the cell, grid lines, color coordination, etc.). In this example you can see flags that overlap around the map seams have blended colors. Notice (8, 18) is a fairly dark purple color, since this contains 3 map fragment flags with the value 0x00000038 (0x00000020, 0x00000010, and 0x00000008). Refer to APPENDIX B for the fragments corresponding to each of these flags.



CONCLUSION

I hope this article was helpful in explaining the map reversing and assembly process. Please feel free to post any corrections if I got something wrong here. It's likely a missed a detail or didn't accurately capture information somewhere.

APPENDIX A: DECIMAL, HEX, AND BINARY TABLE

DECIMAL  HEX    BINARY
----------------------
0        0      0000
1        1      0001
2        2      0010
3        3      0011
4        4      0100
5        5      0101
6        6      0110
7        7      0111
8        8      1000
9        9      1001
10       a      1010
11       b      1011
12       c      1100
13       d      1101
14       e      1110
15       f      1111

APPENDIX B: MAP FLAG VALUES

Some speculation on the map flag values:

  • The low order bits (4 least significant) for M00 seem to refer to actual map fragments the player can pick up, with the exception of 0x00008000 which is the central ocean area with the big cloud. 
  • There's some speculation about this big cloud being centrally located to all divine towers and being possible DLC area, or otherwise interesting location (they form a star shape around the big cloud when all are connected with a line).
  • There also seems to be a missing bit flag in sequence, 0x00004000. This could just be from changes during development, or purposefully leaving a low order bit flag open for use in future content.
  • The high order bits (4 most significant) seem to refer to ocean areas or otherwise fragments that automatically unlock based on some condition (e.g. player picked up adjacent map fragments and these areas get filled in automatically).
 

M00: Overworld/Main Map
-----------------------
0x00000001: Limgrave (West)
0x00000002: Weeping Peninsula
0x00000004: Limgrave (East)
0x00000008: Liurnia (East)
0x00000010: Liurnia (North) [Academy of Raya Lucaria]
0x00000020: Liurnia (West)
0x00000040: Altus Plateau
0x00000080: Leyndell, Royal Capital
0x00000100: Mt. Gelmir
0x00000200: Caelid [Southern Caelid region]
0x00000400: Dragonbarrow [Northern Caelid region]
0x00000800: Mountaintops of the Giants (West)
0x00001000: Mountaintops of the Giants (East)
0x00002000: Consecrated Snowfield
0x00008000: Centeral Ocean [possible DLC area?]
0x00010000: Southwest Ocean
0x00020000: Northwest Ocean
0x00040000: Southeast Ocean
0x00080000: Northeast Ocean [Crumbling Farum Azula]
0x00100000: North Ocean [Miquella's Haligtree]

M01: Underground Map
--------------------
0x00000001: Lake of Rot
0x00000002: Ainsel River
0x00000004: Mohgwyn Palace
0x00000008: Siofra River
0x00000010: Deeproot Depths

Article information

Added on

Edited on

Written by

AelarionX

0 comments