<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[夜航日志]]></title> 
<atom:link href="https://emlog.bj.gwbgwb.top/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[拆解、记录、继续]]></description>
<link>https://emlog.bj.gwbgwb.top/</link>
<language>zh-cn</language>

<item>
    <title>从一个 .pyd 说起：Nuitka 打包之后还剩什么</title>
    <link>https://emlog.bj.gwbgwb.top/?post=2</link>
    <description><![CDATA[<p>拿到一个 Nuitka 打包的项目，目录结构和普通 Python 工程没什么两样，只是所有 <code>.py</code> 都变成了 <code>.pyd</code>。直觉上这是"编译没了"，但实际拆开看，Nuitka 并不会把 Python 字节码完全抹掉——它只是换了一种存放方式。</p>
<h2>先说结论</h2>
<p>Nuitka 默认模式（standalone / onefile）下，模块常量表、字符串、函数名、docstring 大都以明文或简单编码形式留在二进制里。<strong>它不是加密，只是混淆</strong>。真正做加密的方案，通常是在其外面再包一层 loader。</p>
<blockquote>
<p>判断顺序：先看有没有壳，再看常量表能不能直接提取，最后才考虑动态调试。顺序反了会浪费大量时间。</p>
</blockquote>
<h2>文件头里藏着什么</h2>
<p>把 <code>.pyd</code> 拖进十六进制编辑器，前 2 字节是 <code>MZ</code>，标准 PE。真正的信息在 <code>.rdata</code> 段。</p>
<pre><code class="language-python">import pefile

pe = pefile.PE("module.pyd")
for sec in pe.sections:
    name = sec.Name.rstrip(b"\x00").decode(errors="replace")
    print(f"{name:10} vaddr={sec.VirtualAddress:#x} "
          f"rawsize={sec.SizeOfRawData:#x}")
</code></pre>
<p>重点看 <code>.rdata</code> 和 <code>.data</code> 的大小比例。如果 <code>.rdata</code> 异常大（几 MB），基本可以确定常量都堆在那儿。</p>
<h3>关于 IV 与密钥</h3>
<p>如果 loader 用了 AES，密钥和 IV 通常会以两种形式出现：</p>
<ul>
<li>硬编码在 <code>.rdata</code>，长度刚好 32 / 16 字节</li>
<li>由某个常量字符串经过一次哈希派生</li>
</ul>
<p>后者更难找，但特征也明显——一定会引用 SHA-256 或 MD5 的常量表。</p>
<h2>三种方案的对比</h2>
<table>
<thead><tr><th>方案</th><th>破解难度</th><th>典型特征</th></tr></thead>
<tbody>
<tr><td>Nuitka 原生</td><td>低</td><td>常量明文、符号可读</td></tr>
<tr><td>外层 AES loader</td><td>中</td><td>文件头 IV、密文段固定对齐</td></tr>
<tr><td>VM 保护</td><td>高</td><td>大量间接跳转、无标准函数序言</td></tr>
</tbody>
</table>
<h2>下一步做什么</h2>
<p>静态能看到的东西有限。真正跑起来之后，用调试器在 <code>PyImport_ExecCodeModule</code> 下断点，能直接拿到解码后的 code object——这比在二进制里翻字符串高效得多。</p>
<p>整个过程我保持在本地、离线、只针对有授权的样本。这一点没有讨论空间。</p>]]></description>
    <pubDate>Tue, 03 Nov 2026 00:09:10 +0800</pubDate>
    <dc:creator>夜航</dc:creator>
    <guid>https://emlog.bj.gwbgwb.top/?post=2</guid>
</item>
<item>
    <title>用 base64 压状态：2C2G 机器上的实时同步</title>
    <link>https://emlog.bj.gwbgwb.top/?post=3</link>
    <description><![CDATA[<p>实时协作类应用最难的不是逻辑，是带宽。当上行只有 3Mbps、单帧状态又有几十个对象时，JSON 会把你压死——不是性能，是流量。</p>
<h2>第一步：把 JSON 去掉</h2>
<p>每帧的字段名完全可以省略。约定一个固定顺序，用数组代替对象：</p>
<pre><code class="language-javascript">// 之前：每帧 240 字节
{ "players": [{ "id": "a1", "x": 12, "y": 33, "len": 7 }] }

// 之后：每帧 14 字节
[1, 12, 33, 7]
</code></pre>
<p>字段名在协议里只出现一次，之后都是位置约定。这一项直接砍掉 90% 的体积。</p>
<h2>第二步：只发变化</h2>
<p>全量推送没有必要。客户端保留上一帧，服务端只发 diff：</p>
<pre><code class="language-python">def diff(prev, cur):
    out = []
    for pid, obj in cur.items():
        if prev.get(pid) != obj:
            out.append(pack(pid, obj))
    return out
</code></pre>
<p>安静的时候，一帧就是几个字节。热闹的时候才会涨，但热闹的时候用户注意力也在别处，掉几帧没人会发现。</p>
<h2>第三步：二进制 + base64</h2>
<p>SSE 是文本协议，二进制要先转 base64。看起来会膨胀 33%，但因为前面已经砍掉了 90%，最终结果仍然远小于原始 JSON。</p>
<table>
<thead><tr><th>方案</th><th>单帧</th><th>10 人 20fps</th></tr></thead>
<tbody>
<tr><td>原始 JSON</td><td>2.4 KB</td><td>480 KB/s</td></tr>
<tr><td>数组化 JSON</td><td>210 B</td><td>42 KB/s</td></tr>
<tr><td>二进制 + base64</td><td>96 B</td><td>19 KB/s</td></tr>
</tbody>
</table>
<blockquote>
<p>只要协议是你自己定的，压缩永远有空间。真正的成本不在带宽，在于你愿不愿意写一个自定义的编解码器。</p>
</blockquote>
<h2>客户端插值</h2>
<p>服务端 20fps，屏幕 60fps，中间的空档靠客户端补。简单线性插值加一点缓动，肉眼完全看不出差别，但服务端压力降到三分之一。</p>
<pre><code class="language-javascript">const t = (now - lastFrameAt) / FRAME_MS;
render(lerp(prevState, nextState, Math.min(t, 1)));
</code></pre>
<p>整个方案跑在两核机器上，CPU 常年在 8% 以下。省下来的不是钱，是那种"够用"的踏实感。</p>]]></description>
    <pubDate>Sun, 01 Nov 2026 00:09:10 +0800</pubDate>
    <dc:creator>夜航</dc:creator>
    <guid>https://emlog.bj.gwbgwb.top/?post=3</guid>
</item>
<item>
    <title>SQLite 够用，而且比你想的更快</title>
    <link>https://emlog.bj.gwbgwb.top/?post=4</link>
    <description><![CDATA[<p>每次说要给一个小站用 SQLite，都会有人问"能扛住吗"。我的回答通常是：你先告诉我你有多少并发。大多数时候答案是"不到十个人"。</p>
<h2>WAL 之后，读写不互斥</h2>
<p>默认的 journal 模式下，写会锁住整个库。打开 WAL 之后，读写可以并行，单进程写入也能达到每秒几千次事务。</p>
<pre><code class="language-sql">PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -32000;
</code></pre>
<p>这三行基本就是全部调优。第二条在断电时可能丢最后一个事务，但对博客来说完全可接受。</p>
<h2>备份是一个 cp</h2>
<p>这是我坚持用它的真正原因。MySQL 的备份要考虑锁、一致性、binlog 位置；SQLite 只需要：</p>
<pre><code class="language-bash">sqlite3 app.db ".backup /root/backup-$(date +%F).db"
</code></pre>
<p>或者干脆停两秒服务，直接 <code>cp</code>。恢复也是复制回去。没有第二个步骤。</p>
<h2>什么时候不该用它</h2>
<ul>
<li>需要多台机器同时写（它没有主从）</li>
<li>单表上千万行且频繁范围扫描</li>
<li>需要细粒度的权限控制</li>
</ul>
<p>这三条之外的绝大多数场景，SQLite 都不会成为瓶颈。真正的瓶颈通常是你自己的查询写得不对。</p>]]></description>
    <pubDate>Thu, 29 Oct 2026 00:09:10 +0800</pubDate>
    <dc:creator>夜航</dc:creator>
    <guid>https://emlog.bj.gwbgwb.top/?post=4</guid>
</item>
<item>
    <title>够用就好，是一种省力的活法</title>
    <link>https://emlog.bj.gwbgwb.top/?post=5</link>
    <description><![CDATA[<p>有一类疲惫不来自工作量，来自"还可以更好"这个念头。它没有终点，所以永远做不完。</p>
<p>写代码的时候它表现为重构上瘾：功能已经能跑了，但你觉得这个抽象不够干净，于是推倒重来，第三天发现新的抽象也有问题。</p>
<p>选工具的时候它表现为选型焦虑：为了一个日均访问几十次的小站，比较了六个框架，读了二十篇 benchmark，最后选了最复杂的那个，因为"以后可能会用到"。</p>
<blockquote>
<p>以后大概率不会用到。而后来的你，只会为当初多出来的复杂度付维护成本。</p>
</blockquote>
<h2>判断标准其实很简单</h2>
<p>问自己一句：这个"更好"，半年后还有人能感觉到吗？</p>
<p>如果答案是不能，那它就不值得。半年后能感觉到的，通常是启动速度、故障恢复、以及"这东西居然一直没坏"的安心感——这些恰恰是简单方案更容易给出的。</p>
<p>所以我现在写东西的顺序变了：先让它跑起来，再让它不坏，最后才考虑好看。大多数项目停在第一步就已经交付了价值。</p>]]></description>
    <pubDate>Mon, 26 Oct 2026 00:09:10 +0800</pubDate>
    <dc:creator>夜航</dc:creator>
    <guid>https://emlog.bj.gwbgwb.top/?post=5</guid>
</item>
<item>
    <title>深夜的机器最安静</title>
    <link>https://emlog.bj.gwbgwb.top/?post=6</link>
    <description><![CDATA[<p>白天的时间是被切碎的。消息、会议、别人的需求，每隔十几分钟就来一次。在这种节奏里，你只能处理"下一步做什么"，没法想"这件事到底该不该做"。</p>
<p>凌晨不一样。没有人会在这个时候找你，所有未读都安静下来。屏幕的光照着桌面，风扇声音很低，你能听见自己思考的过程。</p>
<p>我不是鼓励熬夜。我只是觉得，每个人都需要一段不被打断的时间，用来处理那些白天排不进日程的事情——比如想清楚一个设计，比如承认某个方向是错的。</p>
<blockquote>
<p>有些决定只能在安静的时候做出来。热闹的时候，人只会顺着惯性往前走。</p>
</blockquote>
<p>所以我把重要的判断都留到晚上。白天只负责执行。</p>]]></description>
    <pubDate>Wed, 21 Oct 2026 00:09:10 +0800</pubDate>
    <dc:creator>夜航</dc:creator>
    <guid>https://emlog.bj.gwbgwb.top/?post=6</guid>
</item>
</channel>
</rss>