<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>C++ on Jayng9663</title><link>https://jayng9663.github.io/blog/tags/c++/</link><description>Recent content in C++ on Jayng9663</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 30 Mar 2026 01:27:09 -0700</lastBuildDate><atom:link href="https://jayng9663.github.io/blog/tags/c++/index.xml" rel="self" type="application/rss+xml"/><item><title>VN Presence</title><link>https://jayng9663.github.io/blog/p/vn-presence/</link><pubDate>Mon, 30 Mar 2026 01:27:09 -0700</pubDate><guid>https://jayng9663.github.io/blog/p/vn-presence/</guid><description>&lt;p&gt;&lt;a class="link" href="https://github.com/jayng9663/VN-Presence" target="_blank" rel="noopener"
 &gt;&lt;img alt="Github Repo" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://img.shields.io/badge/github-repo-blue?logo=github"&gt;&lt;/a&gt;
&lt;a class="link" href="https://www.gnu.org/licenses/agpl-3.0" target="_blank" rel="noopener"
 &gt;&lt;img alt="License: AGPL v3" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://img.shields.io/badge/License-AGPL_v3-blue.svg"&gt;&lt;/a&gt;
&lt;a class="link" href="https://en.cppreference.com/w/cpp/23" target="_blank" rel="noopener"
 &gt;&lt;img alt="C++23" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://img.shields.io/badge/C%2B%2B-23-00599C?logo=cplusplus"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Discord Rich Presence daemon for Visual Novels on Linux. Detects games launched via &lt;strong&gt;Lutris&lt;/strong&gt; or &lt;strong&gt;Steam&lt;/strong&gt;, looks them up on &lt;a class="link" href="https://vndb.org" target="_blank" rel="noopener"
 &gt;VNDB&lt;/a&gt;, and shows the title, cover art, and total playtime in your Discord status.&lt;/p&gt;
&lt;h3 id="voice-channel"&gt;&lt;a href="#voice-channel" class="header-anchor"&gt;&lt;/a&gt;Voice channel
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Voice channel presence with cover" class="gallery-image" data-flex-basis="406px" data-flex-grow="169" height="164" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://jayng9663.github.io/blog/p/vn-presence/voice_with_cover.png" width="278"&gt; &lt;img alt="Voice channel presence without cover" class="gallery-image" data-flex-basis="418px" data-flex-grow="174" height="156" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://jayng9663.github.io/blog/p/vn-presence/voice_no_cover.png" width="272"&gt;&lt;/p&gt;
&lt;h3 id="user-activity"&gt;&lt;a href="#user-activity" class="header-anchor"&gt;&lt;/a&gt;User activity
&lt;/h3&gt;&lt;p&gt;&lt;img alt="User activity with cover" class="gallery-image" data-flex-basis="273px" data-flex-grow="114" height="398" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://jayng9663.github.io/blog/p/vn-presence/activity_with_cover.png" width="454"&gt; &lt;img alt="User activity without cover" class="gallery-image" data-flex-basis="254px" data-flex-grow="105" height="424" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://jayng9663.github.io/blog/p/vn-presence/activity_no_cover.png" width="449"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="features"&gt;&lt;a href="#features" class="header-anchor"&gt;&lt;/a&gt;Features
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Detects games from &lt;strong&gt;Lutris&lt;/strong&gt; (exact name from wrapper) and &lt;strong&gt;Steam&lt;/strong&gt; (AppID → ACF → store name)&lt;/li&gt;
&lt;li&gt;Fuzzy title matching against VNDB using trigram similarity + full-width character normalisation&lt;/li&gt;
&lt;li&gt;Fallback: searches the &lt;strong&gt;release&lt;/strong&gt; endpoint and follows the relation back to the parent VN&lt;/li&gt;
&lt;li&gt;Suppresses explicit cover images (sexual ≥ 1.80 or violence ≥ 1.80) — title still shows&lt;/li&gt;
&lt;li&gt;Playtime from &lt;strong&gt;Lutris DB&lt;/strong&gt; (&lt;code&gt;pga.db&lt;/code&gt;) or &lt;strong&gt;Steam VDF&lt;/strong&gt; (&lt;code&gt;localconfig.vdf&lt;/code&gt;) — no API key needed&lt;/li&gt;
&lt;li&gt;Discord elapsed timer reflects &lt;strong&gt;total hours played&lt;/strong&gt;, not just this session&lt;/li&gt;
&lt;li&gt;Editable &lt;code&gt;cache.csv&lt;/code&gt; for aliases, hard-links, and SKIP entries — reloaded live while running&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ignore.txt&lt;/code&gt; with exact-match for short entries (&amp;lt; 4 chars) to prevent false positives like &lt;code&gt;sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="how-it-works"&gt;&lt;a href="#how-it-works" class="header-anchor"&gt;&lt;/a&gt;How it works
&lt;/h2&gt;&lt;h3 id="main-loop"&gt;&lt;a href="#main-loop" class="header-anchor"&gt;&lt;/a&gt;Main loop
&lt;/h3&gt;&lt;p&gt;Every 5 seconds the daemon runs detection, debounces the result, checks the cache, queries VNDB if needed, and updates Discord.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 A[Detection\nLutris or Steam] --&gt; B[Debounce\nstable 2 polls]
 B --&gt; C{Cache\nlookup}
 C -- hit + fresh --&gt; E[Discord RPC]
 C -- hit + expired --&gt; D[VNDB search]
 C -- miss --&gt; D
 D --&gt; E&lt;/pre&gt;&lt;hr&gt;
&lt;h3 id="detection"&gt;&lt;a href="#detection" class="header-anchor"&gt;&lt;/a&gt;Detection
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 P[Scan /proc every 5s]
 P --&gt; L{lutris-wrapper\nin cmdline?}
 L -- yes --&gt; LN[Game name from\nwrapper args]
 L -- no --&gt; S{SteamAppId\nin environ?}
 S -- yes --&gt; SA[Read appmanifest_ID.acf\nfrom all library paths]
 SA --&gt; SN[Steam store name]
 S -- no --&gt; NONE[Nothing detected]
 LN --&gt; ST[Read starttime\nfrom /proc/pid/stat field 22]
 SN --&gt; ST
 ST --&gt; SORT[Sort candidates\nby starttime ascending]&lt;/pre&gt;&lt;p&gt;Lutris passes the game name explicitly as command-line arguments after &lt;code&gt;lutris-wrapper&lt;/code&gt;, so the name is always exact. Steam injects &lt;code&gt;SteamAppId&lt;/code&gt; as an environment variable into every game process — the daemon reads it from &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/environ&lt;/code&gt;, then finds the matching &lt;code&gt;.acf&lt;/code&gt; file across all Steam library paths (including custom drives read from &lt;code&gt;libraryfolders.vdf&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;After all candidates are collected, the daemon reads &lt;strong&gt;field 22&lt;/strong&gt; (&lt;code&gt;starttime&lt;/code&gt;) from each process&amp;rsquo;s &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/stat&lt;/code&gt;. Candidates are then &lt;strong&gt;sorted ascending by starttime&lt;/strong&gt;, so the process that launched first is always tried first during VNDB resolution. This makes multi-candidate priority deterministic and reproducible across polls.&lt;/p&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;See the &lt;a class="link" href="https://jayng9663.github.io/blog/p/proc/pid/stat-field-reference/" &gt;proc/[PID]/stat field reference&lt;/a&gt; for a full breakdown of all stat fields.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;blockquote class="alert alert-tip"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;💡&lt;/span&gt;
 &lt;span class="alert-title"&gt;Tip&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Run with &lt;code&gt;--verbose&lt;/code&gt; to see candidates list in the debug output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[DEBUG] src/main.cpp:73 4 game candidate(s) found:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [DEBUG] src/main.cpp:75 [lutris] pid=2037324 name=&amp;#34;終ノ空 remake&amp;#34; starttime(clock ticks)=4906595
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [DEBUG] src/main.cpp:75 [lutris] pid=2086738 name=&amp;#34;X-Plane 12&amp;#34; starttime(clock ticks)=4906701
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [DEBUG] src/main.cpp:75 [steam-appid] pid=2037822 name=&amp;#34;心象天儀本線 ~Per aspera ad astra~ Demo&amp;#34; starttime(clock ticks)=4906857
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [DEBUG] src/main.cpp:75 [lutris] pid=2038526 name=&amp;#34;サクラノ詩－櫻の森の上を舞う－&amp;#34; starttime(clock ticks)=4907260
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id="vndb-resolution"&gt;&lt;a href="#vndb-resolution" class="header-anchor"&gt;&lt;/a&gt;VNDB resolution
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 Q[Search query] --&gt; VN[POST /kana/vn]
 VN --&gt; SIM{Trigram similarity\n≥ 0.35 OR substring match?}
 SIM -- yes --&gt; MATCH[VnInfo returned]
 SIM -- no --&gt; REL[POST /kana/release]
 REL --&gt; FOUND{Release found?}
 FOUND -- yes --&gt; ID[Fetch parent VN\nby exact ID]
 ID --&gt; MATCH
 FOUND -- no --&gt; NOMATCH[No match\nclear presence]&lt;/pre&gt;&lt;p&gt;Before the similarity check, full-width ASCII (&lt;code&gt;！２→!2&lt;/code&gt;, &lt;code&gt;（→(&lt;/code&gt;) is normalised to half-width so Japanese game names from Lutris match VNDB&amp;rsquo;s stored titles. There is also a substring boost — if the query appears inside the returned title, the score is raised to at least 0.6, handling cases where the detected name is a short prefix of a long VNDB title.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="cache-ttl"&gt;&lt;a href="#cache-ttl" class="header-anchor"&gt;&lt;/a&gt;Cache TTL
&lt;/h3&gt;&lt;p&gt;VNDB results are stored persistently in &lt;code&gt;cache.csv&lt;/code&gt;. On each cache hit the daemon compares the entry&amp;rsquo;s &lt;code&gt;cached_at&lt;/code&gt; timestamp against &lt;code&gt;VNDB_CACHE_TTL&lt;/code&gt;. If the entry is older than the TTL, it is treated as expired and a fresh VNDB query is issued, updating the stored data and timestamp.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 HIT[Cache hit] --&gt; AGE{age &gt; VNDB_CACHE_TTL?}
 AGE -- no --&gt; USE[Use cached VnInfo]
 AGE -- yes --&gt; REQUERY[Re-query VNDB\nupdate cache.csv]
 REQUERY --&gt; USE&lt;/pre&gt;&lt;p&gt;This means ratings, cover images, and release dates in the cache are automatically refreshed over time without any manual intervention. The log line when a re-query fires looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[INFO] Cache expired for &amp;#34;...&amp;#34; age=1442min/1440min — re-querying VNDB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="playtime--discord-elapsed-timer"&gt;&lt;a href="#playtime--discord-elapsed-timer" class="header-anchor"&gt;&lt;/a&gt;Playtime → Discord elapsed timer
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 SRC{Source?}
 SRC -- lutris --&gt; LDB[Lutris DB\npga.db · hours]
 SRC -- steam-appid --&gt; VDF[Steam VDF\nlocalconfig.vdf · minutes]
 LDB --&gt; PT[playtime in seconds]
 VDF --&gt; PT
 PT --&gt; TS[start_ts = now − playtime]
 TS --&gt; DISC[Discord: elapsed timer\ncounts up from start_ts]&lt;/pre&gt;&lt;hr&gt;
&lt;h3 id="cache-file-cachecsv--cachedb"&gt;&lt;a href="#cache-file-cachecsv--cachedb" class="header-anchor"&gt;&lt;/a&gt;Cache file (&lt;code&gt;cache.csv&lt;/code&gt; / &lt;code&gt;cache.db&lt;/code&gt;)
&lt;/h3&gt;&lt;p&gt;Located at &lt;code&gt;~/.config/vn-discord-rpc/cache.csv&lt;/code&gt; (default) or &lt;code&gt;cache.db&lt;/code&gt; when &lt;code&gt;CACHE_USE_DB = true&lt;/code&gt;. Reloaded automatically whenever the file changes on disk.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Column&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Detected Visual Novels name (the search term)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;alias&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Redirect this key to a different search term&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;vndb_id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;v562&lt;/code&gt;, &lt;code&gt;v67&lt;/code&gt;, &lt;code&gt;SKIP&lt;/code&gt;, or empty&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Romanised VNDB title (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;alt_title&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Original script title, e.g. Japanese (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image_url&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Cover image URL (auto-filled, blank if explicit(sexual or violence))&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image_sexual&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;VNDB sexual rating 0–2 (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image_violence&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;VNDB violence rating 0–2 (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;rating&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;VNDB community rating 0–100 (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;released&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Release date (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;cached_at&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Unix timestamp of last write (auto-filled)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csv" data-lang="csv"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Alias — fix wrong or garbled detection&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Nice boat!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;School Days&lt;/span&gt;&lt;span class="p"&gt;,,,,,,,,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Skip — suppress presence for this title&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;妹ぱらだいす！２&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="s"&gt;SKIP&lt;/span&gt;&lt;span class="p"&gt;,,,,,,,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Hard-link — bypass VNDB query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt; point directly to an entry&lt;/span&gt;&lt;span class="p"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;My VN Title&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="s"&gt;v67&lt;/span&gt;&lt;span class="p"&gt;,,,,,,,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="ignore-list-ignoretxt"&gt;&lt;a href="#ignore-list-ignoretxt" class="header-anchor"&gt;&lt;/a&gt;Ignore list (&lt;code&gt;ignore.txt&lt;/code&gt;)
&lt;/h3&gt;&lt;p&gt;Located at &lt;code&gt;~/.config/vn-discord-rpc/ignore.txt&lt;/code&gt;. One entry per line, &lt;code&gt;#&lt;/code&gt; for comments. Reloaded automatically while running.&lt;/p&gt;
&lt;p&gt;Matching rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Entry &lt;strong&gt;&amp;lt; 4 characters&lt;/strong&gt;: exact match (case-insensitive) — (ie. prevents &lt;code&gt;sh&lt;/code&gt; matching &lt;code&gt;Higurashi&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Entry &lt;strong&gt;≥ 4 characters&lt;/strong&gt;: case-insensitive substring match&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default file includes common false-positives: Steam runtimes, Proton, Wine helpers, and launchers.&lt;/p&gt;
&lt;blockquote class="alert alert-warning"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;⚠️&lt;/span&gt;
 &lt;span class="alert-title"&gt;Warning&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;If a title has no VNDB match and is not in the ignore list, the daemon will not add it
to ignore automatically. It will retry the VNDB query on every detection. Add a &lt;code&gt;SKIP&lt;/code&gt;
entry in &lt;code&gt;cache.csv&lt;/code&gt; or an entry in &lt;code&gt;ignore.txt&lt;/code&gt; to suppress it permanently.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="configuration-srcconfighpp"&gt;&lt;a href="#configuration-srcconfighpp" class="header-anchor"&gt;&lt;/a&gt;Configuration (&lt;code&gt;src/config.hpp&lt;/code&gt;)
&lt;/h2&gt;&lt;blockquote class="alert alert-caution"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;🚨&lt;/span&gt;
 &lt;span class="alert-title"&gt;Caution&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Be caution on changing &lt;code&gt;IMAGE_SEXUAL&lt;/code&gt; and &lt;code&gt;IMAGE_VIOLENCE&lt;/code&gt; thresholds in &lt;code&gt;src/config.hpp&lt;/code&gt;.
Those thresholds are set to &lt;code&gt;1.80&lt;/code&gt; (slightly below VNDB&amp;rsquo;s &lt;strong&gt;Explicit&lt;/strong&gt; level of &lt;code&gt;2.00&lt;/code&gt;)
because VNDB image ratings are user-reported and may be underrated by a small margin —
the 0.20 buffer ensures borderline explicit covers are still suppressed.
Raising the threshold above &lt;code&gt;2.00&lt;/code&gt; would cause explicit (pornographic) cover art to appear
in your Discord status, visible to everyone on your friends list. Displaying explicit
content in Discord Rich Presence may violates &lt;a class="link" href="https://discord.com/terms" target="_blank" rel="noopener"
 &gt;Discord&amp;rsquo;s Terms of Service&lt;/a&gt;
and &lt;strong&gt;may result in a permanent account ban&lt;/strong&gt;.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Constant&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;DISCORD_APP_ID&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1482345564698841189&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discord application ID&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;DISCORD_ACTIVITY_TYPE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Activity type: 0=Game, 1=Streaming, 2=Listening, 3=Watching&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;IMAGE_SEXUAL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1.80&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Maximum sexual rating before cover is suppressed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;IMAGE_VIOLENCE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1.80&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Maximum violence rating before cover is suppressed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;IMAGE_VOTECOUNT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Minimum vote count before ratings are trusted&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;VNDB_MIN_SIMILARITY&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.35&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Minimum trigram score to accept a VNDB match&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;POLL_INTERVAL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How often to scan &lt;code&gt;/proc&lt;/code&gt; for running processes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;VNDB_CACHE_TTL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;24h&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How long a cache entry is valid before re-querying VNDB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;STABLE_TITLE_POLLS&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Polls a candidate must be stable before acting&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;VNDB_MAX_RESULTS&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Maximum results per VNDB query (index 0 is used)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CACHE_USE_DB&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Use SQLite (&lt;code&gt;cache.db&lt;/code&gt;) instead of CSV &lt;em&gt;(experimental)&lt;/em&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RPC_STATE_READING&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;quot;Reading&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discord state string while a VN is active&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RPC_STATE_IDLE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;quot;Idle&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discord state string while idle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RPC_DEFAULT_DETAILS&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;quot;Playing a Visual Novel&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Details line when no VNDB match is found&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RPC_SMALL_IMG_KEY&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;quot;vndb_logo&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discord asset key for the small VNDB logo image&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RPC_SMALL_IMG_TEXT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;quot;VNDB&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Tooltip text for the small image&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="architecture"&gt;&lt;a href="#architecture" class="header-anchor"&gt;&lt;/a&gt;Architecture
&lt;/h2&gt;&lt;p&gt;The project is composed of nine focused, single-responsibility modules:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Module&lt;/th&gt;
 &lt;th&gt;Source Files&lt;/th&gt;
 &lt;th&gt;Responsibility&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Entry point&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;main.cpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Main loop, debounce state machine, candidate resolution pipeline&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Process scanner&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;process_scanner.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Iterates &lt;code&gt;/proc&lt;/code&gt;, detects Lutris and Steam game processes, reads &lt;code&gt;starttime&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Steam detector&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;steam_detector.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Reads &lt;code&gt;SteamAppId&lt;/code&gt; env vars, parses ACF manifests and VDF playtime files&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;VNDB client&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;vndb_client.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;HTTP POST to VNDB Kana API, trigram matching, release endpoint fallback&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;VN cache&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;vn_cache.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Persistent CSV/SQLite cache with alias, SKIP, TTL, and live-reload logic&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Ignore list&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ignore_list.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Live-reloadable process-name suppression list with exact/substring rules&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;RPC manager&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;rpc_manager.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discord IPC wrapper with rate limiting, deferred flush, and change detection&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Config&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;config.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;All compile-time constants in one place&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Logger&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;logger.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Thread-safe ANSI-coloured logger singleton with &lt;code&gt;LOG_DEBUG/INFO/WARN/ERR&lt;/code&gt; macros&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Lutris DB&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;lutris_db.cpp/.hpp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Reads and formats playtime from Lutris&amp;rsquo;s SQLite &lt;code&gt;pga.db&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="key-design-decisions"&gt;&lt;a href="#key-design-decisions" class="header-anchor"&gt;&lt;/a&gt;Key Design Decisions
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Deterministic multi-candidate priority&lt;/strong&gt; — when multiple games are running, candidates are sorted by &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/stat&lt;/code&gt; field 22 (&lt;code&gt;starttime&lt;/code&gt;, clock ticks since boot). The process that launched earliest is always tried first, making priority stable and reproducible across polls without any user configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discord rate-limit compliance&lt;/strong&gt; — Discord silently drops &lt;code&gt;SET_ACTIVITY&lt;/code&gt; calls faster than ~15 seconds apart. &lt;code&gt;RpcManager&lt;/code&gt; tracks the wall-clock time of the last successful push and defers any call that falls within a 16-second window. Deferred updates are flushed on the next &lt;code&gt;runCallbacks()&lt;/code&gt; tick, ensuring no update is ever lost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No presence flicker on stable sessions&lt;/strong&gt; — the early-out in the main loop checks whether the currently displayed VN is still in the candidate set before doing any cache or VNDB work. If it is still running, the loop sleeps immediately, so Discord is never unnecessarily cleared and re-set.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cache-first, ignore-list-second&lt;/strong&gt; — VNDB HTTP queries are only issued on a true cache miss or TTL expiry. Candidates that fail VNDB resolution are added to the ignore list so subsequent polls skip them instantly, letting the next candidate be tried without any network round trip.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live file reloading without restart&lt;/strong&gt; — both &lt;code&gt;cache.csv&lt;/code&gt; and &lt;code&gt;ignore.txt&lt;/code&gt; are checked for &lt;code&gt;mtime&lt;/code&gt; changes on every poll using &lt;code&gt;std::filesystem::last_write_time&lt;/code&gt;. Edits made while the daemon is running take effect within one poll cycle with no signals or process restart needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explicit content safety&lt;/strong&gt; — image ratings are only trusted when backed by at least &lt;code&gt;IMAGE_VOTECOUNT&lt;/code&gt; votes. Cover URLs are stripped from cache entries for explicit results so they can never accidentally appear after a cache reload even if thresholds are later changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="building"&gt;&lt;a href="#building" class="header-anchor"&gt;&lt;/a&gt;Building
&lt;/h2&gt;&lt;blockquote class="alert alert-important"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📌&lt;/span&gt;
 &lt;span class="alert-title"&gt;Important&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Initialise submodules before building — the two header-only dependencies are not
downloaded automatically.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake -B build -DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Release
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake --build build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="dependencies"&gt;&lt;a href="#dependencies" class="header-anchor"&gt;&lt;/a&gt;Dependencies
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Library&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://github.com/EclipseMenu/discord-presence" target="_blank" rel="noopener"
 &gt;discord-presence&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Discord Rich Presence (modern C++ rewrite)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://github.com/nlohmann/json" target="_blank" rel="noopener"
 &gt;nlohmann/json&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;JSON parsing&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://curl.se/libcurl/" target="_blank" rel="noopener"
 &gt;libcurl&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;HTTP requests to VNDB API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://www.sqlite.org/" target="_blank" rel="noopener"
 &gt;libsqlite3&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Read Lutris playtime database&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="usage"&gt;&lt;a href="#usage" class="header-anchor"&gt;&lt;/a&gt;Usage
&lt;/h2&gt;&lt;blockquote class="alert alert-tip"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;💡&lt;/span&gt;
 &lt;span class="alert-title"&gt;Tip&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Run with &lt;code&gt;--verbose&lt;/code&gt; if a title is not being detected or matched — the debug output shows
exactly which process was found, what VNDB returned, and the similarity score.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./vn-discord-rpc &lt;span class="c1"&gt;# normal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./vn-discord-rpc --verbose &lt;span class="c1"&gt;# debug logging&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./vn-discord-rpc --help
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Launch a game through &lt;strong&gt;Lutris&lt;/strong&gt; or &lt;strong&gt;Steam&lt;/strong&gt;, and the daemon will detect it automatically. Press &lt;code&gt;Ctrl+C&lt;/code&gt; to quit cleanly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="file-locations"&gt;&lt;a href="#file-locations" class="header-anchor"&gt;&lt;/a&gt;File locations
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;File&lt;/th&gt;
 &lt;th&gt;Path&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Cache (CSV)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.config/vn-discord-rpc/cache.csv&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cache (SQLite, experimental)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.config/vn-discord-rpc/cache.db&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Ignore list&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.config/vn-discord-rpc/ignore.txt&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Lutris DB&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.local/share/lutris/pga.db&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Steam VDF&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.local/share/Steam/userdata/&amp;lt;id&amp;gt;/config/localconfig.vdf&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>