A small Windows console app that watches your No Man's Sky save folder, decodes the most recently modified save, and appends your galaxy and galactic coordinates to a log file every X minutes.
| File | Purpose |
|---|---|
nms_location_logger.py |
The application (pure standard library, no dependencies) |
config.ini |
All user-configurable settings |
build_exe.bat |
Builds dist\NMSLocationLogger.exe with PyInstaller |
galaxies.txt (optional) |
Your own galaxy-name list, one per line, in order |
Run straight from Python:
python nms_location_logger.py
Or build a standalone exe (Python only needed on the build machine):
build_exe.bat
Then edit dist\config.ini and run dist\NMSLocationLogger.exe.
A different config path can be passed as the first argument:
NMSLocationLogger.exe "D:\configs\my_nms.ini"
Press Ctrl+C to stop. To run it automatically, create a Task Scheduler task
("At log on", action = the exe, "Start in" = the exe's folder).
Everything lives in config.ini. %APPDATA% and %USERPROFILE% are expanded.
save_directory– defaults to%APPDATA%\HelloGames\NMS. The real saves sit in ast_<steamid>subfolder, which is whysearch_subfolders = true.save_file_pattern–save*.hg. Files beginning withmf_are always skipped; they are metadata companions, not saves.output_file– log file to append to (created if missing).interval_minutes– how often to check.lock_retry_seconds– how long to keep retrying while the file is locked.
2026-09-17 16:30:26 | save2.hg | Eissentam (10) | 0796:0083:0D09:0155 | 315585D0B798 | SSI 341 | Planet 3 | Voxel -105,4,1290
Columns: timestamp, save file, galaxy, signal-booster coordinates, portal glyph address, solar system index, planet index, and the raw voxel values.
The game keeps the save open while writing it, so each read does three things:
- Lock probe –
CreateFileWwithdwShareMode = 0andGENERIC_READ. If Windows returnsERROR_SHARING_VIOLATION, another process has it open. Nothing is ever opened for writing. - Stat sandwich – size and modification time are compared before and after the read, so a file that changed mid-read is rejected.
- Structural validation – the chunk headers must be intact and the decompressed payload must parse as complete JSON.
Any failure triggers an exponential back-off (0.5s, 1s, 2s, 4s...) for up to
lock_retry_seconds (default 15). If it still can't be read, that cycle is
skipped and the next one runs at the normal interval.
Modern .hg saves are a sequence of chunks, each with a 16-byte header
(magic 0xFEEDA1E5, compressed size, decompressed size, padding) followed by an
LZ4 block-format payload. The app concatenates the decompressed chunks to
get the JSON. An LZ4 decoder is included in pure Python; if the lz4 package
happens to be installed it will be used instead for speed. Older uncompressed
saves are also handled.
Hello Games obfuscates the JSON key names (PlayerStateData becomes 6f=, and
so on) and these mappings change between game versions. Rather than hard-coding
a mapping that breaks on every update, the app finds the player's address
structurally: it looks for the dictionary shaped like
{RealityIndex: int, GalacticAddress: {5 ints}} sitting one level inside the
largest top-level section — which is PlayerStateData. If the save is not
obfuscated, the real key names are used directly. Values are range-checked and a
warning is printed if they look wrong.
From the voxel values in the save:
Galactic (signal booster) Portal glyph address
X = (VoxelX + 2047) & 0xFFF X = (VoxelX + 2049) & 0xFFF
Y = (VoxelY + 127) & 0xFF Y = (VoxelY + 129) & 0xFF
Z = (VoxelZ + 2047) & 0xFFF Z = (VoxelZ + 2049) & 0xFFF
S = SolarSystemIndex format: P SSS YY ZZZ XXX
A voxel of 0,0,0 therefore gives the portal address ...81 801 801, which is
the familiar pattern for the galactic centre. Worth confirming once against an
in-game signal booster reading; if your build ever disagrees, the four offset
constants are at the top of nms_location_logger.py.
Set updateurl = Y in the [fleet] section of config.ini and every time a
location is logged, the app also sends it to a fleet-tracking URL:
https://fleet.nms-agt.com/?travellerName=...&password=...&coordinate=...&galaxyNumber=...&fleetName=...&fleetType=...&status=...¬es=...
Field rules, enforced when the config is loaded (invalid values are logged as a warning and replaced with the default shown):
| Field | Rule | If missing/invalid |
|---|---|---|
travellername |
up to 42 characters | updateurl is forced to N |
password |
exactly 5 alphanumeric characters | updateurl is forced to N |
fleetname |
up to 42 characters | "Null" |
fleettype |
one of Explorer, Shuttle, Fighter, Hauler, Exotic, Living Ship, Solar, Sentinel, Freighter, Corvette (case-insensitive) |
"Null" |
status |
up to 25 characters | "Null" |
updateurl |
Y or N (case-insensitive) |
N |
notes is not a config setting — it's read automatically from the save
file's own save-summary text (see below) each time a location is logged, up
to 50 characters, defaulting to "Null" if it can't be read.
The URL is sent with a 10-second timeout (configurable as
url_timeout_seconds under [advanced]). If the send fails - no internet,
server down, timeout - it's logged as a warning and the app carries on
normally; it never stops the location log from being written.
Every send attempt - success or failure - is also recorded in
FleetTrackerSendRequest.log, created next to your main output_file. Each
line has the timestamp, the exact URL that was sent, and the outcome:
2026-09-24 15:57:20 | https://fleet.nms-agt.com/?travellerName=... | HTTP 200
2026-09-24 15:58:20 | https://fleet.nms-agt.com/?travellerName=... | HTTP 500 (error response)
2026-09-24 15:59:20 | https://fleet.nms-agt.com/?travellerName=... | No response received (<urlopen error ...>)
"HTTP 200" etc. is the status code the server actually returned. "No response received" means the request never got a reply at all - no internet, DNS failure, connection refused, or timeout - so you can tell that apart from the server replying with an error.
This is the game's own save-summary text: the line shown on the file-select/
loading screen. It's read directly from the save file, not typed into
config.ini. Note that this text is whatever the game currently has set for
that context - in practice this is often the name of the base, freighter, or
ship the player is at/aboard, not necessarily the star system's name. It's
included here verbatim because that's what the field actually is; treat it
as informational context, not a system name.
The obfuscated key for this field is set in config.ini under
[advanced] save_summary_key (default vxi, correct as of the game version
this was built against). Hello Games changes this obfuscation on some
updates; if notes always comes back "Null", that key likely needs
updating - open a save file with a JSON viewer and look for a short,
human-readable string directly inside PlayerStateData.
The first 32 galaxy names are built in; anything beyond that logs as
Galaxy #N. To get full names, drop a galaxies.txt next to the exe with one
name per line in galaxy order (Euclid first) and it will be used instead.