Ledger/galactic history generator

  • We have updated our Community Code of Conduct. Please read through the new rules for the forum that are an integral part of Paradox Interactive’s User Agreement.

gedruchy

Recruit
12 Badges
Mar 16, 2018
6
0
  • Warlock: Master of the Arcane
  • Magicka: Wizard Wars Founder Wizard
  • Magicka 2
  • Stellaris
  • Stellaris: Leviathans Story Pack
  • Stellaris - Path to Destruction bundle
  • Stellaris: Synthetic Dawn
  • Age of Wonders III
  • Stellaris: Apocalypse
  • Stellaris: Megacorp
  • Dungeonland
  • Magicka
My friends and I play a few games together with an AI or 4 and have been looking for an interesting way to record the interactions that occur within a game.
Given that there aren't many functional workarounds (and theres no way I'm just going to write down everything as it happens); I'm writing a script to read Stellaris save files and generate a ledger or history list of all interesting interactions for a given game.
The script will do the following in order (or this is the theory I'm working off):
  1. Get a list of save files (ie open the save folder).
  2. Extract and read a given auto-save file.
  3. Record the date of the auto save for each list of events.
  4. Iterate though the events contained within (ie, species, wars, planetary claims, deaths of leaders, etc).
  5. Order these events by date and commit them to a `Era Block`, stapled with the date of the auto save that these were extracted from.
  6. Compare that Era to the previous Era and note any relevant changes (AI player no longer appears, war no longer happening, changes in system ownership).
  7. Commit each Era Block to a master list, ordered by date.
  8. Iterate the master list and save it as something for humans to read.
However, there are some issues. Until now, I've been extracting the save files and interpreting the `gamestate` file as LUA, then converting the LUA to a dict for easier manipulation within Python. However, some of the entries within the gamestate don't really translate properly and I'm ending up with some pretty dodgy looking histories (ie, nothing happens but peace declarations and leader deaths, highly unlikely for our games and a complete certainty in our games, respectively).

My questions are:
  • What format are the gamestate files?
  • Is there any python (or other package) which can be used to turn some of the data into something easier to manipulate?
  • (Ideal) Is there a known/easy way to get the relevant events of the game without heavy manipulation of save files?


Thanks for reading my blog.
 

serpentskirt

Prince with a Thousand enemies
94 Badges
Feb 9, 2014
518
459
  • Europa Universalis IV
  • Europa Universalis IV: Call to arms event
  • Stellaris
  • Europa Universalis IV: Dharma
  • Age of Wonders: Planetfall
  • Hearts of Iron IV: La Resistance
  • Stellaris: Federations
  • Crusader Kings III: Royal Edition
  • Battle for Bosporus
  • Europa Universalis 4: Emperor
  • Stellaris: Necroids
  • Stellaris: Nemesis
  • Victoria 3 Sign Up
  • Hearts of Iron IV: By Blood Alone
  • Hearts of Iron IV: No Step Back
(Ideal) Is there a known/easy way to get the relevant events of the game without heavy manipulation of save files?
Outside of manipulating save file you have at least three options:
OCR approach: basically everything that shows in a pop-up can be extracted once you've calibrated your text recognition module enough. This is the safest universal method of obtaining data from the game but it suffers from some false recognitions and might be quite performance-heavy.

You can greatly improve performance if you trigger OCR based on couple of pixel checks - e.g. you know the war declaration pop-up draws pixels of certain color in certain coordinates, so you launch your recognition module only when these conditions are met.

Memory scanning: most of the stuff is located in the game's memory and can be extracted from there. This is quite reliable approach, but requires a lot of reverse-engineering.

Stellaris' EULA doesn't allow reverse-engineering "other than may be permitted according to applicable law". If I read it correct, EU laws do not forbid you to do reverse engineering in order to obtain data needed for your own program to work. It would be actually interesting to hear an answer from PDX regarding this matter.

@Jamor may I ask you to check with legal department if reverse-engineering Stellaris in order to create ledger/game history generator is considered a fair practice and does not require the authorisation of the right-holder (PDXIAB in this case)? For residents of EU and US.

Other drawback of this method is that your software might or might not get you flagged by whatever cheat detection system just because you're accessing other process' memory, so even if it's EULA-safe I wouldn't dare to public release it.

Network packets inspection: since you've mentioned that you are playing over the net, you might get data of interest analyzing packets which are being sent/received. This is generally less bothersome than reverse-engineering game itself and is less prone to detection. I'm not sure if that might or might not violate EULA, my gut feeling would say that network protocol is a part of the game and will be treated same as reverse-engineering the game itself.

I will not call these approaches easy but they might get what you want. Don't know if there are existing solutions, but I had impression they've ported whole universe to python at this point.
 

Defiler99

Space Barbarian
89 Badges
Dec 9, 2013
1.426
390
  • Sword of the Stars II
  • Ship Simulator Extremes
  • Sengoku
  • Semper Fi
  • March of the Eagles
  • Majesty 2 Collection
  • Magicka
  • The Kings Crusade
  • Victoria 2: A House Divided
  • Stellaris: Necroids
  • Teleglitch: Die More Edition
  • Victoria 2: Heart of Darkness
  • Warlock: Master of the Arcane
  • War of the Vikings
  • Cities: Skylines
  • Tyranny: Gold Edition
  • Magicka: Wizard Wars Founder Wizard
  • Crusader Kings II: Way of Life
  • Pillars of Eternity
  • Crusader Kings II: Horse Lords
  • Cities: Skylines - After Dark
  • Crusader Kings II: Conclave
  • Stellaris
  • Stellaris: Galaxy Edition
  • Stellaris: Galaxy Edition
  • Crusader Kings II: Reapers Due
  • Tyranny: Archon Edition
  • Tyranny: Archon Edition
  • BATTLETECH - Digital Deluxe Edition
  • Stellaris: Apocalypse
  • Cities: Skylines - Parklife Pre-Order
  • Cities: Skylines - Parklife
  • Stellaris: Distant Stars
  • Shadowrun Returns
  • Shadowrun: Dragonfall
  • Surviving Mars: First Colony Edition
  • Stellaris: Megacorp
  • Crusader Kings II: Holy Fury
  • Surviving Mars: First Colony Edition
  • Surviving Mars: Digital Deluxe Edition
  • Stellaris: Lithoids
  • Stellaris: Humanoids Species Pack
  • Stellaris: Digital Anniversary Edition
  • Stellaris: Leviathans Story Pack
  • Crusader Kings II: Monks and Mystics
  • Stellaris - Path to Destruction bundle
  • Tyranny - Bastards Wound
  • Crusader Kings II: Jade Dragon
  • BATTLETECH
  • Age of Wonders III
The gamestate is in some custom Clausewitz engine format; I'm not sure if it has an explicit name.
If it's any help at all, somebody wrote a syntax file for it (for a text editor I do not use, myself):
https://forum.paradoxplaza.com/foru...ad-stellaris-syntax-highlighting-file.938758/

Very cool idea; I suspect you'll want to use a more full-featured parser generator, or the like, rather than just treating it as Lua. Something like this but for this format vs. .INI: http://pyparsing.wikispaces.com/file/view/configParse.py

By the way, if you figure out what the <key> values actually represent, for example the values assigned to country_flags, I'd love to know!
They look like timestamps to me but I have no idea what format they are packed into, if so.
 
Last edited:

RandomZach

Captain
49 Badges
Nov 11, 2013
359
201
  • Stellaris: Synthetic Dawn
  • Europa Universalis IV: Art of War
  • Europa Universalis IV: Conquest of Paradise
  • Europa Universalis IV: Wealth of Nations
  • Europa Universalis IV: Call to arms event
  • Magicka
  • Europa Universalis IV: Res Publica
  • Europa Universalis IV: Third Rome
  • Stellaris - Path to Destruction bundle
  • Cities: Skylines - Parklife
  • Imperator: Rome Deluxe Edition
  • Europa Universalis IV: Rule Britannia
  • Stellaris: Apocalypse
  • Stellaris: Humanoids Species Pack
  • Hearts of Iron IV: Expansion Pass
  • Europa Universalis IV: Cradle of Civilization
  • Age of Wonders III
  • Stellaris: Distant Stars
  • Europa Universalis IV: Dharma
  • Europa Universalis IV: Golden Century
  • Stellaris: Nemesis
  • Imperator: Rome Sign Up
  • Stellaris: Ancient Relics
  • PDXCon 2019 "Baron"
  • Stellaris: Lithoids
  • Stellaris: Federations
  • Europa Universalis 4: Emperor
  • Stellaris: Necroids
  • BATTLETECH
  • Europa Universalis IV
  • Cities: Skylines
  • Europa Universalis IV: El Dorado
  • Europa Universalis IV: Pre-order
  • Magicka 2
  • Europa Universalis IV: Common Sense
  • Europa Universalis IV: Cossacks
  • Europa Universalis IV: Mare Nostrum
  • Stellaris
  • Hearts of Iron IV Sign-up
  • Hearts of Iron IV: Cadet
  • Europa Universalis IV: Rights of Man
  • Stellaris: Digital Anniversary Edition
  • Stellaris: Leviathans Story Pack
  • Hearts of Iron IV: Together for Victory
  • Europa Universalis IV: Mandate of Heaven
  • Hearts of Iron IV: Death or Dishonor
  • Stellaris: Megacorp
  • Imperator: Rome
  • Crusader Kings II
What format are the gamestate files?

As of others have said, a proprietary format of paradox's own creation. It's most frequent informal name is just 'paradox script'. I'm not sure if it has a formal name. They use it across all their Clausewitz games with some slight tweaks in each different game. A big problem with just interpreting it as Lua is that keys can be repeated inside an object, which is not a native feature of hash/dictionaries in any programming language I'm aware of.

Is there any python (or other package) which can be used to turn some of the data into something easier to manipulate?

I'm not aware of any Python solutions. There is however a javascript parser called Jomini that I've used personally for this same purpose with great success. The author was also quite responsive when I had an issue with the minor differences related to Stellaris itself. There are also parsers written in Ruby that I haven't used, but may be worth checking out that can be found here and here.

Paradox script is not terribly complicated, especially if you are just looking at save files. Your best bet may actually be just writing your own parser using a standard parsing library. I did this for my own purposes in Ruby.

Is there a known/easy way to get the relevant events of the game without heavy manipulation of save files?

I don't believe event history is persisted anywhere in the save file. Only events that are scheduled in the future and ongoing special projects are recorded. You'd need to analyze flags and modifiers to have some idea of what events had fired, and even that would be error prone, wouldn't give you exact dates, and would miss many events. The only solutions i can think of are real time memory scanning and/or making a mod that explicitly logged every single event (this would be a large mod) and then tailing said log. You could probably write a script that automatically generated the mod for you by parsing the game files.

Also, just a small pedantic nitpick, Lua is not an acronym. It means 'moon' in Portuguese, which is the native language of its original creator.
 
Last edited:

gedruchy

Recruit
12 Badges
Mar 16, 2018
6
0
  • Warlock: Master of the Arcane
  • Magicka: Wizard Wars Founder Wizard
  • Magicka 2
  • Stellaris
  • Stellaris: Leviathans Story Pack
  • Stellaris - Path to Destruction bundle
  • Stellaris: Synthetic Dawn
  • Age of Wonders III
  • Stellaris: Apocalypse
  • Stellaris: Megacorp
  • Dungeonland
  • Magicka
OCR approach: basically everything that shows in a pop-up can be extracted once you've calibrated your text recognition module enough. This is the safest universal method of obtaining data from the game but it suffers from some false recognitions and might be quite performance-heavy.
I considered OCR, but it feels to unreliable. I don't want to have to mess around with pixel perfection, especially considering my PC is made of wood, so I don't have a lot of pixels to work with.

Memory scanning: most of the stuff is located in the game's memory and can be extracted from there. This is quite reliable approach, but requires a lot of reverse-engineering.
This was a more serious consideration, but I didn't want to risk the ire of steams banning system for me or my friends. Plus, as you mentioned, this would make sharing the tool a bit of a risk and a README.md that says "hit run to get banned instantly" wont exactly encourage many downloads.

Network packets inspection
I could possibly get a pineapple and a copy of Charles going to do this, but it wont be super useful in single player (which is a minor requirement). Plus this massively lifts the buy in required from other people who may want to use this. Appreciate all the suggestions though.

Would you consider sharing this application once you've finished getting it working? I'd love to use it for some of my RP games.
Totally, its one of the main reason that I'm trying to avoid building something that requires special equipment or a serious programming background. I'll post it here once I have something that operates according to the laws of physics.

By the way, if you figure out what the <key> values actually represent, for example the values assigned to country_flags, I'd love to know!
They look like timestamps to me but I have no idea what format they are packed into, if so.
Once the arcane secrets of the save files have been decoded, I'll be sure to post an analysis (if needed).

I'm not aware of any Python solutions. There is however a javascript parser called Jomini that I've used personally for this same purpose with great success. The author was also quite responsive when I had an issue with the minor differences related to Stellaris itself. There are also parsers written in Ruby that I haven't used, but may be worth checking out that can be found here and here.
This is the stuff I'm looking for. Thanks very much for this. I'm probably going to include this as a node subprocess for my current script. This will at least give me an easy way to transverse what info can be found within the saves themselves.

I don't believe event history is persisted anywhere in the save file.
I was planning on increasing save frequency and max saves via mods then analysing each save in order to get a picture of the current state. This might lose some info, its true, but I'll see what interesting info I can get then apply more complex processes from there.

Also, just a small pedantic nitpick, Lua is not an acronym. It means 'moon' in Portuguese, which is the native language of its original creator.
Huh, the more you know.


This has been a useful thread. I'll be posting updates as the tool progresses.
 

serpentskirt

Prince with a Thousand enemies
94 Badges
Feb 9, 2014
518
459
  • Europa Universalis IV
  • Europa Universalis IV: Call to arms event
  • Stellaris
  • Europa Universalis IV: Dharma
  • Age of Wonders: Planetfall
  • Hearts of Iron IV: La Resistance
  • Stellaris: Federations
  • Crusader Kings III: Royal Edition
  • Battle for Bosporus
  • Europa Universalis 4: Emperor
  • Stellaris: Necroids
  • Stellaris: Nemesis
  • Victoria 3 Sign Up
  • Hearts of Iron IV: By Blood Alone
  • Hearts of Iron IV: No Step Back
I considered OCR, but it feels to unreliable. I don't want to have to mess around with pixel perfection, especially considering my PC is made of wood, so I don't have a lot of pixels to work with.
Actually it can be pretty fast, depends on how do you do it. For one of MMORPGs I'm playing, I'm able to pull and extract from screencaptures about 40 buff icons and corresponding 2-digits timers at least three times per second. For events with unique pop-up graphics you may end up with just 3-5 pixels fully defining your picture. It's a bit tricky with text (unless it has fixed character width), but you may just save image and OCR it later.

It is still pain in the ass because you'll need to support each resolution and text colour.

This was a more serious consideration, but I didn't want to risk the ire of steams banning system for me or my friends. Plus, as you mentioned, this would make sharing the tool a bit of a risk and a README.md that says "hit run to get banned instantly" wont exactly encourage many downloads.
Well, there is always plaza version, but you'll better logout of steam/origin/whatever anyway.