<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://csmantle.top/feed.xml" rel="self" type="application/atom+xml" /><link href="https://csmantle.top/" rel="alternate" type="text/html" /><updated>2026-03-12T03:48:31+00:00</updated><id>https://csmantle.top/feed.xml</id><title type="html">Mantle’s Potato Bakery</title><subtitle>A repository of my thoughts and ideas, organized and unorganized, up-to-date and outdated, and everything in between.</subtitle><author><name>Rong Bao</name></author><entry xml:lang="zh"><title type="html">在龙架构上“养龙虾”</title><link href="https://csmantle.top/2026/03/10/zeroclaw-on-arch-for-loong64.html" rel="alternate" type="text/html" title="在龙架构上“养龙虾”" /><published>2026-03-10T15:15:45+00:00</published><updated>2026-03-10T15:15:45+00:00</updated><id>https://csmantle.top/2026/03/10/zeroclaw-on-arch-for-loong64</id><content type="html" xml:base="https://csmantle.top/2026/03/10/zeroclaw-on-arch-for-loong64.html"><![CDATA[<p>近期以 *Claw 为代表的智能体非常流行，其中不乏 Rust 语言的实现。因此，我试着在运行 <a href="https://loongarchlinux.lcpu.dev/">Arch Linux for Loong64</a> 的龙芯 3B6000 上“养”了一只龙虾，达到了不错的效果。</p>

<h2 id="0-前置条件">0. 前置条件</h2>

<p>这是一篇笔记式博客，包含了多种仅适用于我本人系统环境的操作与考虑，其中有些已在文内标出，有些则不然。我的系统配置如下：</p>

<ul>
  <li><strong>主板：</strong> <a href="https://loongfans.cn/devices/loongson-xb612b0-v1.2">Loongson-3B6000x1-7A2000x1-EVB</a></li>
  <li><strong>内核：</strong> <a href="https://github.com/CSharperMantle/linux-csmantle">Linux 6.19.6-arch1-1-csmantle-aosc-main-16k</a></li>
</ul>

<p>如果你想跟随本篇博客进行操作，需要准备以下材料：</p>

<ul>
  <li>使用 Linux 系统与常见开发工具的经验；
    <ul>
      <li>能理解每步操作的目的、效果及对系统安全的影响</li>
      <li>能不借助 <abbr title="大语言模型">LLM</abbr>，利用手册、文档、Wiki 或搜索引擎进行故障检修</li>
    </ul>
  </li>
  <li>一个正常的网络环境；</li>
  <li>30 分钟以上、一两个小时为宜的空余时间；
    <ul>
      <li>我在安装时花了一个下午，但主要是<a href="#i-花絮失败的尝试">在多个 *Claw 实现与部署方案之间不断试错</a></li>
    </ul>
  </li>
  <li>Rust 编译器套件和 Cargo。
    <ul>
      <li><a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html#:~:text=loongarch64-unknown-linux-gnu">龙架构的 Rust 平台支持已较为完善</a>，一般可以无痛原生编译。</li>
    </ul>
  </li>
</ul>

<h2 id="1-编译二进制">1. 编译二进制</h2>

<p>在 <code class="language-plaintext highlighter-rouge">/opt</code> 下创建目录并设置权限。我在这里选择将所有者设置为 <code class="language-plaintext highlighter-rouge">csmantle:wheel</code>、目录权限设为 <code class="language-plaintext highlighter-rouge">drwxr-sr-x</code>，是为了对齐 <code class="language-plaintext highlighter-rouge">/opt</code> 下软件全局所有的设计用途，实践中可以自行考量：</p>

<blockquote>
  <p>3.13. /opt : Add-on application software packages</p>

  <p>3.13.1. Purpose</p>

  <p><code class="language-plaintext highlighter-rouge">/opt</code> is reserved for the installation of add-on application software packages.</p>

  <p>– <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html#optAddonApplicationSoftwarePackages">https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html#optAddonApplicationSoftwarePackages</a></p>
</blockquote>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /opt/zeroclaw
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo chown</span> <span class="nt">-R</span> csmantle:wheel /opt/zeroclaw
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>find /opt/zeroclaw <span class="nt">-type</span> d <span class="nt">-exec</span> <span class="nb">chmod </span>2755 <span class="o">{}</span> <span class="se">\;</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>setfacl <span class="nt">-R</span> <span class="nt">-d</span> <span class="nt">-m</span> u::rwx,g::w-x,o::r-x /opt/zeroclaw
</code></pre></div></div>

<p>进入目录下后进行克隆、签出并构建。我选择的是 v0.1.7 tag，这是当时最新的 tag，读者可以自行选择基提交。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> /opt/zeroclaw
<span class="gp">$</span><span class="w"> </span>git clone <span class="nt">--</span> https://github.com/zeroclaw-labs/zeroclaw.git <span class="nb">.</span>
<span class="gp">$</span><span class="w"> </span>git checkout v0.1.7
</code></pre></div></div>

<p>进行构建。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo build <span class="nt">--release</span> <span class="nt">--locked</span>
</code></pre></div></div>

<h2 id="2-准备容器">2. 准备容器</h2>

<p>使用 <a href="https://wiki.archlinux.org/title/Systemd-nspawn">systemd-nspawn(1)</a> 创建容器。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>pacstrap <span class="nt">-K</span> <span class="nt">-c</span> /var/lib/machines/claw base vim less
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemd-nspawn <span class="nt">-D</span> /var/lib/machines/claw
<span class="gp">#</span><span class="w"> </span><span class="nb">logout</span>
</code></pre></div></div>

<p>之后配置容器内网络。我这里选择使用 NAT 联网，留出 198.18.1.0/24 作为容器网段。使用 systemd-networkd 创建一个虚拟接口。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee /etc/systemd/network/10-ve-br0.netdev
</span><span class="go">[NetDev]
Name=ve-br0
Kind=bridge
EOF
</span><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee /etc/systemd/network/80-ve-br0.network
</span><span class="go">[Match]
Name=ve-br0

[Link]
RequiredForOnline=no

[Network]
Address=198.18.1.1/24
DHCPServer=yes
IPv4Forwarding=yes
IPMasquerade=ipv4
ConfigureWithoutCarrier=yes
LLMNR=no
MulticastDNS=no

[DHCPServer]
PoolSize=64
DefaultLeaseTimeSec=1h
MaxLeaseTimeSec=1d
EOF
</span><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee /etc/systemd/nspawn/claw.nspawn
</span><span class="go">[Exec]
Boot=yes
ResolvConf=copy-host

[Network]
VirtualEthernet=yes
Bridge=ve-br0
EOF
</span></code></pre></div></div>

<p>之后配置防火墙允许 DHCP。我的机器上部署的是 <a href="https://manpages.org/ufw/8">ufw</a>。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw allow <span class="k">in </span>on ve-br0 to any port 67 proto udp
</code></pre></div></div>

<p>之后启动 systemd-networkd。这里有一个需要注意的地方：虽然 systemd-networkd 并不会抢占其他网络管理器（比如 NetworkManager(1)）管理的接口，但是其 wait-online 服务 <a href="https://www.man7.org/linux/man-pages/man8/systemd-networkd-wait-online.8.html">systemd-networkd-wait-online.service</a> 依然会默认开启。如果你的 systemd-networkd <strong>只</strong> 管理了虚拟接口，由于虚拟接口永远不会 online，这个服务会超时失败。这时候需要将其禁用。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl disable <span class="nt">--now</span> systemd-networkd-wait-online.service
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl mask systemd-networkd-wait-online.service
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl <span class="nb">enable</span> <span class="nt">--now</span> systemd-networkd.service
</code></pre></div></div>

<p>之后将编译好的二进制 bind 至容器内即可。</p>

<pre><code class="language-plain-text"># /etc/systemd/nspawn/claw.nspawn
[Files]
BindReadOnly=/opt/zeroclaw/target/release/zeroclaw:/usr/local/bin/zeroclaw
BindReadOnly=/opt/zeroclaw:/opt/zeroclaw
</code></pre>

<h2 id="3-部署">3. 部署</h2>

<p>进入容器，进行 onboard 并且自定义配置，设置服务。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>machinectl list
<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>machinectl shell claw
<span class="gp">#</span><span class="w"> </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> &gt;/etc/systemd/system/zeroclaw.service
</span><span class="go">[Unit]
Description=ZeroClaw

[Service]
Type=exec
ExecStart=/usr/local/bin/zeroclaw daemon
User=root
WorkingDirectory=/root
Restart=always

[Install]
WantedBy=multi-user.target
EOF
</span><span class="gp">#</span><span class="w"> </span>zeroclaw onboard
<span class="gp">#</span><span class="w"> </span><span class="c"># Other steps...</span>
<span class="gp">#</span><span class="w"> </span>systemctl <span class="nb">enable</span> <span class="nt">--now</span> zeroclaw.service
</code></pre></div></div>

<p>在宿主容器内设置开机启动。</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl <span class="nb">enable</span> <span class="nt">--now</span> systemd-nspawn@claw.service
</code></pre></div></div>

<p>之后还可以在容器内配置代理、隧道、工具等，对容器本身还可以进行资源限制，不多赘述。</p>

<hr />

<blockquote>
  <p>这些都是只很通用的部署步骤，任何一台 x86 Arch 都可以这么操作，跟龙架构有什么关系？</p>
</blockquote>

<p>这才是这个部署流程的关键所在。龙架构生态已经能够大体支持无痛、无缝部署 ZeroClaw，体验上甚至可以与 x86 Arch 并驾齐驱。这离不开龙芯中科与整个龙芯爱好者社区的不懈努力。</p>

<p>当然，以上只是故事的一半……</p>

<h2 id="i-花絮失败的尝试">i. 花絮：失败的尝试</h2>

<p>我最开始尝试的实现是 <a href="https://github.com/nearai/ironclaw">IronClaw</a>。实际上，在整个踩坑过程中的 90% 时间花在这个部分中。</p>

<p>首先尝试的是原生编译 IronClaw。遗憾的是其依赖 <a href="https://wasmtime.dev/">Wasmtime</a> 的 JIT 后端 <a href="https://github.com/bytecodealliance/wasmtime/tree/main/cranelift">Cranelift</a> 并不支持龙架构。为 Cranelift 增加新的后端是一个很大的工作，且目前暂无公开社区努力。</p>

<p>之后尝试交叉编译至 x86_64 并使用 QEMU 模拟执行。首先尝试的目标是 <code class="language-plaintext highlighter-rouge">x86_64-unknown-linux-gnu</code>，但 <abbr title="Arch Linux for Loong64">Arch4Loong</abbr> 对非原生架构的支持十分不完善，缺少 ld 等多种必须运行时库。因此尝试 <code class="language-plaintext highlighter-rouge">x86_64-unknown-linux-musl</code>，静态编译 OpenSSL 后再使用 Cargo 编译项目。这条路径上的第一个问题是 <abbr title="Arch Linux for Loong64">Arch4Loong</abbr> 的 x86_64 binutils 和原生 binutils 并不兼容。后者使用 2.46，<a href="https://archlinux.org/packages/core/x86_64/binutils/#:~:text=%20libsframe.so=3-64">提供 <code class="language-plaintext highlighter-rouge">libsframe.so.3</code></a>，而前者版本为 2.45，需要 <code class="language-plaintext highlighter-rouge">libsframe.so.2</code>。对<a href="https://github.com/lcpu-club/loongarch-packages/blob/511f520ff4583ff80f448c530cc31003a0055425/x86_64-linux-gnu-binutils/PKGBUILD">相关 PKGBUILD</a> 打补丁保留这个 soname 后可以正常工作：</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/x86_64-linux-gnu-binutils/PKGBUILD b/x86_64-linux-gnu-binutils/PKGBUILD
index 547876b..729ae63 100644
</span><span class="gd">--- a/x86_64-linux-gnu-binutils/PKGBUILD
</span><span class="gi">+++ b/x86_64-linux-gnu-binutils/PKGBUILD
</span><span class="p">@@ -3,7 +3,7 @@</span>
 _target=x86_64-linux-gnu
 pkgname=$_target-binutils
 pkgver=2.45
<span class="gd">-pkgrel=1
</span><span class="gi">+pkgrel=2
</span> pkgdesc='A set of programs to assemble and manipulate binary and object files for 32-bit and 64-bit x86'
 arch=(loong64)
 url='https://www.gnu.org/software/binutils/'
<span class="p">@@ -90,7 +90,10 @@</span> package() {
<span class="err">
</span>   # Remove file conflicting with host binutils and manpages for MS Windows tools
   rm "$pkgdir"/usr/share/man/man1/$_target-{dlltool,windres,windmc}*
<span class="gd">-  rm -rf "$pkgdir"/usr/lib
</span><span class="gi">+  find "$pkgdir"/usr/lib -mindepth 1 \
+                         ! -name 'libsframe.so.2' \
+                         ! -name 'libsframe.so.2.0.0' \
+                         -delete
</span><span class="err">
</span>   rm -rf "$pkgdir"/usr/include
</code></pre></div></div>

<p>之后继续尝试静态编译后，编译产物在 QEMU 下和原生环境下始终出现段错误。再加上其令人担忧的代码质量、onboarding 体验、版本兼容性、修复 bug 的方式等种种因素，最终选择放弃部署 IronClaw。</p>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><category term="loongarch" /><summary type="html"><![CDATA[近期以 *Claw 为代表的智能体非常流行，其中不乏 Rust 语言的实现。因此，我试着在运行 Arch Linux for Loong64 的龙芯 3B6000 上“养”了一只龙虾，达到了不错的效果。 0. 前置条件 这是一篇笔记式博客，包含了多种仅适用于我本人系统环境的操作与考虑，其中有些已在文内标出，有些则不然。我的系统配置如下： 主板： Loongson-3B6000x1-7A2000x1-EVB 内核： Linux 6.19.6-arch1-1-csmantle-aosc-main-16k 如果你想跟随本篇博客进行操作，需要准备以下材料： 使用 Linux 系统与常见开发工具的经验； 能理解每步操作的目的、效果及对系统安全的影响 能不借助 LLM，利用手册、文档、Wiki 或搜索引擎进行故障检修 一个正常的网络环境； 30 分钟以上、一两个小时为宜的空余时间； 我在安装时花了一个下午，但主要是在多个 *Claw 实现与部署方案之间不断试错 Rust 编译器套件和 Cargo。 龙架构的 Rust 平台支持已较为完善，一般可以无痛原生编译。 1. 编译二进制 在 /opt 下创建目录并设置权限。我在这里选择将所有者设置为 csmantle:wheel、目录权限设为 drwxr-sr-x，是为了对齐 /opt 下软件全局所有的设计用途，实践中可以自行考量： 3.13. /opt : Add-on application software packages 3.13.1. Purpose /opt is reserved for the installation of add-on application software packages. – https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html#optAddonApplicationSoftwarePackages $ sudo mkdir -p /opt/zeroclaw $ sudo chown -R csmantle:wheel /opt/zeroclaw $ sudo find /opt/zeroclaw -type d -exec chmod 2755 {} \; $ sudo setfacl -R -d -m u::rwx,g::w-x,o::r-x /opt/zeroclaw 进入目录下后进行克隆、签出并构建。我选择的是 v0.1.7 tag，这是当时最新的 tag，读者可以自行选择基提交。 $ cd /opt/zeroclaw $ git clone -- https://github.com/zeroclaw-labs/zeroclaw.git . $ git checkout v0.1.7 进行构建。 $ cargo build --release --locked 2. 准备容器 使用 systemd-nspawn(1) 创建容器。 $ sudo pacstrap -K -c /var/lib/machines/claw base vim less $ sudo systemd-nspawn -D /var/lib/machines/claw # logout 之后配置容器内网络。我这里选择使用 NAT 联网，留出 198.18.1.0/24 作为容器网段。使用 systemd-networkd 创建一个虚拟接口。 $ cat &lt;&lt;EOF | sudo tee /etc/systemd/network/10-ve-br0.netdev [NetDev] Name=ve-br0 Kind=bridge EOF $ cat &lt;&lt;EOF | sudo tee /etc/systemd/network/80-ve-br0.network [Match] Name=ve-br0 [Link] RequiredForOnline=no [Network] Address=198.18.1.1/24 DHCPServer=yes IPv4Forwarding=yes IPMasquerade=ipv4 ConfigureWithoutCarrier=yes LLMNR=no MulticastDNS=no [DHCPServer] PoolSize=64 DefaultLeaseTimeSec=1h MaxLeaseTimeSec=1d EOF $ cat &lt;&lt;EOF | sudo tee /etc/systemd/nspawn/claw.nspawn [Exec] Boot=yes ResolvConf=copy-host [Network] VirtualEthernet=yes Bridge=ve-br0 EOF 之后配置防火墙允许 DHCP。我的机器上部署的是 ufw。 $ sudo ufw allow in on ve-br0 to any port 67 proto udp 之后启动 systemd-networkd。这里有一个需要注意的地方：虽然 systemd-networkd 并不会抢占其他网络管理器（比如 NetworkManager(1)）管理的接口，但是其 wait-online 服务 systemd-networkd-wait-online.service 依然会默认开启。如果你的 systemd-networkd 只 管理了虚拟接口，由于虚拟接口永远不会 online，这个服务会超时失败。这时候需要将其禁用。 $ sudo systemctl disable --now systemd-networkd-wait-online.service $ sudo systemctl mask systemd-networkd-wait-online.service $ sudo systemctl enable --now systemd-networkd.service 之后将编译好的二进制 bind 至容器内即可。 # /etc/systemd/nspawn/claw.nspawn [Files] BindReadOnly=/opt/zeroclaw/target/release/zeroclaw:/usr/local/bin/zeroclaw BindReadOnly=/opt/zeroclaw:/opt/zeroclaw 3. 部署 进入容器，进行 onboard 并且自定义配置，设置服务。 $ sudo machinectl list $ sudo machinectl shell claw # cat &lt;&lt;EOF &gt;/etc/systemd/system/zeroclaw.service [Unit] Description=ZeroClaw [Service] Type=exec ExecStart=/usr/local/bin/zeroclaw daemon User=root WorkingDirectory=/root Restart=always [Install] WantedBy=multi-user.target EOF # zeroclaw onboard # # Other steps... # systemctl enable --now zeroclaw.service 在宿主容器内设置开机启动。 $ sudo systemctl enable --now systemd-nspawn@claw.service 之后还可以在容器内配置代理、隧道、工具等，对容器本身还可以进行资源限制，不多赘述。 这些都是只很通用的部署步骤，任何一台 x86 Arch 都可以这么操作，跟龙架构有什么关系？ 这才是这个部署流程的关键所在。龙架构生态已经能够大体支持无痛、无缝部署 ZeroClaw，体验上甚至可以与 x86 Arch 并驾齐驱。这离不开龙芯中科与整个龙芯爱好者社区的不懈努力。 当然，以上只是故事的一半…… i. 花絮：失败的尝试 我最开始尝试的实现是 IronClaw。实际上，在整个踩坑过程中的 90% 时间花在这个部分中。 首先尝试的是原生编译 IronClaw。遗憾的是其依赖 Wasmtime 的 JIT 后端 Cranelift 并不支持龙架构。为 Cranelift 增加新的后端是一个很大的工作，且目前暂无公开社区努力。 之后尝试交叉编译至 x86_64 并使用 QEMU 模拟执行。首先尝试的目标是 x86_64-unknown-linux-gnu，但 Arch4Loong 对非原生架构的支持十分不完善，缺少 ld 等多种必须运行时库。因此尝试 x86_64-unknown-linux-musl，静态编译 OpenSSL 后再使用 Cargo 编译项目。这条路径上的第一个问题是 Arch4Loong 的 x86_64 binutils 和原生 binutils 并不兼容。后者使用 2.46，提供 libsframe.so.3，而前者版本为 2.45，需要 libsframe.so.2。对相关 PKGBUILD 打补丁保留这个 soname 后可以正常工作： diff --git a/x86_64-linux-gnu-binutils/PKGBUILD b/x86_64-linux-gnu-binutils/PKGBUILD index 547876b..729ae63 100644 --- a/x86_64-linux-gnu-binutils/PKGBUILD +++ b/x86_64-linux-gnu-binutils/PKGBUILD @@ -3,7 +3,7 @@ _target=x86_64-linux-gnu pkgname=$_target-binutils pkgver=2.45 -pkgrel=1 +pkgrel=2 pkgdesc='A set of programs to assemble and manipulate binary and object files for 32-bit and 64-bit x86' arch=(loong64) url='https://www.gnu.org/software/binutils/' @@ -90,7 +90,10 @@ package() { # Remove file conflicting with host binutils and manpages for MS Windows tools rm "$pkgdir"/usr/share/man/man1/$_target-{dlltool,windres,windmc}* - rm -rf "$pkgdir"/usr/lib + find "$pkgdir"/usr/lib -mindepth 1 \ + ! -name 'libsframe.so.2' \ + ! -name 'libsframe.so.2.0.0' \ + -delete rm -rf "$pkgdir"/usr/include 之后继续尝试静态编译后，编译产物在 QEMU 下和原生环境下始终出现段错误。再加上其令人担忧的代码质量、onboarding 体验、版本兼容性、修复 bug 的方式等种种因素，最终选择放弃部署 IronClaw。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">A POSIX-compliant rewrite of reresolve-dns.sh</title><link href="https://csmantle.top/2025/12/14/a-posix-compliant-rewrite-of-reresolve-dns-sh.html" rel="alternate" type="text/html" title="A POSIX-compliant rewrite of reresolve-dns.sh" /><published>2025-12-14T01:14:50+00:00</published><updated>2025-12-14T01:14:50+00:00</updated><id>https://csmantle.top/2025/12/14/a-posix-compliant-rewrite-of-reresolve-dns-sh</id><content type="html" xml:base="https://csmantle.top/2025/12/14/a-posix-compliant-rewrite-of-reresolve-dns-sh.html"><![CDATA[<h2 id="the-script">The script</h2>

<p>Please be careful with line endings if you’re copy-pasting the script from below.</p>

<p>If you’re interested in the story behind it, please scroll down to <a href="#background-story">#background-story</a>.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="c"># SPDX-License-Identifier: GPL-2.0</span>
<span class="c">#</span>
<span class="c"># POSIX sh-compliant rewrite of reresolve-dns.sh.</span>
<span class="c">#</span>
<span class="c"># Notes for BusyBox-based coreutils:</span>
<span class="c">#  * A POSIX-compliant tr(1) is expected for [:upper:] and [:lower:].</span>
<span class="c">#</span>
<span class="c"># Copyright (C) 2015-2020 Jason A. Donenfeld &lt;Jason@zx2c4.com&gt;. All Rights Reserved.</span>
<span class="c"># Copyright (C) 2025 Rong Bao &lt;webmaster@csmantle.top&gt;.</span>
<span class="c">#</span>
<span class="c"># Link: https://github.com/WireGuard/wireguard-tools/blob/master/contrib/reresolve-dns/reresolve-dns.sh</span>
<span class="c"># Link: https://gist.github.com/CSharperMantle/ffff2a5e781fe2e4adff22a293315a65#file-reresolve-dns-posix-sh</span>

<span class="nb">set</span> <span class="nt">-e</span>
<span class="nb">export </span><span class="nv">LC_ALL</span><span class="o">=</span>C

trim<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="s1">'s/^[[:space:]]*//;s/[[:space:]]*$//'</span>
<span class="o">}</span>

lower<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> | <span class="nb">tr</span> <span class="s1">'[:upper:]'</span> <span class="s1">'[:lower:]'</span>
<span class="o">}</span>

<span class="nv">CONFIG_FILE</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>

<span class="k">if </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$CONFIG_FILE</span><span class="s2">"</span> | <span class="nb">grep</span> <span class="nt">-qE</span> <span class="s1">'^[a-zA-Z0-9_=+.-]{1,15}$'</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">CONFIG_FILE</span><span class="o">=</span>/etc/wireguard/<span class="s2">"</span><span class="nv">$CONFIG_FILE</span><span class="s2">"</span>.conf
<span class="k">fi

</span><span class="nv">INTERFACE</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$CONFIG_FILE</span><span class="s2">"</span> .conf<span class="si">)</span>

<span class="nv">PEER_SECTION</span><span class="o">=</span>0
<span class="nv">PUBLIC_KEY</span><span class="o">=</span><span class="s1">''</span>
<span class="nv">ENDPOINT</span><span class="o">=</span><span class="s1">''</span>

process_peer<span class="o">()</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$PEER_SECTION</span><span class="s2">"</span> <span class="nt">-ne</span> 1 <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$PUBLIC_KEY</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$ENDPOINT</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        return </span>0
    <span class="k">fi
    </span><span class="nv">handshake_line</span><span class="o">=</span><span class="si">$(</span>wg show <span class="s2">"</span><span class="nv">$INTERFACE</span><span class="s2">"</span> latest-handshakes | <span class="nb">grep</span> <span class="nt">-F</span> <span class="s2">"</span><span class="nv">$PUBLIC_KEY</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true</span><span class="si">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$handshake_line</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        return </span>0
    <span class="k">fi
    if</span> <span class="o">[</span> <span class="k">$((</span> <span class="si">$(</span><span class="nb">date</span> +%s<span class="si">)</span> <span class="o">-</span> <span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$handshake_line</span><span class="s2">"</span> | <span class="nb">awk</span> <span class="s1">'{print $2}'</span><span class="si">)</span> <span class="k">))</span> <span class="nt">-gt</span> 135 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span>wg <span class="nb">set</span> <span class="s2">"</span><span class="nv">$INTERFACE</span><span class="s2">"</span> peer <span class="s2">"</span><span class="nv">$PUBLIC_KEY</span><span class="s2">"</span> ENDPOINT <span class="s2">"</span><span class="nv">$ENDPOINT</span><span class="s2">"</span>
    <span class="k">fi
    </span>reset_peer_section
<span class="o">}</span>

reset_peer_section<span class="o">()</span> <span class="o">{</span>
    <span class="nv">PEER_SECTION</span><span class="o">=</span>0
    <span class="nv">PUBLIC_KEY</span><span class="o">=</span><span class="s1">''</span>
    <span class="nv">ENDPOINT</span><span class="o">=</span><span class="s1">''</span>
<span class="o">}</span>

reset_peer_section
<span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> line <span class="o">||</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">do
    </span><span class="nv">stripped</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">line</span><span class="p">%%\#*</span><span class="k">}</span><span class="s2">"</span>
    <span class="nv">raw_key</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">stripped</span><span class="p">%%=*</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">if </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$stripped</span><span class="s2">"</span> | <span class="nb">grep</span> <span class="nt">-q</span> <span class="s1">'='</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">raw_value</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">stripped</span><span class="p">#*=</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">else
        </span><span class="nv">raw_value</span><span class="o">=</span><span class="s1">''</span>
    <span class="k">fi
    </span><span class="nv">key</span><span class="o">=</span><span class="si">$(</span>lower <span class="s2">"</span><span class="si">$(</span>trim <span class="s2">"</span><span class="nv">$raw_key</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span><span class="si">)</span>
    <span class="nv">value</span><span class="o">=</span><span class="si">$(</span>trim <span class="s2">"</span><span class="nv">$raw_value</span><span class="s2">"</span><span class="si">)</span>

    <span class="k">case</span> <span class="s2">"</span><span class="nv">$key</span><span class="s2">"</span> <span class="k">in</span>
        <span class="s2">"["</span><span class="k">*</span><span class="p">)</span> 
            process_peer
            reset_peer_section
            <span class="k">if </span>lower <span class="s2">"</span><span class="nv">$key</span><span class="s2">"</span> | <span class="nb">grep</span> <span class="nt">-qEi</span> <span class="s1">'^\[peer\]$'</span><span class="p">;</span> <span class="k">then
                </span><span class="nv">PEER_SECTION</span><span class="o">=</span>1
            <span class="k">fi</span>
            <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="k">if</span> <span class="o">[</span> <span class="nv">$PEER_SECTION</span> <span class="nt">-eq</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then
                case</span> <span class="s2">"</span><span class="si">$(</span>lower <span class="s2">"</span><span class="nv">$key</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span> <span class="k">in
                    </span>publickey<span class="p">)</span> <span class="nv">PUBLIC_KEY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$value</span><span class="s2">"</span> <span class="p">;;</span>
                    endpoint<span class="p">)</span> <span class="nv">ENDPOINT</span><span class="o">=</span><span class="s2">"</span><span class="nv">$value</span><span class="s2">"</span> <span class="p">;;</span>
                <span class="k">esac</span>
            <span class="k">fi</span>
            <span class="p">;;</span>
    <span class="k">esac</span>
<span class="k">done</span> &lt; <span class="s2">"</span><span class="nv">$CONFIG_FILE</span><span class="s2">"</span>
process_peer
</code></pre></div></div>

<h2 id="background-story">Background story</h2>

<p>Recently, I finally decided to setup a lightweight auto-refresher for my <a href="https://www.wireguard.com/">WireGuard</a> peers’s DDNS endpoints. A brief search brought me <a href="https://github.com/WireGuard/wireguard-tools/blob/0b7d9821f2815973a2930ace28a3f73c205d0e5c/contrib/reresolve-dns/reresolve-dns.sh">reresolve-dns.sh</a>, an awesome small script written by Jason A. Donenfeld.</p>

<p>I am running WireGuard on several different boxes. I have:</p>

<ol>
  <li>A small VPS running Debian 13,</li>
  <li>A medium VPS running Ubuntu 24.04,</li>
  <li>A router running OpenWrt 24.10.</li>
</ol>

<p>The first two have all the modern amenities available, so I deployed reresolve-dns.sh using <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html"><code class="language-plaintext highlighter-rouge">systemd</code> timers</a> fairly quickly. The third one is trickier. The coreutils on this OpenWrt distro uses <a href="https://busybox.net/">BusyBox</a> by default, so the built-in shell is ash, a barely POSIX-compliant shell without much extra fuss. Its source docs says:</p>

<blockquote>
  <p>The most complete and most pedantically correct shell included with busybox. This shell is actually a derivative of the Debian ‘dash’ shell (by Herbert Xu), which was created by porting the ‘ash’ shell written by Kenneth Almquist) from NetBSD.</p>

  <p>– <a href="https://github.com/mirror/busybox/blob/371fe9f71d445d18be28c82a2a6d82115c8af19d/shell/ash.c#L28-L31">BusyBox shell/ash.c</a></p>
</blockquote>

<p>The original reresolve-dns.sh uses quite a lot of Bash features. I also don’t want to break the embedded firmware immutability yet once more, so a rewrite seems natural.</p>

<p>After completing the first draft, I tested it on my OpenWrt box. It didn’t work as expected, for the <a href="https://busybox.net/downloads/BusyBox.html#:~:text=tr%20[-cds]%20STRING1%20[STRING2]">BusyBox tr(1)</a> utility came with my OpenWrt distro can’t recognize character classes (like <code class="language-plaintext highlighter-rouge">[:upper:]</code>). In fact, it does have such feature in its source code, but <a href="https://github.com/openwrt/openwrt/blob/ff4546093e0146654007d71a2cc37034fe9f633e/package/utils/busybox/Config-defaults.in#L871-L873">it’s disabled on OpenWrt builds</a>. Breaking immutability and installing <a href="https://openwrt.org/packages/pkgdata/coreutils-tr">coreutils-tr</a> resolves this issue.</p>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><category term="shell" /><category term="networking" /><summary type="html"><![CDATA[The script Please be careful with line endings if you’re copy-pasting the script from below. If you’re interested in the story behind it, please scroll down to #background-story. #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # # POSIX sh-compliant rewrite of reresolve-dns.sh. # # Notes for BusyBox-based coreutils: # * A POSIX-compliant tr(1) is expected for [:upper:] and [:lower:]. # # Copyright (C) 2015-2020 Jason A. Donenfeld &lt;Jason@zx2c4.com&gt;. All Rights Reserved. # Copyright (C) 2025 Rong Bao &lt;webmaster@csmantle.top&gt;. # # Link: https://github.com/WireGuard/wireguard-tools/blob/master/contrib/reresolve-dns/reresolve-dns.sh # Link: https://gist.github.com/CSharperMantle/ffff2a5e781fe2e4adff22a293315a65#file-reresolve-dns-posix-sh set -e export LC_ALL=C trim() { echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' } lower() { echo "$1" | tr '[:upper:]' '[:lower:]' } CONFIG_FILE="$1" if echo "$CONFIG_FILE" | grep -qE '^[a-zA-Z0-9_=+.-]{1,15}$'; then CONFIG_FILE=/etc/wireguard/"$CONFIG_FILE".conf fi INTERFACE=$(basename "$CONFIG_FILE" .conf) PEER_SECTION=0 PUBLIC_KEY='' ENDPOINT='' process_peer() { if [ "$PEER_SECTION" -ne 1 ] || [ -z "$PUBLIC_KEY" ] || [ -z "$ENDPOINT" ]; then return 0 fi handshake_line=$(wg show "$INTERFACE" latest-handshakes | grep -F "$PUBLIC_KEY" || true) if [ -z "$handshake_line" ]; then return 0 fi if [ $(( $(date +%s) - $(echo "$handshake_line" | awk '{print $2}') )) -gt 135 ]; then wg set "$INTERFACE" peer "$PUBLIC_KEY" ENDPOINT "$ENDPOINT" fi reset_peer_section } reset_peer_section() { PEER_SECTION=0 PUBLIC_KEY='' ENDPOINT='' } reset_peer_section while read -r line || [ -n "$line" ]; do stripped="${line%%\#*}" raw_key="${stripped%%=*}" if echo "$stripped" | grep -q '='; then raw_value="${stripped#*=}" else raw_value='' fi key=$(lower "$(trim "$raw_key")") value=$(trim "$raw_value") case "$key" in "["*) process_peer reset_peer_section if lower "$key" | grep -qEi '^\[peer\]$'; then PEER_SECTION=1 fi ;; *) if [ $PEER_SECTION -eq 1 ]; then case "$(lower "$key")" in publickey) PUBLIC_KEY="$value" ;; endpoint) ENDPOINT="$value" ;; esac fi ;; esac done &lt; "$CONFIG_FILE" process_peer Background story Recently, I finally decided to setup a lightweight auto-refresher for my WireGuard peers’s DDNS endpoints. A brief search brought me reresolve-dns.sh, an awesome small script written by Jason A. Donenfeld. I am running WireGuard on several different boxes. I have: A small VPS running Debian 13, A medium VPS running Ubuntu 24.04, A router running OpenWrt 24.10. The first two have all the modern amenities available, so I deployed reresolve-dns.sh using systemd timers fairly quickly. The third one is trickier. The coreutils on this OpenWrt distro uses BusyBox by default, so the built-in shell is ash, a barely POSIX-compliant shell without much extra fuss. Its source docs says: The most complete and most pedantically correct shell included with busybox. This shell is actually a derivative of the Debian ‘dash’ shell (by Herbert Xu), which was created by porting the ‘ash’ shell written by Kenneth Almquist) from NetBSD. – BusyBox shell/ash.c The original reresolve-dns.sh uses quite a lot of Bash features. I also don’t want to break the embedded firmware immutability yet once more, so a rewrite seems natural. After completing the first draft, I tested it on my OpenWrt box. It didn’t work as expected, for the BusyBox tr(1) utility came with my OpenWrt distro can’t recognize character classes (like [:upper:]). In fact, it does have such feature in its source code, but it’s disabled on OpenWrt builds. Breaking immutability and installing coreutils-tr resolves this issue.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">求解 DES 弱密钥</title><link href="https://csmantle.top/2025/11/16/finding-weak-keys-in-des.html" rel="alternate" type="text/html" title="求解 DES 弱密钥" /><published>2025-11-16T05:03:45+00:00</published><updated>2025-11-16T05:03:45+00:00</updated><id>https://csmantle.top/2025/11/16/finding-weak-keys-in-des</id><content type="html" xml:base="https://csmantle.top/2025/11/16/finding-weak-keys-in-des.html"><![CDATA[<style>
.theorem-label {
    padding-right: 1em;
}
</style>

<p><a href="https://en.wikipedia.org/wiki/Weak_key#Weak_keys_in_DES">DES 加密算法存在四个弱密钥</a>，分别为：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0x0101010101010101</code>；</li>
  <li><code class="language-plaintext highlighter-rouge">0xFEFEFEFEFEFEFEFE</code>；</li>
  <li><code class="language-plaintext highlighter-rouge">0xE0E0E0E0F1F1F1F1</code>；</li>
  <li><code class="language-plaintext highlighter-rouge">0x1F1F1F1F0E0E0E0E</code>。</li>
</ul>

<p>它们均满足 $\forall x.\ \mathrm{DESEncrypt}(x, K_w) = \mathrm{DESDecrypt}(x, K_w)$。</p>

<p>在记住结论之外，我们也可以使用 <a href="https://github.com/Z3Prover/z3/wiki">Z3</a> 证明弱密钥的存在性，并借此求出所有 $K_w$。</p>

<p>针对完整的 DES 网络直接使用符号求解是不现实的，因此证明的第一步是将弱密钥存在性问题简化至对密钥编排（key schedule）性质的研究上。DES 密钥编排仅包含循环左移与按位置换两种操作，结构较为简单，容易对其使用符号求解器。</p>

<p>记16个轮密钥按顺序组成序列 $\mathrm{DESKeySched}(K) = \{ k_i \}$，我们首先证明以下引理：</p>

<hr />

<p><span class="theorem-label"><strong>引理</strong></span>$K$ 为弱密钥的充要条件为 $\mathrm{DESKeySched}(K)$ 构成回文。</p>

<p><span class="theorem-label"><strong>证明</strong></span>由于 DES 算法为 Feistel 结构，可将加密、解密过程改写为：</p>

\[\begin{align}
\mathrm{DESEncrypt}(x, K) &amp;= \mathrm{DESNetwork}(x, \mathrm{DESKeySched}(K)) \nonumber \\
&amp;= \mathrm{DESNetwork}(x, \{ k_i \}) \nonumber \\
\mathrm{DESDecrypt}(x, K) &amp;= \mathrm{DESNetwork}(x, \overline{\mathrm{DESKeySched}(K)}) \nonumber \\
&amp;= \mathrm{DESNetwork}(x, \overline{\{ k_i \}}) \nonumber \\
\end{align}\]

<p>其中 $\overline{\{ k_i \}}$ 表示逆序。那么显然 $\forall x.\ \mathrm{DESEncrypt}(x, K_w) = \mathrm{DESDecrypt}(x, K_w)$ 等价于 $\{ k_i \} = \overline{\{ k_i \}}$，即轮密钥组成的序列构成回文。</p>

<p><span class="theorem-label"><strong>备注</strong></span>维基百科上对弱密钥的描述如下：</p>

<blockquote>
  <p>DES weak keys produce sixteen identical subkeys.</p>
</blockquote>

<p>这个结论初看只是弱密钥的一个充分条件，但根据密钥编排轮函数的性质，可以证明其为充要条件，请读者自行思考。</p>

<hr />

<p>证明引理后，我们只需要研究是否有密钥能够产生回文轮密钥序列即可。如上文，由于 DES 的密钥编排仅涉及按位置换，可以较为平凡地使用 SMT 求解器实现，因此不难编写一个 Z3 脚本进行计算：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="c1"># SPDX-License-Identifier: BSD-3-Clause
</span>
<span class="kn">import</span> <span class="n">functools</span> <span class="k">as</span> <span class="n">ft</span>

<span class="kn">import</span> <span class="n">z3</span>


<span class="k">def</span> <span class="nf">des_keysched_round</span><span class="p">(</span><span class="n">i</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="n">z3</span><span class="p">.</span><span class="n">BitVecRef</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="n">z3</span><span class="p">.</span><span class="n">BitVecRef</span><span class="p">,</span> <span class="n">z3</span><span class="p">.</span><span class="n">BitVecRef</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s">
    Args:
        i: 1-indexed round count.
        s: Ci ++ Di, 56 bits.

    Returns:
        * ki without permutation but with dropped bits
        * s</span><span class="sh">'</span><span class="s"> for next iteration
    </span><span class="sh">"""</span>
    <span class="n">ROT_AMOUNT</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">ci</span><span class="p">,</span> <span class="n">di</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="n">s</span><span class="p">),</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">27</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
    <span class="n">ci1</span><span class="p">,</span> <span class="n">di1</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">RotateLeft</span><span class="p">(</span><span class="n">ci</span><span class="p">,</span> <span class="n">ROT_AMOUNT</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]),</span> <span class="n">z3</span><span class="p">.</span><span class="nc">RotateLeft</span><span class="p">(</span>
        <span class="n">di</span><span class="p">,</span> <span class="n">ROT_AMOUNT</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
    <span class="p">)</span>
    <span class="n">s1</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Concat</span><span class="p">(</span><span class="n">ci1</span><span class="p">,</span> <span class="n">di1</span><span class="p">)</span>
    <span class="c1"># Ignore bits (1-indexed) 9, 18, 22, 25, 35, 38, 43, 54
</span>    <span class="c1">#             (0-indexed) 8, 17, 21, 24, 34, 37, 42, 53
</span>    <span class="n">ki</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Concat</span><span class="p">(</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">52</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">41</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">36</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">23</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
        <span class="n">z3</span><span class="p">.</span><span class="nc">Extract</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">s1</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="nf">return </span><span class="p">(</span><span class="n">ki</span><span class="p">,</span> <span class="n">s1</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">des_pc1_inv</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="c1"># fmt: off
</span>    <span class="n">TABLE</span> <span class="o">=</span> <span class="p">(</span>
        <span class="mi">57</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">41</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">17</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span>
        <span class="mi">1</span><span class="p">,</span> <span class="mi">58</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span>
        <span class="mi">10</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">59</span><span class="p">,</span> <span class="mi">51</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">27</span><span class="p">,</span>
        <span class="mi">19</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">52</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">36</span><span class="p">,</span>
        <span class="mi">63</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">39</span><span class="p">,</span> <span class="mi">31</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span>
        <span class="mi">7</span><span class="p">,</span> <span class="mi">62</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span>
        <span class="mi">14</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">61</span><span class="p">,</span> <span class="mi">53</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">29</span><span class="p">,</span>
        <span class="mi">21</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="c1"># fmt: on
</span>    <span class="n">bits</span> <span class="o">=</span> <span class="p">[(</span><span class="n">x</span> <span class="o">&gt;&gt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)]</span>
    <span class="n">new_bits</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="mi">64</span>
    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">ti</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">TABLE</span><span class="p">):</span>
        <span class="n">new_bits</span><span class="p">[</span><span class="n">ti</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">8</span><span class="p">):</span>
        <span class="n">parity</span> <span class="o">=</span> <span class="n">ft</span><span class="p">.</span><span class="nf">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">acc</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">acc</span> <span class="o">^</span> <span class="n">b</span><span class="p">,</span> <span class="n">new_bits</span><span class="p">[</span><span class="n">i</span> <span class="p">:</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">7</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">new_bits</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">7</span><span class="p">]</span> <span class="o">=</span> <span class="n">parity</span>
    <span class="k">return</span> <span class="n">ft</span><span class="p">.</span><span class="nf">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">acc</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="p">(</span><span class="n">acc</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="n">b</span><span class="p">,</span> <span class="n">new_bits</span><span class="p">)</span>


<span class="n">s0</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">BitVec</span><span class="p">(</span><span class="sh">"</span><span class="s">s0</span><span class="sh">"</span><span class="p">,</span> <span class="mi">56</span><span class="p">)</span>

<span class="n">k</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">si</span> <span class="o">=</span> <span class="n">s0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">16</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
    <span class="n">ki</span><span class="p">,</span> <span class="n">si</span> <span class="o">=</span> <span class="nf">des_keysched_round</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">si</span><span class="p">)</span>
    <span class="n">k</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">ki</span><span class="p">)</span>

<span class="n">solver</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Solver</span><span class="p">()</span>

<span class="k">assert</span> <span class="nf">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">==</span> <span class="mi">16</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span><span class="p">):</span>
    <span class="n">solver</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">k</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">k</span><span class="p">[</span><span class="nf">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">-</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">])</span>

<span class="n">sol</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="n">solver</span><span class="p">.</span><span class="nf">check</span><span class="p">()</span> <span class="o">==</span> <span class="n">z3</span><span class="p">.</span><span class="n">sat</span><span class="p">:</span>
    <span class="n">m</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="nf">model</span><span class="p">()</span>
    <span class="n">s0v</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">m</span><span class="p">.</span><span class="nf">decls</span><span class="p">():</span>
        <span class="k">if</span> <span class="n">d</span><span class="p">.</span><span class="nf">name</span><span class="p">()</span> <span class="o">==</span> <span class="sh">"</span><span class="s">s0</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">s0v</span> <span class="o">=</span> <span class="n">m</span><span class="p">[</span><span class="n">d</span><span class="p">].</span><span class="nf">as_long</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">s0v</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
    <span class="n">sol</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">s0v</span><span class="p">)</span>
    <span class="n">solver</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">s0</span> <span class="o">!=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">BitVecVal</span><span class="p">(</span><span class="n">s0v</span><span class="p">,</span> <span class="mi">56</span><span class="p">))</span>

<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">s</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">sol</span><span class="p">):</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Solution </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="nf">hex</span><span class="p">(</span><span class="nf">des_pc1_inv</span><span class="p">(</span><span class="n">s</span><span class="p">))</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

</code></pre></div></div>

<p>这里的 <code class="language-plaintext highlighter-rouge">des_keysched_round</code> 不是完整的轮函数。由于仅需要判断轮密钥间是否相等，可以在这里省略 PC-2 的置换部分，仅保留其抛弃部分比特的特性即可。</p>

<p>运行求解脚本后即可得到如下四个能够产生回文轮密钥序列的 DES 密钥：</p>

<pre><code class="language-plain-text">&gt; py.exe -3 ./des-weak-key.py
Solution 0: 0x101010101010101
Solution 1: 0xe0e0e0e0f1f1f1f1
Solution 2: 0xfefefefefefefefe
Solution 3: 0x1f1f1f1f0e0e0e0e
</code></pre>

<p>之后借助引理即可推出 DES 弱密钥存在，且上述四个密钥即为全部弱密钥。</p>]]></content><author><name>Rong Bao</name></author><category term="topic:research" /><category term="cryptography" /><summary type="html"><![CDATA[DES 加密算法存在四个弱密钥，分别为： 0x0101010101010101； 0xFEFEFEFEFEFEFEFE； 0xE0E0E0E0F1F1F1F1； 0x1F1F1F1F0E0E0E0E。 它们均满足 $\forall x.\ \mathrm{DESEncrypt}(x, K_w) = \mathrm{DESDecrypt}(x, K_w)$。 在记住结论之外，我们也可以使用 Z3 证明弱密钥的存在性，并借此求出所有 $K_w$。 针对完整的 DES 网络直接使用符号求解是不现实的，因此证明的第一步是将弱密钥存在性问题简化至对密钥编排（key schedule）性质的研究上。DES 密钥编排仅包含循环左移与按位置换两种操作，结构较为简单，容易对其使用符号求解器。 记16个轮密钥按顺序组成序列 $\mathrm{DESKeySched}(K) = \{ k_i \}$，我们首先证明以下引理： 引理$K$ 为弱密钥的充要条件为 $\mathrm{DESKeySched}(K)$ 构成回文。 证明由于 DES 算法为 Feistel 结构，可将加密、解密过程改写为： \[\begin{align} \mathrm{DESEncrypt}(x, K) &amp;= \mathrm{DESNetwork}(x, \mathrm{DESKeySched}(K)) \nonumber \\ &amp;= \mathrm{DESNetwork}(x, \{ k_i \}) \nonumber \\ \mathrm{DESDecrypt}(x, K) &amp;= \mathrm{DESNetwork}(x, \overline{\mathrm{DESKeySched}(K)}) \nonumber \\ &amp;= \mathrm{DESNetwork}(x, \overline{\{ k_i \}}) \nonumber \\ \end{align}\] 其中 $\overline{\{ k_i \}}$ 表示逆序。那么显然 $\forall x.\ \mathrm{DESEncrypt}(x, K_w) = \mathrm{DESDecrypt}(x, K_w)$ 等价于 $\{ k_i \} = \overline{\{ k_i \}}$，即轮密钥组成的序列构成回文。 备注维基百科上对弱密钥的描述如下： DES weak keys produce sixteen identical subkeys. 这个结论初看只是弱密钥的一个充分条件，但根据密钥编排轮函数的性质，可以证明其为充要条件，请读者自行思考。 证明引理后，我们只需要研究是否有密钥能够产生回文轮密钥序列即可。如上文，由于 DES 的密钥编排仅涉及按位置换，可以较为平凡地使用 SMT 求解器实现，因此不难编写一个 Z3 脚本进行计算： #!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause import functools as ft import z3 def des_keysched_round(i: int, s: z3.BitVecRef) -&gt; tuple[z3.BitVecRef, z3.BitVecRef]: """ Args: i: 1-indexed round count. s: Ci ++ Di, 56 bits. Returns: * ki without permutation but with dropped bits * s' for next iteration """ ROT_AMOUNT = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1) ci, di = z3.Extract(55, 28, s), z3.Extract(27, 0, s) ci1, di1 = z3.RotateLeft(ci, ROT_AMOUNT[i - 1]), z3.RotateLeft( di, ROT_AMOUNT[i - 1] ) s1 = z3.Concat(ci1, di1) # Ignore bits (1-indexed) 9, 18, 22, 25, 35, 38, 43, 54 # (0-indexed) 8, 17, 21, 24, 34, 37, 42, 53 ki = z3.Concat( z3.Extract(55, 54, s1), z3.Extract(52, 43, s1), z3.Extract(41, 38, s1), z3.Extract(36, 25, s1), z3.Extract(23, 22, s1), z3.Extract(20, 18, s1), z3.Extract(16, 9, s1), z3.Extract(7, 0, s1), ) return (ki, s1) def des_pc1_inv(x: int) -&gt; int: # fmt: off TABLE = ( 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4, ) # fmt: on bits = [(x &gt;&gt; i) &amp; 1 for i in range(55, -1, -1)] new_bits = [0] * 64 for i, ti in enumerate(TABLE): new_bits[ti - 1] = bits[i] for i in range(0, 64, 8): parity = ft.reduce(lambda acc, b: acc ^ b, new_bits[i : i + 7], 1) new_bits[i + 7] = parity return ft.reduce(lambda acc, b: (acc &lt;&lt; 1) | b, new_bits) s0 = z3.BitVec("s0", 56) k = [] si = s0 for i in range(1, 16 + 1): ki, si = des_keysched_round(i, si) k.append(ki) solver = z3.Solver() assert len(k) == 16 for i in range(len(k) // 2): solver.add(k[i] == k[len(k) - i - 1]) sol = [] while solver.check() == z3.sat: m = solver.model() s0v = None for d in m.decls(): if d.name() == "s0": s0v = m[d].as_long() assert s0v is not None sol.append(s0v) solver.add(s0 != z3.BitVecVal(s0v, 56)) for i, s in enumerate(sol): print(f"Solution {i}: {hex(des_pc1_inv(s))}") 这里的 des_keysched_round 不是完整的轮函数。由于仅需要判断轮密钥间是否相等，可以在这里省略 PC-2 的置换部分，仅保留其抛弃部分比特的特性即可。 运行求解脚本后即可得到如下四个能够产生回文轮密钥序列的 DES 密钥： &gt; py.exe -3 ./des-weak-key.py Solution 0: 0x101010101010101 Solution 1: 0xe0e0e0e0f1f1f1f1 Solution 2: 0xfefefefefefefefe Solution 3: 0x1f1f1f1f0e0e0e0e 之后借助引理即可推出 DES 弱密钥存在，且上述四个密钥即为全部弱密钥。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">“To delete your account, chat with our bot.”</title><link href="https://csmantle.top/2025/09/12/to-delete-your-account-chat-with-our-bot.html" rel="alternate" type="text/html" title="“To delete your account, chat with our bot.”" /><published>2025-09-12T02:19:04+00:00</published><updated>2025-09-12T02:19:04+00:00</updated><id>https://csmantle.top/2025/09/12/to-delete-your-account-chat-with-our-bot</id><content type="html" xml:base="https://csmantle.top/2025/09/12/to-delete-your-account-chat-with-our-bot.html"><![CDATA[<p><strong>TL;DR:</strong> I didn’t succeed in deleting my account as of this writing.</p>

<p>Recently, I received an email from Groq titled “Updates to Groq’s Terms &amp; Policies”. I registered on Groq quite a while ago with my Google account, and I didn’t use it ever since. So, I decided to delete my account.</p>

<p>I logged in on their “Console” at console[.]groq[.]com and went to the “Settings” page. I scoured for a button like “Delete Account”, but in vain. Basically, the available tabs on the left were:</p>

<ul>
  <li>Organization
    <ul>
      <li>General</li>
      <li>Billing</li>
      <li>Team</li>
      <li>Profile</li>
      <li>Limits</li>
      <li>Usage</li>
      <li>Model Terms</li>
      <li>Projects</li>
      <li>Data Controls</li>
    </ul>
  </li>
  <li>Project
    <ul>
      <li>General</li>
      <li>Limits</li>
    </ul>
  </li>
</ul>

<p>“Organization - General” contained a textbox to change the organization name, and another one to copy its ID. “Billing” offered an upgrade to “Developer tier” or “Enterprise tier”. “Profile” allowed me to change my avatar and subscribe to their newsletter. “Data control” let me control how the platform stores and keeps my input to AI models. None of them contained a button to delete my account.</p>

<p>“It’s just hiding somewhere,” I encouraged myself. I went to their “Docs” site at console[.]groq[.]com/docs, and searched the keywords “delete”, “account deletion” and “delete account” using the built-in search. Nothing but irrelevant API references.</p>

<p>I turned to search engines and typed “groq delete account” into the searchbox. The first answer came from “Groq Community”:</p>

<blockquote>
  <p>Contact support to request account deletion (Visit Groq Console → Click Organization Name on upper righthand corner → Select Chat with us from the dropdown menu). Note that this will permanently remove all API keys, usage history, and account data.</p>

  <p>– <a href="https://community.groq.com/t/how-do-i-delete-my-groq-account/52">https://community.groq.com/t/how-do-i-delete-my-groq-account/52</a></p>
</blockquote>

<p>I was on mobile when I did all that. Clicking anything on “upper right corner” opens up a panel which displayed no dropdown menu, less for something called “Chat with us”.</p>

<p>After waking up my laptop I could finally “Chat”. A panel opens up on the lower right corner, saying:</p>

<blockquote>
  <p>[GroqCloud icon] [avatar 1] [avatar 2] [avatar 3]</p>

  <p><strong>Hi there 👋</strong></p>

  <p><strong>Questions? Feedback? We’re here to help.</strong></p>

  <ul>
    <li>View API/Model Status</li>
    <li>Check Out Our Documentation</li>
    <li>
      <p>Ask The Community</p>
    </li>
    <li>Send us a message</li>
  </ul>
</blockquote>

<p>I guessed I need to “Send them a message” since Item 1 was not relevant, Item 2 was not helping, and Item 3 led me here.</p>

<blockquote>
  <p>Hi! 👋 What can we help you with?</p>

  <p>Groq • Just now</p>

  <p>[Account Question] [Technical Question]<br />
[Rate Limit Increase] [Report an Issue]<br />
[Request a Feature] [Other]</p>
</blockquote>

<p>I selected “Account Question”.</p>

<blockquote>
  <p>What’s going on with your account?</p>

  <p>Groq • Just now</p>

  <p>[Create an Account] [Upgrade Account]<br />
[Account is Restricted] [Delete Account]<br />
[Billing Questions] [Other]</p>
</blockquote>

<p>I selected “Delete Account”.</p>

<blockquote>
  <p>Thank you for your account deletion request. Please note that if we complete this request, all account and usage data with Groq will be permanently deleted and cannot be recovered. To proceed, we need to first verify your identity. Can you please provide your organization ID below for verification?
We are always looking to improve and would appreciate any more context on the reason you’re looking to delete your account. Please share any feedback for Groq and how we could improve in the future.</p>

  <p>Groq • Just now</p>
</blockquote>

<p>I typed in my “Organization ID” from far above.</p>

<p>… Then, silence.</p>

<p>They could have designed a automated deletion button straight into the Console. After all, nothing can prove the user’s identity more than a valid login.</p>

<p>I haven’t heard from Groq as of this writing. I think I’ll just have to wait until a real, human employee starts working.</p>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><summary type="html"><![CDATA[TL;DR: I didn’t succeed in deleting my account as of this writing. Recently, I received an email from Groq titled “Updates to Groq’s Terms &amp; Policies”. I registered on Groq quite a while ago with my Google account, and I didn’t use it ever since. So, I decided to delete my account. I logged in on their “Console” at console[.]groq[.]com and went to the “Settings” page. I scoured for a button like “Delete Account”, but in vain. Basically, the available tabs on the left were: Organization General Billing Team Profile Limits Usage Model Terms Projects Data Controls Project General Limits “Organization - General” contained a textbox to change the organization name, and another one to copy its ID. “Billing” offered an upgrade to “Developer tier” or “Enterprise tier”. “Profile” allowed me to change my avatar and subscribe to their newsletter. “Data control” let me control how the platform stores and keeps my input to AI models. None of them contained a button to delete my account. “It’s just hiding somewhere,” I encouraged myself. I went to their “Docs” site at console[.]groq[.]com/docs, and searched the keywords “delete”, “account deletion” and “delete account” using the built-in search. Nothing but irrelevant API references. I turned to search engines and typed “groq delete account” into the searchbox. The first answer came from “Groq Community”: Contact support to request account deletion (Visit Groq Console → Click Organization Name on upper righthand corner → Select Chat with us from the dropdown menu). Note that this will permanently remove all API keys, usage history, and account data. – https://community.groq.com/t/how-do-i-delete-my-groq-account/52 I was on mobile when I did all that. Clicking anything on “upper right corner” opens up a panel which displayed no dropdown menu, less for something called “Chat with us”. After waking up my laptop I could finally “Chat”. A panel opens up on the lower right corner, saying: [GroqCloud icon] [avatar 1] [avatar 2] [avatar 3] Hi there 👋 Questions? Feedback? We’re here to help. View API/Model Status Check Out Our Documentation Ask The Community Send us a message I guessed I need to “Send them a message” since Item 1 was not relevant, Item 2 was not helping, and Item 3 led me here. Hi! 👋 What can we help you with? Groq • Just now [Account Question] [Technical Question] [Rate Limit Increase] [Report an Issue] [Request a Feature] [Other] I selected “Account Question”. What’s going on with your account? Groq • Just now [Create an Account] [Upgrade Account] [Account is Restricted] [Delete Account] [Billing Questions] [Other] I selected “Delete Account”. Thank you for your account deletion request. Please note that if we complete this request, all account and usage data with Groq will be permanently deleted and cannot be recovered. To proceed, we need to first verify your identity. Can you please provide your organization ID below for verification? We are always looking to improve and would appreciate any more context on the reason you’re looking to delete your account. Please share any feedback for Groq and how we could improve in the future. Groq • Just now I typed in my “Organization ID” from far above. … Then, silence. They could have designed a automated deletion button straight into the Console. After all, nothing can prove the user’s identity more than a valid login. I haven’t heard from Groq as of this writing. I think I’ll just have to wait until a real, human employee starts working.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh-Hans"><title type="html">WSL2 上使用 Canokeys 踩坑</title><link href="https://csmantle.top/2025/07/17/pitfalls-when-using-canokeys-on-wsl-2.html" rel="alternate" type="text/html" title="WSL2 上使用 Canokeys 踩坑" /><published>2025-07-17T14:08:14+00:00</published><updated>2025-07-17T14:08:14+00:00</updated><id>https://csmantle.top/2025/07/17/pitfalls-when-using-canokeys-on-wsl-2</id><content type="html" xml:base="https://csmantle.top/2025/07/17/pitfalls-when-using-canokeys-on-wsl-2.html"><![CDATA[<blockquote>
  <p>本文可供大多数需要将USB设备直通至WSL2虚拟机内的场景参考，并不局限于Canokeys或USB智能卡等话题。</p>
</blockquote>

<p>WSL2支持使用<a href="https://github.com/dorssel/usbipd-win">usbipd-win</a>实现USB设备直通，可以较为方便地将OpenPGP智能卡直通至虚拟机内供GPG使用，配置方法推荐阅读<a href="https://learn.microsoft.com/en-us/windows/wsl/connect-usb">微软的文章</a>。一般而言，出现类似以下的输出，即可认为USB直通配置正确：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>lsusb
<span class="o">[</span>...]
Bus 001 Device 005: ID 20a0:42d4 Clay Logic CanoKey Canary
<span class="o">[</span>...]
</code></pre></div></div>

<p>但是即使设备直通成功，gpg也可能无法找到智能卡：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gpg <span class="nt">--card-status</span>
gpg: selecting card failed: No such device
gpg: OpenPGP card not available: No such device
</code></pre></div></div>

<p>运行<code class="language-plaintext highlighter-rouge">pcsc_scan</code>可以发现是权限不足：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pcsc_scan
PC/SC device scanner
V 1.7.1 <span class="o">(</span>c<span class="o">)</span> 2001-2022, Ludovic Rousseau &lt;ludovic.rousseau@free.fr&gt;
SCardEstablishContext: Access denied.
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">scdaemon</code>的日志也有类似的问题：</p>

<!-- seo-excerpt-separator -->

<pre><code class="language-plain-text">2025-07-17 21:29:29 scdaemon[112573] DBG: chan_7 &lt;- learn
2025-07-17 21:29:29 scdaemon[112573] ccid open error: skip
2025-07-17 21:29:29 scdaemon[112573] check permission of USB device at Bus 001 Device 002
2025-07-17 21:29:29 scdaemon[112573] DBG: chan_7 -&gt; ERR 100696144 No such device &lt;SCD&gt;
2025-07-17 21:29:29 scdaemon[112573] DBG: chan_7 &lt;- RESTART
2025-07-17 21:29:29 scdaemon[112573] DBG: chan_7 -&gt; OK
</code></pre>

<p>可以通过创建热插拔规则形式自动配置设备的权限，允许当前用户所在的组（可以使用<code class="language-plaintext highlighter-rouge">groups</code>查看，这里选择<code class="language-plaintext highlighter-rouge">plugdev</code>组）写入该设备。</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo tee</span> /etc/udev/rules.d/99-canokey.rules <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
SUBSYSTEM=="usb", ATTR{idVendor}=="20a0", ATTR{idProduct}=="42d4", MODE="0664", GROUP="plugdev"
</span><span class="no">EOF
</span><span class="nb">sudo </span>udevadm control <span class="nt">--reload-rules</span>
<span class="nb">sudo </span>udevadm trigger
</code></pre></div></div>

<p>运行后可验证生效：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-la</span> /dev/bus/usb/<span class="k">*</span>/<span class="k">*</span>
crw-rw-r-- root root    0 B Thu Jul 17 22:22:35 2025  /dev/bus/usb/001/001
crw-rw-r-- root root    0 B Thu Jul 17 22:22:35 2025  /dev/bus/usb/002/001
crw-rw-r-- root plugdev 0 B Thu Jul 17 22:23:04 2025  /dev/bus/usb/001/005
<span class="nv">$ </span>gpg <span class="nt">--card-status</span>
Reader ...........: 20A0:42D4:013138D7:0
Application ID ...: D276000124010304F1D0013138D70000
Application <span class="nb">type</span> .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
<span class="o">[</span>...]
</code></pre></div></div>

<p>在签名时有可能出现以下错误：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git commit <span class="nt">--amend</span> <span class="nt">-S</span> <span class="nt">--no-edit</span>
error: gpg failed to sign the data:
<span class="o">[</span>GNUPG:] KEY_CONSIDERED &lt;...&gt; 0
<span class="o">[</span>GNUPG:] BEGIN_SIGNING H10
<span class="o">[</span>GNUPG:] PINENTRY_LAUNCHED 130663 curses 1.2.1 - xterm-256color :0 - 1000/1000 -
gpg: signing failed: Inappropriate ioctl <span class="k">for </span>device
<span class="o">[</span>GNUPG:] FAILURE sign 83918950
gpg: signing failed: Inappropriate ioctl <span class="k">for </span>device

fatal: failed to write commit object
</code></pre></div></div>

<p>可以参考<a href="https://stackoverflow.com/questions/39494631/gpg-failed-to-sign-the-data-fatal-failed-to-write-commit-object-git-2-10-0/55993078#55993078">https://stackoverflow.com/questions/39494631/gpg-failed-to-sign-the-data-fatal-failed-to-write-commit-object-git-2-10-0/55993078#55993078</a>，导出环境变量：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">GPG_TTY</span><span class="o">=</span><span class="si">$(</span><span class="nb">tty</span><span class="si">)</span>
</code></pre></div></div>

<p>这样就能够正确显示合适的PIN输入界面。若有需要，也可以修改sudoers文件让sudo保留这个环境变量：</p>

<pre><code class="language-sudoers">Defaults env_keep += "GPG_TTY"
</code></pre>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><category term="wsl" /><summary type="html"><![CDATA[本文可供大多数需要将USB设备直通至WSL2虚拟机内的场景参考，并不局限于Canokeys或USB智能卡等话题。 WSL2支持使用usbipd-win实现USB设备直通，可以较为方便地将OpenPGP智能卡直通至虚拟机内供GPG使用，配置方法推荐阅读微软的文章。一般而言，出现类似以下的输出，即可认为USB直通配置正确： $ lsusb [...] Bus 001 Device 005: ID 20a0:42d4 Clay Logic CanoKey Canary [...] 但是即使设备直通成功，gpg也可能无法找到智能卡： $ gpg --card-status gpg: selecting card failed: No such device gpg: OpenPGP card not available: No such device 运行pcsc_scan可以发现是权限不足： $ pcsc_scan PC/SC device scanner V 1.7.1 (c) 2001-2022, Ludovic Rousseau &lt;ludovic.rousseau@free.fr&gt; SCardEstablishContext: Access denied. scdaemon的日志也有类似的问题：]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">JavaScript usage on csmantle.top</title><link href="https://csmantle.top/2025/06/17/csmantle-top-javascript-usage.html" rel="alternate" type="text/html" title="JavaScript usage on csmantle.top" /><published>2025-06-17T13:05:57+00:00</published><updated>2025-06-17T13:05:57+00:00</updated><id>https://csmantle.top/2025/06/17/csmantle-top-javascript-usage</id><content type="html" xml:base="https://csmantle.top/2025/06/17/csmantle-top-javascript-usage.html"><![CDATA[<p><strong>The main blog site does not use JavaScript for any critical functions.</strong></p>

<p>Features that requires JavaScript:</p>

<ul>
  <li>Comments by <a href="https://utteranc.es/">https://utteranc.es/</a></li>
  <li>Broken link converter on <a href="/404.html">404.html</a></li>
  <li>Web apps deployed under their own path prefixes</li>
</ul>

<p>If you don’t need them, feel free to disable JavaScript entirely when browsing through my site.</p>]]></content><author><name>Rong Bao</name></author><category term="topic:site" /><category term="announcement" /><summary type="html"><![CDATA[The main blog site does not use JavaScript for any critical functions. Features that requires JavaScript: Comments by https://utteranc.es/ Broken link converter on 404.html Web apps deployed under their own path prefixes If you don’t need them, feel free to disable JavaScript entirely when browsing through my site.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">When spaces break your CreateProcess (and how to fix it)</title><link href="https://csmantle.top/2025/06/17/when-spaces-break-your-createprocess-and-how-to-fix-it.html" rel="alternate" type="text/html" title="When spaces break your CreateProcess (and how to fix it)" /><published>2025-06-17T12:38:55+00:00</published><updated>2025-06-17T12:38:55+00:00</updated><id>https://csmantle.top/2025/06/17/when-spaces-break-your-createprocess-and-how-to-fix-it</id><content type="html" xml:base="https://csmantle.top/2025/06/17/when-spaces-break-your-createprocess-and-how-to-fix-it.html"><![CDATA[<p>The username on my Windows laptop contains a space (“<code class="language-plaintext highlighter-rouge"> </code>”). This little character is known to <a href="https://github.com/pypa/pip/issues/10114">cause</a> <a href="https://superuser.com/questions/119610/spaces-and-parenthesis-in-windows-path-variable-screws-up-batch-files">dozens</a> <a href="https://forum.posit.co/t/spaces-in-library-path-names-on-windows-causes-problems-installing-packages-after-installing-r-3-5-0/8978">of</a> <a href="https://superuser.com/questions/1420212/im-getting-an-error-indicating-that-the-file-path-has-a-space-in-the-name-when">software</a> <a href="https://github.com/espressif/esp-idf/issues/5576">problems</a>, and most of them are attributed to the existing codebase which does not properly quote file paths.</p>

<p>Recently this space started troubling me more than before. Two pieces of software <em>suddenly</em> refused to work. I spent a while diagnosing the bugs, then filed bug reports and patches to the developers:</p>

<ul>
  <li><strong>Nushell.</strong> It’s my daily shell environment. Nushell is functional, elegant, and expressive. However, it now refused to start up in my Windows Terminal, producing an <a href="https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-#:~:text=ERROR_BAD_EXE_FORMAT"><code class="language-plaintext highlighter-rouge">ERROR_BAD_EXE_FORMAT</code></a>.
    <ul>
      <li>PR <a href="https://github.com/nushell/nushell/pull/15881">nushell/nushell#15881</a> (merged)</li>
      <li>PR <a href="https://github.com/nushell/nushell/pull/15889">nushell/nushell#15889</a> (merged)</li>
      <li>PR <a href="https://github.com/nushell/integrations/pull/57">nushell/integrations#57</a> (merged)</li>
    </ul>
  </li>
  <li><strong>Galarius/vscode-opencl.</strong> I have used this extension on my other Windows machines without problems, so I installed it in my VS Code. It failed to start, producing a “file not found” error.
    <ul>
      <li>Issue <a href="https://github.com/Galarius/vscode-opencl/issues/72">Galarius/vscode-opencl#72</a> (closed as done)</li>
    </ul>
  </li>
</ul>

<p>Here’s a TL;DR version for them: Both apps pass the target executable name <em>in a shell command</em> rather than <em>as an image path</em>. <!-- seo-excerpt-separator --> For Nushell &lt; 0.105.0 on Windows, it put the bare path in the <a href="https://learn.microsoft.com/en-us/windows/terminal/customize-settings/profile-general#command-line">“<code class="language-plaintext highlighter-rouge">commandline</code>” property of the Windows Terminal profile</a>. For vscode-opencl, it runs <a href="https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback"><code class="language-plaintext highlighter-rouge">child_process.exec</code></a> with bare path and CLI arguments. In both cases, the first fragment delimited by space is recognized as the “real” executable name, and that does not exist of course.</p>

<p>Wait, did I say “suddenly”? Both software worked fine <em>up to a recent time point</em>. That’s not normal, since my username was set long ago. They should either work or break from the very beginning. So I dug deeper and took a look at the “Users” directory. I found a file with the same name as the first fragment of my username:</p>

<pre><code class="language-nushell">&gt; ls `C:\Users\` | find "Mantle" | select name type
╭───┬─────────────────────────────────────────────────┬──────╮
│ # │                      name                       │ type │
├───┼─────────────────────────────────────────────────┼──────┤
│ 0 │ C:\Users\Mantle                                 │ file │
│ 1 │ C:\Users\Mantle Bao                             │ dir  │
╰───┴─────────────────────────────────────────────────┴──────╯
&gt;
</code></pre>

<p>File #0 is an empty file and not an executable, thus the <code class="language-plaintext highlighter-rouge">ERROR_BAD_EXE_FORMAT</code>. So I proposed the three PRs to Nushell upstream that fixes the profile creation. They worked as intended, but one question remains: Why did they work before?</p>

<p>Windows apps call <code class="language-plaintext highlighter-rouge">CreateProcess</code> series of functions to load and execute another image. Take <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#parameters"><code class="language-plaintext highlighter-rouge">CreateProcessW</code></a> as an example. When an image name (<code class="language-plaintext highlighter-rouge">lpApplicationName</code>) is not specified, the command line (<code class="language-plaintext highlighter-rouge">lpCommandLine</code>) would be used to specify the executable to run. Here, Windows applies a curious strategy to <em>iteratively</em> determine which executable to run, literally:</p>

<blockquote>
  <p>For example, consider the string “c:\program files\sub dir\program name”. This string can be interpreted in a number of ways. The system tries to interpret the possibilities in the following order:</p>

  <ol>
    <li><strong>c:\program.exe</strong></li>
    <li><strong>c:\program files\sub.exe</strong></li>
    <li><strong>c:\program files\sub dir\program.exe</strong></li>
    <li><strong>c:\program files\sub dir\program name.exe</strong></li>
  </ol>
</blockquote>

<p>I think this is a compatibility and developer experience consideration. Very often developers forget to quote their executable names, and spaces are common in standard Windows paths (like “Program Files”, and “Application Data”, and even mixed with parentheses like “Program Files (x86)”). Having this strategy can handle most misquoted situations and end up finding the right executable. But under some conditions, it could go wrong in a disastrous way.</p>

<p>Suppose an application named “My App” creates a data directory at <code class="language-plaintext highlighter-rouge">%APPDATA%\My App\</code>, and drops a helper executable into <code class="language-plaintext highlighter-rouge">%APPDATA%\My App\helper.exe</code>. Suppose the developers of this app forget to quote the path and pass it to <code class="language-plaintext highlighter-rouge">CreateProcess</code>. An attacker or hacker can create a backdoor executable named <code class="language-plaintext highlighter-rouge">My</code> at <code class="language-plaintext highlighter-rouge">%APPDATA%\</code>. When the app tries to run the helper executable, the backdoor would be called instead.</p>

<p>How to prevent these from happening? There are three common approaches:</p>

<ol>
  <li>Tell the user that they cannot use any paths with spaces inside. When the app launches, it checks all used paths and complains about the spaces found in them. It’s effective, but some users may find it annoying.</li>
  <li>Quote every path when building <code class="language-plaintext highlighter-rouge">lpCommandLine</code>. This is safer and more user-friendly than the last approach.</li>
  <li>Pass the executable name in <code class="language-plaintext highlighter-rouge">lpApplicationName</code>, and use <code class="language-plaintext highlighter-rouge">lpCommandLine</code> solely for command line arguments. Some quotes may still be needed to help the callee parse the correct <code class="language-plaintext highlighter-rouge">argv[0]</code>. This is the safest, as it’s now impossible to have the executable path truncated.</li>
</ol>

<p>But as the developers review their codebase and take whatever approaches above, maybe it’s time for me to switch my username.</p>]]></content><author><name>Rong Bao</name></author><category term="topic:dev" /><category term="windows" /><summary type="html"><![CDATA[The username on my Windows laptop contains a space (“ ”). This little character is known to cause dozens of software problems, and most of them are attributed to the existing codebase which does not properly quote file paths. Recently this space started troubling me more than before. Two pieces of software suddenly refused to work. I spent a while diagnosing the bugs, then filed bug reports and patches to the developers: Nushell. It’s my daily shell environment. Nushell is functional, elegant, and expressive. However, it now refused to start up in my Windows Terminal, producing an ERROR_BAD_EXE_FORMAT. PR nushell/nushell#15881 (merged) PR nushell/nushell#15889 (merged) PR nushell/integrations#57 (merged) Galarius/vscode-opencl. I have used this extension on my other Windows machines without problems, so I installed it in my VS Code. It failed to start, producing a “file not found” error. Issue Galarius/vscode-opencl#72 (closed as done) Here’s a TL;DR version for them: Both apps pass the target executable name in a shell command rather than as an image path.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">Numbers as expressions</title><link href="https://csmantle.top/2025/05/02/numbers-as-expressions.html" rel="alternate" type="text/html" title="Numbers as expressions" /><published>2025-05-02T11:08:57+00:00</published><updated>2025-05-02T11:08:57+00:00</updated><id>https://csmantle.top/2025/05/02/numbers-as-expressions</id><content type="html" xml:base="https://csmantle.top/2025/05/02/numbers-as-expressions.html"><![CDATA[<p>A while ago, I saw <a href="https://t.me/abcthoughts/5957">an interesting post</a> from a Telegram channel I subscribed to. Briefly, it said:</p>

<blockquote>
  <p>With these constants defined…</p>

  <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="o">-</span><span class="mi">3</span> <span class="o">/</span> <span class="mi">80</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">e</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">f</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">g</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">h</span> <span class="o">=</span> <span class="mi">9</span> <span class="o">/</span> <span class="mi">10</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">l</span> <span class="o">=</span> <span class="mi">11</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">n</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">o</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">s</span> <span class="o">=</span> <span class="mi">7</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">t</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">u</span> <span class="o">=</span> <span class="mi">12</span> <span class="o">/</span> <span class="mi">5</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">w</span> <span class="o">=</span> <span class="mi">9</span> <span class="o">/</span> <span class="mi">5</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">18</span> <span class="o">/</span> <span class="mi">7</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">z</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div>  </div>

  <p>… you can write numbers directly and perform arithmetic on it!</p>

  <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">t</span><span class="o">*</span><span class="nx">w</span><span class="o">*</span><span class="nx">o</span> <span class="o">+</span> <span class="nx">n</span><span class="o">*</span><span class="nx">i</span><span class="o">*</span><span class="nx">n</span><span class="o">*</span><span class="nx">e</span><span class="p">)</span>
<span class="c1">// 11</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">e</span><span class="o">*</span><span class="nx">l</span><span class="o">*</span><span class="nx">e</span><span class="o">*</span><span class="nx">v</span><span class="o">*</span><span class="nx">e</span><span class="o">*</span><span class="nx">n</span> <span class="o">-</span> <span class="nx">f</span><span class="o">*</span><span class="nx">i</span><span class="o">*</span><span class="nx">v</span><span class="o">*</span><span class="nx">e</span><span class="p">)</span>
<span class="c1">// 6</span>
</code></pre></div>  </div>
</blockquote>

<p>We can use one of the many nonlinear solvers to produce a number of feasible solutions, with arbitrary number of words taken into consideration. My version uses <a href="https://pypi.org/project/z3-solver/">Z3.py</a> and defines “zero” to “nine”.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">string</span>

<span class="kn">import</span> <span class="n">z3</span>

<span class="n">ALPHABET</span> <span class="o">=</span> <span class="p">{</span><span class="n">ch</span><span class="p">:</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Real</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">ch</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">string</span><span class="p">.</span><span class="n">ascii_lowercase</span><span class="p">}</span>
<span class="n">VALUES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">zero</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">one</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">two</span><span class="sh">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">three</span><span class="sh">"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">four</span><span class="sh">"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">five</span><span class="sh">"</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">six</span><span class="sh">"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">seven</span><span class="sh">"</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">eight</span><span class="sh">"</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">nine</span><span class="sh">"</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span>
<span class="p">}</span>

<span class="n">solver</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">Solver</span><span class="p">()</span>

<span class="k">for</span> <span class="n">word</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">VALUES</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
    <span class="n">expr</span><span class="p">:</span> <span class="n">z3</span><span class="p">.</span><span class="n">ArithRef</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="nc">IntVal</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">word</span><span class="p">:</span>
        <span class="k">assert</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">ALPHABET</span>
        <span class="n">expr</span> <span class="o">=</span> <span class="n">expr</span> <span class="o">*</span> <span class="n">ALPHABET</span><span class="p">[</span><span class="n">ch</span><span class="p">]</span>  <span class="c1"># type: ignore
</span>    <span class="n">solver</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">expr</span> <span class="o">==</span> <span class="n">value</span><span class="p">)</span>

<span class="k">assert</span> <span class="n">solver</span><span class="p">.</span><span class="nf">check</span><span class="p">()</span> <span class="o">==</span> <span class="n">z3</span><span class="p">.</span><span class="n">sat</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="nf">model</span><span class="p">()</span>
<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">model</span><span class="p">.</span><span class="nf">decls</span><span class="p">():</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">d</span><span class="p">.</span><span class="nf">name</span><span class="p">()</span>
    <span class="n">value</span> <span class="o">=</span> <span class="n">model</span><span class="p">[</span><span class="n">d</span><span class="p">]</span>
    <span class="k">assert</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">z3</span><span class="p">.</span><span class="n">RatNumRef</span><span class="p">)</span>
    <span class="nf">locals</span><span class="p">()[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span><span class="p">.</span><span class="nf">as_fraction</span><span class="p">()</span>
</code></pre></div></div>

<p>It can be run as follows and produce the desired output:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># python -i alpha_numeric.py
</span><span class="o">&gt;&gt;&gt;</span> <span class="n">o</span><span class="o">*</span><span class="n">n</span><span class="o">*</span><span class="n">e</span> <span class="o">+</span> <span class="n">t</span><span class="o">*</span><span class="n">w</span><span class="o">*</span><span class="n">o</span>
<span class="nc">Fraction</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="p">(</span><span class="n">t</span><span class="o">*</span><span class="n">h</span><span class="o">*</span><span class="n">r</span><span class="o">*</span><span class="n">e</span><span class="o">*</span><span class="n">e</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">f</span><span class="o">*</span><span class="n">o</span><span class="o">*</span><span class="n">u</span><span class="o">*</span><span class="n">r</span><span class="p">)</span>
<span class="nc">Fraction</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">n</span><span class="o">*</span><span class="n">i</span><span class="o">*</span><span class="n">n</span><span class="o">*</span><span class="n">e</span> <span class="o">-</span> <span class="n">s</span><span class="o">*</span><span class="n">e</span><span class="o">*</span><span class="n">v</span><span class="o">*</span><span class="n">e</span><span class="o">*</span><span class="n">n</span>
<span class="nc">Fraction</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span>
</code></pre></div></div>

<p><strong>Open question:</strong> What is the maximum number of consecutive “words” that can be added to this nonlinear system while maintaining its compatibility (i.e., ensuring it has at least one solution)?</p>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><category term="snippet" /><category term="python" /><summary type="html"><![CDATA[A while ago, I saw an interesting post from a Telegram channel I subscribed to. Briefly, it said: With these constants defined… const a = -3 / 80; const e = 1; const f = 5; const g = 8 / 3; const h = 9 / 10; const i = 1; const l = 11 / 3; const n = 3; const o = 1 / 3; const r = 1; const s = 7 / 3; const t = 10 / 3; const u = 12 / 5; const v = 1; const w = 9 / 5; const x = 18 / 7; const z = 0; … you can write numbers directly and perform arithmetic on it! console.log(t*w*o + n*i*n*e) // 11 console.log(e*l*e*v*e*n - f*i*v*e) // 6 We can use one of the many nonlinear solvers to produce a number of feasible solutions, with arbitrary number of words taken into consideration. My version uses Z3.py and defines “zero” to “nine”. import string import z3 ALPHABET = {ch: z3.Real(f"{ch}") for ch in string.ascii_lowercase} VALUES = { "zero": 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, } solver = z3.Solver() for word, value in VALUES.items(): expr: z3.ArithRef = z3.IntVal(1) for ch in word: assert ch in ALPHABET expr = expr * ALPHABET[ch] # type: ignore solver.add(expr == value) assert solver.check() == z3.sat model = solver.model() for d in model.decls(): name = d.name() value = model[d] assert isinstance(value, z3.RatNumRef) locals()[name] = value.as_fraction() It can be run as follows and produce the desired output: # python -i alpha_numeric.py &gt;&gt;&gt; o*n*e + t*w*o Fraction(3, 1) &gt;&gt;&gt; (t*h*r*e*e) / (f*o*u*r) Fraction(3, 4) &gt;&gt;&gt; n*i*n*e - s*e*v*e*n Fraction(2, 1) &gt;&gt;&gt; Open question: What is the maximum number of consecutive “words” that can be added to this nonlinear system while maintaining its compatibility (i.e., ensuring it has at least one solution)?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Building chart database for AstroDX: Revisited</title><link href="https://csmantle.top/2025/04/14/astrodx-database-construction-revisited.html" rel="alternate" type="text/html" title="Building chart database for AstroDX: Revisited" /><published>2025-04-14T00:19:48+00:00</published><updated>2025-04-14T00:19:48+00:00</updated><id>https://csmantle.top/2025/04/14/astrodx-database-construction-revisited</id><content type="html" xml:base="https://csmantle.top/2025/04/14/astrodx-database-construction-revisited.html"><![CDATA[<p><strong>See <a href="/2023/10/07/astrodx-database-construction.html">the previous article</a> for a list of needed tools and supplementary tutorials.</strong></p>

<hr />

<p>The first step is, as always, obtaining a copy of original game image. If you have an “.app” file, refer to <a href="https://nyac.at/posts/from-app-to-playable-game">this awesome post</a> to decrypt it into a mountable VHD. An extra note: When you are asked to use <a href="https://sourceforge.net/projects/imdisk-toolkit/">ImDisk</a> (<a href="https://community.chocolatey.org/packages/ImDisk-Toolkit">Chocolatey</a>), <em>just use it.</em> Don’t use any “remastered” versions. They lack critical features.</p>

<p>These revised scripts utilize <a href="https://github.com/Neskol/MaichartConverter">MaichartConverter</a>’s built-in database creation feature. That tool has some peculiarities of its own:</p>

<ul>
  <li>For a chart with ID 123456, its corresponding BGA file should be “003456.mp4”, and its sound file should be “music003456.mp3”</li>
  <li>All chart variants (DX/Party charts) corresponds to the same BGA and sound files. For example, chart 00abcd, 01abcd, 10abcd, 11abcd, 12abcd, … all corresponds to “00abcd.mp4”, “music00abcd.mp3”, etc.</li>
</ul>

<h2 id="scripts">Scripts</h2>

<p>The function <code class="language-plaintext highlighter-rouge">New-TemporaryDirectory</code> comes from <a href="https://stackoverflow.com/questions/34559553/create-a-temporary-directory-in-powershell">a StackOverflow answer</a>.</p>

<!-- seo-excerpt-separator -->

<h3 id="prepare-maichart-soundps1">Prepare-Maichart-Sound.ps1</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">boolean</span><span class="p">]</span><span class="nv">$Overwrite</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$MusicPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$SoundPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$OutputPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$CriToolsPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$EncKey</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$Parallelism</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">24</span><span class="w">
</span><span class="p">)</span><span class="w">


</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$MusicPath</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"music*"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Foreach-Object</span><span class="w"> </span><span class="nt">-ThrottleLimit</span><span class="w"> </span><span class="nv">$Parallelism</span><span class="w"> </span><span class="nt">-Parallel</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">New-TemporaryDirectory</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$tmp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetTempPath</span><span class="p">()</span><span class="w"> </span><span class="c"># Not $env:TEMP, see https://stackoverflow.com/a/946017</span><span class="w">
        </span><span class="nv">$name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">New-Guid</span><span class="p">)</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s2">"N"</span><span class="p">)</span><span class="w">
        </span><span class="nx">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$tmp</span><span class="w"> </span><span class="nv">$name</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$music_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$PSItem</span><span class="o">.</span><span class="nf">BaseName</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_name</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"^music([0-9]{6})$"</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Invalid music name: </span><span class="nv">$music_name</span><span class="s2">"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="nv">$music_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$Matches</span><span class="o">.</span><span class="nf">1</span><span class="w">
    </span><span class="nv">$music_id_int</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$music_id</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_id_int</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="mi">10000</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_id</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"^[0-9]{2}([0-9]{4})$"</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Invalid DX/Party music name: </span><span class="nv">$music_name</span><span class="s2">"</span><span class="w">
            </span><span class="kr">break</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="nv">$music_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"00</span><span class="si">$(</span><span class="bp">$Matches</span><span class="o">.</span><span class="nf">1</span><span class="si">)</span><span class="s2">"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  
    </span><span class="nv">$sound_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">SoundPath</span><span class="s2">/music</span><span class="nv">$music_id</span><span class="s2">.acb"</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$sound_path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Sound file not found: </span><span class="nv">$sound_path</span><span class="s2">"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$metadata_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="bp">$PSItem</span><span class="o">.</span><span class="nf">FullName</span><span class="si">)</span><span class="s2">/Music.xml"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$metadata_path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Metadata not found: </span><span class="nv">$metadata_path</span><span class="s2">"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">Overwrite</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">OutputPath</span><span class="s2">/music</span><span class="nv">$music_id</span><span class="s2">.mp3"</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Skipping existing music</span><span class="nv">$music_id</span><span class="s2">.mp3"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-TemporaryDirectory</span><span class="w">

    </span><span class="n">node.exe</span><span class="w"> </span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">CriToolsPath</span><span class="nx">/src/index.js</span><span class="w"> </span><span class="nx">acb2wavs</span><span class="w"> </span><span class="nt">-k</span><span class="w"> </span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">EncKey</span><span class="w"> </span><span class="nt">-o</span><span class="w"> </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="nv">$sound_path</span><span class="w">
    </span><span class="n">ffmpeg.exe</span><span class="w"> </span><span class="nt">-hide_banner</span><span class="w"> </span><span class="nt">-loglevel</span><span class="w"> </span><span class="nx">error</span><span class="w"> </span><span class="nt">-i</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/stream_1.wav"</span><span class="w"> </span><span class="nt">-codec</span><span class="p">:</span><span class="nx">a</span><span class="w"> </span><span class="nx">libmp3lame</span><span class="w"> </span><span class="nt">-qscale</span><span class="p">:</span><span class="nx">a</span><span class="w"> </span><span class="nx">2</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/stream_1.mp3"</span><span class="w">
    </span><span class="n">Remove-Item</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/stream_1.wav"</span><span class="w">
    </span><span class="n">Move-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/stream_1.mp3"</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">OutputPath</span><span class="s2">/music</span><span class="nv">$music_id</span><span class="s2">.mp3"</span><span class="w"> </span><span class="nt">-Force</span><span class="w">

    </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Run this script as follows:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Prepare-Maichart-Sound.ps1</span><span class="w"> </span><span class="nt">-MusicPath</span><span class="w"> </span><span class="s2">"X:/Package/Sinmai_Data/StreamingAssets/A000/music"</span><span class="w"> </span><span class="nt">-SoundPath</span><span class="w"> </span><span class="s2">"X:/Package/Sinmai_Data/StreamingAssets/A000/SoundData"</span><span class="w"> </span><span class="nt">-OutputPath</span><span class="w"> </span><span class="s2">"[Your output path]/sound_out"</span><span class="w"> </span><span class="nt">-CriToolsPath</span><span class="w"> </span><span class="s2">"[Cloned CriTools root]"</span><span class="w"> </span><span class="nt">-EncKey</span><span class="w"> </span><span class="s2">"0x[Your encryption key in HEX]"</span><span class="w">
</span></code></pre></div></div>

<h3 id="prepare-maichart-movieps1">Prepare-Maichart-Movie.ps1</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">boolean</span><span class="p">]</span><span class="nv">$Overwrite</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$MusicPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$MoviePath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$OutputPath</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$EncKey</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$Parallelism</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span><span class="p">)</span><span class="w">


</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$MusicPath</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"music*"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Foreach-Object</span><span class="w"> </span><span class="nt">-ThrottleLimit</span><span class="w"> </span><span class="nv">$Parallelism</span><span class="w"> </span><span class="nt">-Parallel</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">New-TemporaryDirectory</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$tmp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetTempPath</span><span class="p">()</span><span class="w"> </span><span class="c"># Not $env:TEMP, see https://stackoverflow.com/a/946017</span><span class="w">
        </span><span class="nv">$name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">New-Guid</span><span class="p">)</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s2">"N"</span><span class="p">)</span><span class="w">
        </span><span class="nx">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$tmp</span><span class="w"> </span><span class="nv">$name</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$music_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$PSItem</span><span class="o">.</span><span class="nf">BaseName</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_name</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"^music([0-9]{6})$"</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Invalid music name: </span><span class="nv">$music_name</span><span class="s2">"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="nv">$music_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$Matches</span><span class="o">.</span><span class="nf">1</span><span class="w">
    </span><span class="nv">$music_id_int</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$music_id</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_id_int</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="mi">10000</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="nv">$music_id</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"^[0-9]{2}([0-9]{4})$"</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Invalid DX/Party music name: </span><span class="nv">$music_name</span><span class="s2">"</span><span class="w">
            </span><span class="kr">break</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="nv">$music_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"00</span><span class="si">$(</span><span class="bp">$Matches</span><span class="o">.</span><span class="nf">1</span><span class="si">)</span><span class="s2">"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$metadata_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="bp">$PSItem</span><span class="o">.</span><span class="nf">FullName</span><span class="si">)</span><span class="s2">/Music.xml"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$metadata_path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Metadata not found: </span><span class="nv">$metadata_path</span><span class="s2">"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">Overwrite</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">OutputPath</span><span class="s2">/</span><span class="nv">$music_id</span><span class="s2">.mp4"</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Skipping existing </span><span class="nv">$music_id</span><span class="s2">.mp4"</span><span class="w">
        </span><span class="kr">break</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-TemporaryDirectory</span><span class="w">

    </span><span class="n">wannacri.exe</span><span class="w"> </span><span class="nx">extractusm</span><span class="w"> </span><span class="nt">-k</span><span class="w"> </span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">EncKey</span><span class="w"> </span><span class="nt">-o</span><span class="w"> </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">MoviePath</span><span class="s2">/</span><span class="nv">$music_id</span><span class="s2">.dat"</span><span class="w">

    </span><span class="nv">$movie_export_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/</span><span class="nv">$music_id</span><span class="s2">.dat/videos"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$movie_export_path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Container</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Exported movie not found: </span><span class="nv">$movie_export_path</span><span class="s2">; ignoring"</span><span class="w">
        </span><span class="kr">continue</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="nv">$movie_file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"*.ivf"</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w">
    </span><span class="nv">$movie_file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$movie_file</span><span class="o">.</span><span class="nf">FullName</span><span class="w">
    </span><span class="n">ffmpeg.exe</span><span class="w"> </span><span class="nt">-hide_banner</span><span class="w"> </span><span class="nt">-loglevel</span><span class="w"> </span><span class="nx">error</span><span class="w"> </span><span class="nt">-stats</span><span class="w"> </span><span class="nt">-hwaccel</span><span class="w"> </span><span class="nx">cuda</span><span class="w"> </span><span class="nt">-i</span><span class="w"> </span><span class="nv">$movie_file</span><span class="w"> </span><span class="nt">-c</span><span class="p">:</span><span class="nx">v</span><span class="w"> </span><span class="nx">hevc_nvenc</span><span class="w"> </span><span class="nt">-rc</span><span class="p">:</span><span class="nx">v</span><span class="w"> </span><span class="nx">vbr</span><span class="w"> </span><span class="nt">-cq</span><span class="p">:</span><span class="nx">v</span><span class="w"> </span><span class="nx">32</span><span class="w"> </span><span class="nt">-qmin</span><span class="w"> </span><span class="nx">30</span><span class="w"> </span><span class="nt">-qmax</span><span class="w"> </span><span class="nx">36</span><span class="w"> </span><span class="nt">-an</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/</span><span class="nv">$music_name</span><span class="s2">.mp4"</span><span class="w">
    </span><span class="n">Move-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$tmp_dir</span><span class="s2">/</span><span class="nv">$music_name</span><span class="s2">.mp4"</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">USING</span><span class="p">:</span><span class="nv">OutputPath</span><span class="s2">/</span><span class="nv">$music_id</span><span class="s2">.mp4"</span><span class="w"> </span><span class="nt">-Force</span><span class="w">

    </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tmp_dir</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Run this script as follows:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Prepare-Maichart-Movie.ps1</span><span class="w"> </span><span class="nt">-MusicPath</span><span class="w"> </span><span class="s2">"X:/Package/Sinmai_Data/StreamingAssets/A000/music"</span><span class="w"> </span><span class="nt">-MoviePath</span><span class="w"> </span><span class="s2">"X:/Package/Sinmai_Data/StreamingAssets/A000/MovieData"</span><span class="w"> </span><span class="nt">-OutputPath</span><span class="w"> </span><span class="s2">"[Your output path]/movie_out"</span><span class="w"> </span><span class="nt">-EncKey</span><span class="w"> </span><span class="s2">"0x[Your encryption key in HEX]"</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>Rong Bao</name></author><category term="topic:misc" /><category term="game" /><category term="maimai-dx" /><category term="astrodx" /><summary type="html"><![CDATA[See the previous article for a list of needed tools and supplementary tutorials. The first step is, as always, obtaining a copy of original game image. If you have an “.app” file, refer to this awesome post to decrypt it into a mountable VHD. An extra note: When you are asked to use ImDisk (Chocolatey), just use it. Don’t use any “remastered” versions. They lack critical features. These revised scripts utilize MaichartConverter’s built-in database creation feature. That tool has some peculiarities of its own: For a chart with ID 123456, its corresponding BGA file should be “003456.mp4”, and its sound file should be “music003456.mp3” All chart variants (DX/Party charts) corresponds to the same BGA and sound files. For example, chart 00abcd, 01abcd, 10abcd, 11abcd, 12abcd, … all corresponds to “00abcd.mp4”, “music00abcd.mp3”, etc. Scripts The function New-TemporaryDirectory comes from a StackOverflow answer.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">When to use Seq or Vec in Chisel?</title><link href="https://csmantle.top/2025/03/08/when-to-use-seq-or-vec.html" rel="alternate" type="text/html" title="When to use Seq or Vec in Chisel?" /><published>2025-03-08T04:00:26+00:00</published><updated>2025-03-08T04:00:26+00:00</updated><id>https://csmantle.top/2025/03/08/when-to-use-seq-or-vec</id><content type="html" xml:base="https://csmantle.top/2025/03/08/when-to-use-seq-or-vec.html"><![CDATA[<p><strong>Use <code class="language-plaintext highlighter-rouge">Seq</code> if you just need a Scala array or container. Use <code class="language-plaintext highlighter-rouge">Vec</code> if you want a multiplexer.</strong></p>

<p><a href="https://www.scala-lang.org/api/2.13.x/scala/collection/immutable/Seq.html"><code class="language-plaintext highlighter-rouge">scala.collection.immutable.Seq</code></a> is purely a Scala-land concept. You can index into it via a <code class="language-plaintext highlighter-rouge">Int</code> thanks to its <a href="https://www.scala-lang.org/api/2.13.x/scala/collection/immutable/Seq.html#apply(i:Int):A"><code class="language-plaintext highlighter-rouge">apply</code></a> function.</p>

<p><a href="https://javadoc.io/doc/org.chipsalliance/chisel_2.13/latest/chisel3/Vec.html"><code class="language-plaintext highlighter-rouge">chisel3.Vec</code></a> is a hardware container that can be indexed by Scala-land <code class="language-plaintext highlighter-rouge">Int</code>s and hardware <code class="language-plaintext highlighter-rouge">UInt</code>s. It has two <code class="language-plaintext highlighter-rouge">apply</code> overloads: <a href="https://javadoc.io/static/org.chipsalliance/chisel_2.13/7.0.0-M2/chisel3/Vec.html#apply(idx:Int):T"><code class="language-plaintext highlighter-rouge">apply(idx: Int): T</code></a> and <a href="https://javadoc.io/static/org.chipsalliance/chisel_2.13/7.0.0-M2/chisel3/Vec.html#apply(p:chisel3.UInt):T"><code class="language-plaintext highlighter-rouge">apply(p: UInt): T</code></a>. It also have connection operators like <a href="https://javadoc.io/static/org.chipsalliance/chisel_2.13/7.0.0-M2/chisel3/Vec.html#:=(that:chisel3.Vec[T])(implicitsourceInfo:chisel3.experimental.SourceInfo):Unit"><code class="language-plaintext highlighter-rouge">:=</code></a>, allowing for element-wise connection.</p>

<p>Always refer to the (scarce) Chisel documentation when in doubt. If the API in question is not documented (which is very likely), read the source code for its exact behavior.</p>]]></content><author><name>Rong Bao</name></author><category term="topic:dev" /><category term="ee" /><category term="digital-logics" /><category term="chisel" /><summary type="html"><![CDATA[Use Seq if you just need a Scala array or container. Use Vec if you want a multiplexer. scala.collection.immutable.Seq is purely a Scala-land concept. You can index into it via a Int thanks to its apply function. chisel3.Vec is a hardware container that can be indexed by Scala-land Ints and hardware UInts. It has two apply overloads: apply(idx: Int): T and apply(p: UInt): T. It also have connection operators like :=, allowing for element-wise connection. Always refer to the (scarce) Chisel documentation when in doubt. If the API in question is not documented (which is very likely), read the source code for its exact behavior.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://csmantle.top/social.svg" /><media:content medium="image" url="https://csmantle.top/social.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>