Parsing Escape from Tarkov screenshot filenames into a live minimap in Go

I built a small overlay for Escape from Tarkov called TarkyMappy. It shows a rotating minimap, exits, hazards, quest points and a loot route on top of the game. The part I want to talk about is not the UI, though. The whole thing starts with a weird but very convenient fact: EFT writes the player position into the name of the screenshot file.
So instead of reading game memory, hooking into the process or sniffing traffic, the overlay watches a folder and parses a file name.
This is the kind of screenshot EFT saves when you press PrtSc:
2026-08-30[17-30]_388.36, 2.64, -114.48_0.00027, 0.98771, -0.00171, 0.15626_8.60 (0).png That contains:
- world position:
X, Y, Z; - view quaternion:
qx, qy, qz, qw; - a timestamp and screenshot counter.
The PNG itself is never opened. Only the file name is parsed.
Why not just read game memory?
Because I did not want to go anywhere near that.
The overlay is deliberately boring at the system level. It reads:
- screenshot file names from the EFT screenshots folder;
- optionally, EFT text logs if the user enables quest tracking;
-
prices/prices.jsonnext to the exe if the flea market panel is used.
It does not attach to the game process, read memory, hook the renderer, inject code or intercept network traffic. During normal play it does not use the network at all.
I still avoid saying “ban-proof”. I cannot speak for Battlestate Games or BattlEye. But the design is intentionally on the safe side: it treats the game as a black box that happens to drop useful metadata into the file system.
Watching the screenshots folder
The default folder is:
Documents\Escape from Tarkov\Screenshots There is a small complication: Documents can be redirected to OneDrive, so the app checks the known-folder location first and then falls back to a few normal paths. The watcher itself is fsnotify, not a polling loop.
When a new .png appears, the app parses only the base name.
Parsing the screenshot name
The format looks ugly, but it is regular enough for one regex:
var shotRe = regexp.MustCompile( `^\d{4}-\d{2}-\d{2}\[\d{2}-\d{2}\]_` + `(-?[\d.]+),\s*(-?[\d.]+),\s*(-?[\d.]+)_` + `(-?[\d.]+),\s*(-?[\d.]+),\s*(-?[\d.]+),\s*(-?[\d.]+)_` + `.*?\(\d+\)\.png$`) Then the parser takes the first three numeric groups as world coordinates and the next four as a quaternion:
func ParseScreenshotName(name string) (x, y, z, rotY float64, ok bool) { m := shotRe.FindStringSubmatch(name) if m == nil { return 0, 0, 0, 0, false } x, y, z = mustAtof(m[1]), mustAtof(m[2]), mustAtof(m[3]) rotY = YawFromQuat(mustAtof(m[4]), mustAtof(m[5]), mustAtof(m[6]), mustAtof(m[7])) return x, y, z, rotY, true } If the file name has no coordinates, it is ignored. That matters because EFT also saves screenshots from menus.
Converting the quaternion to yaw
The view direction is stored as a Unity quaternion. I only need yaw, because the map rotates around the player.
func YawFromQuat(qx, qy, qz, qw float64) float64 { s := 2 * (qw*qy + qx*qz) c := 1 - 2*(qy*qy + qz*qz) yaw := math.Atan2(s, c) * 180 / math.Pi if yaw < 0 { yaw += 360 } return yaw } This took longer to trust than to write. The formula is standard Unity ZXY yaw, but I still kept several real screenshot names as tests so a bad angle would be obvious immediately.
Finding the current map
The overlay has local bounds for every map:
"bounds": [ [-67, 69], [76.6, -65.5] ] Given a world point, it converts it to normalized UV coordinates:
u = (x - bounds[0][0]) / (bounds[1][0] - bounds[0][0]) v = (z - bounds[0][1]) / (bounds[1][1] - bounds[0][1]) If the point is inside the rectangle, the map is a candidate. If several maps overlap, the app scores them:
margin := math.Min(math.Min(u, 1-u), math.Min(v, 1-v)) area := math.Abs((b[1][0] - b[0][0]) * (b[1][1] - b[0][1])) score := margin * margin / area The idea is simple: prefer a map where the point is comfortably inside and where the map rectangle is smaller. Then there is hysteresis: if the previous map still contains the new point with a small margin, it stays selected. That prevents flicker around map borders.
There is also a manual picker and a “next map” hotkey, because open data is not perfect. West Customs and Reserve overlap a little, so auto-detection can be ambiguous without raid history.
Drawing the map around the player
The renderer uses Ebitengine. I wanted one exe, GPU rendering and no browser window, so it fit well.
The useful part is this transform:
px, py := PlayerTexPoint(bounds, x, z, imgW, imgH) var m ebiten.GeoM m.Translate(-px, -py) // move the player texture point to 0,0 m.Scale(zoom, zoom) // zoom m.Rotate(psi) // rotate so the view direction is up m.Translate(float64(screenW)/2, float64(screenH)/2) // move to screen center Ebitengine applies GeoM operations in order, so the sequence above effectively says:
- put the player at the origin;
- scale;
- rotate;
- put the origin in the middle of the window.
After that, every world marker — exit, hazard, quest point, loot cluster — goes through the same transform. That keeps the map, markers and labels consistent while zooming or rotating.
The overlay window itself is transparent and click-through:
ebiten.SetWindowDecorated(false) ebiten.SetWindowFloating(true) ebiten.SetWindowMousePassthrough(true) err := ebiten.RunGameWithOptions(g, &ebiten.RunGameOptions{ ScreenTransparent: true, }) When a panel opens, the app turns mouse passthrough off. When the panel closes, it turns it back on.
Keeping GPU and CPU usage down
The first version redrew every frame, which was wasteful. Now the app runs at 30 logic ticks per second and uses a dirty-frame check.
Every visible piece of state goes into a signature: player position, zoom, current map, floor, panel state, scroll positions, quest selection, mouse position and so on. If the signature has not changed, Draw returns immediately.
if g.sig() != g.lastSig { g.dirty = true } The window keeps the previous frame, so there is no flicker. This is especially useful when the overlay is just sitting quietly while you walk.
There is also a small memory trick. Decoded PNGs are larger than you expect, and holding several high-resolution map textures at once is unnecessary. The app caches only the current map texture and calls debug.FreeOSMemory() after switching maps or floors.
Floors and multi-floor maps
Some maps have several floors. The data contains floor names and Y ranges:
"floorBounds": { "Basement": [-999, -2.5], "Ground_Floor": [-2.5, 5.5], "Second_Floor": [5.5, 10.5] } The player’s Y coordinate decides which floor texture to load. You can also move manually with PageUp / PageDown. Exits and quest points on other floors are still drawn, just dimmer, with a small arrow.
Quest helper without touching the game
The quest panel can work fully manually, but it gets more useful when you point it at the EFT Logs folder.
The game writes text logs such as:
push-notifications_000.log application_000.log The overlay tails them by offset. Two events matter:
- a push notification containing
quest started; - a scene preset line from the application log, which identifies the loaded map.
That second one is useful in offline and PvE raids, where the online-style confirmation event does not arrive.
The parser is more careful than it looks. Log lines arrive in chunks, and some JSON notifications are split across writes, so the reader keeps partial state between ticks. A simpler “read line by line” approach missed events.
What it looks like
Lighthouse with extracts and transits:
Tracked quests and key doors:
Farm route:
One caveat: I cannot reach Lighthouse in-game yet because of progression, so those previews are rendered from the embedded data, not captured from a real raid. The overlay code is the same, but the map itself still needs in-game verification.
Things I would do differently
If I started again, I would separate the renderer from the game state earlier. The dirty-frame signature works well, but it is easy to add a new UI field and forget to include it in the signature, which produces a stale frame.
I would also avoid storing some UI geometry in ad-hoc rectangle functions. It works, but a small layout layer would be less fragile.
The farm route is another compromise. Open data has no navigation mesh, so routes are straight lines between loot clusters. That is useful as a direction hint, but it is not a real pathfinder.
Source and license
The repository is here:
https://github.com/snowbizzon/TarkyMappy
The source is public, but the project is not MIT-licensed. It is for personal, non-commercial use because some map art is CC BY-NC-SA. No in-game assets are extracted: map art comes from TarkovTracker/tarkovdata and reemr/re3mr.com, while exits, quests, loot and hazards come from tarkov.dev snapshots.
If you want to try it, the important thing to remember is that the overlay needs one screenshot after spawning. Until then, it knows the map but not your position.

