Task 193: .EU4 File Format
Task 193: .EU4 File Format
File Format Specifications for .EU4
The .EU4 file format is utilized for save games in Europa Universalis IV, a strategy game developed by Paradox Interactive. Based on comprehensive research from reliable sources such as the official game wiki and community discussions, the format varies depending on the save type. Regular (non-ironman) saves may be stored as plain text or compressed ZIP archives containing text files (primarily "gamestate" and "meta"). Ironman saves are always compressed ZIP archives with a binary "gamestate" file. The text format employs a hierarchical key-value pair structure, encoded in Windows-1252, resembling a scripted configuration language with nested braces for sections. File signatures include "EU4txt" for text-based content and "EU4bin" for binary content (after decompression if applicable). The binary format lacks publicly available detailed specifications, limiting full decoding without proprietary tools; thus, the subsequent analysis and code examples focus on the text format for practicality. Compression, when present, uses standard ZIP methods, allowing extraction via common utilities.
List of All Properties Intrinsic to the File Format
The properties refer to the top-level keys typically found in the decompressed text-based .EU4 save file structure. These are intrinsic to the format's organization within the game's file system, representing core game state elements. The list is derived from standard save file layouts as documented in the Europa Universalis IV wiki:
- date
- save_game
- player
- displayed_country_name
- savegame_version
- dlc_enabled
- mod_enabled
- multi_player
- not_observer
- campaign_id
- campaign_length
- speed
- current_age
- map_area_data
- trade
- production_leader_tag
- tradegoods_total_produced
- change_price
- id
- dynasty
- rebel_faction
- great_powers
- empire
- active_trade_league
- catholic (and other religions, e.g., protestant, muslim)
- fired_events
- pending_events
- provinces
- countries
- active_advisors
- diplomacy
- active_war
- previous_war
- income_statistics
- nation_size_statistics
- score_statistics
- inflation_statistics
- expanded_dip_action_groups
- achievement_ok
- unit
- trade_company_manager
- tech_level_dates
- idea_dates
- ai
These properties may include simple values (e.g., strings, numbers) or nested structures (e.g., provinces contains sub-keys for each province ID).
Two Direct Download Links for .EU4 Files
- https://www.dropbox.com/s/lgxjew5gfg5g5u2/Engrancepaintugal.eu4?dl=1
- https://jumpshare.com/v/Ke7Jh5SuPHJVCkyEOJOQ
Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .EU4 File Dump
The following is a self-contained HTML page with embedded JavaScript suitable for embedding in a Ghost blog post (or any HTML-compatible platform). It provides a drag-and-drop area for uploading a .EU4 file. The script checks if the file is compressed (ZIP), extracts the gamestate if applicable, verifies the "EU4txt" signature, and dumps the specified properties to the screen by parsing the text content. Binary files are not supported and will trigger an error message. For ZIP handling, it uses the JSZip library (loaded via CDN for simplicity).
Drag and Drop .EU4 File to Dump Properties
Python Class for .EU4 File Handling
The following Python class handles opening, decoding (decompressing if ZIP), reading, writing, and printing the specified properties. It assumes text format; binary is detected and noted as unsupported. Writing modifies an existing file by updating a property (example: update 'date') and saves back, potentially as ZIP if original was compressed.
import zipfile
import io
import os
class EU4FileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.content = None
self.is_zip = False
self.properties = [
'date', 'save_game', 'player', 'displayed_country_name', 'savegame_version',
'dlc_enabled', 'mod_enabled', 'multi_player', 'not_observer', 'campaign_id',
'campaign_length', 'speed', 'current_age', 'map_area_data', 'trade',
'production_leader_tag', 'tradegoods_total_produced', 'change_price', 'id',
'dynasty', 'rebel_faction', 'great_powers', 'empire', 'active_trade_league',
'catholic', 'fired_events', 'pending_events', 'provinces', 'countries',
'active_advisors', 'diplomacy', 'active_war', 'previous_war', 'income_statistics',
'nation_size_statistics', 'score_statistics', 'inflation_statistics',
'expanded_dip_action_groups', 'achievement_ok', 'unit', 'trade_company_manager',
'tech_level_dates', 'idea_dates', 'ai'
]
def read(self):
with open(self.filepath, 'rb') as f:
data = f.read()
if data[:2] == b'\x50\x4b': # ZIP magic
self.is_zip = True
with zipfile.ZipFile(io.BytesIO(data)) as z:
if 'gamestate' in z.namelist():
self.content = z.read('gamestate').decode('windows-1252', errors='ignore')
else:
self.content = data.decode('windows-1252', errors='ignore')
if self.content and not self.content.startswith('EU4txt'):
print('Unsupported binary format.')
self.content = None
def print_properties(self):
if not self.content:
print('No content loaded.')
return
for prop in self.properties:
start = self.content.find(f'{prop}=')
if start != -1:
end = self.content.find('\n', start)
while end != -1 and self.content[end+1].isspace(): # Simple nested handling
end = self.content.find('\n', end+1)
value = self.content[start:end].strip()
print(f'{value}\n')
else:
print(f'{prop}: Not found')
def write(self, new_date='1444.11.11'): # Example: update 'date'
if not self.content:
print('No content to write.')
return
self.content = self.content.replace('date=1444.11.11', f'date={new_date}', 1) # Simple replace
output_path = self.filepath + '.modified.eu4'
if self.is_zip:
with io.BytesIO() as mem:
with zipfile.ZipFile(mem, 'w') as z:
z.writestr('gamestate', self.content.encode('windows-1252'))
mem.seek(0)
with open(output_path, 'wb') as f:
f.write(mem.read())
else:
with open(output_path, 'w', encoding='windows-1252') as f:
f.write(self.content)
print(f'File written to {output_path}')
# Example usage:
# handler = EU4FileHandler('example.eu4')
# handler.read()
# handler.print_properties()
# handler.write('1500.1.1')
Java Class for .EU4 File Handling
The following Java class performs similar operations: opening, decoding, reading, writing, and printing properties. It uses java.util.zip for decompression and assumes text format.
import java.io.*;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class EU4FileHandler {
private String filepath;
private String content;
private boolean isZip;
private String[] properties = {
"date", "save_game", "player", "displayed_country_name", "savegame_version",
"dlc_enabled", "mod_enabled", "multi_player", "not_observer", "campaign_id",
"campaign_length", "speed", "current_age", "map_area_data", "trade",
"production_leader_tag", "tradegoods_total_produced", "change_price", "id",
"dynasty", "rebel_faction", "great_powers", "empire", "active_trade_league",
"catholic", "fired_events", "pending_events", "provinces", "countries",
"active_advisors", "diplomacy", "active_war", "previous_war", "income_statistics",
"nation_size_statistics", "score_statistics", "inflation_statistics",
"expanded_dip_action_groups", "achievement_ok", "unit", "trade_company_manager",
"tech_level_dates", "idea_dates", "ai"
};
public EU4FileHandler(String filepath) {
this.filepath = filepath;
this.content = null;
this.isZip = false;
}
public void read() throws IOException {
File file = new File(filepath);
byte[] data = new byte[(int) file.length()];
try (FileInputStream fis = new FileInputStream(file)) {
fis.read(data);
}
if (data[0] == 0x50 && data[1] == 0x4B) { // ZIP
isZip = true;
try (ZipFile zip = new ZipFile(file)) {
ZipEntry entry = zip.getEntry("gamestate");
if (entry != null) {
try (InputStream is = zip.getInputStream(entry);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
content = baos.toString("windows-1252");
}
}
}
} else {
content = new String(data, Charset.forName("windows-1252"));
}
if (content != null && !content.startsWith("EU4txt")) {
System.out.println("Unsupported binary format.");
content = null;
}
}
public void printProperties() {
if (content == null) {
System.out.println("No content loaded.");
return;
}
for (String prop : properties) {
int start = content.indexOf(prop + "=");
if (start != -1) {
int end = content.indexOf("\n", start);
while (end != -1 && Character.isWhitespace(content.charAt(end + 1))) {
end = content.indexOf("\n", end + 1);
}
String value = content.substring(start, end != -1 ? end : content.length()).trim();
System.out.println(value + "\n");
} else {
System.out.println(prop + ": Not found");
}
}
}
public void write(String newDate) throws IOException { // Example: update 'date'
if (content == null) {
System.out.println("No content to write.");
return;
}
content = content.replaceFirst("date=\\d{4}\\.\\d{1,2}\\.\\d{1,2}", "date=" + newDate);
String outputPath = filepath + ".modified.eu4";
if (isZip) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos)) {
zos.putNextEntry(new ZipEntry("gamestate"));
zos.write(content.getBytes("windows-1252"));
zos.closeEntry();
} catch (IOException e) {
throw e;
}
// Write baos to file (omitted for brevity)
} else {
try (FileWriter fw = new FileWriter(outputPath, Charset.forName("windows-1252"))) {
fw.write(content);
}
}
System.out.println("File written to " + outputPath);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// EU4FileHandler handler = new EU4FileHandler("example.eu4");
// handler.read();
// handler.printProperties();
// handler.write("1500.1.1");
// }
}
JavaScript Class for .EU4 File Handling
The following JavaScript class is designed for Node.js (requires 'fs' and 'adm-zip' modules; install via npm if needed). It handles opening, decoding, reading, writing, and printing properties.
const fs = require('fs');
const AdmZip = require('adm-zip');
class EU4FileHandler {
constructor(filepath) {
this.filepath = filepath;
this.content = null;
this.isZip = false;
this.properties = [
'date', 'save_game', 'player', 'displayed_country_name', 'savegame_version',
'dlc_enabled', 'mod_enabled', 'multi_player', 'not_observer', 'campaign_id',
'campaign_length', 'speed', 'current_age', 'map_area_data', 'trade',
'production_leader_tag', 'tradegoods_total_produced', 'change_price', 'id',
'dynasty', 'rebel_faction', 'great_powers', 'empire', 'active_trade_league',
'catholic', 'fired_events', 'pending_events', 'provinces', 'countries',
'active_advisors', 'diplomacy', 'active_war', 'previous_war', 'income_statistics',
'nation_size_statistics', 'score_statistics', 'inflation_statistics',
'expanded_dip_action_groups', 'achievement_ok', 'unit', 'trade_company_manager',
'tech_level_dates', 'idea_dates', 'ai'
];
}
read() {
const data = fs.readFileSync(this.filepath);
if (data[0] === 0x50 && data[1] === 0x4B) { // ZIP
this.isZip = true;
const zip = new AdmZip(this.filepath);
const entry = zip.getEntry('gamestate');
if (entry) {
this.content = zip.readAsText(entry, 'win1252');
}
} else {
this.content = data.toString('win1252');
}
if (this.content && !this.content.startsWith('EU4txt')) {
console.log('Unsupported binary format.');
this.content = null;
}
}
printProperties() {
if (!this.content) {
console.log('No content loaded.');
return;
}
this.properties.forEach(prop => {
const regex = new RegExp(`^${prop}=(.*?(?=\\n\\w+=|$))`, 'gms');
const match = regex.exec(this.content);
if (match) {
console.log(`${prop}=${match[1].trim()}\n`);
} else {
console.log(`${prop}: Not found`);
}
});
}
write(newDate = '1444.11.11') { // Example: update 'date'
if (!this.content) {
console.log('No content to write.');
return;
}
this.content = this.content.replace(/date=\d{4}\.\d{1,2}\.\d{1,2}/, `date=${newDate}`);
const outputPath = this.filepath + '.modified.eu4';
if (this.isZip) {
const zip = new AdmZip();
zip.addFile('gamestate', Buffer.from(this.content, 'win1252'));
zip.writeZip(outputPath);
} else {
fs.writeFileSync(outputPath, this.content, { encoding: 'win1252' });
}
console.log(`File written to ${outputPath}`);
}
}
// Example usage:
// const handler = new EU4FileHandler('example.eu4');
// handler.read();
// handler.printProperties();
// handler.write('1500.1.1');
C++ Class for .EU4 File Handling
The following C++ class handles similar functionality. It uses <zip.h> for ZIP (assuming libzip installed) and assumes text format. Compilation requires linking to libzip.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <zip.h>
#include <regex>
class EU4FileHandler {
private:
std::string filepath;
std::string content;
bool isZip;
std::vector<std::string> properties = {
"date", "save_game", "player", "displayed_country_name", "savegame_version",
"dlc_enabled", "mod_enabled", "multi_player", "not_observer", "campaign_id",
"campaign_length", "speed", "current_age", "map_area_data", "trade",
"production_leader_tag", "tradegoods_total_produced", "change_price", "id",
"dynasty", "rebel_faction", "great_powers", "empire", "active_trade_league",
"catholic", "fired_events", "pending_events", "provinces", "countries",
"active_advisors", "diplomacy", "active_war", "previous_war", "income_statistics",
"nation_size_statistics", "score_statistics", "inflation_statistics",
"expanded_dip_action_groups", "achievement_ok", "unit", "trade_company_manager",
"tech_level_dates", "idea_dates", "ai"
};
public:
EU4FileHandler(const std::string& fp) : filepath(fp), content(""), isZip(false) {}
void read() {
std::ifstream file(filepath, std::ios::binary);
std::vector<char> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (data.size() > 1 && data[0] == 'P' && data[1] == 'K') { // ZIP
isZip = true;
zip *z = zip_open(filepath.c_str(), 0, nullptr);
if (z) {
zip_file *zf = zip_fopen(z, "gamestate", 0);
if (zf) {
zip_stat_t stat;
zip_stat(z, "gamestate", 0, &stat);
content.resize(stat.size);
zip_fread(zf, &content[0], stat.size);
zip_fclose(zf);
}
zip_close(z);
}
} else {
content = std::string(data.begin(), data.end());
}
if (!content.empty() && content.substr(0, 6) != "EU4txt") {
std::cout << "Unsupported binary format." << std::endl;
content = "";
}
}
void printProperties() {
if (content.empty()) {
std::cout << "No content loaded." << std::endl;
return;
}
for (const auto& prop : properties) {
std::regex regex("^" + prop + "=(.*?(?=\\n\\w+=|$))", std::regex::multiline | std::regex::dotall);
std::smatch match;
if (std::regex_search(content, match, regex)) {
std::cout << prop << "=" << match[1].str() << std::endl << std::endl;
} else {
std::cout << prop << ": Not found" << std::endl;
}
}
}
void write(const std::string& newDate = "1444.11.11") { // Example: update 'date'
if (content.empty()) {
std::cout << "No content to write." << std::endl;
return;
}
std::regex dateRegex("date=\\d{4}\\.\\d{1,2}\\.\\d{1,2}");
content = std::regex_replace(content, dateRegex, "date=" + newDate, std::regex_constants::format_first_only);
std::string outputPath = filepath + ".modified.eu4";
if (isZip) {
zip *z = zip_open(outputPath.c_str(), ZIP_CREATE, nullptr);
if (z) {
zip_source *src = zip_source_buffer(z, content.c_str(), content.size(), 0);
zip_file_add(z, "gamestate", src, ZIP_FL_OVERWRITE);
zip_close(z);
}
} else {
std::ofstream out(outputPath);
out << content;
}
std::cout << "File written to " << outputPath << std::endl;
}
};
// Example usage:
// int main() {
// EU4FileHandler handler("example.eu4");
// handler.read();
// handler.printProperties();
// handler.write("1500.1.1");
// return 0;
// }