The moon of Masser (from Skyrim) and a cacophony of stars are obscured by clouds, overlooking a small voxel mountain.

It’s been ported to PC, Xbox, Playstation, Nintendo Switch, PC (Again), Xbox (Again), Playstation (Again), PC (Again Again), Xbox (Again Again), Playstation (Again Again), and even Alexa. Why not Minecraft?

Background

I got back into Skyrim recently1. Over the course of the past few days, I’ve been very nostalgic thinking about my childhood and all the things I never actually managed to complete. One of those things was a functional CreationKit mod. I tried to read the tutorials, use the CreationKit tools, and eventually I just gave up. Modding was too hard as someone who barely knew how to interface with his own computer. But now? Now I’m a grown adult. Now I have a certificate from an accredited institution proving that I know how computers work. Now, I will break Skyrim open, oh yes.

The key realization is in Skyrim’s (and Morrowind’s, and Oblivion’s) .esm files. These store a huge amount of information. NPCs, Quests, Lighting, Objects, Factions, the list goes on. The thing is, almost all of Skyrim is contained in these files2. You don’t even really need the Skyrim executable. As long as you have something that knows how to parse Skyrim.esm, you can run Skyrim anywhere.

Like maybe say… Minecraft?

Skyrim in Minecraft

I’m going to put a big

DISCLAIMER

sign here before we get started. Making Minecraft into a .esm reader is a daunting task. Even picking one facet of Skyrim to port over would take months to get running in Minecraft. But it’s fun to imagine doing things, and so I’m going to pick a (relatively) simple task for us to tackle.

Say, taking Skyrim’s landscape and converting it into a world map for Minecraft.

Part One: Understanding .esm

If you have Skyrim and a hex editor, I recommend you join along! I’m using HxD. You could (and probably should) use something more advanced. To provide one last note, I will be using Skyrim Special Edition’s Skyrim.esm file, you may see some differences in the file based on your version of Skyrim.

Here’s what we get when we look at Data/Skyrim.esm:

Hex editor data

The first four bytes are recognizable right away: TES4 (0x54455334) refers to The Elder Scrolls IV: Oblivion. A lot of Skyrim’s data is based directly on Oblivion, so we’ll see some references to that while we go through these files. I’m not sure I understand the rest of these bytes. This is where Skyrim’s active modding community comes to save the day. After entering a few different search terms, I eventually find this page: https://en.uesp.net/wiki/Skyrim_Mod:File_Formats.

Thank you, Unofficial Elder Scrolls Pages! The hard work of reverse engineering this file format has been done for us; we just need to make sure that we can understand how .esm works. UESP tells us that all .esm files are made up of two things:

That seems very straightforward. Let’s read the TES4 record first to see what we can learn about how this file format works.

Parsing TES4

Here’s the full record:

54 45 53 34 36 00 00 00 81 00 00 00 00 00 00 00
00 00 00 00 2C 00 00 00 48 45 44 52 0C 00 48 E1
DA 3F 75 0A 0E 00 93 0F 00 FF 43 4E 41 4D 0A 00
6D 63 61 72 6F 66 61 6E 6F 00 49 4E 54 56 04 00
C5 26 01 00 49 4E 43 43 04 00 4E 02 00 00

78 bytes in total.

But of course, the UTF-8 representation will also be very helpful to us. I’m going to replace every human unreadable character with � for clarity:

TES46�����������
����,���HEDR��Há
Ú?u���“��ÿCNAM��
mcarofano�INTV��
Å&��INCC��N���

In the documentation, UESP tells us that the first field we should see after TES4 is the HEDR structure. But you’ll notice HEDR is about 20 bytes ahead of TES4. What’s the big idea?

Reading back through UESP’s documentation, we see that “TES4 record” is not just jargon. A record is a specific type that we have to parse before we can move on to the information it actually contains.

So, a Record is made up of:

After parsing each of these, we’ve finally reached HEDR! We’ve just read 24 bytes, and we know the whole record is 78 bytes. Therefore, as the size told us, we have exactly 64 bytes left to read before we read the whole record. Great!

HEDR is a field is made up of 12 + 6 bytes:

The next few fields we also read fairly easily.

CNAM (the Author) has data of 10 bytes, and reads as mcarofano in ASCII. This is most likely Matthew Carofano, the Lead Artist on Skyrim.

INTV has data of 4 bytes. We don’t really care about this one.

INCC has data of 4 bytes. We also don’t care about this one.

And just like that, we’ve reached the end of TES4!

Wow!

Parsing Everything Else

Now that we know how to read records and fields, everything else should be easy!

We have one more data structure to learn about though, and that’s the Group structure, or GRUP.

Let’s read the GRUP that’s right after the TES4 record:

47 52
55 50 5E 7A 01 00 47 4D 53 54 00 00 00 00 0C 4B
0A 00 00 00 00 00 47 4D 53 54 ...

The ... symbolizes lots more bytes because, as we’ll find out, there are lots more bytes than this!

Here’s the UTF-8 representation:

GR
UP^z��GMST�����K
�����GMST ...

Now we settle back into the routine of reading bytes:

Finally, we’ve hit the second GMST. This actually represents the GMST record that UESP documents for us.

So we have some practice reading .esm files! Let’s put it all together.

Putting it all together

Now that we’ve read UESP’s documentation on .esm, I think we can extrapolate a fairly simple hierarchy:

So this makes reasing .esm a cinch. We just need to find the right group associated with the data we want, and read from that. UESP lists all of the groups that Skyrim contains:

GMST, KYWD, LCRT, AACT, TXST, GLOB, CLAS, FACT, HDPT, HAIR, EYES, RACE, SOUN, ASPC,
MGEF, SCPT, LTEX, ENCH, SPEL, SCRL, ACTI, TACT, ARMO, BOOK, CONT, DOOR, INGR, LIGH,
MISC, APPA, STAT, SCOL, MSTT, PWAT, GRAS, TREE, CLDC, FLOR, FURN, WEAP, AMMO, NPC_,
LVLN, KEYM, ALCH, IDLM, COBJ, PROJ, HAZD, SLGM, LVLI, WTHR, CLMT, SPGD, RFCT, REGN,
NAVI, CELL, WRLD, DIAL, QUST, IDLE, PACK, CSTY, LSCR, LVSP, ANIO, WATR, EFSH, EXPL,
DEBR, IMGS, IMAD, FLST, PERK, BPTD, ADDN, AVIF, CAMS, CPTH, VTYP, MATT, IPCT, IPDS,
ARMA, ECZN, LCTN, MESG, RGDL, DOBJ, LGTM, MUSC, FSTP, FSTS, SMBN, SMQN, SMEN, DLBR,
MUST, DLVW, WOOP, SHOU, EQUP, RELA, SCEN, ASTP, OTFT, ARTO, MATO, MOVT, HAZD, SNDR,
DUAL, SNCT, SOPM, COLL, CLFM, REVB

In fact, you may recognize some of these names from somewhere…

Creation Kit "Object Window" that shows a list of potential objects. Lots of objects are listed, including Actor, Music Track, Class, and Faction objects.

NPC_ represents actors. MUST, music tracks. CLAS, classes. FACT, factions. This is all the data you’re directly editing when you work with Creation Kit. Except now we have the tools to read through all of these directly. CreationKit isn’t even strictly necessarily. You could, if you were so inclined, write a Skyrim mod entirely in a hex editor (or notepad, if you’re really twisted inside). In fact, because there’s so much information here, we’ll be cross-referencing what we can read ourselves with what CreationKit can tell us5. For the purposes of this thought experiment though, we don’t need all of it. We’re only interested in the WRLD group, which contains the information we need. That is, world data!

Part Two: Reading WRLD

Let’s just go to the start of the WRLD group and see what’s there.

Hex data from the WRLD group

There’s a lot here. Let’s grab the first 80 bytes or so:

                                 47 52 55 50 99
7C 7D 0A 57 52 4C 44 00 00 00 00 03 3D 02 00 00
00 00 00 57 52 4C 44 75 11 16 00 00 00 00 00 3C
00 00 00 2F 30 1E 00 2C 00 0C 00 45 44 49 44 08
00 54 61 6D 72 69 65 6C 00 52 4E 41 4D 10 01 E4
FF 02 00 21 00 00 00 FC EB 10 00

In UTF-8, we get:

GRUP™
|}�WRLD�����=���
���WRLDu�������<
���/0��,���EDID�
�Tamriel�RNAM��ä
ÿ��!���üë��

We’re just going to skip everything up until the second WRLD, since we’ve read all this group information before. That helpfully eliminates 24 bytes, bringing us to the WRLD Record. I’ll just print some information about this WRLD record before we dig into the real data:

Skipping over the rest of the information, we can see this WRLD record has an EDID (Editor ID) of Tamriel. Where have I seen that name before?

Skyrim's Tamriel Object information in Creation Kit

And there’s our FormID! Note that it’s flipped because CreationKit is trying to represent the least significant digit last (to make it human readable, whereas .esm actually represents things Little Endian).

Before we go any further, let’s see what UESP has to say about WRLD.

Already, most of this information doesn’t look helpful. We’re interested in raw vertex data. But we have at least confirmed that we are looking at the World data for Skyrim, which will hold the vertex data we want. UESP mentions that each WRLD record is followed by group containing a CELL record and then multiple sub-GRUPs, each containing Exterior Cell Block information. So let’s skip ahead 1,446,261 bytes past WRLD to get our first sub-GRUP record:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
47 52 55 50 CC 03 22 09 3C 00 00 00 01 00 00 00
02 48 0A 00 58 B8 17 03 43 45 4C 4C 8B 00 00 00
00 04 04 00 74 0D 00 00 0F 68 3E 00 28 00 04 00

In UTF-8:

����������������
GRUP�"�<�������
�H��X¸��CELL‹���
����t�����h>�(���

You know the drill. We read the GRUP information, and then we skip ahead to CELL. Although, hold on one second.

CELL’s flags are 0x00040400. These flags are:

0x00040000 - Data is compressed with ZLIB.

0x00000400 - Persistent6 Cell.

While we might be interested in this CELL later on, CELL does not contain any useful terrain data that we’re interested in. It’s used for location, lighting, and placing objects. This will be useful for knowing where to place terrain data, but we’re still not there just yet. Theoretically you could keep reading through GRUPs and sub-GRUPs until you get to the right GRUP that has the information you need, but let’s just skip there for convenience’s sake. That is, we’re interested in the first LAND record in the hex data:

                                 47 52 55 50 84
58 00 00 E3 90 00 00 09 00 00 00 02 48 0A 00 38
DF 12 00 4C 41 4E 44 CA 2B 00 00 00 00 04 00 E3
A0 00 00 0B 67 3A 00 25 00 0B 00 FE 44 00 00 78
GRUP„
X���������H��8
ß��LANDÊ+������ã
����g:�%���þD��x

The group:

Meanwhile, LAND:

Let’s look for this FormID in the Creation Kit! We use Edit->Find Text.

Search window with result text: LAND Form " (0000A0E3)

Land ho! Let’s load this sucker in.

Shot of some dirt and tree leaves

Looking around, we can actually see that we’re very close to the city of Whiterun:

Shot of a hilly landscape and the city of Whiterun in the background

Exciting! Let’s figure out how to actually read LAND, since it’s compressed. UESP tells us that compressed records give the first 4 bytes as the decompressed size.

In this case, that’s

FE 44 00 00

Or 17,662 bytes.

We can’t just decompress a little bit of LAND to read it, we have to decompress the whole thing.

So we decompress with Zlib’s DEFLATE8:

44 41 54 41 04 00 1F 00 00 00 56 4E 4D 4C
...
DATA������VNML
...

VNML is just vertex normals, so we jump to VHGT:

...            56 48 47 54 48 04 00 80 39 C4 00 F9
F9 FA FA FA FA FB FB FB FC FC FE FF FF FD FD FE FD
... VHGTH��€9Ä�ùù
úúúúûûûüüþÿÿýýþý

Now UESP tells us how we can exactly read vertex heights.

Congrats, now we’re reading height data! We can verify this is the correct height data by opening the heightmap in Creation Kit. Looking at our Cell’s reference, the Cell is at coordinates (7, 7). We look at the southwest corner of the cell:

Cursor hovering over the southwest corner of Cell (7, 7). Z = -5936 at this position.

Now we just have to take our height value (-742), and multiply it by 8 to get our in-game units height.

-742 * 8 = -5936. This is our Z-value exactly!

Alright. We know how to read .esm. We know how to interpret vertex data. Let’s move into Minecraft.

Part Three: Minecraft!

Forget everything you know about video games. We’re going to put Skyrim in Minecraft. Or at least, Skyrim’s terrain. Now that we’ve actually got Skyrim’s terrain data, there are two things we need to deal with before we have a working map:

  1. Converting Skyrim units to Minecraft blocks.

  2. Knowing how to make Minecraft parse that block data.

Unit Conversion

There are so many debates about this, but we don’t want to jump into the deep end on things. Let’s make things super simple for ourselves.

Width and Length

UESP says that each Cell is 4096 in-game units by 4096 in-game units. This is equivalent, apparently, to 58.5 meters.

A Minecraft block’s length is 1 meter. So one Minecraft block length is 70 in-game units.

70 units do not create nice and even divisions into 4096 units however, so we’re going to instead scale Skyrim up just a little, by about 9.4%. This way, we can say that one Minecraft block will be 64 Skyrim Units.

A Minecraft chunk is 16 x 16 blocks, so about 1,024 x 1,1024 Skyrim units. So we say one Skyrim Cell is 4 Minecraft Chunks. Skyrim has a grid of 119 x 94 cells in the Tamriel WRLD, so our Minecraft world is going to be roughly 476 * 376 Chunks.

We also need to know how many blocks are in a vertex, so we know how many blocks to place for each vertex we read. Luckily, UESP has this info too! Each vertex from VHGT is 128 units apart. So each vertex covers 2 x 2 blocks, which means we’ll be placing groups of 4 blocks for each vertex.

Height

It also makes since to look at Minecraft’s build limit to see what sort of height range we have.

Theoretically, Skyrim has a maximum height limit of around 16 billion in-game units, and a minimum height limit of roughly negative 16 billion in game units. This is patently ridiculous, however. Instead, we’re more concerned with the largest height the devs ever threw in the game. For that, Skyrim has a maximum height of 39,392 in-game units and a minimum height of -37,032 in-game units9. For a total maximum height of around 80,000 units from top to bottom.

Converting to Minecraft units, our lowest point can be captured by -592 blocks, while our highest at 624 blocks. That gives us roughly 1,216 blocks to render top to bottom, which Minecraft can handle using custom world files.

Now that we know how to draw our blocks, let’s get to putting them in Minecraft!

Parsing Block Data

We have a few options for ensuring Minecraft can parse the block data we want to give it:

Option 1: Minecraft Java Mod

This would be ideal for long term projects looking to actually mod Skyrim INTO Minecraft.

This is the most technically involved, as we’d basically be looking to recreate major chunks of the Skyrim executable in Minecraft Java. This would be parsing terrain data in real-time and generating blocks on the fly from that streamed data.

For obvious reasons, this one is out. I am not going to be the guy who spends 50 years of his life making a total conversion Skyrim mod in Minecraft. No thank you.

Option 2: Datapack

Datapacks offer us nice .mcfunction files that we can use to execute commands like say, placing blocks.

This is also out, for a few reasons. For one, .mcfunctions are notoriously limiting. We’d have to use something like a pre-processor to encode ALL of the blocks we want to place. Then we’d have to open a Minecraft save and place them all at once. For two, performance is a huge issue here. Even without factoring in heights, we would be placing 45,817,856 blocks. Minecraft cannot possibly handle all of that in real time.

We’re still going to be using Datapacks to increase the height limit of the world, but we’re going to have to use something else to create the terrain data for us.

Option 3: World Save

We can have a program generate a Minecraft save for us to use.

This is the most convenient, as all Minecraft Java worlds use the .mca file format for terrain data. If we can just write chunk data into these files, then we’re golden.

I found the Fastanvil Rust library for parsing Chunk data. It even has a function for writing Chunk byte data to .mca files! Unfortunately, I did not realize that Fastanvil is primarily designed for reading Chunk data, and does not provide any helpers for creating Chunk byte data in Rust. That’s okay! We’ll just have to write our own serializer. Fastanvil uses serde, which is great news for us because I can just define a few structs:

#[derive(Serialize, Debug)]
#[serde(rename_all="PascalCase")]
pub struct Chunk {
	pub data_version : i32,

	#[serde(rename="xPos")]
	pub x_pos : i32,
	#[serde(rename="zPos")]
	pub z_pos : i32,
	#[serde(rename="yPos")]
	pub y_pos : i32,
	
	pub status : String,

	#[serde(rename="sections")]
	pub sections : Vec<Section>,
}

And serde handles the serialization into Minecraft’s NBT format for us!

So, I can hack out a Rust parser of the .esm file format (like we talked about) and a Rust Minecraft save file writer over the course of a few days or so, and we can finally test the results of all our hard work. Let’s generate the first Cell we found at (7, 7) to see what we get:

Huge chunk of stone rising into the air that eventually flattens out

Well, clearly there’s something wrong here. No way that we should be getting such huge jumps between our vertices. Thankfully, this was just an issue of me reading vertex offset data as an unsigned byte instead of a signed byte. A quick fix and:

More reasonable chunk of stone, that looks like a descending gradient

Ta-da! Now to generate the rest of the terrain.

I’ll skip over a few more hours of debugging and math corrections. After a healthy dose of persistence (along with all of the self-imposed breaks that requires) we finally can see the end:

Full stone landscape in Minecraft

SKYRIM!!! IN MINECRAFT!!!

Wrapping Up

Feel free to download the world and try things out for yourself!

NOTE

It’s probably a good idea to turn down your render distance before downloading the world. I have mine set to 16 chunks.

You can get any chunk’s X and Z coordinates in blocks from a Cell’s X and Y:

Chunk X Block = Cell X * 4 * 16 Chunk Z Block = Cell Z * 4 * 16

You can also get any block’s coordinates from Skyrim Units:

Block X = Skyrim X / 64 Block Y = Skyrim Z / 64 Block Z = Skyrim Y / 64

Some choice locations for you to try:

You can grab lots more coordinates from this map!

All of the code I used to generate the world is open source, so feel free to make your own stuff with it! I’d also be delighted to accept any PRs. Speaking of:

Improvements

There are a few things I can think of off the top of my head that I might (or anyone else could!) do if I ever decide to revisit this project:

Of course, if anyone really wanted to take this project to its logical conclusion, they could develop a Minecraft mod that streams all this data live and on-the-fly in-game. Then you could convert object models to Minecraft models in real-time. Then weapons. Spells. NPCs. Quests. You could be running Skyrim IN Minecraft. Then it’d be super simple to port Skyrim expansions into Minecraft. Then mods. Then you could start adding support for all past Elder Scrolls games. Morrowind? Oblivion? In Minecraft? You got it. The Fallout games run on the same engine, you could move them in there too. Starfield. Once you’ve put it all in Minecraft, then you don’t even need Bethesda anymore. You can make Elder Scrolls VI in Minecraft without even playing it. Anything is possible so long as you sacrifice your meaningless time to such daunting tasks. Your life is but one of many; if you falter in this quest there will be others. Skyrim will be of them, and of you, for so long as there is life in the body. And even after? You will be of service in Sovngarde.

Regardless, that whole racket is probably not for me. I have better things to do with my life, but if anyone wants to put themselves through such an arduous task, I would be excited to see it happen.

Lessons Learned

Bethesda’s pretty cool for making all of this accessible to the Skyrim modding community, and I’m glad that I got to be able to just root around and have some fun in there. And huge thanks to the folks at UESP for documenting everything that I used in this post. This project certainly would not exist without them. So, to sum it up: Modders are cool. Skyrim is cool. Minecraft is cool. And you? You’re pretty cool. Thanks for stopping by.

Epilogue

Please check out my other projects! I just released a huge update to my hobby project, Spider-880. And I’ve got many other pies baking in the background. I’m on Bluesky now as well as Mastodon, if you have any questions about what you just read.

And thank you again for your time.

See ya!

  1. Which is especially weird because I abandoned my Baldur’s Gate 3 playthrough in the middle to instead chop up some Dragur in Bleak Fall’s Barrow. The heart wants what it wants, I guess. 

  2. Skyrim also splits heavier data like Textures, Models, and Voice Files into .bsa files. These are super interesting to talk about in and of themselves, but are beyond the scope of this post. 

  3. From UESP’s documentation, I calculated Y = (74/12 + 3) % 10 = 9, M = (74 % 12) + 1 = 3, D = 12. So (assuming my math and guesses as to the time format are right), this would be March 12th, 2009. 

  4. We treat the subgroup the same as a Group, just with a different type. 

  5. If you’re interested in following along, please install CreationKit! Make sure CreationKit is installed on the same drive as Skyrim, as otherwise you’ll encounter a missing steamapi.dll error like I did the first time I tried this. 

  6. A persistent cell is a cell that is designed to remember what stuff is in it when you leave. It’s what allows bodies to persist in towns once you come back. A temporary cell is just the opposite: it will be unloaded when you leave and will reset when you come back. 

  7. These are records belonging to this CELL that the game does not keep track of when we have unloaded it. 

  8. I actually just copied the raw data into a txt file and used openssl zlib -d < data.txt > out.txt on the command line, if you’re curious as to how to do this yourself. 

  9. Funnily enough, Skyrim’s heightmap editor lies about these min and max heights for whatever reason. So I had to read all vertex data across all LAND tags to grab the lowest and tallest points.